[ui] refine desktop controls and align localized copy

This commit is contained in:
dijunkun
2026-10-26 13:00:03 +08:00
parent dad8384666
commit 778c3575cd
13 changed files with 311 additions and 229 deletions
+87 -45
View File
@@ -4,7 +4,7 @@ export global FloatingBarStyle {
out property <length> corner-radius: UiRadius.menu;
}
// Filled controls remain distinct from white cards without restoring outlines.
// Fields and selection controls share a muted fill.
export global ControlStyle {
out property <color> surface: #dce6f3;
out property <color> hover: #cedef1;
@@ -23,6 +23,20 @@ export global ControlStyle {
out property <color> input-focus-tint: #235eaa1e;
}
// Action buttons use a light surface and a crisp edge, distinct from fields.
export global ButtonStyle {
out property <color> surface: #ffffff;
out property <color> hover: #f0f5ff;
out property <color> pressed: #dfe9fa;
out property <color> border: #b8c7db;
out property <color> hover-border: #7e9fd1;
out property <color> pressed-border: #6488bf;
out property <color> disabled-border: #dce2eb;
out property <color> selected: #3564e3;
out property <color> selected-hover: #4574f0;
out property <color> selected-pressed: #254cc0;
}
// ImGui loads a 32px font atlas and applies per-window font scales. These
// logical sizes reproduce those visual tiers on Slint's HiDPI renderer.
export global ImGuiFontStyle {
@@ -218,43 +232,48 @@ export component CompactComboBox inherits ShadowSurface {
width: 73px;
height: 24px;
background: !root.enabled ? ControlStyle.disabled
: combo-touch.pressed || combo-popup.is-open ? ControlStyle.pressed
: combo-touch.has-hover ? ControlStyle.hover : ControlStyle.surface;
shadow-color: root.enabled && combo-touch.has-hover ? root.hover-shadow-color : root.normal-shadow-color;
: combo-touch.pressed ? ButtonStyle.pressed
: combo-popup.is-open || combo-touch.has-hover ? ButtonStyle.hover : ButtonStyle.surface;
border-width: 1px;
border-color: !root.enabled ? ButtonStyle.disabled-border
: combo-popup.is-open ? UiShadow.focus
: combo-touch.pressed ? ButtonStyle.pressed-border
: combo-touch.has-hover ? ButtonStyle.hover-border : ButtonStyle.border;
shadow-enabled: root.enabled && !combo-touch.pressed;
shadow-focused: root.enabled && combo-popup.is-open;
shadow-color: combo-popup.is-open ? UiShadow.focus
: root.enabled && combo-touch.has-hover ? root.hover-shadow-color : root.normal-shadow-color;
border-radius: UiRadius.control;
animate background, border-color, shadow-color { duration: 120ms; }
accessible-role: combobox;
accessible-enabled: root.enabled;
accessible-expandable: true;
accessible-expanded: combo-popup.is-open;
accessible-value: root.model[root.current-index];
Text {
x: root.subtle ? 10px : 5px;
width: parent.width - (root.show-arrow ? (root.subtle ? 36px : 24px) : (root.subtle ? 20px : 10px));
x: root.subtle ? 10px : 8px;
width: parent.width - self.x - (root.show-arrow ? 28px : self.x);
height: parent.height;
text: root.model[root.current-index];
color: root.enabled ? #202124 : #8b8f95;
font-size: root.subtle ? 12px : ImGuiFontStyle.body;
overflow: elide;
vertical-alignment: center;
accessible-role: none;
}
if root.show-arrow: Rectangle {
x: parent.width - (root.subtle ? 27px : 21px);
width: 20px;
height: parent.height - 2px;
y: 1px;
background: root.subtle ? transparent : root.enabled ? ControlStyle.selected : #d7d9dc;
border-top-right-radius: UiRadius.control - 1px;
border-bottom-right-radius: UiRadius.control - 1px;
Text {
text: FontAwesomeIcons.angle-down;
color: root.subtle ? ControlStyle.icon : #202124;
font-family: "Font Awesome 6 Free";
font-weight: 900;
font-size: 10px;
horizontal-alignment: center;
vertical-alignment: center;
transform-rotation: combo-popup.is-open ? 180deg : 0deg;
animate transform-rotation {
duration: 140ms;
easing: ease-out;
enabled: combo-popup.is-open;
}
}
if root.show-arrow: Text {
x: parent.width - 26px;
width: 18px; height: parent.height;
// Switch glyphs so the software renderer also shows the open state.
text: combo-popup.is-open ? FontAwesomeIcons.angle-up : FontAwesomeIcons.angle-down;
color: !root.enabled ? #98a2b3 : combo-popup.is-open ? UiShadow.focus : ControlStyle.icon;
font-family: "Font Awesome 6 Free";
font-weight: 900;
font-size: 10px;
horizontal-alignment: center;
vertical-alignment: center;
accessible-role: none;
}
combo-touch := TouchArea {
enabled: root.enabled; mouse-cursor: pointer;
@@ -282,9 +301,9 @@ export component CompactComboBox inherits ShadowSurface {
combo-popup := PopupWindow {
x: 0px;
y: root.height;
y: root.height + 4px;
width: root.width;
height: root.model.length * 23px;
height: root.model.length * 23px + 8px;
close-policy: close-on-click-outside;
ShadowSurface {
@@ -299,20 +318,29 @@ export component CompactComboBox inherits ShadowSurface {
border-radius: UiRadius.menu;
clip: true;
for choice[index] in root.model: Rectangle {
y: index * 23px;
width: parent.width;
x: 4px; y: 4px + index * 23px;
width: parent.width - 8px;
height: 23px;
border-radius: UiRadius.control;
background: index == root.current-index
? (option-touch.pressed ? ControlStyle.selected-pressed : option-touch.has-hover ? ControlStyle.selected-hover : ControlStyle.selected)
: option-touch.pressed ? ControlStyle.pressed : option-touch.has-hover ? ControlStyle.hover : white;
? (option-touch.pressed ? ButtonStyle.pressed : ButtonStyle.hover)
: option-touch.pressed ? ButtonStyle.pressed : option-touch.has-hover ? ButtonStyle.hover : white;
Text {
x: 5px; width: parent.width - 10px; text: choice;
x: 6px; width: parent.width - 28px; height: parent.height; text: choice;
color: index == root.current-index ? ControlStyle.selected-text : #202124;
font-weight: index == root.current-index ? 600 : 400;
font-size: root.subtle ? 12px : ImGuiFontStyle.body; overflow: elide; vertical-alignment: center;
}
if index == root.current-index: Text {
x: parent.width - 20px; width: 14px; height: parent.height;
text: FontAwesomeIcons.check; color: UiShadow.focus;
font-family: "Font Awesome 6 Free"; font-weight: 900; font-size: 10px;
horizontal-alignment: center; vertical-alignment: center;
accessible-role: none;
}
option-touch := TouchArea {
enabled: root.enabled;
mouse-cursor: pointer;
clicked => {
popup-open-timer.running = false;
root.popup-animation-enabled = false;
@@ -332,6 +360,8 @@ export component CompactComboBox inherits ShadowSurface {
clip: true;
shadow-color: root.normal-shadow-color;
border-radius: UiRadius.menu;
border-width: 1px;
border-color: ButtonStyle.border;
z: 2;
}
}
@@ -401,11 +431,18 @@ export component CompactButton inherits ShadowSurface {
width: 34px;
height: 24px;
background: !root.enabled ? (root.quiet ? transparent : ControlStyle.disabled)
: button-touch.pressed ? (root.primary ? #82b8f2 : ControlStyle.pressed)
: button-touch.has-hover ? (root.primary ? #b8d9fb : ControlStyle.hover)
: root.primary ? #a8d0fa : root.quiet ? transparent : ControlStyle.surface;
shadow-color: root.primary ? UiShadow.focus : root.quiet ? transparent : UiShadow.edge;
: button-touch.pressed ? (root.primary ? #82b8f2 : ButtonStyle.pressed)
: button-touch.has-hover ? (root.primary ? #b8d9fb : ButtonStyle.hover)
: root.primary ? #a8d0fa : root.quiet ? transparent : ButtonStyle.surface;
border-width: root.quiet ? 0px : 1px;
border-color: !root.enabled ? ButtonStyle.disabled-border
: root.primary ? UiShadow.focus
: button-touch.pressed ? ButtonStyle.pressed-border
: button-touch.has-hover ? ButtonStyle.hover-border : ButtonStyle.border;
shadow-enabled: root.enabled && !root.quiet && !button-touch.pressed;
shadow-color: root.primary ? UiShadow.focus : button-touch.has-hover ? UiShadow.hover : UiShadow.edge;
border-radius: UiRadius.control;
animate background, border-color { duration: 100ms; }
Text {
text: root.text;
color: root.enabled ? #24272d : #8e9298;
@@ -414,7 +451,7 @@ export component CompactButton inherits ShadowSurface {
horizontal-alignment: center;
vertical-alignment: center;
}
button-touch := TouchArea { enabled: root.enabled; clicked => { root.clicked(); } }
button-touch := TouchArea { enabled: root.enabled; mouse-cursor: pointer; clicked => { root.clicked(); } }
}
export component PrimaryButton inherits ShadowSurface {
@@ -459,13 +496,17 @@ export component SecondaryButton inherits ShadowSurface {
min-width: 72px;
height: 32px;
background: !root.enabled ? ControlStyle.disabled
: touch.pressed ? ControlStyle.pressed
: touch.has-hover ? ControlStyle.hover : ControlStyle.surface;
shadow-color: UiShadow.edge;
: touch.pressed ? ButtonStyle.pressed
: touch.has-hover ? ButtonStyle.hover : ButtonStyle.surface;
border-width: 1px;
border-color: !root.enabled ? ButtonStyle.disabled-border
: touch.pressed ? ButtonStyle.pressed-border
: touch.has-hover ? ButtonStyle.hover-border : ButtonStyle.border;
shadow-color: touch.has-hover ? UiShadow.hover : UiShadow.edge;
shadow-compact: true;
shadow-enabled: root.enabled && !touch.pressed;
border-radius: UiRadius.control;
animate background { duration: 100ms; }
animate background, border-color { duration: 100ms; }
Text {
text: root.text;
@@ -478,6 +519,7 @@ export component SecondaryButton inherits ShadowSurface {
touch := TouchArea {
enabled: root.enabled;
mouse-cursor: pointer;
clicked => { root.clicked(); }
}
}
@@ -3,4 +3,16 @@ export global GalleryStrings {
public pure function tr(zh: string, en: string, ru: string) -> string {
return self.language == 0 ? zh : self.language == 2 ? ru : en;
}
public pure function device-count(count: int) -> string {
if self.language == 0 { return count + " 台设备"; }
if self.language == 2 {
let last-two = mod(count, 100);
let last = mod(count, 10);
if last-two >= 11 && last-two <= 14 { return count + " устройств"; }
if last == 1 { return count + " устройство"; }
if last >= 2 && last <= 4 { return count + " устройства"; }
return count + " устройств";
}
return count + (count == 1 ? " device" : " devices");
}
}
@@ -67,7 +67,7 @@ component NetworkRow inherits Rectangle {
text: !root.device.encryption-known
? GalleryStrings.tr("待确认", "Unknown", "Неизвестно")
: root.device.encryption-enabled
? GalleryStrings.tr("已加密", "Encrypted", "Защищено")
? GalleryStrings.tr("已加密", "Encrypted", "Зашифровано")
: GalleryStrings.tr("未加密", "Unencrypted", "Без шифр.");
color: !root.device.encryption-known ? #7a8490
: root.device.encryption-enabled ? #18794e : #8a6428;
@@ -208,7 +208,7 @@ export component GalleryNetworkDialog inherits Rectangle {
}
Text {
x: 20px; y: 17px; width: parent.width - 120px; height: 24px;
text: GalleryStrings.tr("设备网络状态", "Device network status", "Сеть устройств");
text: GalleryStrings.tr("设备网络状态", "Device network status", "Состояние сети устройств");
font-size: 17px; font-weight: 600; color: #293746;
}
CompactButton {
@@ -11,7 +11,7 @@ export component GallerySettings inherits Rectangle {
y: 0px; width: parent.width;
title: GalleryStrings.tr("画面传输", "Video transmission", "Передача видео"); rows: 3;
SettingsRow {
label: GalleryStrings.tr("分辨率上限", "Resolution limit", "Разрешение");
label: GalleryStrings.tr("分辨率上限", "Resolution limit", "Макс. разрешение");
label-reserve: self.width - 112px;
SettingsSelect {
border-radius: UiRadius.control;
@@ -33,7 +33,7 @@ export component GallerySettings inherits Rectangle {
}
}
SettingsRow {
label: GalleryStrings.tr("视频总带宽", "Video budget", "Бюджет видео");
label: GalleryStrings.tr("视频总带宽", "Total bandwidth", "Общий битрейт");
label-reserve: self.width - 112px; divider: false;
SettingsSelect {
border-radius: UiRadius.control;
+21 -21
View File
@@ -290,7 +290,7 @@ export component GalleryView inherits Rectangle {
selected(index) => { root.action("group", index); }
}
Text {
text: root.count + GalleryStrings.tr(" 台设备", " devices", " устройств");
text: GalleryStrings.device-count(root.count);
min-width: 0px; overflow: elide; font-size: 11px; color: #858c96; vertical-alignment: center;
}
Rectangle { horizontal-stretch: 1; min-width: 0px; }
@@ -336,8 +336,8 @@ export component GalleryView inherits Rectangle {
: 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); } }
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("断开", "Disconnect", "Отключить"); 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;
@@ -579,7 +579,7 @@ export component GalleryView inherits Rectangle {
background: root.controlling ? #e7f1fd : #f0f2f5;
focus-state := Text {
x: 8px; width: parent.width - 16px; height: parent.height;
text: root.group-controlling ? GalleryStrings.tr("群控中", "Group control", "Групповое")
text: root.group-controlling ? GalleryStrings.tr("群控中", "Group control", "Групп. управление")
: root.controlling ? GalleryStrings.tr("正在操控", "Controlling", "Управление")
: root.focused.connected && root.focused.has-frame && !root.focused.stale
? GalleryStrings.tr("只读预览", "View only", "Просмотр") : root.focused.status;
@@ -590,8 +590,8 @@ export component GalleryView inherits Rectangle {
horizontal-padding: root.toolbar-button-padding;
primary: root.group-controlling;
enabled: root.group-controlling || (root.focused.connected && root.focused.has-frame && !root.focused.stale);
text: root.group-controlling ? GalleryStrings.tr("关闭群控", "Stop group", "Откл. группу") : GalleryStrings.tr("开启群控", "Group control", "Групп. ввод");
alternate-text: root.group-controlling ? GalleryStrings.tr("开启群控", "Group control", "Групп. ввод") : GalleryStrings.tr("关闭群控", "Stop group", "Откл. группу");
text: root.group-controlling ? GalleryStrings.tr("关闭群控", "Stop group control", "Откл. групп. ввод") : GalleryStrings.tr("开启群控", "Group control", "Групп. ввод");
alternate-text: root.group-controlling ? GalleryStrings.tr("开启群控", "Group control", "Групп. ввод") : GalleryStrings.tr("关闭群控", "Stop group control", "Откл. групп. ввод");
accessible-role: button;
accessible-label: self.text + GalleryStrings.tr(":同步键鼠到所有已连接设备", ": mirror input to all connected devices", ": ввод на всех подключённых устройствах");
accessible-action-default => { self.clicked(); }
@@ -846,7 +846,7 @@ export component GalleryView inherits Rectangle {
if root.dragging: ShadowSurface {
x: clamp(grid.x + root.drag-x + 14px, 8px, parent.width - self.width - 8px);
y: clamp(grid.y + root.drag-y + 14px, 8px, footer.y - self.height - 12px);
width: 224px; height: 54px; z: 10;
width: 224px; height: max(54px, drag-hint.y + drag-hint.height + 9px); z: 10;
background: #fffffff2; shadow-color: UiShadow.focus; border-radius: UiRadius.surface;
Rectangle {
x: 8px; y: 9px; width: 64px; height: 36px;
@@ -857,12 +857,12 @@ export component GalleryView inherits Rectangle {
}
}
Text { x: 80px; y: 8px; width: parent.width - 88px; height: 18px; text: root.dragged-tile.name; font-size: 12px; color: #303943; overflow: elide; }
Text {
x: 80px; y: 29px; width: parent.width - 88px; height: 16px;
text: root.drag-page-direction != 0 ? GalleryStrings.tr("停留翻页,松开取消", "Hold to turn page", "Удерживайте для перелистывания")
drag-hint := Text {
x: 80px; y: 29px; width: parent.width - 88px; height: max(16px, self.preferred-height);
text: root.drag-page-direction != 0 ? GalleryStrings.tr("停留翻页,松开取消", "Hold to turn page; release to cancel", "Удерживайте для перелистывания; отпустите для отмены")
: root.drag-target >= 0 ? GalleryStrings.tr("松开鼠标交换位置", "Release to swap", "Отпустите для обмена")
: GalleryStrings.tr("拖到另一台设备上", "Drag onto another device", "На другое устройство");
font-size: 10px; color: #527ca8; overflow: elide;
: GalleryStrings.tr("拖到另一台设备上", "Drag onto another device", "Перетащите на другое устройство");
font-size: 10px; color: #527ca8; wrap: word-wrap;
}
}
if root.swap-save-failed: ShadowSurface {
@@ -922,10 +922,10 @@ export component GalleryView inherits Rectangle {
TouchArea { clicked => { remember.checked = !remember.checked; } }
}
Text { x: 18px; y: 216px; width: 364px; height: 28px; text: root.notice; color: #8e4740; font-size: 11px; wrap: word-wrap; overflow: elide; }
GalleryButton { x: 172px; y: 260px; width: 72px; height: 28px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { password.text = ""; root.adding = false; } }
GalleryButton { x: 136px; y: 260px; width: 72px; height: 28px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { password.text = ""; root.adding = false; } }
GalleryButton {
x: 254px; y: 260px; width: 128px; height: 28px; primary: true;
text: GalleryStrings.tr("添加并连接", "Add and connect", "Подключить");
x: 218px; y: 260px; width: 164px; height: 28px; primary: true;
text: GalleryStrings.tr("添加并连接", "Add and connect", "Добавить и подключить");
enabled: device-id.text != "" && password.text != "";
clicked => { add-dialog.submit(); }
}
@@ -937,8 +937,8 @@ export component GalleryView inherits Rectangle {
ShadowSurface {
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 440px; height: 378px;
background: white; border-radius: UiRadius.surface; shadow-color: UiShadow.edge;
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.tr("从最近连接添加", "Add from recent connections", "Добавить из недавних"); font-size: 16px; }
Text { x: 18px; y: 44px; width: 404px; height: 18px; text: GalleryStrings.tr("加入桌面墙后,可通过批量选择连接", "Add devices, then use Select to connect them.", "После добавления подключайтесь через выбор."); color: #62666d; font-size: 11px; }
Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.tr("从近期连接添加", "Add from recent connections", "Добавить из недавних"); font-size: 16px; }
Text { x: 18px; y: 44px; width: 404px; height: 18px; text: GalleryStrings.tr("加入桌面墙后,可通过批量选择连接", "Add to DeskHub, then select devices to connect.", "Добавьте в DeskHub, затем выберите устройства для подключения."); color: #62666d; font-size: 11px; }
ImGuiCheckBox { x: 18px; y: 74px; enabled: root.recent-available > 0; checked: root.recent-devices.length > 0 && root.recent-selected == root.recent-available; }
Text { x: 45px; y: 74px; height: 21px; text: GalleryStrings.tr("全选", "Select all", "Выбрать все"); color: root.recent-available > 0 ? #24272d : #92969e; font-size: 12px; vertical-alignment: center; }
TouchArea { x: 18px; y: 74px; width: 144px; height: 21px; enabled: root.recent-available > 0; clicked => { root.action("recent-all", ""); } }
@@ -967,7 +967,7 @@ export component GalleryView inherits Rectangle {
row-touch := TouchArea { enabled: !device.added; clicked => { root.action("recent-select", device.id); } }
}
}
if root.recent-devices.length == 0: Text { text: GalleryStrings.tr("暂无最近连接", "No recent connections", "Нет недавних подключений"); color: #808080; font-size: 12px; horizontal-alignment: center; vertical-alignment: center; }
if root.recent-devices.length == 0: Text { text: GalleryStrings.tr("暂无近期连接", "No recent connections", "Нет недавних подключений"); color: #808080; font-size: 12px; horizontal-alignment: center; vertical-alignment: center; }
Rectangle {
x: -UiRadius.surface; y: -UiRadius.surface;
width: parent.width + 2 * UiRadius.surface;
@@ -985,7 +985,7 @@ export component GalleryView inherits Rectangle {
import-group := RoundedLineEdit { x: 84px; y: 274px; width: 338px; height: 30px; text: root.import-group; placeholder-text: GalleryStrings.tr("分组名称(可选)", "Group name (optional)", "Группа (необязательно)"); }
Text { x: 18px; y: 309px; width: 404px; height: 20px; text: root.notice; font-size: 11px; color: #8e4740; overflow: elide; }
GalleryButton { x: 192px; y: 336px; width: 72px; text: GalleryStrings.tr("取消", "Cancel", "Отмена"); clicked => { root.importing = false; } }
GalleryButton { x: 274px; y: 336px; width: 148px; primary: true; text: GalleryStrings.tr("加入桌面墙", "Add selected", "Добавить"); enabled: root.recent-selected > 0; clicked => { if root.import-recent(import-group.text) { root.importing = false; } } }
GalleryButton { x: 274px; y: 336px; width: 148px; primary: true; text: GalleryStrings.tr("加入桌面墙", "Add to DeskHub", "В DeskHub"); enabled: root.recent-selected > 0; clicked => { if root.import-recent(import-group.text) { root.importing = false; } } }
}
}
if root.confirming: Rectangle {
@@ -1002,7 +1002,7 @@ export component GalleryView inherits Rectangle {
Rectangle {
x: parent.width - 108px; y: 20px; width: 88px; height: 22px;
background: #eff3f8; border-radius: UiRadius.surface;
Text { text: root.selected-count + GalleryStrings.tr(" 台设备", " devices", " устр."); color: #626e7d; font-size: 11px; horizontal-alignment: center; vertical-alignment: center; }
Text { text: GalleryStrings.device-count(root.selected-count); color: #626e7d; font-size: 11px; horizontal-alignment: center; vertical-alignment: center; }
}
confirmation-list := ShadowSurface {
x: 20px; y: 58px; width: parent.width - 40px;
@@ -1021,7 +1021,7 @@ export component GalleryView inherits Rectangle {
width: GalleryStrings.language == 0 ? 68px : 104px; height: 20px; border-radius: 10px;
background: device.connected ? #eaf6ee : #f0f2f5;
Rectangle { x: 9px; y: 7px; width: 6px; height: 6px; border-radius: 3px; background: device.connected ? #27a762 : #a0a6b0; }
Text { x: 21px; width: parent.width - 27px; height: parent.height; text: device.connected ? GalleryStrings.tr("已连接", "Connected", "Подключено") : GalleryStrings.tr("未连接", "Not connected", "Нет связи"); color: device.connected ? #348259 : #858d98; font-size: 10px; vertical-alignment: center; }
Text { x: 21px; width: parent.width - 27px; height: parent.height; text: device.connected ? GalleryStrings.tr("已连接", "Connected", "Подключено") : GalleryStrings.tr("未连接", "Not connected", "Нет соединения"); color: device.connected ? #348259 : #858d98; font-size: 10px; vertical-alignment: center; }
}
if index + 1 < root.selected-devices.length: ShadowSeparator { x: 12px; y: parent.height - 1px; width: parent.width - 24px; height: 1px; shadow-color: UiShadow.separator; }
}
+19 -8
View File
@@ -20,9 +20,9 @@ export global LanStrings {
GalleryStrings.tr("原设置已保留。", "Your previous settings are unchanged.", "Прежние настройки сохранены.")
]; }
if code == "Selected LAN interface is unavailable" { return [
GalleryStrings.tr("未找到可用的局域网连接。", "Connect to Wi-Fi or Ethernet,", "Подключитесь к Wi-Fi или Ethernet"),
GalleryStrings.tr("请连接 Wi-Fi 或网线,", "or select a local IP", "либо выберите IP"),
GalleryStrings.tr("或在主界面选择本机 IP。", "on the main screen.", "в главном окне.")
GalleryStrings.tr("未找到可用的局域网连接。", "No local network connection was found.", "Нет доступного подключения к LAN."),
GalleryStrings.tr("请连接 Wi-Fi 或网线,", "Connect to Wi-Fi or Ethernet,", "Подключитесь к Wi-Fi или Ethernet"),
GalleryStrings.tr("或在主界面选择本机 IP。", "or select a local IP on the main screen.", "или выберите локальный IP в главном окне.")
]; }
// Keep all branches the same size for Slint's C++ model generator.
return ["", "", ""];
@@ -33,15 +33,26 @@ export global LanStrings {
let separator = GalleryStrings.tr("", " ", " ");
return lines[0] + separator + lines[1] + separator + lines[2];
}
if code == "InvalidCredentials" { return GalleryStrings.tr("密码不正确,或对方密码已更新。请查看对方的局域网访问密码后重试。", "Check the other desktop's current LAN password and try again.", "Проверьте текущий пароль LAN другого компьютера и повторите попытку."); }
if code == "" { return ""; }
if code == "InvalidCredentials" { return GalleryStrings.tr("密码不正确,或对方密码已更新。请查看对方的局域网访问密码后重试。", "The password is incorrect or has changed. Check the other desktop's current LAN password and try again.", "Пароль неверен или изменён. Проверьте текущий пароль LAN другого компьютера и повторите попытку."); }
if code == "AuthenticationRateLimited" { return GalleryStrings.tr("尝试次数过多,请等待倒计时结束后重试。", "Too many attempts. Try again when the countdown ends.", "Слишком много попыток. Дождитесь окончания обратного отсчёта."); }
if code == "CredentialsChanged" { return GalleryStrings.tr("对方密码已更新,请使用新密码重试。", "The password changed. Try again with the new password.", "Пароль изменился. Повторите с новым паролем."); }
if code == "Busy" { return GalleryStrings.tr("设备正忙,请稍后重试。", "The desktop is busy. Try again shortly.", "Компьютер занят. Повторите позже."); }
if code == "LAN password must contain exactly 6 ASCII letters or digits" { return GalleryStrings.tr("访问密码必须为 6 位英文字母或数字,区分大小写。", "Use exactly 6 letters (A–Z, a–z) or digits (0–9). Case-sensitive.", "Пароль: ровно 6 латинских букв или цифр. Регистр учитывается."); }
if code == "ConnectionFailed" || code == "ConnectionTimeout" || code == "Timeout" || code == "ConnectionClosed" { return GalleryStrings.tr("无法连接,请检查对方 IP、程序是否运行,以及防火墙是否放行。", "Check the IP, whether the other desktop is running CrossDesk, and its firewall settings.", "Проверьте IP, работу CrossDesk на другом компьютере и брандмауэр."); }
if code == "Enter a numeric IPv4 address or IPv4:port" { return GalleryStrings.tr("请输入有效的 IP,例如 192.168.1.20;指定端口可用 IP:端口,端口范围 1–65535。", "Enter a valid IP such as 192.168.1.20, or IP:port (1–65535).", "Введите IP, например 192.168.1.20, или IP:порт (1–65535)."); }
if code == "ConnectionFailed" || code == "ConnectionTimeout" || code == "IceConnectionTimeout" || code == "Timeout" || code == "ConnectionClosed" || code == "HeartbeatTimeout" || code == "SignalSendFailed" { return GalleryStrings.tr("无法连接,请检查对方 IP、程序是否运行,以及防火墙是否放行。", "Could not connect. Check the IP, whether the other desktop is running CrossDesk, and its firewall settings.", "Не удалось подключиться. Проверьте IP, работу CrossDesk на другом компьютере и брандмауэр."); }
if code == "Enter a numeric IPv4 address or IPv4:port" || code == "Invalid LAN endpoint" { return GalleryStrings.tr("请输入有效的 IPv4,例如 192.168.1.20;指定端口可用 IPv4:端口,端口范围 1–65535。", "Enter a valid IPv4 address such as 192.168.1.20, or IPv4:port (1–65535).", "Введите IPv4, например 192.168.1.20, или IPv4:порт (1–65535)."); }
if code == "Invalid LAN port" { return GalleryStrings.tr("请输入 1–65535 之间的整数端口。", "Enter a port number from 1 to 65535.", "Введите целый номер порта от 1 до 65535."); }
if code == "LAN password is not ready" { return GalleryStrings.tr("请点击主界面密码旁的修改按钮设置密码。", "Set a password using Edit next to the password on the main screen.", "Задайте пароль кнопкой редактирования в главном окне."); }
return code;
if code == "LAN password is not ready" { return GalleryStrings.tr("请点击主界面密码旁的修改按钮设置密码。", "Set a password using Edit next to the password on the main screen.", "Задайте пароль кнопкой редактирования рядом с паролем в главном окне."); }
if code == "LAN connection limit reached" { return GalleryStrings.tr("IP 连接数量已达上限,请先断开部分连接后重试。", "The IP connection limit has been reached. Disconnect some sessions and try again.", "Достигнут лимит IP-подключений. Завершите часть сеансов и повторите попытку."); }
if code == "UnsupportedNetworkPath" || code == "NetworkPolicyFailed" { return GalleryStrings.tr("当前网络路径不支持 IP 连接,请检查双方的局域网连接。", "This network path does not support IP connections. Check both devices' LAN connections.", "Этот сетевой маршрут не поддерживает IP-подключения. Проверьте подключения обоих устройств к LAN."); }
if code == "IncompatibleProtocol" || code == "RequiredCryptoUnsupported" || code == "TLS 1.3 is required for LAN connections" { return GalleryStrings.tr("双方的协议或加密能力不兼容,请将双方 CrossDesk 升级至最新版本后重试。", "The protocol or encryption capabilities are incompatible. Update CrossDesk on both devices and try again.", "Протоколы или возможности шифрования несовместимы. Обновите CrossDesk на обоих устройствах и повторите попытку."); }
if code == "InvalidCertificate" || code == "FingerprintMissing" || code == "FingerprintMismatch" { return GalleryStrings.tr("无法验证对方设备的身份,请确认连接地址和对方设备后重试。", "Could not verify the remote device's identity. Check the address and the remote device before retrying.", "Не удалось проверить подлинность удалённого устройства. Проверьте адрес и устройство перед повторной попыткой."); }
if code == "DtlsHandshakeFailed" || code == "SrtpInstallFailed" || code == "SrtpKeyExportFailed" || code == "SecureTransportFailed" { return GalleryStrings.tr("无法建立加密连接,请检查双方网络并重试。", "Could not establish an encrypted connection. Check both devices' networks and try again.", "Не удалось установить зашифрованное соединение. Проверьте сеть на обоих устройствах и повторите попытку."); }
if code == "InvalidSignal" || code == "InvalidLocalSignal" || code == "Invalid LAN protocol message" { return GalleryStrings.tr("连接协商数据无效,请重试并确认双方 CrossDesk 版本兼容。", "Invalid connection negotiation data. Try again and check that both CrossDesk versions are compatible.", "Неверные данные согласования соединения. Повторите попытку и проверьте совместимость версий CrossDesk."); }
if code == "ListenerChanged" { return GalleryStrings.tr("连接地址或监听端口已更改,请确认最新地址后重试。", "The connection address or listening port has changed. Check the current address and try again.", "Адрес подключения или порт прослушивания изменился. Проверьте текущий адрес и повторите попытку."); }
if code == "PeerPreparationFailed" { return GalleryStrings.tr("无法准备远程会话,请稍后重试。", "Could not prepare the remote session. Try again shortly.", "Не удалось подготовить удалённый сеанс. Повторите позже."); }
if code == "Cannot copy text" { return GalleryStrings.tr("复制失败,请重试。", "Could not copy. Try again.", "Не удалось скопировать. Повторите попытку."); }
// Keep diagnostic details for unexpected system errors, with a localized explanation.
return GalleryStrings.tr("IP 连接操作失败,详情:", "IP connection operation failed. Details: ", "Ошибка операции IP-подключения. Подробности: ") + code;
}
}
+45 -41
View File
@@ -43,6 +43,8 @@ enum CopyFeedback { idle, success, failed }
enum SponsorPlatform { afdian, kofi, github }
export global UiStrings {
in-out property <string> window-minimize: "Minimize";
in-out property <string> window-close: "Close window";
in-out property <string> local-desktop: "Local Desktop";
in-out property <string> remote-desktop: "Remote Desktop";
in-out property <string> recent-connections: "Recent Connections";
@@ -65,7 +67,7 @@ export global UiStrings {
in-out property <string> cancel: "Cancel";
in-out property <string> save: "Save";
in-out property <string> new-password: "New password (6 characters)";
in-out property <string> invalid-password: "Password must contain exactly 6 characters";
in-out property <string> invalid-password: "Use exactly 6 letters (A–Z, a–z) or digits (0–9).";
in-out property <string> edit-alias: "Connection alias";
in-out property <string> delete-connection: "Delete connection";
in-out property <string> confirm-delete: "Delete this recent connection?";
@@ -86,7 +88,7 @@ export global UiStrings {
in-out property <string> server-host: "Server host";
in-out property <string> server-port: "Signal port";
in-out property <string> version: "Version";
in-out property <string> copyright: "© 2026 by JUNKUN DI. All right reserved.";
in-out property <string> copyright: "© 2026 by JUNKUN DI. All rights reserved.";
in-out property <string> license: "Licensed under GNU GPL v3.";
in-out property <string> signal-connected: "Signal server connected";
in-out property <string> signal-disconnected: "Signal server disconnected";
@@ -122,7 +124,7 @@ export global UiStrings {
in-out property <string> wayland-notice-acknowledge: "Got it";
in-out property <string> service-setup-title: "Install CrossDesk Service";
in-out property <string> service-setup-message: "Administrator permission is required to control this device while it is locked.";
in-out property <string> service-settings-label: "Lock Screen Service:";
in-out property <string> service-settings-label: "Lock-screen control service:";
in-out property <string> install-service: "Install";
in-out property <string> service-installed: "Installed";
in-out property <string> do-not-remind: "Do not remind again";
@@ -898,7 +900,7 @@ export component MainWindow inherits Window {
: root.window-active ? #e6e6e6
: transparent;
accessible-role: button;
accessible-label: "Minimize";
accessible-label: UiStrings.window-minimize;
accessible-action-default => { wayland-minimize-touch.clicked(); }
Rectangle {
x: 8px;
@@ -924,7 +926,7 @@ export component MainWindow inherits Window {
: root.window-active ? #e6e6e6
: transparent;
accessible-role: button;
accessible-label: "Close";
accessible-label: UiStrings.window-close;
accessible-action-default => { wayland-close-touch.clicked(); }
Path {
width: parent.width;
@@ -992,7 +994,7 @@ export component MainWindow inherits Window {
: minimize-touch.has-hover ? #e5e5e5
: transparent;
accessible-role: button;
accessible-label: "Minimize";
accessible-label: UiStrings.window-minimize;
accessible-action-default => { minimize-touch.clicked(); }
Text {
text: FontAwesomeIcons.minus;
@@ -1014,7 +1016,7 @@ export component MainWindow inherits Window {
: close-touch.has-hover ? #c42b1c
: transparent;
accessible-role: button;
accessible-label: "Close";
accessible-label: UiStrings.window-close;
accessible-action-default => { close-touch.clicked(); }
Text {
text: FontAwesomeIcons.xmark;
@@ -1111,9 +1113,9 @@ export component MainWindow inherits Window {
width: 136px; height: 28px;
background: WorkbenchStyle.field-surface;
enabled: !root.connection-pending && !root.lan-connecting;
tooltip: root.lan-mode ? GalleryStrings.tr("切换到 ID 连接模式", "Switch to ID mode", "Переключить в режим ID") : GalleryStrings.tr("切换到 IP 直连模式", "Switch to direct IP mode", "Переключить в режим IP");
tooltip: root.lan-mode ? GalleryStrings.tr("切换到 ID 连接模式", "Switch to ID connection mode", "Переключить в режим ID") : GalleryStrings.tr("切换到 IP 连接模式", "Switch to IP connection mode", "Переключить в режим IP");
accessible-role: switch;
accessible-label: GalleryStrings.tr("IP 直连模式", "Direct IP mode", "Режим IP");
accessible-label: GalleryStrings.tr("IP 连接模式", "IP connection mode", "Режим IP");
accessible-checked: root.lan-mode;
z: 8;
clicked => {
@@ -1135,7 +1137,7 @@ export component MainWindow inherits Window {
y: 2px;
width: (parent.width - 6px) / 2;
height: parent.height - 4px;
text: lan ? GalleryStrings.tr("IP 直连", "Direct IP", "Режим IP") : GalleryStrings.tr("ID 连接", "ID mode", "Режим ID");
text: lan ? GalleryStrings.tr("IP 连接", "IP mode", "Режим IP") : GalleryStrings.tr("ID 连接", "ID mode", "Режим ID");
font-size: 12px;
font-weight: 500;
color: root.lan-mode == lan ? white : WorkbenchStyle.muted;
@@ -1244,7 +1246,7 @@ export component MainWindow inherits Window {
x: 0px; y: 0px;
width: (parent.width - 16px) / 2; height: parent.height;
Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.local-desktop; font-size: 16px; font-weight: 600; color: WorkbenchStyle.text; }
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("将 IP 和密码分享给连接方", "Share your IP and password to get connected", "Передайте IP и пароль для управления") : GalleryStrings.tr("将 ID 和密码分享给连接方", "Share your ID and password to get connected", "Передайте ID и пароль для подключения"); font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
Text { x: 16px; y: 38px; width: parent.width - 32px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("将 IP 和密码分享给连接方", "Share your IP and password to get connected", "Передайте IP и пароль для подключения") : GalleryStrings.tr("将 ID 和密码分享给连接方", "Share your ID and password to get connected", "Передайте ID и пароль для подключения"); font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
local-address-label := Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("本机 IP / 端口", "Local IP / Port", "IP / Порт") : UiStrings.local-id; font-size: 12px; color: WorkbenchStyle.muted; vertical-alignment: top; }
lan-switch-ip-metrics := Text {
visible: false; accessible-role: none;
@@ -1382,7 +1384,7 @@ export component MainWindow inherits Window {
font-size: 18px * local-address-field.lan-font-scale; font-weight: 600;
input-type: InputType.number; color: WorkbenchStyle.text;
enabled: root.lan-mode && !root.lan-password-pending;
accessible-label: GalleryStrings.tr("局域网端口", "LAN port", "Порт ЛВС");
accessible-label: GalleryStrings.tr("局域网端口", "LAN port", "Порт LAN");
vertical-alignment: center; horizontal-alignment: left;
selection-foreground-color: white; selection-background-color: WorkbenchStyle.accent;
changed has-focus => {
@@ -1567,7 +1569,7 @@ export component MainWindow inherits Window {
Text {
x: 84px; y: 36px; width: parent.width - 104px; height: 24px;
text: root.lan-mode
? GalleryStrings.tr("暂无 IP 直连设备", "No recent IP connections", "Нет недавних IP-подключений")
? GalleryStrings.tr("暂无近期 IP 连接", "No recent IP connections", "Нет недавних IP-подключений")
: root.recent-group-index == 0
? GalleryStrings.tr("暂无近期连接", "No recent connections", "Нет недавних подключений")
: GalleryStrings.tr("该分组暂无近期连接", "No recent connections in this group", "В этой группе нет недавних подключений");
@@ -1886,7 +1888,7 @@ export component MainWindow inherits Window {
height: parent.height;
width: parent.width - 160px;
overflow: elide;
text: root.lan-mode ? (root.lan-message != "" ? LanStrings.error-message(root.lan-message) : root.lan-listener-error != "" ? (root.lan-listener-address == "" ? "" : root.lan-listener-address + " · ") + LanStrings.error-message(root.lan-listener-error) : root.lan-listening ? GalleryStrings.tr("局域网模式已启用", "LAN mode enabled", "Режим LAN включён") : root.lan-password-pending ? GalleryStrings.tr("局域网模式准备中…", "Preparing LAN mode…", "Подготовка режима LAN…") : GalleryStrings.tr("局域网模式未就绪", "LAN mode not ready", "Режим LAN не готов"))
text: root.lan-mode ? (root.lan-message != "" ? LanStrings.error-message(root.lan-message) : root.lan-listener-error != "" ? (root.lan-listener-address == "" ? "" : root.lan-listener-address + " · ") + LanStrings.error-message(root.lan-listener-error) : root.lan-listening ? GalleryStrings.tr("IP 连接已启用", "IP connections enabled", "Подключения по IP включены") : root.lan-password-pending ? GalleryStrings.tr("IP 连接准备中…", "Preparing IP connections…", "Подготовка подключений по IP…") : GalleryStrings.tr("IP 连接未就绪", "IP connections not ready", "Подключения по IP не готовы"))
: root.signal-tls-error ? UiStrings.tls-error
: root.signal-connected ? UiStrings.signal-connected
: root.signal-reconnecting ? UiStrings.signal-reconnecting
@@ -2412,7 +2414,7 @@ export component MainWindow inherits Window {
}
}
Text {
text: GalleryStrings.tr("当前远程控制会话将结束。", "The current remote session will end.", "Текущий сеанс удалённого управления завершится.");
text: GalleryStrings.tr("当前远程控制会话将结束。", "The current remote session will end.", "Текущий сеанс удалённого управления будет завершён.");
color: WorkbenchStyle.muted;
font-size: 13px;
wrap: word-wrap;
@@ -2626,6 +2628,7 @@ export component MainWindow inherits Window {
// Center every content section; the scrollbar sits in the right margin.
private property <length> content-inset: 26px;
private property <length> scrollbar-gutter: 22px;
private property <length> shadow-gutter: 6px;
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: min(400px, parent.width - 40px);
@@ -2638,31 +2641,28 @@ export component MainWindow inherits Window {
width: parent.width - 2 * settings-dialog.content-inset; height: 27px;
text: UiStrings.settings; font-size: 20px; font-weight: 600; color: #22324a;
}
// Match the compact connection-mode switch, centered in the tab row.
ShadowSurface {
x: settings-dialog.content-inset; y: 56px;
width: parent.width - 2 * settings-dialog.content-inset; height: 40px;
background: #dfe7f2; border-radius: UiRadius.menu;
shadow-color: UiShadow.edge;
ShadowSurface {
x: 4px + root.settings-tab * (parent.width - 4px) / 2;
y: 4px; width: (parent.width - 12px) / 2; height: parent.height - 8px;
background: WorkbenchStyle.surface; border-radius: UiRadius.control;
shadow-color: UiShadow.edge;
animate x { duration: 140ms; easing: ease-out; }
x: settings-dialog.content-inset; y: 62px;
width: parent.width - 2 * settings-dialog.content-inset; height: 28px;
background: WorkbenchStyle.field-surface; border-radius: UiRadius.control;
border-width: 1px; border-color: WorkbenchStyle.accent;
shadow-compact: true; shadow-color: UiShadow.edge;
Rectangle {
x: root.settings-tab == 1 ? parent.width / 2 + 1px : 2px;
y: 2px; width: (parent.width - 6px) / 2; height: parent.height - 4px;
background: WorkbenchStyle.accent; border-radius: UiRadius.control;
animate x { duration: 180ms; easing: ease-in-out; }
}
for label[index] in [GalleryStrings.tr("常规", "General", "Общие"), GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub")]: Rectangle {
x: 4px + index * (parent.width - 4px) / 2;
y: 4px; width: (parent.width - 12px) / 2; height: parent.height - 8px;
border-radius: UiRadius.control;
background: root.settings-tab == index ? transparent
: tab-touch.pressed ? WorkbenchStyle.pressed-surface
: tab-touch.has-hover ? WorkbenchStyle.hover-surface : transparent;
animate background { duration: 100ms; }
x: index == 1 ? parent.width / 2 + 1px : 2px;
y: 2px; width: (parent.width - 6px) / 2; height: parent.height - 4px;
accessible-role: button; accessible-label: label;
accessible-action-default => { tab-touch.clicked(); }
Text {
text: label; font-size: 12px; font-weight: root.settings-tab == index ? 600 : 500;
color: root.settings-tab == index ? WorkbenchStyle.accent : WorkbenchStyle.muted;
text: label; font-size: 12px; font-weight: 500;
color: root.settings-tab == index ? white : WorkbenchStyle.muted;
animate color { duration: 180ms; }
horizontal-alignment: center; vertical-alignment: center;
}
tab-touch := TouchArea {
@@ -2672,8 +2672,9 @@ export component MainWindow inherits Window {
}
}
if root.settings-tab == 0: settings-scroll := ScrollView {
x: settings-dialog.content-inset; y: 108px;
width: parent.width - 2 * settings-dialog.content-inset + settings-dialog.scrollbar-gutter;
x: settings-dialog.content-inset - settings-dialog.shadow-gutter; y: 108px;
width: parent.width - 2 * settings-dialog.content-inset
+ settings-dialog.scrollbar-gutter + settings-dialog.shadow-gutter;
height: parent.height - 174px - (root.settings-save-error == "" ? 0px : 28px);
viewport-width: self.visible-width;
viewport-height: settings-content.preferred-height;
@@ -2687,7 +2688,10 @@ export component MainWindow inherits Window {
width: settings-scroll.viewport-width;
height: settings-scroll.viewport-height;
settings-content := VerticalLayout {
padding: 0px; padding-right: settings-dialog.scrollbar-gutter; padding-bottom: 4px;
// Keep the six outer shadow layers inside the scroll clip
// without moving or narrowing the settings cards.
padding: 0px; padding-left: settings-dialog.shadow-gutter;
padding-right: settings-dialog.scrollbar-gutter; padding-bottom: 4px;
spacing: 14px;
SettingsGroup {
title: GalleryStrings.tr("界面", "Interface", "Интерфейс"); rows: 1;
@@ -2711,7 +2715,7 @@ export component MainWindow inherits Window {
SettingsGroup {
title: GalleryStrings.tr("网络连接", "Network", "Сеть"); rows: 3;
SettingsToggleRow {
label: GalleryStrings.tr("启用 IP 直连", "Enable direct IP", "Прямое IP-подключение");
label: GalleryStrings.tr("启用 IP 连接", "Enable IP connections", "Подключение по IP");
checked <=> root.ip-direct-enabled-draft;
}
SettingsToggleRow { label: UiStrings.force-relay; checked <=> root.force-relay-enabled; enabled: !root.settings-session-active; }
@@ -2733,7 +2737,7 @@ export component MainWindow inherits Window {
}
}
Text {
text: GalleryStrings.tr("关闭 IP 直连将隐藏模式按钮,并断开 IP 直连会话。", "Disabling direct IP hides the mode button and disconnects direct IP sessions.", "Отключение скроет кнопку режима и завершит прямые IP-сеансы.");
text: GalleryStrings.tr("关闭 IP 连接将隐藏模式按钮,并断开 IP 连接会话。", "Disabling IP connections hides the mode button and ends IP sessions.", "Отключение подключений по IP скроет кнопку режима и завершит IP-сеансы.");
font-size: 11px; color: WorkbenchStyle.muted; wrap: word-wrap;
}
SettingsGroup {
@@ -2829,7 +2833,7 @@ export component MainWindow inherits Window {
text: root.autostart-status == AutostartStatus.unsupported ? GalleryStrings.tr(
"当前设备不支持开机自启动。", "This device does not support auto start.", "Это устройство не поддерживает автозапуск.") :
root.autostart-status == AutostartStatus.failed ? GalleryStrings.tr(
"无法读取系统状态。请刷新重试,或在系统设置中核对。", "Could not read the system status. Retry or check it in Settings.", "Не удалось прочитать состояние. Повторите попытку или проверьте настройки системы.") :
"无法读取系统状态。请刷新重试,或在系统设置中核对。", "Could not read the system status. Refresh to retry, or check System Settings.", "Не удалось прочитать состояние. Повторите попытку или проверьте настройки системы.") :
GalleryStrings.tr("请在系统设置中添加或移除本应用,返回后会自动刷新状态。", "Add or remove this app in system Settings. The status refreshes when you return.", "Добавьте или удалите приложение в настройках системы. После возвращения статус обновится.");
color: WorkbenchStyle.text; font-size: 13px; wrap: word-wrap;
}
@@ -2844,7 +2848,7 @@ export component MainWindow inherits Window {
}
SettingsButton {
x: 24px; y: parent.height - 48px; width: 112px;
text: GalleryStrings.tr("刷新状态", "Refresh status", "Обновить");
text: GalleryStrings.tr("刷新状态", "Refresh status", "Обновить статус");
enabled: root.autostart-status != AutostartStatus.checking;
clicked => { root.refresh-autostart-status(); }
}
@@ -1,6 +1,6 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint";
import { CompactComboBox, ToggleSwitch, DialogSurface, IconButton, FontAwesomeIcons, ControlStyle } from "common.slint";
import { CompactComboBox, ToggleSwitch, DialogSurface, IconButton, FontAwesomeIcons, ControlStyle, ButtonStyle } from "common.slint";
import { WorkbenchStyle } from "workbench_controls.slint";
// Shared visual treatment for the global settings pages.
@@ -18,12 +18,18 @@ export component SettingsButton inherits ShadowSurface {
width: 88px; height: 32px; border-radius: UiRadius.control;
background: !root.enabled ? ControlStyle.disabled
: root.primary ? (touch.pressed ? #1e4fa3 : touch.has-hover ? #2866c9 : #3275dc)
: touch.pressed ? ControlStyle.pressed : touch.has-hover ? ControlStyle.hover
: root.quiet ? transparent : ControlStyle.surface;
shadow-enabled: !root.quiet && !root.primary;
shadow-color: !root.enabled ? UiShadow.edge
: touch.pressed ? ButtonStyle.pressed : touch.has-hover ? ButtonStyle.hover
: root.quiet ? transparent : ButtonStyle.surface;
border-width: root.quiet || root.primary ? 0px : 1px;
border-color: !root.enabled ? ButtonStyle.disabled-border
: touch.pressed ? ButtonStyle.pressed-border
: touch.has-hover ? ButtonStyle.hover-border : ButtonStyle.border;
shadow-enabled: root.enabled && !root.quiet && !touch.pressed;
shadow-color: root.primary ? UiShadow.focus
: touch.has-hover ? UiShadow.hover : UiShadow.edge;
animate background, border-color { duration: 120ms; }
accessible-role: button; accessible-label: root.text;
accessible-enabled: root.enabled;
accessible-action-default => { if root.enabled { root.clicked(); } }
Rectangle {
x: root.align-left ? root.content-padding : (parent.width - self.width) / 2;
@@ -113,7 +119,7 @@ export component SettingsDialog inherits DialogSurface {
export component SettingsSelect inherits CompactComboBox {
subtle: true;
show-arrow: false;
show-arrow: true;
normal-shadow-color: UiShadow.edge;
hover-shadow-color: UiShadow.hover;
height: 28px;
+24 -12
View File
@@ -2,7 +2,7 @@ import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint";
import { ComboBox, ScrollView } from "std-widgets.slint";
import { RemoteCursor } from "remote_cursor.slint";
import { FontAwesomeIcons, IconButton, PrimaryButton, SecondaryButton, DialogSurface, ImGuiFontStyle, ControlStyle } from "common.slint";
import { FontAwesomeIcons, IconButton, PrimaryButton, SecondaryButton, DialogSurface, ImGuiFontStyle, ControlStyle, ButtonStyle } from "common.slint";
export struct StreamTab {
remote-id: string,
@@ -26,6 +26,10 @@ export struct NetworkStatsRow {
}
export global StreamStrings {
in-out property <string> window-minimize: "Minimize";
in-out property <string> window-maximize: "Maximize";
in-out property <string> window-restore: "Restore window";
in-out property <string> window-close: "Close window";
in-out property <string> remote-update-title: "Remote update recommended";
in-out property <string> remote-update: "Update the remote device to the latest version for improved features and performance.";
in-out property <string> remote-update-dismiss: "Got it";
@@ -47,11 +51,11 @@ export global StreamStrings {
in-out property <string> privacy-disable: "Turn privacy off / recover";
in-out property <string> control-mouse: "Control Mouse";
in-out property <string> release-mouse: "Release Mouse";
in-out property <string> audio: "Audio Capture";
in-out property <string> audio: "Play audio";
in-out property <string> mute: "Mute";
in-out property <string> select-file: "Select File to Send";
in-out property <string> show-stats: "Show Net Traffic Stats";
in-out property <string> hide-stats: "Hide Net Traffic Stats";
in-out property <string> show-stats: "Show network status";
in-out property <string> hide-stats: "Hide network status";
in-out property <string> fullscreen: "Fullscreen";
in-out property <string> exit-fullscreen: "Exit fullscreen";
in-out property <string> disconnect: "Disconnect";
@@ -65,7 +69,7 @@ export global StreamStrings {
in-out property <string> stats-out: "Out";
in-out property <string> stats-loss-rate: "Loss Rate";
in-out property <string> stats-resolution: "Res";
in-out property <string> stats-video-latency: "Video delay";
in-out property <string> stats-video-latency: "Video latency";
in-out property <string> stats-connection-latency: "Connection RTT";
in-out property <string> stats-connection-mode: "Mode";
in-out property <string> stats-encoder: "Remote encoder";
@@ -245,13 +249,21 @@ component VideoSettingRow inherits VerticalLayout {
for option[index] in root.options: ShadowSurface {
height: 27px;
background: !root.enabled ? ControlStyle.disabled
: root.selected == index ? (option-touch.pressed ? ControlStyle.selected-pressed : option-touch.has-hover ? ControlStyle.selected-hover : ControlStyle.selected)
: option-touch.pressed ? ControlStyle.pressed
: option-touch.has-hover ? ControlStyle.hover : ControlStyle.surface;
: root.selected == index ? (option-touch.pressed ? ButtonStyle.selected-pressed : option-touch.has-hover ? ButtonStyle.selected-hover : ButtonStyle.selected)
: option-touch.pressed ? ButtonStyle.pressed
: option-touch.has-hover ? ButtonStyle.hover : ButtonStyle.surface;
border-radius: UiRadius.control;
border-width: 1px;
border-color: !root.enabled ? ButtonStyle.disabled-border
: root.selected == index ? ButtonStyle.selected-pressed
: option-touch.pressed ? ButtonStyle.pressed-border
: option-touch.has-hover ? ButtonStyle.hover-border : ButtonStyle.border;
shadow-compact: true;
shadow-enabled: root.enabled && !option-touch.pressed;
shadow-color: root.enabled && root.selected == index ? UiShadow.focus : UiShadow.edge;
animate background, border-color { duration: 120ms; }
Text { text: option; color: !root.enabled ? #8b8f95
: root.selected == index ? ControlStyle.selected-text : #30343b;
: root.selected == index ? white : #30343b;
font-weight: root.selected == index ? 600 : 400;
font-size: 11px; horizontal-alignment: center; vertical-alignment: center; }
option-touch := TouchArea {
@@ -809,7 +821,7 @@ export component StreamWindow inherits Window {
: root.window-active ? #e6e6e6
: transparent;
accessible-role: button;
accessible-label: "Minimize";
accessible-label: StreamStrings.window-minimize;
accessible-action-default => { stream-minimize-touch.clicked(); }
Rectangle {
x: 8px;
@@ -835,7 +847,7 @@ export component StreamWindow inherits Window {
: root.window-active ? #e6e6e6
: transparent;
accessible-role: button;
accessible-label: root.window-maximized ? "Restore" : "Maximize";
accessible-label: root.window-maximized ? StreamStrings.window-restore : StreamStrings.window-maximize;
accessible-action-default => { stream-maximize-touch.clicked(); }
if !root.window-maximized: Rectangle {
x: 8px;
@@ -885,7 +897,7 @@ export component StreamWindow inherits Window {
: root.window-active ? #e6e6e6
: transparent;
accessible-role: button;
accessible-label: "Close";
accessible-label: StreamStrings.window-close;
accessible-action-default => { stream-close-touch.clicked(); }
Path {
width: parent.width;
+10 -1
View File
@@ -42,6 +42,11 @@ inline int ApplyMainWindowStrings(
localization::detail::ClampLanguageIndex(language_index);
window->global<ui::GalleryStrings>().set_language(language);
auto& strings = window->global<ui::UiStrings>();
strings.set_window_minimize(Text(localization::window_minimize[language]));
strings.set_window_close(Text(localization::window_close[language]));
strings.set_save(Text(localization::save[language]));
strings.set_copyright(Text(localization::copyright_notice[language]));
strings.set_license(Text(localization::license_notice[language]));
auto& messages = window->global<ui::MessageStrings>();
messages.set_messages(Text(localization::messages[language]));
messages.set_unread(Text(localization::message_unread[language]));
@@ -81,7 +86,7 @@ inline int ApplyMainWindowStrings(
strings.set_ok(Text(localization::ok[language]));
strings.set_cancel(Text(localization::cancel[language]));
strings.set_new_password(Text(localization::new_password[language]));
strings.set_invalid_password(Text(localization::max_password_len[language]));
strings.set_invalid_password(Text(localization::password_format_invalid[language]));
strings.set_edit_alias(
Text(localization::input_connection_alias[language]));
strings.set_delete_connection(
@@ -192,6 +197,10 @@ inline int ApplyStreamWindowStrings(
const int language =
localization::detail::ClampLanguageIndex(language_index);
auto& strings = window->template global<ui::StreamStrings>();
strings.set_window_minimize(Text(localization::window_minimize[language]));
strings.set_window_maximize(Text(localization::window_maximize[language]));
strings.set_window_restore(Text(localization::window_restore[language]));
strings.set_window_close(Text(localization::window_close[language]));
strings.set_remote_update_title(Text(localization::remote_update_title[language]));
strings.set_remote_update(Text(localization::remote_update[language]));
strings.set_remote_update_dismiss(Text(localization::remote_update_dismiss[language]));
@@ -1,6 +1,6 @@
import { ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint";
import { ControlStyle } from "common.slint";
import { ControlStyle, ButtonStyle } from "common.slint";
// Colors and spacing for the connection workbench; shapes are shared app-wide.
export global WorkbenchStyle {
out property <color> canvas: #edf2f8;
@@ -21,7 +21,7 @@ export component WorkbenchButton inherits ShadowSurface {
in property <length> text-size: 13px;
in property <color> text-color: root.destructive || root.primary ? white : WorkbenchStyle.text;
in property <color> normal-shadow-color: UiShadow.edge;
in property <color> normal-background: ControlStyle.surface;
in property <color> normal-background: ButtonStyle.surface;
in property <string> icon;
in property <string> tooltip;
in property <bool> tooltip-above: false;
@@ -46,7 +46,12 @@ export component WorkbenchButton inherits ShadowSurface {
width: 112px;
height: 36px;
border-radius: UiRadius.control;
shadow-enabled: root.show-focus || !root.soft;
border-width: root.show-focus || (!root.quiet && !root.soft && !root.primary && !root.destructive) ? 1px : 0px;
border-color: !root.enabled ? ButtonStyle.disabled-border
: root.show-focus ? WorkbenchStyle.accent
: touch.pressed ? ButtonStyle.pressed-border
: touch.has-hover ? ButtonStyle.hover-border : ButtonStyle.border;
shadow-enabled: root.enabled && (root.show-focus || (!root.soft && !root.quiet && !touch.pressed));
shadow-focused: root.show-focus;
shadow-color: root.destructive ? UiShadow.error
: root.show-focus || root.primary ? UiShadow.focus
@@ -57,12 +62,12 @@ export component WorkbenchButton inherits ShadowSurface {
: root.soft
? (root.primary
? (touch.pressed ? #254cc0 : touch.has-hover || root.show-focus ? WorkbenchStyle.accent : #dce7ff)
: (touch.pressed ? ControlStyle.pressed : touch.has-hover ? ControlStyle.hover : ControlStyle.surface))
: (touch.pressed ? ButtonStyle.pressed : touch.has-hover ? ButtonStyle.hover : ButtonStyle.surface))
: root.primary
? (touch.pressed ? #254cc0 : touch.has-hover ? #4574f0 : WorkbenchStyle.accent)
: (touch.pressed ? ControlStyle.pressed : touch.has-hover ? ControlStyle.hover : root.quiet ? transparent : root.normal-background);
: (touch.pressed ? ButtonStyle.pressed : touch.has-hover ? ButtonStyle.hover : root.quiet ? transparent : root.normal-background);
opacity: root.enabled ? 1 : 0.45;
animate background, shadow-color { duration: 120ms; }
animate background, border-color, shadow-color { duration: 120ms; }
forward-focus: button-focus;
accessible-role: button;
accessible-label: root.tooltip != "" ? root.tooltip : root.text;