mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 00:09:39 +08:00
[ui] streamline DeskHub toolbar layout and buttons
This commit is contained in:
@@ -268,7 +268,7 @@ void X11ActivateGalleryWindow(Display* display) {
|
||||
0, 256, False, XInternAtom(display, "UTF8_STRING", False),
|
||||
&name_type, &name_format, &length, &remaining, &name);
|
||||
const bool matches = name && name_format == 8 &&
|
||||
std::string_view(reinterpret_cast<char*>(name), length).starts_with("CrossDesk · ");
|
||||
std::string_view(reinterpret_cast<char*>(name), length).starts_with("CrossDesk ");
|
||||
if (name) XFree(name);
|
||||
if (!matches) continue;
|
||||
XEvent event{};
|
||||
|
||||
@@ -8,6 +8,8 @@ export component GalleryMenuButton inherits Rectangle {
|
||||
in property <bool> enabled: true;
|
||||
in property <bool> opens-up: false;
|
||||
in property <length> menu-width: 200px;
|
||||
in property <length> horizontal-padding: 12px;
|
||||
out property <length> preferred-button-width: ceil(button-label.preferred-width / 1px) * 1px + root.horizontal-padding * 2 + 18px;
|
||||
in property <[GalleryMenuItem]> items;
|
||||
private property <int> current-index: 0;
|
||||
callback opened;
|
||||
@@ -62,13 +64,13 @@ export component GalleryMenuButton inherits Rectangle {
|
||||
enabled: root.enabled; primary: root.primary;
|
||||
clicked => { trigger-focus.focus(); root.open-menu(); }
|
||||
}
|
||||
Text {
|
||||
x: 6px; width: parent.width - 24px; height: parent.height;
|
||||
button-label := Text {
|
||||
x: root.horizontal-padding; width: parent.width - root.horizontal-padding * 2 - 18px; height: parent.height;
|
||||
text: root.text; color: root.enabled ? #24272d : #8e9298;
|
||||
font-size: 11px; overflow: elide; horizontal-alignment: center; vertical-alignment: center;
|
||||
}
|
||||
Text {
|
||||
x: parent.width - 19px; width: 12px; height: parent.height;
|
||||
x: parent.width - root.horizontal-padding - 12px; width: 12px; height: parent.height;
|
||||
text: root.opens-up ? FontAwesomeIcons.angle-up : FontAwesomeIcons.angle-down;
|
||||
font-family: "Font Awesome 6 Free"; font-weight: 900; font-size: 9px;
|
||||
color: root.enabled ? #62666d : #8e9298;
|
||||
|
||||
@@ -3,7 +3,17 @@ import { DeviceCardActions, DeviceCardStyle } from "device_card_actions.slint";
|
||||
import { GalleryMenuButton } from "gallery_menu_button.slint";
|
||||
import { GroupFilter } from "group_filter.slint";
|
||||
import { ScrollView, LineEdit } from "std-widgets.slint";
|
||||
import { CompactButton, SecondaryButton, FontAwesomeIcons, ImGuiCheckBox } from "common.slint";
|
||||
import { CompactButton, SecondaryButton, IconButton, FontAwesomeIcons, ImGuiCheckBox } from "common.slint";
|
||||
|
||||
component GalleryToolbarButton inherits CompactButton {
|
||||
in property <string> alternate-text;
|
||||
in property <length> horizontal-padding: 12px;
|
||||
// Reserve both labels so toggling a mode never shifts adjacent controls.
|
||||
width: max(48px, ceil(max(label-width-probe.preferred-width,
|
||||
alternate-width-probe.preferred-width) / 1px) * 1px + root.horizontal-padding * 2);
|
||||
label-width-probe := Text { visible: false; text: root.text; font-size: root.text-size; }
|
||||
alternate-width-probe := Text { visible: false; text: root.alternate-text; font-size: root.text-size; }
|
||||
}
|
||||
|
||||
export struct GalleryTile {
|
||||
id: string,
|
||||
@@ -34,6 +44,13 @@ export struct GalleryActionDevice {
|
||||
export component GalleryView inherits Rectangle {
|
||||
in property <bool> controlling: false;
|
||||
in property <bool> simplified: false;
|
||||
in property <bool> fullscreen: false;
|
||||
callback toggle-simplified;
|
||||
callback toggle-fullscreen;
|
||||
// Keep long localized actions and the selection summary visible at 640px.
|
||||
private property <length> toolbar-button-padding: root.width < 720px ? 8px : 12px;
|
||||
private property <length> view-controls-width: compact-toggle.width + 30px;
|
||||
private property <length> view-controls-space: root.view-controls-width + 6px;
|
||||
in property <bool> window-active: true;
|
||||
in-out property <float> tile-width: 216;
|
||||
out property <bool> resizing: false;
|
||||
@@ -154,7 +171,7 @@ export component GalleryView inherits Rectangle {
|
||||
root.batch = false; root.adding = false; root.importing = false; root.confirming = false;
|
||||
root.device-tooltip-id = ""; add-menu.close-menu(); batch-menu.close-menu();
|
||||
}
|
||||
changed batch => { root.device-tooltip-id = ""; root.cancel-device-drag(); batch-menu.close-menu(); }
|
||||
changed batch => { root.device-tooltip-id = ""; root.cancel-device-drag(); add-menu.close-menu(); batch-menu.close-menu(); }
|
||||
function cancel-device-drag() {
|
||||
root.dragging = false; root.drag-source = ""; root.drag-page = -1;
|
||||
}
|
||||
@@ -235,40 +252,92 @@ export component GalleryView inherits Rectangle {
|
||||
function confirm-action(code: string, name: string) {
|
||||
root.action-code = code; root.action-name = name; root.confirming = true;
|
||||
}
|
||||
toolbar := HorizontalLayout {
|
||||
x: 29px; y: 12px; width: parent.width - 58px; height: 24px;
|
||||
// Swap toolbar contents in place so selection never moves the grid or pager.
|
||||
toolbar := Rectangle {
|
||||
x: 29px; y: 12px; width: parent.width - 58px - root.view-controls-space; height: 24px;
|
||||
visible: root.focused.id == "" && !root.simplified;
|
||||
spacing: 8px;
|
||||
GroupFilter { width: 155px; height: 24px; system-count: 1; model: root.groups; current-index <=> root.group-index;
|
||||
selected(index) => { root.action("group", index); }
|
||||
HorizontalLayout {
|
||||
x: 0px; y: 0px; width: parent.width - selection-toggle.width - 6px; height: parent.height;
|
||||
visible: !root.batch; spacing: 6px;
|
||||
GroupFilter { width: 155px; height: 24px; system-count: 1; model: root.groups; current-index <=> root.group-index;
|
||||
selected(index) => { root.action("group", index); }
|
||||
}
|
||||
Text {
|
||||
text: root.count + GalleryStrings.tr(" 台设备", " devices", " устройств");
|
||||
min-width: 0px; overflow: elide; font-size: 11px; color: #858c96; vertical-alignment: center;
|
||||
}
|
||||
Rectangle { horizontal-stretch: 1; min-width: 0px; }
|
||||
add-menu := GalleryMenuButton {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
width: self.preferred-button-width;
|
||||
text: GalleryStrings.tr("添加", "Add", "Добавить"); menu-width: 232px;
|
||||
items: [
|
||||
{label: GalleryStrings.tr("通过设备 ID 添加", "Add by device ID", "Добавить по ID устройства"), enabled: true},
|
||||
{label: GalleryStrings.tr("从近期连接添加", "Add from recent", "Добавить из недавних"), enabled: true},
|
||||
];
|
||||
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
||||
selected(index) => {
|
||||
if index == 0 { root.action("add-open", ""); root.adding = true; }
|
||||
else { root.action("recent-open", ""); root.importing = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
text: root.count + GalleryStrings.tr(" 台设备", " devices", " устройств");
|
||||
font-size: 11px; color: #858c96; vertical-alignment: center;
|
||||
selection-actions := HorizontalLayout {
|
||||
x: 0px; y: 0px; height: parent.height;
|
||||
visible: root.batch; width: parent.width - selection-toggle.width - 6px; spacing: 6px;
|
||||
Rectangle {
|
||||
horizontal-stretch: 1; min-width: 0px;
|
||||
Text {
|
||||
x: 0px; y: 0px; width: parent.width; height: root.hidden-selected > 0 ? 13px : parent.height;
|
||||
text: GalleryStrings.tr("已选 ", "Selected ", "Выбрано ") + root.selected-count + GalleryStrings.tr(" 台", "", "");
|
||||
color: #62666d; vertical-alignment: center; font-size: 11px; overflow: elide;
|
||||
}
|
||||
if root.hidden-selected > 0: Text {
|
||||
y: 14px; width: parent.width; height: 10px;
|
||||
text: GalleryStrings.tr("跨页 ", "Other pages ", "Вне страницы ") + root.hidden-selected;
|
||||
color: #80858d; font-size: 10px; overflow: elide;
|
||||
}
|
||||
}
|
||||
GalleryToolbarButton {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
enabled: root.count > 0;
|
||||
text: root.count > 0 && root.selected-count == root.count
|
||||
? GalleryStrings.tr("取消全选", "Deselect all", "Снять выбор")
|
||||
: GalleryStrings.tr("全选", "Select all", "Выбрать все");
|
||||
alternate-text: root.count > 0 && root.selected-count == root.count
|
||||
? GalleryStrings.tr("全选", "Select all", "Выбрать все")
|
||||
: GalleryStrings.tr("取消全选", "Deselect all", "Снять выбор");
|
||||
clicked => { root.action("all-select", ""); }
|
||||
}
|
||||
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; primary: true; enabled: root.selected-count > 0; text: GalleryStrings.tr("连接", "Connect", "Связь"); clicked => { root.action("connect", ""); } }
|
||||
GalleryToolbarButton { horizontal-padding: root.toolbar-button-padding; enabled: root.selected-count > 0; text: GalleryStrings.tr("断开", "Close", "Отключить"); clicked => { root.confirm-action("disconnect", self.text); } }
|
||||
batch-menu := GalleryMenuButton {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
width: self.preferred-button-width; enabled: root.selected-count > 0;
|
||||
text: GalleryStrings.tr("更多", "More", "Ещё");
|
||||
items: [
|
||||
{label: root.selected-audio-muted ? GalleryStrings.tr("取消静音", "Unmute", "Со звуком") : GalleryStrings.tr("静音", "Mute", "Без звука"), enabled: root.selected-audio-available},
|
||||
{label: GalleryStrings.tr("分发文件", "Send file", "Отправить файл"), enabled: root.selected-count > 0},
|
||||
];
|
||||
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
||||
selected(index) => {
|
||||
if index == 0 { root.action(root.selected-audio-muted ? "unmute" : "mute", ""); }
|
||||
else { root.confirm-action("file", GalleryStrings.tr("分发文件", "Send file", "Отправить файл")); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle { horizontal-stretch: 1; }
|
||||
CompactButton {
|
||||
width: 72px; quiet: true; primary: root.batch;
|
||||
selection-toggle := GalleryToolbarButton {
|
||||
x: parent.width - self.width; y: 0px;
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
primary: root.batch;
|
||||
alternate-text: GalleryStrings.tr("选择", "Select", "Выбрать");
|
||||
text: root.batch ? GalleryStrings.tr("完成", "Done", "Готово") : GalleryStrings.tr("选择", "Select", "Выбрать");
|
||||
clicked => { root.batch = !root.batch; root.action("selection", ""); }
|
||||
}
|
||||
add-menu := GalleryMenuButton {
|
||||
width: 90px; primary: true;
|
||||
text: GalleryStrings.tr("添加", "Add", "Добавить"); menu-width: 232px;
|
||||
items: [
|
||||
{label: GalleryStrings.tr("通过设备 ID 添加", "Add by device ID", "Добавить по ID устройства"), enabled: true},
|
||||
{label: GalleryStrings.tr("从近期连接添加", "Add from recent", "Добавить из недавних"), enabled: true},
|
||||
];
|
||||
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
||||
selected(index) => {
|
||||
if index == 0 { root.action("add-open", ""); root.adding = true; }
|
||||
else { root.action("recent-open", ""); root.importing = true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
grid := TouchArea {
|
||||
x: root.simplified ? 12px : toolbar.x;
|
||||
y: root.simplified ? 12px : toolbar.y + toolbar.height + 12px;
|
||||
y: toolbar.y + toolbar.height + 12px;
|
||||
width: parent.width - self.x * 2;
|
||||
height: footer.y - 12px - self.y;
|
||||
visible: root.focused.id == "";
|
||||
@@ -467,10 +536,10 @@ export component GalleryView inherits Rectangle {
|
||||
}
|
||||
focus-view := Rectangle {
|
||||
visible: root.focused.id != "";
|
||||
x: 29px; y: 7px; width: parent.width - 58px;
|
||||
x: 29px; y: toolbar.y; width: parent.width - 58px;
|
||||
height: footer.y - 12px - self.y;
|
||||
HorizontalLayout {
|
||||
x: 0px; y: 0px; width: parent.width; height: 28px; spacing: 8px;
|
||||
x: 0px; y: 0px; width: parent.width - root.view-controls-space; height: 24px; spacing: 8px;
|
||||
CompactButton { width: 52px; text: GalleryStrings.tr("返回", "Back", "Назад"); clicked => { root.action("focus", ""); } }
|
||||
Rectangle {
|
||||
horizontal-stretch: 1; min-width: 0px;
|
||||
@@ -603,13 +672,12 @@ export component GalleryView inherits Rectangle {
|
||||
y: parent.height - 44px; height: 44px; background: #fafbfc;
|
||||
Rectangle { y: 0px; width: parent.width; height: 1px; background: #e1e4e8; }
|
||||
}
|
||||
// Fixed selection widths keep the cards, actions and pager stationary.
|
||||
// Connection status and pagination remain available in every selection state.
|
||||
footer := Rectangle {
|
||||
x: root.simplified ? 12px : toolbar.x; y: parent.height - 34px;
|
||||
width: parent.width - self.x * 2; height: 24px;
|
||||
status-area := Rectangle {
|
||||
x: 0px; y: 0px; width: parent.width - 124px; height: parent.height;
|
||||
visible: !root.batch || root.simplified;
|
||||
connection-count := Text {
|
||||
x: 0px; width: min(self.preferred-width, parent.width - network-button.width - 12px); height: parent.height;
|
||||
text: root.connected-summary;
|
||||
@@ -626,45 +694,6 @@ export component GalleryView inherits Rectangle {
|
||||
clicked => { root.device-tooltip-id = ""; root.network-open(); }
|
||||
}
|
||||
}
|
||||
selection-actions := HorizontalLayout {
|
||||
x: 0px; y: 0px; height: parent.height;
|
||||
visible: root.batch && !root.simplified; width: 408px; spacing: 6px;
|
||||
Rectangle {
|
||||
width: 100px;
|
||||
Text {
|
||||
x: 0px; y: 0px; width: parent.width; height: root.hidden-selected > 0 ? 13px : parent.height;
|
||||
text: GalleryStrings.tr("已选 ", "Selected ", "Выбрано ") + root.selected-count + GalleryStrings.tr(" 台", "", "");
|
||||
color: #62666d; vertical-alignment: center; font-size: 11px; overflow: elide;
|
||||
}
|
||||
if root.hidden-selected > 0: Text {
|
||||
y: 14px; width: parent.width; height: 10px;
|
||||
text: GalleryStrings.tr("跨页 ", "Other pages ", "Вне страницы ") + root.hidden-selected;
|
||||
color: #80858d; font-size: 10px; overflow: elide;
|
||||
}
|
||||
}
|
||||
CompactButton {
|
||||
width: 90px; quiet: true; enabled: root.count > 0;
|
||||
text: root.count > 0 && root.selected-count == root.count
|
||||
? GalleryStrings.tr("取消全选", "Deselect all", "Снять выбор")
|
||||
: GalleryStrings.tr("全选", "Select all", "Выбрать все");
|
||||
clicked => { root.action("all-select", ""); }
|
||||
}
|
||||
CompactButton { width: 60px; primary: true; enabled: root.selected-count > 0; text: GalleryStrings.tr("连接", "Connect", "Связь"); clicked => { root.action("connect", ""); } }
|
||||
CompactButton { width: 64px; enabled: root.selected-count > 0; text: GalleryStrings.tr("断开", "Close", "Отключить"); clicked => { root.confirm-action("disconnect", self.text); } }
|
||||
batch-menu := GalleryMenuButton {
|
||||
width: 70px; enabled: root.selected-count > 0; opens-up: true;
|
||||
text: GalleryStrings.tr("更多", "More", "Ещё");
|
||||
items: [
|
||||
{label: root.selected-audio-muted ? GalleryStrings.tr("取消静音", "Unmute", "Со звуком") : GalleryStrings.tr("静音", "Mute", "Без звука"), enabled: root.selected-audio-available},
|
||||
{label: GalleryStrings.tr("分发文件", "Send file", "Отправить файл"), enabled: root.selected-count > 0},
|
||||
];
|
||||
opened => { root.device-tooltip-id = ""; root.action("release", ""); }
|
||||
selected(index) => {
|
||||
if index == 0 { root.action(root.selected-audio-muted ? "unmute" : "mute", ""); }
|
||||
else { root.confirm-action("file", GalleryStrings.tr("分发文件", "Send file", "Отправить файл")); }
|
||||
}
|
||||
}
|
||||
}
|
||||
pager := HorizontalLayout {
|
||||
x: parent.width - self.width; y: 0px; width: 112px; height: 24px; spacing: 6px;
|
||||
previous-page := CompactButton { primary: root.drag-page-direction < 0; width: 24px; text: "‹"; text-size: 16px; enabled: root.page > 0; clicked => { root.action("page", "previous"); } }
|
||||
@@ -672,6 +701,25 @@ export component GalleryView inherits Rectangle {
|
||||
next-page := CompactButton { primary: root.drag-page-direction > 0; width: 24px; text: "›"; text-size: 16px; enabled: root.page + 1 < root.pages; clicked => { root.action("page", "next"); } }
|
||||
}
|
||||
}
|
||||
// Paint the fullscreen tooltip above the grid/rail, below modal overlays.
|
||||
view-controls := HorizontalLayout {
|
||||
x: parent.width - (root.simplified ? 12px : 29px) - self.width;
|
||||
y: toolbar.y; width: root.view-controls-width; height: toolbar.height; spacing: 6px;
|
||||
compact-toggle := GalleryToolbarButton {
|
||||
horizontal-padding: root.toolbar-button-padding;
|
||||
primary: root.simplified;
|
||||
alternate-text: GalleryStrings.tr("退出精简", "Exit compact", "Обычный вид");
|
||||
text: root.simplified ? GalleryStrings.tr("退出精简", "Exit compact", "Обычный вид")
|
||||
: GalleryStrings.tr("精简模式", "Compact", "Компактно");
|
||||
clicked => { root.toggle-simplified(); }
|
||||
}
|
||||
IconButton {
|
||||
width: 24px; height: 24px;
|
||||
icon: root.fullscreen ? FontAwesomeIcons.compress : FontAwesomeIcons.expand;
|
||||
tooltip: root.fullscreen ? GalleryStrings.tr("退出全屏", "Exit fullscreen", "Выйти из полноэкранного режима") : GalleryStrings.tr("全屏", "Fullscreen", "На весь экран");
|
||||
clicked => { root.toggle-fullscreen(); }
|
||||
}
|
||||
}
|
||||
for edge in 2: Rectangle {
|
||||
visible: root.dragging && (edge == 0 ? root.page > 0 : root.page + 1 < root.pages);
|
||||
private property <bool> active: root.drag-page-direction == (edge == 0 ? -1 : 1);
|
||||
|
||||
@@ -265,7 +265,7 @@ component PasswordInput inherits Rectangle {
|
||||
}
|
||||
|
||||
export component MainWindow inherits Window {
|
||||
title: root.gallery-mode ? "CrossDesk · " + GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub") : "CrossDesk";
|
||||
title: root.gallery-mode ? "CrossDesk " + GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub") : "CrossDesk";
|
||||
icon: @image-url("../../../resources/linux/crossdesk_256x256.png");
|
||||
preferred-width: 640px;
|
||||
preferred-height: root.minimum-window-height;
|
||||
@@ -371,9 +371,8 @@ export component MainWindow inherits Window {
|
||||
in property <bool> window-active: true;
|
||||
private property <length> titlebar-height:
|
||||
root.custom-titlebar && !root.gallery-fullscreen ? (root.wayland-titlebar ? 32px : 31px) : 0px;
|
||||
private property <length> content-header-height: root.gallery-mode ? 38px : 0px;
|
||||
out property <length> minimum-window-height:
|
||||
448px + root.content-header-height + root.titlebar-height;
|
||||
448px + root.titlebar-height;
|
||||
private property <length> wayland-corner-radius: 12px;
|
||||
in property <string> local-id: "";
|
||||
in property <string> local-password: "";
|
||||
@@ -804,7 +803,6 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
|
||||
// The connection page shares its section heading row with the menu.
|
||||
// Only DeskHub needs a separate toolbar above its content.
|
||||
menu-button := IconButton {
|
||||
visible: !root.gallery-mode;
|
||||
x: parent.width - 29px - self.width;
|
||||
@@ -894,29 +892,8 @@ export component MainWindow inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
if root.gallery-mode: Text {
|
||||
x: 29px; y: root.content-header-height - self.height; width: 164px; height: 24px;
|
||||
text: GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub");
|
||||
color: #24272d; font-weight: 600; font-size: 12px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
if root.gallery-mode: CompactButton {
|
||||
x: parent.width - 29px - 24px - 8px - self.width;
|
||||
y: root.content-header-height - self.height; width: 94px; height: 24px;
|
||||
primary: root.gallery-simplified;
|
||||
text: root.gallery-simplified ? GalleryStrings.tr("退出精简", "Exit compact", "Обычный вид")
|
||||
: GalleryStrings.tr("精简模式", "Compact", "Компактно");
|
||||
clicked => { root.gallery-simplified = !root.gallery-simplified; }
|
||||
}
|
||||
if root.gallery-mode: IconButton {
|
||||
x: parent.width - 29px - self.width;
|
||||
y: root.content-header-height - self.height; width: 24px; height: 24px;
|
||||
icon: root.gallery-fullscreen ? FontAwesomeIcons.compress : FontAwesomeIcons.expand;
|
||||
tooltip: root.gallery-fullscreen ? GalleryStrings.tr("退出全屏", "Exit fullscreen", "Выйти из полноэкранного режима") : GalleryStrings.tr("全屏", "Fullscreen", "На весь экран");
|
||||
clicked => { root.toggle-gallery-fullscreen(); }
|
||||
}
|
||||
Rectangle {
|
||||
y: root.content-header-height; height: parent.height - self.y - 24px; visible: !root.gallery-mode;
|
||||
y: 0px; height: parent.height - self.y - 24px; visible: !root.gallery-mode;
|
||||
// Local and remote desktop panels.
|
||||
Rectangle {
|
||||
y: 0px;
|
||||
@@ -1337,10 +1314,13 @@ export component MainWindow inherits Window {
|
||||
|
||||
}
|
||||
gallery := GalleryView {
|
||||
y: root.content-header-height; width: parent.width;
|
||||
y: 0px; width: parent.width;
|
||||
height: parent.height - self.y;
|
||||
visible: root.gallery-mode;
|
||||
simplified: root.gallery-simplified;
|
||||
fullscreen: root.gallery-fullscreen;
|
||||
toggle-simplified => { root.gallery-simplified = !root.gallery-simplified; }
|
||||
toggle-fullscreen => { root.toggle-gallery-fullscreen(); }
|
||||
window-active: root.window-active;
|
||||
tile-width <=> root.gallery-tile-width;
|
||||
network-open => { root.gallery-network-open = true; }
|
||||
|
||||
+5
-5
@@ -2,9 +2,9 @@
|
||||
|
||||
主窗口“近期连接”标题行右侧、分组筛选左边的“桌面墙”按钮打开独立的桌面墙窗口。菜单与上方桌面标题共用一行,连接窗口保持紧凑尺寸。重复点击按钮会唤起已有桌面墙窗口,最小化时恢复显示,不创建重复窗口。每次启动软件只显示连接窗口;桌面墙首次打开时恢复保存的位置、尺寸及全屏状态;没有已保存尺寸时默认采用 960 × 720 逻辑像素(含标题栏,当前 macOS 的内容区为 960 × 688),两个窗口分别记忆位置。关闭桌面墙只隐藏该窗口并释放远程操控,保留设备连接;再次点击“桌面墙”即可打开。已断开的显示器所对应的位置会修正到可用屏幕内。
|
||||
|
||||
桌面墙窗口顶部显示“桌面墙”标题,右侧保留纯文字“精简模式”按钮和全屏图标,不显示菜单及消息、更新角标。设备管理栏集中显示分组、设备数量、“选择”和“添加”入口。桌面墙可以单独调整尺寸及全屏,不改变连接窗口的尺寸和位置。全局设置从主窗口菜单进入;设备操作和桌面墙连接的密码弹窗显示在桌面墙窗口内。
|
||||
桌面墙内容区不再重复显示“桌面墙”标题,顶部仅保留一行工具栏,距窗口内容区顶部 12 逻辑像素;分组、设备数量、“添加”、“选择/完成”、纯文字“精简模式”按钮和全屏图标共用这一行,不显示菜单及消息、更新角标。“添加”、“选择”和“精简模式”默认统一使用浅灰背景、灰色边框及相同的悬停、按下样式;进入选择或精简模式后,对应按钮以蓝色高亮表示当前状态。按钮按当前语言的文字宽度加统一左右内边距自适应,普通文字按钮宽度至少为 48 逻辑像素;内边距默认为 12 逻辑像素,窗口内容区宽度小于 720 时统一收紧为 8,保证最窄窗口下的多语言操作和已选数量可见。下拉按钮额外预留箭头及间距;“选择/完成”和“精简模式/退出精简”按两种状态中较宽的文字预留空间,切换时保持宽度稳定。“选择/完成”固定在视图控制按钮左侧,设备列表紧接工具栏下方,释放原有标题行占用的空间。桌面墙可以单独调整尺寸及全屏,不改变连接窗口的尺寸和位置。全局设置从主窗口菜单进入;设备操作和桌面墙连接的密码弹窗显示在桌面墙窗口内。
|
||||
|
||||
全屏按钮通过短暂淡出、等待窗口尺寸稳定、淡入完成切换,常规总时长约 0.2 秒;过渡期间屏蔽设备操作与重复全屏点击。窗口尺寸变化会立即更新网格,避免在新窗口中先显示旧的行列布局。过渡复用现有 UI 更新循环,不启动嵌套事件循环;失败或超时会恢复内容与交互。
|
||||
全屏按钮的提示绘制在设备网格和放大视图上方,靠右对齐并保持在窗口内。全屏按钮通过短暂淡出、等待窗口尺寸稳定、淡入完成切换,常规总时长约 0.2 秒;过渡期间屏蔽设备操作与重复全屏点击。窗口尺寸变化会立即更新网格,避免在新窗口中先显示旧的行列布局。过渡复用现有 UI 更新循环,不启动嵌套事件循环;失败或超时会恢复内容与交互。
|
||||
|
||||
可见的普通窗口若低于连接页最小尺寸,桌面墙恢复上次有效大小,连接页恢复固定大小;最小化、隐藏和全屏窗口不会触发这一恢复。窗口恢复以实际尺寸确认完成,期间不记录过渡尺寸;macOS 的最大化恢复使用无动画原生窗口调整,避免系统缩放动画嵌套事件循环。
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
- “近期连接”标题行右侧可按“全部设备”、未分组或已有分组筛选;一个设备可出现在它所属的多个分组中。筛选与桌面墙当前分组独立,不改变历史记录、在线状态订阅或桌面墙成员。分组修改后自动更新结果;当前分组被清空删除时回到全部设备。
|
||||
- 网格中每台设备的预览区域统一固定为 16:9,普通模式的设备名称栏位于画面下方;其他比例的远端桌面留边完整显示。设备信息提示显示在该设备预览区域内部左下角,宽高受画面范围限制;过长的名称省略显示,保留 ID 和在线状态,不遮挡相邻设备或底部操作栏,精简模式及放大视图的缩略设备同样适用。设备直接放在连续表格的单元格中,单元格之间没有卡片间距,共用贯通整行、整列的单条分隔线;空单元格保留表格线,不再为每台设备绘制独立边框和右下角拖拽标记。拖动任意横向、竖向分隔线或交点,可同步放大、缩小全部单元格,预览区域始终保持 16:9;分隔线两侧的拖动方向一致。普通和精简模式共用单元格宽度,表格按实际尺寸自动换行、调整分页容量;窗口较小时限制格子大小,避免超出可用区域,窗口恢复后继续使用原尺寸。宽度至少为 184 逻辑像素、至多为 960 逻辑像素,并受当前可用区域限制;松开鼠标后保存尺寸,重启后恢复。拖动分隔线不会触发展开、选择或设备操作。翻页不关闭连接,分组内顺序不会按在线状态自动重排。
|
||||
- 按住单元格内的设备画面或名称拖动,可与另一台设备交换位置;目标单元格高亮,鼠标旁显示设备缩略图和松开提示。跨页时保持按住鼠标,将设备拖到窗口内容区左、右边缘内侧 32 逻辑像素范围,或上一页、下一页按钮,悬停 700 毫秒后翻页;边缘显示方向提示并在触发区域高亮,继续停留可连续翻页,到首尾页自动停止。翻页期间保留拖动源,末页设备不足一整页时也可继续拖动,移到目标设备后松开完成交换;离开翻页区域会重新计算悬停时间,在可翻页的边缘区域或按钮上松开只取消拖动,不会误与边缘下方的设备换位;窗口外不触发翻页。普通、精简及批量选择模式均支持,短距离点击仍执行原来的查看或选择操作。拖到自身、空单元格、分隔线或表格外不会换位;Esc、窗口失焦、切换分组或调整窗口大小会取消拖动。交换后的顺序保存到本地,并应用于所有分组视图,不修改分组、批量选择、连接或静音状态;保存失败时保持原顺序并提示重试。仅活动会话也可换位,排序不会自动将它加入保存的桌面墙成员。
|
||||
- 顶部“精简模式”切换为仅画面的设备卡片,隐藏名称、在线状态、状态文字和悬停操作条,同时收起设备管理工具栏;画面完整等比显示,不裁切。顶部固定宽度的文字按钮高亮显示“退出精简”,不带图标;全屏入口仍可见。底部保留连接计数、网络详情入口和分页,不显示服务器状态栏。点击图片仍可展开查看和接管操控,返回后保持精简模式;模式切换会释放操控、退出批量选择并返回网格,不改变连接、分组和设备顺序。
|
||||
- 顶部“精简模式”切换为仅画面的设备卡片,隐藏名称、在线状态、状态文字和悬停操作条,同时收起设备管理工具栏;画面完整等比显示,不裁切。顶部保持当前语言下宽度稳定的文字按钮高亮显示“退出精简”,不带图标;全屏入口仍可见。底部保留连接计数、网络详情入口和分页,不显示服务器状态栏。点击图片仍可展开查看和接管操控,返回后保持精简模式;模式切换会释放操控、退出批量选择并返回网格,不改变连接、分组和设备顺序。
|
||||
- 主窗口菜单中的全局“设置”顶部并排提供“常规”和“桌面墙”两个选项卡;“桌面墙”可统一配置桌面墙设备的传输分辨率上限和帧率,桌面墙处于普通、精简、展开或全屏视图时,也可通过主窗口菜单打开设置。切换选项卡保留未保存修改,底部统一保存或取消。分辨率支持自动、320 × 180、640 × 360、1280 × 720、1920 × 1080、2560 × 1440、3840 × 2160和原始分辨率;固定上限适用于缩略图和展开画面,保持比例且不放大源画面。帧率支持跟随设备设置、15、30、60 FPS。保存后立即更新当前桌面墙会话,也用于后续桌面墙连接,不影响普通连接,跨分组和分页生效;取消不应用修改,恢复默认需保存后生效。配置保存在本地,重启后保留;保存失败时保留面板和输入。打开设置会释放远程操控。
|
||||
- 服务器连接状态、指示灯和重连入口仅在主窗口显示,桌面墙的普通、精简、展开、批量选择和全屏视图均不显示服务器状态栏。桌面墙底部操作栏左侧以普通文字显示已连接设备数量,旁边是紧凑的“网络状态”按钮;点击按钮可打开当前已建立连接的设备列表,空白区域不响应点击,也不显示整条悬停提示。弹窗采用紧凑宽度,高度随设备数量调整,最多同时显示 6 行,更多设备通过滚动查看。网络列表不受桌面墙分组和分页限制;离线、未连接和连接中的设备不列入,断开后自动移除。列表显示设备名称、ID、直连/中继、实际协商的 SRTP 加密状态、往返延迟(RTT)、控制端接收与发送速率、视频丢包率、实际接收分辨率与帧率,打开期间每秒刷新。加密状态紧随连接方式显示为“已加密/未加密”,尚无统计或报告超过 3 秒未更新时显示“待确认”。分辨率与帧率在解码交付时统计,墙内预览和后台会话均可用;尚未收到画面时显示“—”,画面停止更新超过 1 秒时帧率显示 0 FPS。网络数值尚无统计或报告超过 3 秒未更新时显示“—”。可通过关闭按钮、Esc 或点击列表外关闭;打开列表会释放远程操控。
|
||||
- 点卡片进入只读放大视图,点“接管操控”才开始发送键鼠。点“释放操控”或按 Ctrl/⌘ + Shift + Esc 可以释放。切换设备、翻页、打开本地操作面板、切换模式及窗口失焦也会释放。
|
||||
- 桌面墙连接默认静音,包含新建连接、重连、复用已有连接、添加或导入已有会话,以及切入桌面墙时已有的连接。展开、全屏或接管操控不会自动开启声音;需要声音时,可通过批量“取消静音”或在独立串流窗口主动开启。两个入口共用同一静音状态,主动开启的声音在桌面墙可见时播放,翻页、展开及释放操控保留声音设置;每台设备使用独立音频流同时播放,静音一台不会清空其他设备的声音。新版本对端按会话关闭音频订阅;旧版本发送的音频由控制端丢弃,不通过停止对端的全局声音采集影响其他控制端。
|
||||
- 放大视图使用一行设备操作栏,只保留返回、设备名称、状态和接管/释放操控按钮;选中的桌面在下方放大显示,同页其他桌面的缩略图统一在上方横向排列,超出宽度时可横向滚动。点击上方缩略图可切换下方桌面,当前桌面不在缩略图中重复显示;同页只有一台设备时隐藏缩略图栏。普通模式缩略图下方以一行显示设备名,长名称省略,不显示状态圆点;不在线设备的缩略图和下方放大预览统一使用灰色,精简模式同样生效;悬停可查看设备名称、ID 和在线状态,精简模式也支持悬停查看。缺少画面或画面停滞时保留状态占位;滚动条单独留出空间。普通和精简模式的缩略图预览区域均固定为 16:9,普通模式的设备名称单独显示在画面下方。普通、精简、宽窗口及全屏均保持上下布局,调整尺寸后仍保留当前设备所在页。主预览和缩略图均完整等比显示,鼠标坐标按实际预览区域映射。
|
||||
- 放大视图使用一行设备操作栏,显示返回、设备名称、状态和接管/释放操控按钮,右侧保留精简模式及全屏入口;选中的桌面在下方放大显示,同页其他桌面的缩略图统一在上方横向排列,超出宽度时可横向滚动。点击上方缩略图可切换下方桌面,当前桌面不在缩略图中重复显示;同页只有一台设备时隐藏缩略图栏。普通模式缩略图下方以一行显示设备名,长名称省略,不显示状态圆点;不在线设备的缩略图和下方放大预览统一使用灰色,精简模式同样生效;悬停可查看设备名称、ID 和在线状态,精简模式也支持悬停查看。缺少画面或画面停滞时保留状态占位;滚动条单独留出空间。普通和精简模式的缩略图预览区域均固定为 16:9,普通模式的设备名称单独显示在画面下方。普通、精简、宽窗口及全屏均保持上下布局,调整尺寸后仍保留当前设备所在页。主预览和缩略图均完整等比显示,鼠标坐标按实际预览区域映射。
|
||||
- 在“近期连接”中点击已连接设备的卡片画面,可打开原有独立串流窗口,使用声音、显示器切换、隐私屏、文件传输等完整单会话工具。关闭墙内设备的独立窗口不会断开墙中的会话。
|
||||
- 点击“选择”进入批量模式,“完成”退出;批量选择复用固定底栏,设备卡片的位置和尺寸不变。底栏中的已选数量和“全选/取消全选”使用固定宽度,切换选中数量或按钮文字时,后续按钮和分页保持原位。点击“全选”会选中当前筛选下的所有设备,跨页生效;全部选中后显示“取消全选”。翻页保留选择,切换分组清空选择;已选数量与跨页数量、连接、断开、“更多”及分页控件在同一底栏显示;静音和文件分发收进“更多”菜单。声音菜单项根据已选且已连接设备的状态切换:全部静音时显示“取消静音”,有设备开启声音时显示“静音”,无已连接设备时禁用。点击后统一设置所有已选的已连接设备,包含跨页选择,跳过未连接设备,保留当前选择。断开和文件分发会先列出完整目标;提交后的操作目标不会跟随之后的选择变化。
|
||||
- 点击“选择”进入批量模式,“完成”在同一位置退出;顶部设备管理栏原位切换为批量操作栏,从左到右显示已选数量(包含跨页数量)、“全选/取消全选”、“连接”、“断开”、“更多”和“完成”,无需往返底栏。批量模式暂时收起分组筛选和“添加”入口,退出后恢复;工具栏高度不变,设备卡片的位置、尺寸及分页容量保持不变。已选数量占用剩余空间,批量操作按钮同样按文字宽度和统一内边距自适应;“全选/取消全选”使用与“断开”、“更多”一致的浅灰背景和边框,并预留两种文字中较宽的一种,切换选中数量或按钮文字时,后续按钮保持原位。“连接”保留主操作的蓝色强调,“完成”以蓝色表示当前选择模式。点击“全选”会选中当前筛选下的所有设备,跨页生效;全部选中后显示“取消全选”。翻页保留选择,切换分组清空选择;底栏始终保留已连接数量、网络状态入口和分页;静音和文件分发收进顶部向下展开的“更多”菜单。声音菜单项根据已选且已连接设备的状态切换:全部静音时显示“取消静音”,有设备开启声音时显示“静音”,无已连接设备时禁用。点击后统一设置所有已选的已连接设备,包含跨页选择,跳过未连接设备,保留当前选择。断开和文件分发会先列出完整目标;提交后的操作目标不会跟随之后的选择变化。
|
||||
|
||||
## 预览与兼容性
|
||||
|
||||
|
||||
Reference in New Issue
Block a user