mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-20 19:14:39 +08:00
[fix] sync DeskHub previews with tile size and unify tooltips
This commit is contained in:
@@ -1276,7 +1276,8 @@ void GuiApplication::InitializeUi() {
|
|||||||
int(saved.at("connection_y").get<float>() * scale)});
|
int(saved.at("connection_y").get<float>() * scale)});
|
||||||
const float tile_width = saved.value("tile_width", 216.0f);
|
const float tile_width = saved.value("tile_width", 216.0f);
|
||||||
if (std::isfinite(tile_width))
|
if (std::isfinite(tile_width))
|
||||||
ui_->wall->set_gallery_tile_width(std::clamp(tile_width, 184.0f, 960.0f));
|
// The rendered grid constrains the restored width to available space.
|
||||||
|
ui_->wall->set_gallery_tile_width(std::max(tile_width, 184.0f));
|
||||||
ui_->connection_window.BeginRestore();
|
ui_->connection_window.BeginRestore();
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
@@ -5369,6 +5370,13 @@ void GuiApplication::SyncGallery() {
|
|||||||
SaveGalleryWindowState();
|
SaveGalleryWindowState();
|
||||||
|
|
||||||
auto& wall = ui_->gallery;
|
auto& wall = ui_->gallery;
|
||||||
|
std::unordered_map<std::string, std::string> group_labels;
|
||||||
|
for (const auto& member : wall.members) {
|
||||||
|
if (member.group.empty()) continue;
|
||||||
|
auto& label = group_labels[member.id];
|
||||||
|
if (!label.empty()) label += " · ";
|
||||||
|
label += member.group;
|
||||||
|
}
|
||||||
std::vector<slint::SharedString> groups{
|
std::vector<slint::SharedString> groups{
|
||||||
UiText(tr("全部设备", "All devices", "Все устройства"))};
|
UiText(tr("全部设备", "All devices", "Все устройства"))};
|
||||||
for (const auto& group : wall.groups) groups.push_back(UiText(group));
|
for (const auto& group : wall.groups) groups.push_back(UiText(group));
|
||||||
@@ -5422,14 +5430,29 @@ void GuiApplication::SyncGallery() {
|
|||||||
ui_->wall->get_gallery_video_width(),
|
ui_->wall->get_gallery_video_width(),
|
||||||
ui_->wall->get_gallery_video_height(), window.scale_factor())
|
ui_->wall->get_gallery_video_height(), window.scale_factor())
|
||||||
: gallery::PreviewResolution{};
|
: gallery::PreviewResolution{};
|
||||||
|
const auto gallery_preview_viewport = visible
|
||||||
|
? gallery::PreviewResolution::Viewport(
|
||||||
|
ui_->wall->get_gallery_preview_width(),
|
||||||
|
ui_->wall->get_gallery_preview_height(), window.scale_factor())
|
||||||
|
: gallery::PreviewResolution{};
|
||||||
std::set<std::string> preview_ids;
|
std::set<std::string> preview_ids;
|
||||||
std::vector<ui::GalleryTile> tiles;
|
std::vector<ui::GalleryTile> tiles;
|
||||||
const auto make_tile = [&](const std::string& id, bool preview) {
|
const auto make_tile = [&](const std::string& id, bool preview) {
|
||||||
ui::GalleryTile tile{};
|
ui::GalleryTile tile{};
|
||||||
tile.id = UiText(id);
|
tile.id = UiText(id);
|
||||||
tile.name = UiText(name(id));
|
tile.name = UiText(name(id));
|
||||||
|
tile.group_names = UiText(group_labels[id]);
|
||||||
tile.selected = wall.selected.count(id);
|
tile.selected = wall.selected.count(id);
|
||||||
auto props = FindRemoteSession(id);
|
auto props = FindRemoteSession(id);
|
||||||
|
for (const auto& [_, recent] : recent_connections_) {
|
||||||
|
if (recent.remote_id != id) continue;
|
||||||
|
tile.host_name = UiText(recent.remote_host_name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (props && tile.host_name.empty()) {
|
||||||
|
std::lock_guard lock(props->remote_version_mutex_);
|
||||||
|
tile.host_name = UiText(props->remote_host_name_);
|
||||||
|
}
|
||||||
tile.connected = props &&
|
tile.connected = props &&
|
||||||
props->connection_status_ == ConnectionStatus::Connected &&
|
props->connection_status_ == ConnectionStatus::Connected &&
|
||||||
!props->closing_;
|
!props->closing_;
|
||||||
@@ -5455,9 +5478,10 @@ void GuiApplication::SyncGallery() {
|
|||||||
props->connection_status_.load(), localization_language_index_));
|
props->connection_status_.load(), localization_language_index_));
|
||||||
if (preview && visible && tile.connected) {
|
if (preview && visible && tile.connected) {
|
||||||
preview_ids.insert(id);
|
preview_ids.insert(id);
|
||||||
props->gallery_preview_width_ = ui_->gallery_focus == id
|
const auto viewport = ui_->gallery_focus == id
|
||||||
? std::max({320, gallery_viewport.width, gallery_viewport.height})
|
? gallery_viewport : gallery_preview_viewport;
|
||||||
: 320;
|
props->gallery_preview_width_ =
|
||||||
|
std::max({320, viewport.width, viewport.height});
|
||||||
std::lock_guard lock(props->gallery_frame_mutex_);
|
std::lock_guard lock(props->gallery_frame_mutex_);
|
||||||
if (props->gallery_rgba_) {
|
if (props->gallery_rgba_) {
|
||||||
auto& cached = ui_->gallery_previews[id];
|
auto& cached = ui_->gallery_previews[id];
|
||||||
@@ -5586,10 +5610,13 @@ void GuiApplication::SyncGallery() {
|
|||||||
const bool stream_visible = stream_window_visible &&
|
const bool stream_visible = stream_window_visible &&
|
||||||
props->tab_opened_ && id == focused_remote_id_;
|
props->tab_opened_ && id == focused_remote_id_;
|
||||||
if (props->preview_resolution_supported_) {
|
if (props->preview_resolution_supported_) {
|
||||||
|
const auto wall_viewport = !preview_ids.count(id)
|
||||||
|
? gallery::PreviewResolution{}
|
||||||
|
: ui_->gallery_focus == id ? gallery_viewport
|
||||||
|
: gallery_preview_viewport;
|
||||||
const auto resolution = props->preview_resolution_.Update(
|
const auto resolution = props->preview_resolution_.Update(
|
||||||
wall.stream_settings.Resolution(
|
wall.stream_settings.Resolution(
|
||||||
props->wall_managed_, ui_->gallery_focus == id
|
props->wall_managed_, wall_viewport,
|
||||||
? gallery_viewport : gallery::PreviewResolution{},
|
|
||||||
stream_visible ? stream_viewport : gallery::PreviewResolution{}),
|
stream_visible ? stream_viewport : gallery::PreviewResolution{}),
|
||||||
now, needs_sync);
|
now, needs_sync);
|
||||||
demand.version = 2;
|
demand.version = 2;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ struct StreamSettings {
|
|||||||
PreviewResolution stream_view) const {
|
PreviewResolution stream_view) const {
|
||||||
if (!wall_managed) return {};
|
if (!wall_managed) return {};
|
||||||
if (resolution != 0 && Valid()) return kResolutions[resolution];
|
if (resolution != 0 && Valid()) return kResolutions[resolution];
|
||||||
// A square bound limits the longest edge of an unexpanded wall tile.
|
// Keep a bounded fallback until a rendered viewport is available.
|
||||||
if (!wall_view.width && !stream_view.width) return {320, 320};
|
if (!wall_view.width && !stream_view.width) return {320, 320};
|
||||||
return {std::max(wall_view.width, stream_view.width),
|
return {std::max(wall_view.width, stream_view.width),
|
||||||
std::max(wall_view.height, stream_view.height)};
|
std::max(wall_view.height, stream_view.height)};
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { CompactButton, SecondaryButton, FontAwesomeIcons, ImGuiCheckBox } from
|
|||||||
export struct GalleryTile {
|
export struct GalleryTile {
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
|
host-name: string,
|
||||||
|
group-names: string,
|
||||||
status: string,
|
status: string,
|
||||||
connected: bool,
|
connected: bool,
|
||||||
online: bool,
|
online: bool,
|
||||||
@@ -29,29 +31,6 @@ export struct GalleryActionDevice {
|
|||||||
connected: bool,
|
connected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
component DeviceDetailsTip inherits Rectangle {
|
|
||||||
in property <string> device-name;
|
|
||||||
in property <string> device-id;
|
|
||||||
in property <string> presence;
|
|
||||||
in property <length> available-height;
|
|
||||||
height: min(root.available-height, 58px);
|
|
||||||
background: #22252bf2; border-width: 1px; border-color: #ffffff38; border-radius: 5px;
|
|
||||||
clip: true;
|
|
||||||
tip-name := Text {
|
|
||||||
x: 8px; y: 8px; width: parent.width - 16px;
|
|
||||||
height: 14px;
|
|
||||||
text: root.device-name; color: white; font-size: 11px; overflow: elide; vertical-alignment: top;
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
x: 8px; y: tip-name.y + tip-name.height; width: parent.width - 16px; height: 14px;
|
|
||||||
text: "ID: " + root.device-id; color: white; font-size: 11px; overflow: elide;
|
|
||||||
}
|
|
||||||
Text {
|
|
||||||
x: 8px; y: tip-name.y + tip-name.height + 14px; width: parent.width - 16px; height: 14px;
|
|
||||||
text: root.presence; color: white; font-size: 11px; overflow: elide;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export component GalleryView inherits Rectangle {
|
export component GalleryView inherits Rectangle {
|
||||||
in property <bool> controlling: false;
|
in property <bool> controlling: false;
|
||||||
in property <bool> simplified: false;
|
in property <bool> simplified: false;
|
||||||
@@ -61,18 +40,24 @@ export component GalleryView inherits Rectangle {
|
|||||||
private property <float> preview-aspect-ratio: 16 / 9;
|
private property <float> preview-aspect-ratio: 16 / 9;
|
||||||
private property <length> card-inset: 6px;
|
private property <length> card-inset: 6px;
|
||||||
private property <length> card-chrome-height: root.simplified ? root.card-inset * 2 : 36px;
|
private property <length> card-chrome-height: root.simplified ? root.card-inset * 2 : 36px;
|
||||||
private property <length> max-card-width: max(184px, min(960px, grid.width,
|
// Keep the complete cell inside the current grid in both dimensions.
|
||||||
|
private property <length> max-card-width: max(184px, min(grid.width,
|
||||||
(grid.height - root.card-chrome-height) * root.preview-aspect-ratio + root.card-inset * 2));
|
(grid.height - root.card-chrome-height) * root.preview-aspect-ratio + root.card-inset * 2));
|
||||||
private property <length> card-width: clamp(root.tile-width * 1px, 184px, root.max-card-width);
|
private property <length> card-width: clamp(root.tile-width * 1px, 184px, root.max-card-width);
|
||||||
private property <length> card-preview-height: (root.card-width - root.card-inset * 2) / root.preview-aspect-ratio;
|
private property <length> card-preview-height: (root.card-width - root.card-inset * 2) / root.preview-aspect-ratio;
|
||||||
private property <length> card-height: root.card-preview-height + root.card-chrome-height;
|
private property <length> card-height: root.card-preview-height + root.card-chrome-height;
|
||||||
private property <length> rail-preview-height: 126px / root.preview-aspect-ratio;
|
private property <length> rail-preview-width: 126px;
|
||||||
|
private property <length> rail-preview-height: root.rail-preview-width / root.preview-aspect-ratio;
|
||||||
private property <length> rail-card-height: root.rail-preview-height + (root.simplified ? 10px : 24px);
|
private property <length> rail-card-height: root.rail-preview-height + (root.simplified ? 10px : 24px);
|
||||||
private property <string> focused-id: root.focused.id;
|
private property <string> focused-id: root.focused.id;
|
||||||
private property <string> device-tooltip-id;
|
private property <string> device-tooltip-id;
|
||||||
private property <string> device-tooltip-text;
|
private property <string> device-tooltip-text;
|
||||||
private property <length> device-tooltip-x;
|
out property <length> device-tooltip-x;
|
||||||
private property <length> device-tooltip-y;
|
out property <length> device-tooltip-y;
|
||||||
|
private property <GalleryTile> device-tooltip-tile;
|
||||||
|
// MainWindow renders the same tooltip panel for the wall and recent list.
|
||||||
|
out property <GalleryTile> hovered-device: root.device-tooltip-id == root.device-tooltip-tile.id
|
||||||
|
&& !root.adding && !root.importing && !root.confirming ? root.device-tooltip-tile : {};
|
||||||
private property <bool> device-tooltip-visible: false;
|
private property <bool> device-tooltip-visible: false;
|
||||||
private property <string> drag-source;
|
private property <string> drag-source;
|
||||||
private property <GalleryTile> dragged-tile;
|
private property <GalleryTile> dragged-tile;
|
||||||
@@ -90,15 +75,15 @@ export component GalleryView inherits Rectangle {
|
|||||||
changed visible => { root.device-tooltip-id = ""; if !root.visible { root.resizing = false; root.cancel-device-drag(); add-menu.close-menu(); batch-menu.close-menu(); } }
|
changed visible => { root.device-tooltip-id = ""; if !root.visible { root.resizing = false; root.cancel-device-drag(); add-menu.close-menu(); batch-menu.close-menu(); } }
|
||||||
changed width => { root.device-tooltip-id = ""; root.resizing = false; root.cancel-device-drag(); }
|
changed width => { root.device-tooltip-id = ""; root.resizing = false; root.cancel-device-drag(); }
|
||||||
changed height => { root.device-tooltip-id = ""; root.resizing = false; root.cancel-device-drag(); }
|
changed height => { root.device-tooltip-id = ""; root.resizing = false; root.cancel-device-drag(); }
|
||||||
changed window-active => { if !root.window-active { root.resizing = false; root.cancel-device-drag(); } }
|
changed window-active => { if !root.window-active { root.device-tooltip-id = ""; root.resizing = false; root.cancel-device-drag(); } }
|
||||||
changed page => { if root.page != root.drag-page { root.cancel-device-drag(); } }
|
changed page => { root.device-tooltip-id = ""; if root.page != root.drag-page { root.cancel-device-drag(); } }
|
||||||
changed group-index => { root.cancel-device-drag(); }
|
changed group-index => { root.device-tooltip-id = ""; root.cancel-device-drag(); }
|
||||||
changed card-width => { root.cancel-device-drag(); }
|
changed card-width => { root.cancel-device-drag(); }
|
||||||
changed card-height => { root.cancel-device-drag(); }
|
changed card-height => { root.cancel-device-drag(); }
|
||||||
changed device-tooltip-id => {
|
changed device-tooltip-id => {
|
||||||
root.device-tooltip-visible = false;
|
root.device-tooltip-visible = false;
|
||||||
device-tooltip-timer.running = root.device-tooltip-id != "";
|
device-tooltip-timer.running = root.device-tooltip-id == "resize-grid";
|
||||||
if root.device-tooltip-id != "" { device-tooltip-timer.restart(); }
|
if root.device-tooltip-id == "resize-grid" { device-tooltip-timer.restart(); }
|
||||||
}
|
}
|
||||||
device-tooltip-timer := Timer {
|
device-tooltip-timer := Timer {
|
||||||
interval: 450ms; running: false;
|
interval: 450ms; running: false;
|
||||||
@@ -110,6 +95,11 @@ export component GalleryView inherits Rectangle {
|
|||||||
// Pointer mapping must use the exact image viewport, including its inset.
|
// Pointer mapping must use the exact image viewport, including its inset.
|
||||||
out property <float> video-width: video.width / 1px;
|
out property <float> video-width: video.width / 1px;
|
||||||
out property <float> video-height: video.height / 1px;
|
out property <float> video-height: video.height / 1px;
|
||||||
|
// Non-focused previews use the grid cell or the focused view's rail.
|
||||||
|
out property <float> preview-width: (root.focused.id == ""
|
||||||
|
? root.card-width - root.card-inset * 2 : root.rail-preview-width) / 1px;
|
||||||
|
out property <float> preview-height: (root.focused.id == ""
|
||||||
|
? root.card-preview-height : root.rail-preview-height) / 1px;
|
||||||
callback pointer-input(PointerEventButton, PointerEventKind, length, length);
|
callback pointer-input(PointerEventButton, PointerEventKind, length, length);
|
||||||
callback scroll-input(length, length, length, length);
|
callback scroll-input(length, length, length, length);
|
||||||
callback key-input(string, bool, bool, bool, bool, bool);
|
callback key-input(string, bool, bool, bool, bool, bool);
|
||||||
@@ -152,6 +142,12 @@ export component GalleryView inherits Rectangle {
|
|||||||
background: white;
|
background: white;
|
||||||
changed adding => { if root.adding { root.action("release", ""); } }
|
changed adding => { if root.adding { root.action("release", ""); } }
|
||||||
changed importing => { root.action(root.importing ? "release" : "recent-close", ""); }
|
changed importing => { root.action(root.importing ? "release" : "recent-close", ""); }
|
||||||
|
function show-device-details(tile: GalleryTile, x: length, y: length) {
|
||||||
|
root.device-tooltip-tile = tile;
|
||||||
|
root.device-tooltip-x = x;
|
||||||
|
root.device-tooltip-y = y;
|
||||||
|
root.device-tooltip-id = tile.id;
|
||||||
|
}
|
||||||
public function reset-transient() {
|
public function reset-transient() {
|
||||||
root.resizing = false;
|
root.resizing = false;
|
||||||
root.cancel-device-drag();
|
root.cancel-device-drag();
|
||||||
@@ -330,8 +326,8 @@ export component GalleryView inherits Rectangle {
|
|||||||
root.tile-width = clamp(self.start-width + delta, 184px, root.max-card-width) / 1px;
|
root.tile-width = clamp(self.start-width + delta, 184px, root.max-card-width) / 1px;
|
||||||
}
|
}
|
||||||
changed has-hover => { self.show-hint(); }
|
changed has-hover => { self.show-hint(); }
|
||||||
changed mouse-x => { self.show-hint(); }
|
changed mouse-x => { root.device-tooltip-x = grid.x + self.mouse-x; self.show-hint(); }
|
||||||
changed mouse-y => { self.show-hint(); }
|
changed mouse-y => { root.device-tooltip-y = grid.y + self.mouse-y; self.show-hint(); }
|
||||||
pointer-event(event) => {
|
pointer-event(event) => {
|
||||||
if event.button == PointerEventButton.left && event.kind == PointerEventKind.down {
|
if event.button == PointerEventButton.left && event.kind == PointerEventKind.down {
|
||||||
root.cancel-device-drag();
|
root.cancel-device-drag();
|
||||||
@@ -371,7 +367,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
for tile[index] in root.tiles: tile-card := Rectangle {
|
for tile[index] in root.tiles: tile-card := Rectangle {
|
||||||
private property <string> presence: tile.online ? GalleryStrings.tr("在线", "Online", "В сети")
|
private property <string> presence: tile.online ? GalleryStrings.tr("在线", "Online", "В сети")
|
||||||
: tile.presence-known ? GalleryStrings.tr("离线", "Offline", "Не в сети") : GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
: tile.presence-known ? GalleryStrings.tr("离线", "Offline", "Не в сети") : GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
||||||
private property <string> details: tile.name + "\nID: " + tile.id + "\n" + self.presence;
|
private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
|
||||||
accessible-role: button; accessible-label: self.details;
|
accessible-role: button; accessible-label: self.details;
|
||||||
accessible-action-default => { root.action(root.batch && !root.simplified ? "select" : "focus", tile.id); }
|
accessible-action-default => { root.action(root.batch && !root.simplified ? "select" : "focus", tile.id); }
|
||||||
changed details => { if tile-touch.has-hover { tile-touch.show-details(); } }
|
changed details => { if tile-touch.has-hover { tile-touch.show-details(); } }
|
||||||
@@ -394,11 +390,6 @@ export component GalleryView inherits Rectangle {
|
|||||||
horizontal-alignment: center; vertical-alignment: center;
|
horizontal-alignment: center; vertical-alignment: center;
|
||||||
wrap: word-wrap;
|
wrap: word-wrap;
|
||||||
}
|
}
|
||||||
if root.device-tooltip-visible && root.device-tooltip-id == tile.id: DeviceDetailsTip {
|
|
||||||
x: 4px; y: parent.height - self.height - 4px;
|
|
||||||
width: min(320px, parent.width - 8px); available-height: parent.height - 8px;
|
|
||||||
device-name: tile.name; device-id: tile.id; presence: tile-card.presence;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
visible: !root.simplified; x: 9px; y: parent.height - 30px;
|
visible: !root.simplified; x: 9px; y: parent.height - 30px;
|
||||||
@@ -424,7 +415,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
function show-details() {
|
function show-details() {
|
||||||
// Avoid covering the per-device action buttons with a tooltip.
|
// Avoid covering the per-device action buttons with a tooltip.
|
||||||
if root.resizing || root.drag-source != "" || (grid.mouse-y - tile-card.y >= tile-card.height - 30px && !root.simplified) { root.device-tooltip-id = ""; return; }
|
if root.resizing || root.drag-source != "" || (grid.mouse-y - tile-card.y >= tile-card.height - 30px && !root.simplified) { root.device-tooltip-id = ""; return; }
|
||||||
root.device-tooltip-id = tile.id;
|
root.show-device-details(tile, grid.x + grid.mouse-x, grid.y + grid.mouse-y);
|
||||||
}
|
}
|
||||||
changed has-hover => {
|
changed has-hover => {
|
||||||
if self.has-hover { self.show-details(); }
|
if self.has-hover { self.show-details(); }
|
||||||
@@ -569,7 +560,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
rail-card := Rectangle {
|
rail-card := Rectangle {
|
||||||
private property <string> presence: tile.online ? GalleryStrings.tr("在线", "Online", "В сети")
|
private property <string> presence: tile.online ? GalleryStrings.tr("在线", "Online", "В сети")
|
||||||
: tile.presence-known ? GalleryStrings.tr("离线", "Offline", "Не в сети") : GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
: tile.presence-known ? GalleryStrings.tr("离线", "Offline", "Не в сети") : GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
||||||
private property <string> details: tile.name + "\nID: " + tile.id + "\n" + self.presence;
|
private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
|
||||||
width: 136px; height: root.rail-card-height; border-radius: 5px;
|
width: 136px; height: root.rail-card-height; border-radius: 5px;
|
||||||
background: tile.online ? #f5f6f8 : DeviceCardStyle.offline-background; border-width: 1px; border-color: tile.online ? #d5dbe3 : DeviceCardStyle.offline-border;
|
background: tile.online ? #f5f6f8 : DeviceCardStyle.offline-background; border-width: 1px; border-color: tile.online ? #d5dbe3 : DeviceCardStyle.offline-border;
|
||||||
accessible-role: button;
|
accessible-role: button;
|
||||||
@@ -577,17 +568,12 @@ export component GalleryView inherits Rectangle {
|
|||||||
accessible-action-default => { root.action("focus", tile.id); }
|
accessible-action-default => { root.action("focus", tile.id); }
|
||||||
changed details => { if rail-touch.has-hover { rail-touch.show-details(); } }
|
changed details => { if rail-touch.has-hover { rail-touch.show-details(); } }
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: 5px; y: 5px; width: 126px; height: root.rail-preview-height; background: tile.online ? #24272d : DeviceCardStyle.offline-preview;
|
x: 5px; y: 5px; width: root.rail-preview-width; height: root.rail-preview-height; background: tile.online ? #24272d : DeviceCardStyle.offline-preview;
|
||||||
if tile.has-frame && tile.online: Image { width: parent.width; height: parent.height; source: tile.frame; image-fit: contain; opacity: tile.stale || !tile.connected ? 0.4 : 1; }
|
if tile.has-frame && tile.online: Image { width: parent.width; height: parent.height; source: tile.frame; image-fit: contain; opacity: tile.stale || !tile.connected ? 0.4 : 1; }
|
||||||
if !root.simplified && (!tile.has-frame || tile.stale || !tile.connected): Text {
|
if !root.simplified && (!tile.has-frame || tile.stale || !tile.connected): Text {
|
||||||
text: tile.status; color: tile.online ? #e2e6ed : DeviceCardStyle.offline-placeholder; font-size: 10px; overflow: elide;
|
text: tile.status; color: tile.online ? #e2e6ed : DeviceCardStyle.offline-placeholder; font-size: 10px; overflow: elide;
|
||||||
horizontal-alignment: center; vertical-alignment: center;
|
horizontal-alignment: center; vertical-alignment: center;
|
||||||
}
|
}
|
||||||
if root.device-tooltip-visible && root.device-tooltip-id == tile.id: DeviceDetailsTip {
|
|
||||||
x: 4px; y: parent.height - self.height - 4px;
|
|
||||||
width: parent.width - 8px; available-height: parent.height - 8px;
|
|
||||||
device-name: tile.name; device-id: tile.id; presence: rail-card.presence;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
visible: !root.simplified;
|
visible: !root.simplified;
|
||||||
@@ -597,7 +583,9 @@ export component GalleryView inherits Rectangle {
|
|||||||
}
|
}
|
||||||
rail-touch := TouchArea {
|
rail-touch := TouchArea {
|
||||||
function show-details() {
|
function show-details() {
|
||||||
root.device-tooltip-id = tile.id;
|
root.show-device-details(tile,
|
||||||
|
focus-view.x + rail.x + rail.viewport-x + rail-slot.x + rail-card.x + self.mouse-x,
|
||||||
|
focus-view.y + rail.y + rail.viewport-y + rail-slot.y + rail-card.y + self.mouse-y);
|
||||||
}
|
}
|
||||||
changed has-hover => {
|
changed has-hover => {
|
||||||
if self.has-hover { self.show-details(); }
|
if self.has-hover { self.show-details(); }
|
||||||
@@ -726,7 +714,7 @@ export component GalleryView inherits Rectangle {
|
|||||||
text: GalleryStrings.tr("换位未保存,请重试", "Couldn't save positions. Please try again.", "Не удалось сохранить порядок. Повторите попытку."); overflow: elide;
|
text: GalleryStrings.tr("换位未保存,请重试", "Couldn't save positions. Please try again.", "Не удалось сохранить порядок. Повторите попытку."); overflow: elide;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Resize hints follow the pointer; device details stay inside their preview.
|
// Device details are rendered by MainWindow; resize hints stay local.
|
||||||
if root.device-tooltip-visible && root.device-tooltip-id == "resize-grid": Rectangle {
|
if root.device-tooltip-visible && root.device-tooltip-id == "resize-grid": Rectangle {
|
||||||
x: clamp(root.device-tooltip-x, 8px, parent.width - self.width - 8px);
|
x: clamp(root.device-tooltip-x, 8px, parent.width - self.width - 8px);
|
||||||
y: clamp(root.device-tooltip-y + self.height > footer.y ? root.device-tooltip-y - self.height - 8px : root.device-tooltip-y, 4px, parent.height - self.height - 4px);
|
y: clamp(root.device-tooltip-y + self.height > footer.y ? root.device-tooltip-y - self.height - 8px : root.device-tooltip-y, 4px, parent.height - self.height - 4px);
|
||||||
|
|||||||
@@ -311,6 +311,8 @@ export component MainWindow inherits Window {
|
|||||||
in property <bool> gallery-controlling: false;
|
in property <bool> gallery-controlling: false;
|
||||||
out property <float> gallery-video-width: gallery.video-width;
|
out property <float> gallery-video-width: gallery.video-width;
|
||||||
out property <float> gallery-video-height: gallery.video-height;
|
out property <float> gallery-video-height: gallery.video-height;
|
||||||
|
out property <float> gallery-preview-width: gallery.preview-width;
|
||||||
|
out property <float> gallery-preview-height: gallery.preview-height;
|
||||||
callback gallery-pointer(PointerEventButton, PointerEventKind, length, length);
|
callback gallery-pointer(PointerEventButton, PointerEventKind, length, length);
|
||||||
callback gallery-scroll(length, length, length, length);
|
callback gallery-scroll(length, length, length, length);
|
||||||
callback gallery-key(string, bool, bool, bool, bool, bool);
|
callback gallery-key(string, bool, bool, bool, bool, bool);
|
||||||
@@ -531,13 +533,6 @@ export component MainWindow inherits Window {
|
|||||||
private property <bool> recent-tooltip-online: false;
|
private property <bool> recent-tooltip-online: false;
|
||||||
private property <length> recent-tooltip-pointer-x: 0px;
|
private property <length> recent-tooltip-pointer-x: 0px;
|
||||||
private property <length> recent-tooltip-pointer-y: 0px;
|
private property <length> recent-tooltip-pointer-y: 0px;
|
||||||
private property <length> recent-tooltip-width: 260px;
|
|
||||||
private property <length> recent-tooltip-x: min(
|
|
||||||
root.width - root.recent-tooltip-width - 8px,
|
|
||||||
max(8px,
|
|
||||||
root.recent-tooltip-pointer-x > root.width * 0.7
|
|
||||||
? root.recent-tooltip-pointer-x - root.recent-tooltip-width - 12px
|
|
||||||
: root.recent-tooltip-pointer-x + 12px));
|
|
||||||
|
|
||||||
changed menu-open => { if root.menu-open { root.gallery-action("release", ""); } }
|
changed menu-open => { if root.menu-open { root.gallery-action("release", ""); } }
|
||||||
changed connection-dialog-open => { if root.connection-dialog-open { root.gallery-action("release", ""); } }
|
changed connection-dialog-open => { if root.connection-dialog-open { root.gallery-action("release", ""); } }
|
||||||
@@ -1433,16 +1428,25 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match the ImGui recent-connection tooltip while keeping it outside the
|
// Shared device details sit outside the clipped recent list and wall rail,
|
||||||
// clipped ScrollView. It is deliberately limited to the thumbnail area.
|
// below the modal layer (z >= 39).
|
||||||
// Stay under the modal layer (z >= 39): a hover tooltip that outlives the
|
if (root.gallery-mode ? gallery.hovered-device.id != "" : root.recent-tooltip-visible)
|
||||||
// pointer can otherwise cover a dialog button and hide it from the user.
|
&& !root.connection-dialog-open: device-tip := Rectangle {
|
||||||
if root.recent-tooltip-visible && !root.connection-dialog-open: Rectangle {
|
private property <string> device-name: root.gallery-mode ? gallery.hovered-device.name : root.recent-tooltip-name;
|
||||||
x: root.recent-tooltip-x;
|
private property <string> host-name: root.gallery-mode ? gallery.hovered-device.host-name : root.recent-tooltip-host;
|
||||||
|
private property <string> device-id: root.gallery-mode ? gallery.hovered-device.id : root.recent-tooltip-id;
|
||||||
|
private property <string> group-names: root.gallery-mode ? gallery.hovered-device.group-names : root.recent-tooltip-groups;
|
||||||
|
private property <bool> online: root.gallery-mode ? gallery.hovered-device.online : root.recent-tooltip-online;
|
||||||
|
private property <bool> presence-known: !root.gallery-mode || gallery.hovered-device.presence-known;
|
||||||
|
private property <length> pointer-x: root.gallery-mode ? gallery.x + gallery.device-tooltip-x : root.recent-tooltip-pointer-x;
|
||||||
|
private property <length> pointer-y: root.gallery-mode ? gallery.y + gallery.device-tooltip-y : root.recent-tooltip-pointer-y;
|
||||||
|
x: min(root.width - self.width - 8px,
|
||||||
|
max(8px, self.pointer-x > root.width * 0.7
|
||||||
|
? self.pointer-x - self.width - 12px : self.pointer-x + 12px));
|
||||||
y: min(root.height - root.titlebar-height - self.height - 8px,
|
y: min(root.height - root.titlebar-height - self.height - 8px,
|
||||||
max(8px, root.recent-tooltip-pointer-y - self.height - 8px));
|
max(8px, self.pointer-y - self.height - 8px));
|
||||||
width: root.recent-tooltip-width;
|
width: 260px;
|
||||||
height: (root.recent-tooltip-host != "" && root.recent-tooltip-host != root.recent-tooltip-name
|
height: (self.host-name != "" && self.host-name != self.device-name
|
||||||
? 80px : 64px) + tooltip-groups.preferred-height + 2px;
|
? 80px : 64px) + tooltip-groups.preferred-height + 2px;
|
||||||
background: #22252bf2;
|
background: #22252bf2;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
@@ -1454,34 +1458,35 @@ export component MainWindow inherits Window {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
spacing: 2px;
|
spacing: 2px;
|
||||||
Text {
|
Text {
|
||||||
text: UiStrings.device-name + ": " + root.recent-tooltip-name;
|
text: UiStrings.device-name + ": " + device-tip.device-name;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: ImGuiFontStyle.small;
|
font-size: ImGuiFontStyle.small;
|
||||||
overflow: elide;
|
overflow: elide;
|
||||||
}
|
}
|
||||||
if root.recent-tooltip-host != "" && root.recent-tooltip-host != root.recent-tooltip-name: Text {
|
if device-tip.host-name != "" && device-tip.host-name != device-tip.device-name: Text {
|
||||||
text: root.recent-tooltip-host;
|
text: device-tip.host-name;
|
||||||
color: #e1e4e8;
|
color: #e1e4e8;
|
||||||
font-size: ImGuiFontStyle.small;
|
font-size: ImGuiFontStyle.small;
|
||||||
overflow: elide;
|
overflow: elide;
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: UiStrings.remote-id + ": " + root.recent-tooltip-id;
|
text: UiStrings.remote-id + ": " + device-tip.device-id;
|
||||||
color: #e1e4e8;
|
color: #e1e4e8;
|
||||||
font-size: ImGuiFontStyle.small;
|
font-size: ImGuiFontStyle.small;
|
||||||
overflow: elide;
|
overflow: elide;
|
||||||
}
|
}
|
||||||
tooltip-groups := Text {
|
tooltip-groups := Text {
|
||||||
text: GalleryStrings.tr("分组", "Groups", "Группы") + ": " +
|
text: GalleryStrings.tr("分组", "Groups", "Группы") + ": " +
|
||||||
(root.recent-tooltip-groups == "" ? GalleryStrings.tr("未分组", "Ungrouped", "Без группы") : root.recent-tooltip-groups);
|
(device-tip.group-names == "" ? GalleryStrings.tr("未分组", "Ungrouped", "Без группы") : device-tip.group-names);
|
||||||
color: #e1e4e8;
|
color: #e1e4e8;
|
||||||
font-size: ImGuiFontStyle.small;
|
font-size: ImGuiFontStyle.small;
|
||||||
wrap: word-wrap;
|
wrap: word-wrap;
|
||||||
vertical-stretch: 0;
|
vertical-stretch: 0;
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: root.recent-tooltip-online ? UiStrings.online : UiStrings.offline;
|
text: device-tip.online ? UiStrings.online : device-tip.presence-known ? UiStrings.offline
|
||||||
color: root.recent-tooltip-online ? #62d98b : #d1d5db;
|
: GalleryStrings.tr("未知", "Unknown", "Неизвестно");
|
||||||
|
color: device-tip.online ? #62d98b : #d1d5db;
|
||||||
font-size: ImGuiFontStyle.small;
|
font-size: ImGuiFontStyle.small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user