[ui] refine desktop shadows and control contrast

This commit is contained in:
dijunkun
2026-10-26 11:18:41 +08:00
parent e3b3f78b06
commit e551e04e8f
16 changed files with 557 additions and 412 deletions
+11 -10
View File
@@ -1,9 +1,10 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { ScrollView, TextEdit } from "std-widgets.slint"; import { ScrollView, TextEdit } from "std-widgets.slint";
import { SettingsDialog, SettingsButton } from "settings_controls.slint"; import { SettingsDialog, SettingsButton } from "settings_controls.slint";
import { WorkbenchStyle } from "workbench_controls.slint"; import { WorkbenchStyle } from "workbench_controls.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { GalleryStrings } from "device_strings.slint"; import { GalleryStrings } from "device_strings.slint";
import { FontAwesomeIcons } from "common.slint"; import { FontAwesomeIcons, ControlStyle } from "common.slint";
import "../assets/fonts/message_spacing.ttf"; import "../assets/fonts/message_spacing.ttf";
export enum AboutPage { overview, components, component, document, source } export enum AboutPage { overview, components, component, document, source }
@@ -26,11 +27,11 @@ export global AboutStrings {
out property <string> back: GalleryStrings.tr("返回", "Back", "Назад"); out property <string> back: GalleryStrings.tr("返回", "Back", "Назад");
} }
export component AboutNavigationGroup inherits Rectangle { export component AboutNavigationGroup inherits ShadowSurface {
height: rows.preferred-height + 2px; height: rows.preferred-height + 2px;
background: WorkbenchStyle.surface; background: WorkbenchStyle.surface;
border-radius: UiRadius.control; border-radius: UiRadius.control;
border-width: 1px; border-color: WorkbenchStyle.separator; shadow-color: UiShadow.edge;
clip: true; clip: true;
rows := VerticalLayout { rows := VerticalLayout {
x: 1px; y: 1px; width: parent.width - 2px; x: 1px; y: 1px; width: parent.width - 2px;
@@ -41,13 +42,13 @@ export component AboutNavigationGroup inherits Rectangle {
export component AboutRowSeparator inherits Rectangle { export component AboutRowSeparator inherits Rectangle {
height: 1px; height: 1px;
Rectangle { ShadowSeparator {
x: 14px; width: parent.width - 30px; height: 1px; x: 14px; width: parent.width - 30px; height: 1px;
background: WorkbenchStyle.separator; shadow-color: UiShadow.separator;
} }
} }
export component AboutNavigationRow inherits Rectangle { export component AboutNavigationRow inherits ShadowSurface {
in property <string> title; in property <string> title;
in property <string> subtitle; in property <string> subtitle;
in property <string> icon; in property <string> icon;
@@ -60,7 +61,7 @@ export component AboutNavigationRow inherits Rectangle {
background: touch.pressed ? WorkbenchStyle.pressed-surface background: touch.pressed ? WorkbenchStyle.pressed-surface
: touch.has-hover ? WorkbenchStyle.hover-surface : WorkbenchStyle.surface; : touch.has-hover ? WorkbenchStyle.hover-surface : WorkbenchStyle.surface;
border-radius: root.grouped ? 0px : UiRadius.control; border-radius: root.grouped ? 0px : UiRadius.control;
border-width: root.grouped ? 0px : 1px; border-color: WorkbenchStyle.separator; shadow-enabled: !root.grouped; shadow-color: UiShadow.edge;
accessible-role: button; accessible-role: button;
accessible-label: root.title + (root.subtitle != "" ? ", " + root.subtitle : "") accessible-label: root.title + (root.subtitle != "" ? ", " + root.subtitle : "")
+ (root.detail != "" ? ", " + root.detail : ""); + (root.detail != "" ? ", " + root.detail : "");
@@ -69,10 +70,10 @@ export component AboutNavigationRow inherits Rectangle {
x: 12px; y: (parent.height - self.height) / 2; x: 12px; y: (parent.height - self.height) / 2;
width: 28px; height: 28px; width: 28px; height: 28px;
border-radius: UiRadius.control; border-radius: UiRadius.control;
background: WorkbenchStyle.field-surface; background: ControlStyle.surface;
Text { Text {
text: root.icon; font-family: "Font Awesome 6 Free"; font-weight: 900; text: root.icon; font-family: "Font Awesome 6 Free"; font-weight: 900;
font-size: 13px; color: WorkbenchStyle.muted; font-size: 13px; color: ControlStyle.icon;
horizontal-alignment: center; vertical-alignment: center; horizontal-alignment: center; vertical-alignment: center;
accessible-role: none; accessible-role: none;
} }
@@ -226,7 +227,7 @@ export component AboutPanel inherits SettingsDialog {
footer := VerticalLayout { footer := VerticalLayout {
x: 24px; y: parent.height - self.height - 24px; x: 24px; y: parent.height - self.height - 24px;
width: parent.width - 48px; height: self.preferred-height; spacing: 12px; width: parent.width - 48px; height: self.preferred-height; spacing: 12px;
Rectangle { height: 1px; background: WorkbenchStyle.separator; } ShadowSeparator { height: 1px; shadow-color: UiShadow.separator; }
HorizontalLayout { HorizontalLayout {
alignment: end; alignment: end;
SettingsButton { text: AboutStrings.back; clicked => { root.back(); } } SettingsButton { text: AboutStrings.back; clicked => { root.back(); } }
+88 -61
View File
@@ -1,17 +1,28 @@
import { ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
// The legacy client uses ImGui::StyleColorsLight(). Keep the structural line
// colors in one place so the Slint renderer produces the same visual weight.
export global ImGuiLineStyle {
out property <color> border: #0000004d;
out property <color> separator: #6363639e;
out property <color> divider: #0000007a;
out property <color> control-separator: #b2b2b2;
}
export global FloatingBarStyle { export global FloatingBarStyle {
out property <length> corner-radius: UiRadius.menu; out property <length> corner-radius: UiRadius.menu;
} }
// Filled controls remain distinct from white cards without restoring outlines.
export global ControlStyle {
out property <color> surface: #dce6f3;
out property <color> hover: #cedef1;
out property <color> pressed: #bfd2ec;
out property <color> selected: #bcd4f7;
out property <color> selected-hover: #adcaf1;
out property <color> selected-pressed: #99bce8;
out property <color> selected-text: #1d4e9e;
out property <color> disabled: #f0f2f5;
out property <color> icon: #52657f;
out property <color> input-surface: #dee7f2;
out property <color> input-focused: #e5ecf5;
out property <color> input-placeholder: #566a84;
// These tints over white match the field fills for standard LineEdit.
out property <color> input-tint: #235eaa26;
out property <color> input-focus-tint: #235eaa1e;
}
// ImGui loads a 32px font atlas and applies per-window font scales. These // ImGui loads a 32px font atlas and applies per-window font scales. These
// logical sizes reproduce those visual tiers on Slint's HiDPI renderer. // logical sizes reproduce those visual tiers on Slint's HiDPI renderer.
export global ImGuiFontStyle { export global ImGuiFontStyle {
@@ -69,10 +80,10 @@ export component IconButton inherits Rectangle {
in property <bool> enabled: true; in property <bool> enabled: true;
in property <bool> danger: false; in property <bool> danger: false;
in property <length> icon-size: 16px; in property <length> icon-size: 16px;
in property <color> icon-color: #24272d; in property <color> icon-color: ControlStyle.icon;
in property <color> normal-background: transparent; in property <color> normal-background: transparent;
in property <color> hover-background: #edf0f5; in property <color> hover-background: ControlStyle.hover;
in property <color> pressed-background: #d9dde5; in property <color> pressed-background: ControlStyle.pressed;
in property <bool> slashed: false; in property <bool> slashed: false;
callback clicked; callback clicked;
@@ -138,8 +149,8 @@ export component ImageButton inherits Rectangle {
width: 30px; width: 30px;
height: 30px; height: 30px;
background: touch.pressed ? (danger ? #d92d20 : #d9dde5) background: touch.pressed ? (danger ? #d92d20 : ControlStyle.pressed)
: touch.has-hover ? (danger ? #f04438 : #edf0f5) : touch.has-hover ? (danger ? #f04438 : ControlStyle.hover)
: transparent; : transparent;
border-radius: UiRadius.control; border-radius: UiRadius.control;
@@ -174,14 +185,15 @@ export component ImageButton inherits Rectangle {
} }
} }
export component CompactComboBox inherits Rectangle { export component CompactComboBox inherits ShadowSurface {
shadow-compact: true;
in property <[string]> model; in property <[string]> model;
in-out property <int> current-index: 0; in-out property <int> current-index: 0;
in property <bool> enabled: true; in property <bool> enabled: true;
in property <bool> subtle: false; in property <bool> subtle: false;
in property <bool> show-arrow: true; in property <bool> show-arrow: true;
in property <color> normal-border-color: root.subtle ? #dfe6ef : #bfc2c7; in property <color> normal-shadow-color: UiShadow.edge;
in property <color> hover-border-color: root.normal-border-color; in property <color> hover-shadow-color: UiShadow.hover;
property <bool> popup-animation-enabled: false; property <bool> popup-animation-enabled: false;
property <float> popup-progress: 0.0; property <float> popup-progress: 0.0;
callback selected(int); callback selected(int);
@@ -205,9 +217,10 @@ export component CompactComboBox inherits Rectangle {
width: 73px; width: 73px;
height: 24px; height: 24px;
background: root.enabled ? (root.subtle ? #f8fafc : white) : root.subtle ? #f1f3f6 : #f1f1f1; background: !root.enabled ? ControlStyle.disabled
border-width: 1px; : combo-touch.pressed || combo-popup.is-open ? ControlStyle.pressed
border-color: root.enabled && combo-touch.has-hover ? root.hover-border-color : root.normal-border-color; : combo-touch.has-hover ? ControlStyle.hover : ControlStyle.surface;
shadow-color: root.enabled && combo-touch.has-hover ? root.hover-shadow-color : root.normal-shadow-color;
border-radius: UiRadius.control; border-radius: UiRadius.control;
Text { Text {
@@ -224,12 +237,12 @@ export component CompactComboBox inherits Rectangle {
width: 20px; width: 20px;
height: parent.height - 2px; height: parent.height - 2px;
y: 1px; y: 1px;
background: root.subtle ? transparent : root.enabled ? #9bc9f8 : #d7d9dc; background: root.subtle ? transparent : root.enabled ? ControlStyle.selected : #d7d9dc;
border-top-right-radius: UiRadius.control - 1px; border-top-right-radius: UiRadius.control - 1px;
border-bottom-right-radius: UiRadius.control - 1px; border-bottom-right-radius: UiRadius.control - 1px;
Text { Text {
text: FontAwesomeIcons.angle-down; text: FontAwesomeIcons.angle-down;
color: root.subtle ? #79879a : #202124; color: root.subtle ? ControlStyle.icon : #202124;
font-family: "Font Awesome 6 Free"; font-family: "Font Awesome 6 Free";
font-weight: 900; font-weight: 900;
font-size: 10px; font-size: 10px;
@@ -274,7 +287,7 @@ export component CompactComboBox inherits Rectangle {
height: root.model.length * 23px; height: root.model.length * 23px;
close-policy: close-on-click-outside; close-policy: close-on-click-outside;
Rectangle { ShadowSurface {
// Keep the reveal anchored to the popup's top edge while the // Keep the reveal anchored to the popup's top edge while the
// content settles downward into its final position. // content settles downward into its final position.
y: (1.0 - root.popup-progress) * -8px; y: (1.0 - root.popup-progress) * -8px;
@@ -282,16 +295,22 @@ export component CompactComboBox inherits Rectangle {
height: parent.height * root.popup-progress; height: parent.height * root.popup-progress;
opacity: root.popup-progress; opacity: root.popup-progress;
background: white; background: white;
border-width: 1px; shadow-color: root.normal-shadow-color;
border-color: root.normal-border-color;
border-radius: UiRadius.menu; border-radius: UiRadius.menu;
clip: true; clip: true;
for choice[index] in root.model: Rectangle { for choice[index] in root.model: Rectangle {
y: index * 23px; y: index * 23px;
width: parent.width; width: parent.width;
height: 23px; height: 23px;
background: option-touch.has-hover ? #dbeafb : index == root.current-index ? #eef5fd : white; background: index == root.current-index
Text { x: 5px; width: parent.width - 10px; text: choice; color: #202124; font-size: root.subtle ? 12px : ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; } ? (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;
Text {
x: 5px; width: parent.width - 10px; 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;
}
option-touch := TouchArea { option-touch := TouchArea {
enabled: root.enabled; enabled: root.enabled;
clicked => { clicked => {
@@ -306,12 +325,12 @@ export component CompactComboBox inherits Rectangle {
} }
} }
} }
Rectangle { ShadowSurface {
width: parent.width; width: parent.width;
height: parent.height; height: parent.height;
background: transparent; background: transparent;
border-width: 1px; clip: true;
border-color: root.normal-border-color; shadow-color: root.normal-shadow-color;
border-radius: UiRadius.menu; border-radius: UiRadius.menu;
z: 2; z: 2;
} }
@@ -319,19 +338,21 @@ export component CompactComboBox inherits Rectangle {
} }
} }
export component ImGuiCheckBox inherits Rectangle { export component ImGuiCheckBox inherits ShadowSurface {
shadow-compact: true;
in-out property <bool> checked; in-out property <bool> checked;
in property <bool> enabled: true; in property <bool> enabled: true;
width: 21px; width: 21px;
height: 21px; height: 21px;
background: root.enabled ? white : #f1f1f1; background: !root.enabled ? ControlStyle.disabled
border-width: 1px; : root.checked ? (check-touch.pressed ? #254cc0 : check-touch.has-hover ? #4574f0 : #3564e3)
border-color: check-touch.has-hover && root.enabled ? #8cbef2 : #b9bdc2; : check-touch.pressed ? ControlStyle.pressed : check-touch.has-hover ? ControlStyle.hover : ControlStyle.surface;
shadow-color: check-touch.has-hover && root.enabled ? UiShadow.focus : UiShadow.edge;
border-radius: 4px; border-radius: 4px;
if root.checked: Text { if root.checked: Text {
text: FontAwesomeIcons.check; text: FontAwesomeIcons.check;
color: root.enabled ? #2e86de : #8b8f95; color: root.enabled ? white : #8b8f95;
font-family: "Font Awesome 6 Free"; font-family: "Font Awesome 6 Free";
font-weight: 900; font-weight: 900;
font-size: 16px; font-size: 16px;
@@ -368,7 +389,8 @@ export component MiniToggleSwitch inherits Rectangle {
} }
} }
export component CompactButton inherits Rectangle { export component CompactButton inherits ShadowSurface {
shadow-compact: true;
in property <string> text; in property <string> text;
in property <length> text-size: ImGuiFontStyle.body; in property <length> text-size: ImGuiFontStyle.body;
in property <bool> primary: false; in property <bool> primary: false;
@@ -378,12 +400,11 @@ export component CompactButton inherits Rectangle {
width: 34px; width: 34px;
height: 24px; height: 24px;
background: !root.enabled ? (root.quiet ? transparent : #eceef0) background: !root.enabled ? (root.quiet ? transparent : ControlStyle.disabled)
: button-touch.pressed ? (root.primary ? #82b8f2 : #d8dadd) : button-touch.pressed ? (root.primary ? #82b8f2 : ControlStyle.pressed)
: button-touch.has-hover ? (root.primary ? #b8d9fb : #f0f1f2) : button-touch.has-hover ? (root.primary ? #b8d9fb : ControlStyle.hover)
: root.primary ? #a8d0fa : root.quiet ? transparent : #e2e4e7; : root.primary ? #a8d0fa : root.quiet ? transparent : ControlStyle.surface;
border-width: 1px; shadow-color: root.primary ? UiShadow.focus : root.quiet ? transparent : UiShadow.edge;
border-color: root.primary ? #9bc3ed : root.quiet ? transparent : #c8cbd0;
border-radius: UiRadius.control; border-radius: UiRadius.control;
Text { Text {
text: root.text; text: root.text;
@@ -396,24 +417,29 @@ export component CompactButton inherits Rectangle {
button-touch := TouchArea { enabled: root.enabled; clicked => { root.clicked(); } } button-touch := TouchArea { enabled: root.enabled; clicked => { root.clicked(); } }
} }
export component PrimaryButton inherits Rectangle { export component PrimaryButton inherits ShadowSurface {
in property <string> text; in property <string> text;
in property <length> text-size: ImGuiFontStyle.body; in property <length> text-size: 12px;
in property <bool> enabled: true; in property <bool> enabled: true;
callback clicked; callback clicked;
min-width: 72px; min-width: 72px;
height: 30px; height: 32px;
background: !root.enabled ? #b8c1d1 background: !root.enabled ? #b8c1d1
: touch.pressed ? #1849a9 : touch.pressed ? #254cc0
: touch.has-hover ? #2970d6 : touch.has-hover ? #4574f0
: #2463c7; : #3564e3;
shadow-compact: true;
shadow-enabled: root.enabled && !touch.pressed;
shadow-color: UiShadow.focus;
border-radius: UiRadius.control; border-radius: UiRadius.control;
animate background { duration: 100ms; }
Text { Text {
text: root.text; text: root.text;
color: white; color: white;
font-size: root.text-size; font-size: root.text-size;
font-weight: 600;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center; vertical-alignment: center;
} }
@@ -424,26 +450,28 @@ export component PrimaryButton inherits Rectangle {
} }
} }
export component SecondaryButton inherits Rectangle { export component SecondaryButton inherits ShadowSurface {
in property <string> text; in property <string> text;
in property <length> text-size: ImGuiFontStyle.body; in property <length> text-size: 12px;
in property <bool> enabled: true; in property <bool> enabled: true;
callback clicked; callback clicked;
min-width: 72px; min-width: 72px;
height: 30px; height: 32px;
background: !root.enabled ? #f1f2f4 background: !root.enabled ? ControlStyle.disabled
: touch.pressed ? #dfe3e9 : touch.pressed ? ControlStyle.pressed
: touch.has-hover ? #f3f4f6 : white; : touch.has-hover ? ControlStyle.hover : ControlStyle.surface;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: #c9ced7; shadow-compact: true;
shadow-enabled: root.enabled && !touch.pressed;
border-radius: UiRadius.control; border-radius: UiRadius.control;
clip: true; animate background { duration: 100ms; }
Text { Text {
text: root.text; text: root.text;
color: root.enabled ? #24272d : #949aa5; color: root.enabled ? #303f55 : #949aa5;
font-size: root.text-size; font-size: root.text-size;
font-weight: 500;
horizontal-alignment: center; horizontal-alignment: center;
vertical-alignment: center; vertical-alignment: center;
} }
@@ -495,9 +523,8 @@ export component SectionTitle inherits Text {
vertical-alignment: center; vertical-alignment: center;
} }
export component DialogSurface inherits Rectangle { export component DialogSurface inherits ShadowSurface {
background: white; background: white;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: ImGuiLineStyle.border;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
} }
@@ -1,7 +1,8 @@
import { ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { ScrollView } from "std-widgets.slint"; import { ScrollView } from "std-widgets.slint";
import { RoundedLineEdit } from "rounded_line_edit.slint"; import { RoundedLineEdit } from "rounded_line_edit.slint";
import { CompactButton, ImGuiCheckBox } from "common.slint"; import { CompactButton, ImGuiCheckBox, ControlStyle } from "common.slint";
import { GalleryStrings } from "device_strings.slint"; import { GalleryStrings } from "device_strings.slint";
export struct DeviceGroupOption { export struct DeviceGroupOption {
@@ -20,13 +21,13 @@ export component DeviceGroupDialog inherits Rectangle {
callback cancel(); callback cancel();
background: #00000040; background: #00000040;
TouchArea {} TouchArea {}
dialog := Rectangle { dialog := ShadowSurface {
x: (parent.width - self.width) / 2; x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: min(360px, parent.width - 40px); width: min(360px, parent.width - 40px);
height: 208px + group-list.height + (root.error == "" ? 0px : 32px); height: 208px + group-list.height + (root.error == "" ? 0px : 32px);
background: white; border-radius: UiRadius.surface; background: white; border-radius: UiRadius.surface;
border-width: 1px; border-color: #b2b2b2; shadow-color: UiShadow.edge;
TouchArea {} TouchArea {}
Text { Text {
x: 18px; y: 16px; height: 24px; x: 18px; y: 16px; height: 24px;
@@ -41,10 +42,10 @@ export component DeviceGroupDialog inherits Rectangle {
x: 18px; y: 67px; width: parent.width - 36px; height: 16px; x: 18px; y: 67px; width: parent.width - 36px; height: 16px;
text: "ID " + root.device-id; color: #858c97; font-size: 11px; text: "ID " + root.device-id; color: #858c97; font-size: 11px;
} }
group-list := Rectangle { group-list := ShadowSurface {
x: 18px; y: 95px; width: parent.width - 36px; x: 18px; y: 95px; width: parent.width - 36px;
height: min(5, max(1, root.options.length)) * 32px + 2px; height: min(5, max(1, root.options.length)) * 32px + 2px;
background: #fbfcfd; border-width: 1px; border-color: #e1e5eb; border-radius: UiRadius.surface; background: #fbfcfd; shadow-color: UiShadow.edge; border-radius: UiRadius.surface;
clip: true; clip: true;
ScrollView { ScrollView {
x: 1px; y: 1px; width: parent.width - 2px; height: parent.height - 2px; x: 1px; y: 1px; width: parent.width - 2px; height: parent.height - 2px;
@@ -52,16 +53,21 @@ export component DeviceGroupDialog inherits Rectangle {
VerticalLayout { VerticalLayout {
spacing: 0px; spacing: 0px;
for option[index] in root.options: Rectangle { for option[index] in root.options: Rectangle {
private property <bool> selected: option.selected && (option.name != "" || root.new-group == "");
height: 32px; height: 32px;
background: row-touch.has-hover ? #eef4fc : transparent; background: self.selected
? (row-touch.pressed ? ControlStyle.selected-pressed : row-touch.has-hover ? ControlStyle.selected-hover : ControlStyle.selected)
: row-touch.pressed ? ControlStyle.pressed : row-touch.has-hover ? ControlStyle.hover : transparent;
ImGuiCheckBox { ImGuiCheckBox {
x: 9px; y: 6px; width: 20px; height: 20px; x: 9px; y: 6px; width: 20px; height: 20px;
checked: option.selected && (option.name != "" || root.new-group == ""); checked: parent.selected;
} }
Text { Text {
x: 39px; width: parent.width - 48px; height: parent.height; x: 39px; width: parent.width - 48px; height: parent.height;
text: option.name == "" ? GalleryStrings.tr("未分组", "Ungrouped", "Без группы") : option.name; text: option.name == "" ? GalleryStrings.tr("未分组", "Ungrouped", "Без группы") : option.name;
color: #30343b; font-size: 12px; overflow: elide; vertical-alignment: center; color: parent.selected ? ControlStyle.selected-text : #30343b;
font-weight: parent.selected ? 600 : 400;
font-size: 12px; overflow: elide; vertical-alignment: center;
} }
row-touch := TouchArea { clicked => { root.toggle-group(option.name); } } row-touch := TouchArea { clicked => { root.toggle-group(option.name); } }
} }
@@ -75,10 +81,10 @@ export component DeviceGroupDialog inherits Rectangle {
border-width: UiRadius.surface; border-radius: 2 * UiRadius.surface; border-width: UiRadius.surface; border-radius: 2 * UiRadius.surface;
border-color: white; border-color: white;
} }
Rectangle { ShadowSurface {
width: parent.width; height: parent.height; width: parent.width; height: parent.height;
border-width: 1px; border-radius: UiRadius.surface; border-radius: UiRadius.surface;
border-color: #e1e5eb; shadow-color: UiShadow.edge;
} }
} }
RoundedLineEdit { RoundedLineEdit {
@@ -1,3 +1,4 @@
import { ShadowSeparator, UiShadow } from "ui_shadow.slint";
import { FileTransferEntry } from "stream_window.slint"; import { FileTransferEntry } from "stream_window.slint";
import { DialogSurface, FontAwesomeIcons, IconButton, ImGuiFontStyle } from "common.slint"; import { DialogSurface, FontAwesomeIcons, IconButton, ImGuiFontStyle } from "common.slint";
import { WorkbenchStyle, WorkbenchButton } from "workbench_controls.slint"; import { WorkbenchStyle, WorkbenchButton } from "workbench_controls.slint";
@@ -26,7 +27,7 @@ export component FileTransferNotification inherits Window {
DialogSurface { DialogSurface {
width: parent.width; height: parent.height; width: parent.width; height: parent.height;
background: WorkbenchStyle.surface; background: WorkbenchStyle.surface;
border-color: WorkbenchStyle.separator; shadow-color: UiShadow.edge;
Text { Text {
x: 16px; y: 8px; x: 16px; y: 8px;
width: parent.width - 64px - (root.remote-id == "" ? 0px : 108px); width: parent.width - 64px - (root.remote-id == "" ? 0px : 108px);
@@ -50,9 +51,9 @@ export component FileTransferNotification inherits Window {
pressed-background: WorkbenchStyle.pressed-surface; pressed-background: WorkbenchStyle.pressed-surface;
clicked => { root.dismissed(); } clicked => { root.dismissed(); }
} }
Rectangle { ShadowSeparator {
x: 16px; y: 40px; width: parent.width - 32px; height: 1px; x: 16px; y: 40px; width: parent.width - 32px; height: 1px;
background: WorkbenchStyle.separator; shadow-color: UiShadow.separator;
} }
Text { Text {
x: 16px; y: 47px; width: parent.width - 32px; height: 18px; x: 16px; y: 47px; width: parent.width - 32px; height: 18px;
@@ -96,7 +97,7 @@ export component FileTransferNotification inherits Window {
text: root.cancel-text; text: root.cancel-text;
text-size: ImGuiFontStyle.base; text-size: ImGuiFontStyle.base;
text-color: WorkbenchStyle.muted; text-color: WorkbenchStyle.muted;
normal-border-color: WorkbenchStyle.border; normal-shadow-color: UiShadow.edge;
clicked => { root.cancel-requested(); } clicked => { root.cancel-requested(); }
} }
} }
@@ -1,5 +1,6 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { CompactButton, FontAwesomeIcons } from "common.slint"; import { CompactButton, FontAwesomeIcons, ControlStyle } from "common.slint";
export struct GalleryMenuItem { label: string, enabled: bool } export struct GalleryMenuItem { label: string, enabled: bool }
@@ -88,12 +89,11 @@ export component GalleryMenuButton inherits Rectangle {
color: root.enabled ? #62666d : #8e9298; color: root.enabled ? #62666d : #8e9298;
horizontal-alignment: center; vertical-alignment: center; horizontal-alignment: center; vertical-alignment: center;
} }
if root.compact-trigger: Rectangle { if root.compact-trigger: ShadowSurface {
background: more-touch.pressed || popup.is-open ? #e5edfc : more-touch.has-hover ? white : #fffffff0; background: more-touch.pressed || popup.is-open ? ControlStyle.selected : more-touch.has-hover ? ControlStyle.hover : white;
border-radius: UiRadius.menu; border-radius: UiRadius.menu;
border-width: 1px; shadow-color: trigger-focus.has-focus ? UiShadow.focus : UiShadow.edge;
border-color: trigger-focus.has-focus ? #3564e3 : #ffffff80; animate background, shadow-color { duration: 120ms; }
animate background, border-color { duration: 120ms; }
for dot in 3: Rectangle { for dot in 3: Rectangle {
x: parent.width / 2 - 6px + dot * 5px; x: parent.width / 2 - 6px + dot * 5px;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
@@ -117,11 +117,12 @@ export component GalleryMenuButton inherits Rectangle {
forward-focus: popup-focus; forward-focus: popup-focus;
popup-focus := FocusScope { popup-focus := FocusScope {
key-pressed(event) => { return root.handle-key(event); } key-pressed(event) => { return root.handle-key(event); }
Rectangle { ShadowSurface {
background: white; border-width: 1px; border-color: #c9cdd2; border-radius: UiRadius.menu; background: white; shadow-color: UiShadow.edge; border-radius: UiRadius.menu;
if root.destructive-index > 0: Rectangle { clip: true;
if root.destructive-index > 0: ShadowSeparator {
x: 10px; y: root.menu-padding + root.destructive-index * root.row-height + (root.divider-spacing - 1px) / 2; x: 10px; y: root.menu-padding + root.destructive-index * root.row-height + (root.divider-spacing - 1px) / 2;
width: parent.width - 20px; height: 1px; background: #e8edf4; width: parent.width - 20px; height: 1px; shadow-color: UiShadow.separator;
} }
for item[index] in root.items: Rectangle { for item[index] in root.items: Rectangle {
private property <bool> destructive: index == root.destructive-index; private property <bool> destructive: index == root.destructive-index;
@@ -132,7 +133,9 @@ export component GalleryMenuButton inherits Rectangle {
+ (root.destructive-index > 0 && index >= root.destructive-index ? root.divider-spacing : 0px); + (root.destructive-index > 0 && index >= root.destructive-index ? root.divider-spacing : 0px);
width: parent.width - root.menu-padding * 2; height: root.row-height; width: parent.width - root.menu-padding * 2; height: root.row-height;
border-radius: UiRadius.control; border-radius: UiRadius.control;
background: item.enabled && index == root.current-index ? (self.destructive ? #fff0f0 : #edf4fc) : transparent; background: item.enabled && index == root.current-index
? (self.destructive ? (item-touch.pressed ? #f6babb : #fbe0e0)
: item-touch.pressed ? ControlStyle.pressed : ControlStyle.hover) : transparent;
accessible-role: button; accessible-label: item.label; accessible-enabled: item.enabled; accessible-role: button; accessible-label: item.label; accessible-enabled: item.enabled;
accessible-action-default => { root.choose(index); } accessible-action-default => { root.choose(index); }
if self.has-icon: Text { if self.has-icon: Text {
@@ -148,7 +151,7 @@ export component GalleryMenuButton inherits Rectangle {
text: item.label; color: parent.foreground; text: item.label; color: parent.foreground;
font-size: root.compact-menu ? 11px : 12px; vertical-alignment: center; font-size: root.compact-menu ? 11px : 12px; vertical-alignment: center;
} }
TouchArea { item-touch := TouchArea {
enabled: item.enabled; enabled: item.enabled;
mouse-cursor: pointer; mouse-cursor: pointer;
changed has-hover => { if self.has-hover { root.current-index = index; } } changed has-hover => { if self.has-hover { root.current-index = index; } }
@@ -1,3 +1,4 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { GalleryStrings } from "device_strings.slint"; import { GalleryStrings } from "device_strings.slint";
import { CompactButton } from "common.slint"; import { CompactButton } from "common.slint";
@@ -190,13 +191,13 @@ export component GalleryNetworkDialog inherits Rectangle {
callback close(); callback close();
background: #00000042; background: #00000042;
TouchArea { clicked => { root.close(); } } TouchArea { clicked => { root.close(); } }
Rectangle { ShadowSurface {
x: (parent.width - self.width) / 2; x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: min(760px, parent.width - 32px); width: min(760px, parent.width - 32px);
height: min(120px + root.list-height, parent.height - 32px); height: min(120px + root.list-height, parent.height - 32px);
background: white; border-radius: UiRadius.surface; background: white; border-radius: UiRadius.surface;
border-width: 1px; border-color: #c6cdd5; shadow-color: UiShadow.edge;
TouchArea {} TouchArea {}
FocusScope { FocusScope {
init => { self.focus(); } init => { self.focus(); }
@@ -237,7 +238,7 @@ export component GalleryNetworkDialog inherits Rectangle {
for device[index] in root.devices: NetworkRow { for device[index] in root.devices: NetworkRow {
y: index * root.row-height; width: list.viewport-width; height: root.row-height; y: index * root.row-height; width: list.viewport-width; height: root.row-height;
device: device; background: mod(index, 2) == 0 ? white : #f8fafc; device: device; background: mod(index, 2) == 0 ? white : #f8fafc;
Rectangle { y: parent.height - 1px; height: 1px; background: #edf0f4; } ShadowSeparator { y: parent.height - 1px; height: 1px; shadow-color: UiShadow.separator; }
} }
} }
if root.devices.length == 0: NetworkEmptyState { if root.devices.length == 0: NetworkEmptyState {
+49 -44
View File
@@ -1,3 +1,4 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { GalleryStrings } from "device_strings.slint"; import { GalleryStrings } from "device_strings.slint";
import { RemoteCursor } from "remote_cursor.slint"; import { RemoteCursor } from "remote_cursor.slint";
@@ -6,7 +7,7 @@ import { GalleryMenuButton } from "gallery_menu_button.slint";
import { GroupFilter } from "group_filter.slint"; import { GroupFilter } from "group_filter.slint";
import { ScrollView } from "std-widgets.slint"; import { ScrollView } from "std-widgets.slint";
import { RoundedLineEdit } from "rounded_line_edit.slint"; import { RoundedLineEdit } from "rounded_line_edit.slint";
import { CompactButton, SecondaryButton, IconButton, FontAwesomeIcons, ImGuiCheckBox } from "common.slint"; import { CompactButton, SecondaryButton, IconButton, FontAwesomeIcons, ImGuiCheckBox, ControlStyle } from "common.slint";
component GalleryButton inherits CompactButton { component GalleryButton inherits CompactButton {
border-radius: UiRadius.control; border-radius: UiRadius.control;
@@ -473,7 +474,7 @@ export component GalleryView inherits Rectangle {
height: root.card-height; height: root.card-height;
opacity: root.dragging && root.drag-source == tile.id ? 0.45 : 1; opacity: root.dragging && root.drag-source == tile.id ? 0.45 : 1;
background: !tile.online ? DeviceCardStyle.offline-background background: !tile.online ? DeviceCardStyle.offline-background
: tile.selected ? #eaf3ff : root.batch && tile-touch.has-hover ? #f3f8fe : white; : tile.selected ? ControlStyle.selected : root.batch && tile-touch.has-hover ? ControlStyle.hover : white;
Rectangle { Rectangle {
x: root.card-inset; y: self.x; x: root.card-inset; y: self.x;
width: parent.width - self.x * 2; width: parent.width - self.x * 2;
@@ -491,12 +492,13 @@ export component GalleryView inherits Rectangle {
width: parent.width - self.x - (tile-touch.has-hover && !root.batch ? DeviceCardStyle.toolbar-width + 12px : root.batch ? 35px : 9px); width: parent.width - self.x - (tile-touch.has-hover && !root.batch ? DeviceCardStyle.toolbar-width + 12px : root.batch ? 35px : 9px);
height: 24px; text: tile.name; color: tile.online ? #24272d : DeviceCardStyle.offline-text; font-size: 12px; overflow: elide; vertical-alignment: center; height: 24px; text: tile.name; color: tile.online ? #24272d : DeviceCardStyle.offline-text; font-size: 12px; overflow: elide; vertical-alignment: center;
} }
if root.batch && !root.simplified: Rectangle { if root.batch && !root.simplified: ShadowSurface {
x: parent.width - 27px; y: parent.height - 27px; x: parent.width - 27px; y: parent.height - 27px;
width: 18px; height: 18px; border-radius: 4px; border-width: 1px; width: 18px; height: 18px; border-radius: 4px;
background: tile.selected ? (tile-touch.pressed ? #5c9fdf : tile-touch.has-hover ? #659fe0 : #6dabea) : tile-touch.has-hover ? #f3f8fe : white; background: tile.selected ? (tile-touch.pressed ? #254cc0 : tile-touch.has-hover ? #4574f0 : #3564e3)
border-color: tile.selected ? #6dabea : tile-touch.has-hover ? #76aeea : #b7bec8; : tile-touch.pressed ? ControlStyle.pressed : tile-touch.has-hover ? ControlStyle.hover : ControlStyle.surface;
animate background, border-color { duration: 120ms; } shadow-color: tile.selected ? UiShadow.focus : tile-touch.has-hover ? UiShadow.hover : UiShadow.edge;
animate background, shadow-color { duration: 120ms; }
if tile.selected: Text { if tile.selected: Text {
text: FontAwesomeIcons.check; color: white; text: FontAwesomeIcons.check; color: white;
font-family: "Font Awesome 6 Free"; font-weight: 900; font-size: 11px; font-family: "Font Awesome 6 Free"; font-weight: 900; font-size: 11px;
@@ -531,27 +533,27 @@ export component GalleryView inherits Rectangle {
} }
} }
// Draw each table line once, continuously across occupied and empty cells. // Draw each table line once, continuously across occupied and empty cells.
for column in root.columns + 1: Rectangle { for column in root.columns + 1: ShadowSeparator {
visible: root.count > 0; visible: root.count > 0;
x: min(column * root.card-width, grid.table-width - 1px); x: min(column * root.card-width, grid.table-width - 1px);
y: 0px; y: 0px;
width: 1px; height: grid.table-height; width: 1px; height: grid.table-height;
background: root.resizing || (grid.has-hover && grid.edge-x != 0 && grid.column-line == column) vertical: true; shadow-color: root.resizing || (grid.has-hover && grid.edge-x != 0 && grid.column-line == column)
? #76aeea : #c9cdd2; ? UiShadow.focus : UiShadow.separator;
} }
for row in root.rows + 1: Rectangle { for row in root.rows + 1: ShadowSeparator {
visible: root.count > 0; visible: root.count > 0;
x: 0px; x: 0px;
y: min(row * root.card-height, grid.table-height - 1px); y: min(row * root.card-height, grid.table-height - 1px);
width: grid.table-width; height: 1px; width: grid.table-width; height: 1px;
background: root.resizing || (grid.has-hover && grid.edge-y != 0 && grid.row-line == row) shadow-color: root.resizing || (grid.has-hover && grid.edge-y != 0 && grid.row-line == row)
? #76aeea : #c9cdd2; ? UiShadow.focus : UiShadow.separator;
} }
if root.drag-target >= 0: Rectangle { if root.drag-target >= 0: ShadowSurface {
x: mod(root.drag-target, root.columns) * root.card-width + 1px; x: mod(root.drag-target, root.columns) * root.card-width + 1px;
y: floor(root.drag-target / root.columns) * root.card-height + 1px; y: floor(root.drag-target / root.columns) * root.card-height + 1px;
width: root.card-width - 2px; height: root.card-height - 2px; width: root.card-width - 2px; height: root.card-height - 2px;
background: #76aeea26; border-width: 2px; border-color: #4a94de; background: #76aeea26; shadow-color: UiShadow.focus;
} }
if root.count == 0: Text { if root.count == 0: Text {
text: root.simplified text: root.simplified
@@ -597,11 +599,11 @@ export component GalleryView inherits Rectangle {
} }
GalleryButton { width: 100px; primary: true; enabled: !root.focused.connected || root.controlling || (root.focused.has-frame && !root.focused.stale); text: root.focused.connected ? (root.controlling ? GalleryStrings.tr("释放操控", "Release", "Отпустить") : GalleryStrings.tr("接管操控", "Control", "Управлять")) : GalleryStrings.tr("连接", "Connect", "Подключить"); clicked => { root.action(root.focused.connected ? (root.controlling ? "release" : "control") : "connect", root.focused.id); } } GalleryButton { width: 100px; primary: true; enabled: !root.focused.connected || root.controlling || (root.focused.has-frame && !root.focused.stale); text: root.focused.connected ? (root.controlling ? GalleryStrings.tr("释放操控", "Release", "Отпустить") : GalleryStrings.tr("接管操控", "Control", "Управлять")) : GalleryStrings.tr("连接", "Connect", "Подключить"); clicked => { root.action(root.focused.connected ? (root.controlling ? "release" : "control") : "connect", root.focused.id); } }
} }
Rectangle { ShadowSurface {
x: 0px; y: rail.visible ? focus-divider.y + focus-divider.height : 36px; x: 0px; y: rail.visible ? focus-divider.y + focus-divider.height : 36px;
width: parent.width; height: parent.height - self.y; width: parent.width; height: parent.height - self.y;
background: root.focused.online ? #eff1f4 : DeviceCardStyle.offline-background; border-radius: UiRadius.surface; background: root.focused.online ? #eff1f4 : DeviceCardStyle.offline-background; border-radius: UiRadius.surface;
border-width: root.controlling ? 2px : 1px; border-color: root.controlling ? #76aeea : root.focused.online ? #d5dbe3 : DeviceCardStyle.offline-border; shadow-color: root.controlling ? UiShadow.focus : UiShadow.edge;
video := Rectangle { video := Rectangle {
x: 4px; y: 4px; width: parent.width - 8px; height: parent.height - 8px; x: 4px; y: 4px; width: parent.width - 8px; height: parent.height - 8px;
background: root.focused.online ? #24272d : DeviceCardStyle.offline-preview; background: root.focused.online ? #24272d : DeviceCardStyle.offline-preview;
@@ -668,12 +670,12 @@ export component GalleryView inherits Rectangle {
// Collapse the focused tile and its gap without changing the page model. // Collapse the focused tile and its gap without changing the page model.
width: tile.id == root.focused.id ? 0px : root.rail-slot-width; width: tile.id == root.focused.id ? 0px : root.rail-slot-width;
height: root.rail-card-height; visible: tile.id != root.focused.id; height: root.rail-card-height; visible: tile.id != root.focused.id;
rail-card := Rectangle { rail-card := ShadowSurface {
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 + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence; private property <string> details: tile.name + "\n" + tile.host-name + "\nID: " + tile.id + "\n" + tile.group-names + "\n" + self.presence;
width: root.rail-preview-width + 10px; height: root.rail-card-height; border-radius: UiRadius.surface; width: root.rail-preview-width + 10px; height: root.rail-card-height; border-radius: UiRadius.surface;
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; shadow-color: UiShadow.edge;
accessible-role: button; accessible-role: button;
accessible-label: self.details; accessible-label: self.details;
accessible-action-default => { root.action("focus", tile.id); } accessible-action-default => { root.action("focus", tile.id); }
@@ -714,9 +716,9 @@ export component GalleryView inherits Rectangle {
visible: root.focus-rail-visible; visible: root.focus-rail-visible;
x: 0px; y: rail.y + rail.height; width: parent.width; height: 12px; x: 0px; y: rail.y + rail.height; width: parent.width; height: 12px;
private property <bool> highlighted: divider-touch.has-hover || root.focus-resizing || divider-focus.has-focus; private property <bool> highlighted: divider-touch.has-hover || root.focus-resizing || divider-focus.has-focus;
Rectangle { ShadowSeparator {
x: 0px; y: 5px; width: parent.width; height: 1px; x: 0px; y: 5px; width: parent.width; height: 1px;
background: focus-divider.highlighted ? #a6c9ed : #e6e9ed; shadow-color: focus-divider.highlighted ? UiShadow.focus : UiShadow.separator;
} }
Rectangle { Rectangle {
x: (parent.width - self.width) / 2; y: 4px; width: 36px; height: 3px; border-radius: 1.5px; x: (parent.width - self.width) / 2; y: 4px; width: 36px; height: 3px; border-radius: 1.5px;
@@ -775,7 +777,7 @@ export component GalleryView inherits Rectangle {
} }
Rectangle { Rectangle {
y: parent.height - 44px; height: 44px; background: #fafbfc; y: parent.height - 44px; height: 44px; background: #fafbfc;
Rectangle { y: 0px; width: parent.width; height: 1px; background: #e1e4e8; } ShadowSeparator { y: 0px; width: parent.width; height: 1px; shadow-color: UiShadow.separator; }
} }
// Connection status and pagination remain available in every selection state. // Connection status and pagination remain available in every selection state.
footer := Rectangle { footer := Rectangle {
@@ -793,7 +795,7 @@ export component GalleryView inherits Rectangle {
network-button := SecondaryButton { network-button := SecondaryButton {
x: connection-count.x + connection-count.width + 12px; x: connection-count.x + connection-count.width + 12px;
min-width: 64px; width: 64px; height: parent.height; min-width: 64px; width: 64px; height: parent.height;
border-color: #c8cbd0; border-radius: UiRadius.control; shadow-color: UiShadow.edge; border-radius: UiRadius.control;
text: GalleryStrings.tr("网络状态", "Network", "Сеть"); text: GalleryStrings.tr("网络状态", "Network", "Сеть");
accessible-role: button; accessible-role: button;
accessible-label: self.text; accessible-label: self.text;
@@ -823,6 +825,7 @@ export component GalleryView inherits Rectangle {
IconButton { IconButton {
width: 24px; height: 24px; width: 24px; height: 24px;
border-radius: UiRadius.control; border-radius: UiRadius.control;
normal-background: ControlStyle.surface;
icon: root.fullscreen ? FontAwesomeIcons.compress : FontAwesomeIcons.expand; icon: root.fullscreen ? FontAwesomeIcons.compress : FontAwesomeIcons.expand;
tooltip: root.fullscreen ? GalleryStrings.tr("退出全屏", "Exit fullscreen", "Выйти из полноэкранного режима") : GalleryStrings.tr("全屏", "Fullscreen", "На весь экран"); tooltip: root.fullscreen ? GalleryStrings.tr("退出全屏", "Exit fullscreen", "Выйти из полноэкранного режима") : GalleryStrings.tr("全屏", "Fullscreen", "На весь экран");
clicked => { root.toggle-fullscreen(); } clicked => { root.toggle-fullscreen(); }
@@ -840,11 +843,11 @@ export component GalleryView inherits Rectangle {
horizontal-alignment: center; vertical-alignment: center; horizontal-alignment: center; vertical-alignment: center;
} }
} }
if root.dragging: Rectangle { if root.dragging: ShadowSurface {
x: clamp(grid.x + root.drag-x + 14px, 8px, parent.width - self.width - 8px); 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); y: clamp(grid.y + root.drag-y + 14px, 8px, footer.y - self.height - 12px);
width: 224px; height: 54px; z: 10; width: 224px; height: 54px; z: 10;
background: #fffffff2; border-width: 1px; border-color: #76aeea; border-radius: UiRadius.surface; background: #fffffff2; shadow-color: UiShadow.focus; border-radius: UiRadius.surface;
Rectangle { Rectangle {
x: 8px; y: 9px; width: 64px; height: 36px; x: 8px; y: 9px; width: 64px; height: 36px;
background: root.dragged-tile.online ? #24272d : DeviceCardStyle.offline-preview; background: root.dragged-tile.online ? #24272d : DeviceCardStyle.offline-preview;
@@ -862,20 +865,20 @@ export component GalleryView inherits Rectangle {
font-size: 10px; color: #527ca8; overflow: elide; font-size: 10px; color: #527ca8; overflow: elide;
} }
} }
if root.swap-save-failed: Rectangle { if root.swap-save-failed: ShadowSurface {
x: (parent.width - self.width) / 2; y: footer.y - self.height - 14px; x: (parent.width - self.width) / 2; y: footer.y - self.height - 14px;
width: min(420px, parent.width - 32px); height: 36px; z: 10; width: min(420px, parent.width - 32px); height: 36px; z: 10;
background: #fff2f0; border-width: 1px; border-color: #e9b7b0; border-radius: UiRadius.surface; background: #fff2f0; shadow-color: UiShadow.edge; border-radius: UiRadius.surface;
Text { x: 12px; y: 0px; width: parent.width - 24px; height: parent.height; font-size: 12px; color: #9d4339; vertical-alignment: center; Text { x: 12px; y: 0px; width: parent.width - 24px; height: parent.height; font-size: 12px; color: #9d4339; vertical-alignment: center;
text: GalleryStrings.tr("换位未保存,请重试", "Couldn't save positions. Please try again.", "Не удалось сохранить порядок. Повторите попытку."); overflow: elide; text: GalleryStrings.tr("换位未保存,请重试", "Couldn't save positions. Please try again.", "Не удалось сохранить порядок. Повторите попытку."); overflow: elide;
} }
} }
// Device details are rendered by MainWindow; resize hints stay local. // Device details are rendered by MainWindow; resize hints stay local.
if root.device-tooltip-visible && (root.device-tooltip-id == "resize-grid" || root.device-tooltip-id == "resize-focus"): Rectangle { if root.device-tooltip-visible && (root.device-tooltip-id == "resize-grid" || root.device-tooltip-id == "resize-focus"): ShadowSurface {
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);
width: min(320px, parent.width - 16px); height: device-tooltip-label.preferred-height + 16px; width: min(320px, parent.width - 16px); height: device-tooltip-label.preferred-height + 16px;
background: #22252bf2; border-width: 1px; border-color: #ffffff38; border-radius: UiRadius.control; background: #22252bf2; shadow-color: UiShadow.edge; border-radius: UiRadius.control;
device-tooltip-label := Text { device-tooltip-label := Text {
x: 8px; y: 8px; width: parent.width - 16px; x: 8px; y: 8px; width: parent.width - 16px;
text: root.device-tooltip-text; color: white; font-size: 11px; wrap: word-wrap; text: root.device-tooltip-text; color: white; font-size: 11px; wrap: word-wrap;
@@ -884,9 +887,9 @@ export component GalleryView inherits Rectangle {
if root.adding: Rectangle { if root.adding: Rectangle {
background: #00000042; background: #00000042;
TouchArea {} TouchArea {}
add-dialog := Rectangle { add-dialog := ShadowSurface {
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 400px; height: 306px; x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 400px; height: 306px;
background: white; border-radius: UiRadius.surface; border-width: 1px; border-color: #b2b2b2; background: white; border-radius: UiRadius.surface; shadow-color: UiShadow.edge;
function submit() { function submit() {
if root.add-device(device-id.text, password.text, group.text, remember.checked) { if root.add-device(device-id.text, password.text, group.text, remember.checked) {
password.text = ""; password.text = "";
@@ -931,25 +934,27 @@ export component GalleryView inherits Rectangle {
if root.importing: Rectangle { if root.importing: Rectangle {
background: #00000042; background: #00000042;
TouchArea {} TouchArea {}
Rectangle { ShadowSurface {
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 440px; height: 378px; x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; width: 440px; height: 378px;
background: white; border-radius: UiRadius.surface; border-width: 1px; border-color: #b2b2b2; 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: 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: 44px; width: 404px; height: 18px; text: GalleryStrings.tr("加入桌面墙后,可通过批量选择连接", "Add devices, then use Select to connect them.", "После добавления подключайтесь через выбор."); 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; } 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; } 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", ""); } } TouchArea { x: 18px; y: 74px; width: 144px; height: 21px; enabled: root.recent-available > 0; clicked => { root.action("recent-all", ""); } }
Text { x: 224px; y: 74px; width: 198px; height: 21px; text: GalleryStrings.tr("待添加 ", "To add ", "К добавлению ") + root.recent-selected + " / " + root.recent-available; horizontal-alignment: right; vertical-alignment: center; font-size: 12px; color: #62666d; } Text { x: 224px; y: 74px; width: 198px; height: 21px; text: GalleryStrings.tr("待添加 ", "To add ", "К добавлению ") + root.recent-selected + " / " + root.recent-available; horizontal-alignment: right; vertical-alignment: center; font-size: 12px; color: #62666d; }
Rectangle { ShadowSurface {
x: 18px; y: 102px; width: 404px; height: 160px; x: 18px; y: 102px; width: 404px; height: 160px;
border-width: 1px; border-color: #d8dadd; border-radius: UiRadius.surface; clip: true; shadow-color: UiShadow.edge; border-radius: UiRadius.surface; clip: true;
recent-list := ScrollView { recent-list := ScrollView {
x: 1px; y: 1px; width: parent.width - 2px; height: parent.height - 2px; x: 1px; y: 1px; width: parent.width - 2px; height: parent.height - 2px;
viewport-width: self.width - 12px; viewport-width: self.width - 12px;
viewport-height: max(self.height, root.recent-devices.length * 44px); viewport-height: max(self.height, root.recent-devices.length * 44px);
for device[index] in root.recent-devices: Rectangle { for device[index] in root.recent-devices: Rectangle {
y: index * 44px; width: recent-list.viewport-width; height: 44px; y: index * 44px; width: recent-list.viewport-width; height: 44px;
background: device.added ? #f1f2f4 : device.selected ? #edf5fe : row-touch.has-hover ? #f4f5f6 : white; background: device.added ? ControlStyle.disabled
: device.selected ? (row-touch.pressed ? ControlStyle.selected-pressed : row-touch.has-hover ? ControlStyle.selected-hover : ControlStyle.selected)
: row-touch.pressed ? ControlStyle.pressed : row-touch.has-hover ? ControlStyle.hover : white;
ImGuiCheckBox { x: 8px; y: 11px; enabled: !device.added; checked: device.added || device.selected; } ImGuiCheckBox { x: 8px; y: 11px; enabled: !device.added; checked: device.added || device.selected; }
Text { x: 36px; y: 3px; width: parent.width - 126px; height: 20px; text: device.name; color: device.added ? #92969e : #24272d; font-size: 12px; overflow: elide; vertical-alignment: center; } Text { x: 36px; y: 3px; width: parent.width - 126px; height: 20px; text: device.name; color: device.added ? #92969e : #24272d; font-size: 12px; overflow: elide; vertical-alignment: center; }
Text { x: 36px; y: 23px; width: parent.width - 126px; height: 16px; text: device.id; font-size: 11px; color: device.added ? #92969e : #808080; } Text { x: 36px; y: 23px; width: parent.width - 126px; height: 16px; text: device.id; font-size: 11px; color: device.added ? #92969e : #808080; }
@@ -970,10 +975,10 @@ export component GalleryView inherits Rectangle {
border-width: UiRadius.surface; border-radius: 2 * UiRadius.surface; border-width: UiRadius.surface; border-radius: 2 * UiRadius.surface;
border-color: white; border-color: white;
} }
Rectangle { ShadowSurface {
width: parent.width; height: parent.height; width: parent.width; height: parent.height;
border-width: 1px; border-radius: UiRadius.surface; border-radius: UiRadius.surface;
border-color: #d8dadd; shadow-color: UiShadow.edge;
} }
} }
Text { x: 18px; y: 274px; width: 60px; height: 30px; text: GalleryStrings.tr("分组", "Group", "Группа"); font-size: 12px; vertical-alignment: center; } Text { x: 18px; y: 274px; width: 60px; height: 30px; text: GalleryStrings.tr("分组", "Group", "Группа"); font-size: 12px; vertical-alignment: center; }
@@ -985,10 +990,10 @@ export component GalleryView inherits Rectangle {
} }
if root.confirming: Rectangle { if root.confirming: Rectangle {
background: #00000042; TouchArea {} background: #00000042; TouchArea {}
Rectangle { ShadowSurface {
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2;
width: 360px; height: confirmation-list.height + 118px; width: 360px; height: confirmation-list.height + 118px;
background: white; border-radius: UiRadius.surface; border-width: 1px; border-color: #b2b2b2; background: white; border-radius: UiRadius.surface; shadow-color: UiShadow.edge;
Text { Text {
x: 20px; y: 18px; width: parent.width - 132px; height: 24px; x: 20px; y: 18px; width: parent.width - 132px; height: 24px;
text: root.action-code == "disconnect" ? GalleryStrings.tr("断开连接", "Disconnect devices", "Отключение") : root.action-name; text: root.action-code == "disconnect" ? GalleryStrings.tr("断开连接", "Disconnect devices", "Отключение") : root.action-name;
@@ -999,10 +1004,10 @@ export component GalleryView inherits Rectangle {
background: #eff3f8; border-radius: UiRadius.surface; 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: root.selected-count + GalleryStrings.tr(" 台设备", " devices", " устр."); color: #626e7d; font-size: 11px; horizontal-alignment: center; vertical-alignment: center; }
} }
confirmation-list := Rectangle { confirmation-list := ShadowSurface {
x: 20px; y: 58px; width: parent.width - 40px; x: 20px; y: 58px; width: parent.width - 40px;
height: clamp(root.selected-devices.length * 52px + 2px, 54px, 210px); height: clamp(root.selected-devices.length * 52px + 2px, 54px, 210px);
background: #fcfdfe; border-width: 1px; border-color: #e2e6eb; border-radius: UiRadius.surface; clip: true; background: #fcfdfe; shadow-color: UiShadow.edge; border-radius: UiRadius.surface; clip: true;
confirmation-scroll := ScrollView { confirmation-scroll := ScrollView {
x: 1px; y: 1px; width: parent.width - 2px; height: parent.height - 2px; x: 1px; y: 1px; width: parent.width - 2px; height: parent.height - 2px;
viewport-width: self.width - 12px; viewport-width: self.width - 12px;
@@ -1018,7 +1023,7 @@ export component GalleryView inherits Rectangle {
Rectangle { x: 9px; y: 7px; width: 6px; height: 6px; border-radius: 3px; background: device.connected ? #27a762 : #a0a6b0; } 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: Rectangle { x: 12px; y: parent.height - 1px; width: parent.width - 24px; height: 1px; background: #edf0f3; } if index + 1 < root.selected-devices.length: ShadowSeparator { x: 12px; y: parent.height - 1px; width: parent.width - 24px; height: 1px; shadow-color: UiShadow.separator; }
} }
} }
} }
+29 -20
View File
@@ -1,22 +1,24 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { ScrollView } from "std-widgets.slint"; import { ScrollView } from "std-widgets.slint";
import { FontAwesomeIcons } from "common.slint"; import { FontAwesomeIcons, ControlStyle } from "common.slint";
// Shared by history and DeskHub; system choices precede the named groups. // Shared by history and DeskHub; system choices precede the named groups.
export component GroupFilter inherits Rectangle { export component GroupFilter inherits ShadowSurface {
in property <[string]> model; in property <[string]> model;
in property <int> system-count: 2; in property <int> system-count: 2;
in property <bool> enabled: true; in property <bool> enabled: true;
// An embedded picker shares its enclosing field's border and focus ring. // An embedded picker shares its enclosing field's shadow and focus glow.
in property <bool> embedded: false; in property <bool> embedded: false;
in property <bool> show-indicator: true; in property <bool> show-indicator: true;
in property <length> popup-min-width: 0px; in property <length> popup-min-width: 0px;
in property <length> text-size: 12px; in property <length> text-size: 12px;
in property <int> text-weight: 400; in property <int> text-weight: 400;
in property <color> text-color: #30343b; in property <color> text-color: #30343b;
in property <color> normal-border-color: #c9cdd2; in property <color> normal-background: ControlStyle.surface;
in property <color> hover-border-color: root.normal-border-color; in property <color> normal-shadow-color: UiShadow.edge;
in property <color> focus-border-color: #8db5df; in property <color> hover-shadow-color: UiShadow.hover;
in property <color> focus-shadow-color: UiShadow.focus;
in-out property <int> current-index: 0; in-out property <int> current-index: 0;
out property <bool> has-focus: trigger-focus.has-focus || popup.is-open; out property <bool> has-focus: trigger-focus.has-focus || popup.is-open;
out property <bool> has-hover: root.enabled && trigger-touch.has-hover && !popup.is-open; out property <bool> has-hover: root.enabled && trigger-touch.has-hover && !popup.is-open;
@@ -34,13 +36,16 @@ export component GroupFilter inherits Rectangle {
} }
width: 155px; height: 28px; width: 155px; height: 28px;
background: root.embedded ? (root.enabled && trigger-touch.has-hover ? #eef3fb : transparent) background: root.embedded ? (root.enabled && trigger-touch.has-hover ? ControlStyle.hover : transparent)
: !root.enabled ? #f3f4f5 : trigger-touch.has-hover ? #f8f9fb : white; : !root.enabled ? ControlStyle.disabled
border-width: root.embedded ? 0px : 1px; : trigger-touch.pressed || popup.is-open ? ControlStyle.pressed
border-color: popup.is-open || trigger-focus.has-focus ? root.focus-border-color : trigger-touch.has-hover ? ControlStyle.hover : root.normal-background;
: trigger-touch.has-hover ? root.hover-border-color : root.normal-border-color; shadow-enabled: !root.embedded;
shadow-focused: popup.is-open || trigger-focus.has-focus;
shadow-color: popup.is-open || trigger-focus.has-focus ? root.focus-shadow-color
: trigger-touch.has-hover ? root.hover-shadow-color : root.normal-shadow-color;
border-radius: UiRadius.control; border-radius: UiRadius.control;
animate background, border-color { duration: 100ms; easing: ease-out; } animate background, shadow-color { duration: 100ms; easing: ease-out; }
forward-focus: trigger-focus; forward-focus: trigger-focus;
accessible-role: combobox; accessible-role: combobox;
accessible-enabled: root.enabled; accessible-enabled: root.enabled;
@@ -135,12 +140,13 @@ export component GroupFilter inherits Rectangle {
forward-focus: popup-focus; forward-focus: popup-focus;
popup-focus := FocusScope { popup-focus := FocusScope {
key-pressed(event) => { return root.handle-key(event); } key-pressed(event) => { return root.handle-key(event); }
Rectangle { ShadowSurface {
y: (1 - root.popup-progress) * -4px; y: (1 - root.popup-progress) * -4px;
width: parent.width; height: parent.height; width: parent.width; height: parent.height;
opacity: root.popup-progress; opacity: root.popup-progress;
background: white; border-width: 1px; background: white;
border-color: root.normal-border-color; border-radius: UiRadius.menu; clip: true;
shadow-color: root.normal-shadow-color; border-radius: UiRadius.menu;
ScrollView { ScrollView {
x: 4px; y: 4px; width: parent.width - 8px; height: parent.height - 8px; x: 4px; y: 4px; width: parent.width - 8px; height: parent.height - 8px;
viewport-width: self.width; viewport-width: self.width;
@@ -148,27 +154,30 @@ export component GroupFilter inherits Rectangle {
viewport-y <=> root.scroll-y; viewport-y <=> root.scroll-y;
Rectangle { Rectangle {
width: parent.width; height: root.list-height; width: parent.width; height: root.list-height;
if root.has-divider: Rectangle { if root.has-divider: ShadowSeparator {
x: 6px; y: root.system-count * 26px + 3px; x: 6px; y: root.system-count * 26px + 3px;
width: parent.width - 12px; height: 1px; background: #e9ebee; width: parent.width - 12px; height: 1px; shadow-color: UiShadow.separator;
} }
for choice[index] in root.model: Rectangle { for choice[index] in root.model: Rectangle {
y: index * 26px + (root.has-divider && index >= root.system-count ? 6px : 0px); y: index * 26px + (root.has-divider && index >= root.system-count ? 6px : 0px);
width: parent.width; height: 26px; border-radius: UiRadius.control; width: parent.width; height: 26px; border-radius: UiRadius.control;
background: index == root.current-index ? #edf4fc : option-touch.has-hover ? #f1f3f5 : transparent; 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 : transparent;
animate background { duration: 100ms; easing: ease-out; } animate background { duration: 100ms; easing: ease-out; }
accessible-role: list-item; accessible-role: list-item;
accessible-label: choice; accessible-label: choice;
Text { Text {
x: 8px; width: parent.width - 34px; height: parent.height; x: 8px; width: parent.width - 34px; height: parent.height;
text: choice; color: #30343b; font-size: 12px; text: choice; color: index == root.current-index ? ControlStyle.selected-text : #30343b; font-size: 12px;
font-weight: index == root.current-index ? 600 : 400;
overflow: elide; vertical-alignment: center; overflow: elide; vertical-alignment: center;
accessible-role: none; accessible-role: none;
} }
if index == root.current-index: Text { if index == root.current-index: Text {
x: parent.width - 24px; width: 16px; height: parent.height; x: parent.width - 24px; width: 16px; height: parent.height;
text: FontAwesomeIcons.check; font-family: "Font Awesome 6 Free"; text: FontAwesomeIcons.check; font-family: "Font Awesome 6 Free";
font-weight: 900; font-size: 10px; color: #4e87c0; font-weight: 900; font-size: 10px; color: ControlStyle.selected-text;
horizontal-alignment: center; vertical-alignment: center; horizontal-alignment: center; vertical-alignment: center;
accessible-role: none; accessible-role: none;
} }
+140 -132
View File
@@ -1,3 +1,4 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { LanStrings } from "lan_strings.slint"; import { LanStrings } from "lan_strings.slint";
import { AboutPanel, AboutPage, AboutComponent, AboutStrings, AboutNavigationRow, AboutNavigationGroup, AboutRowSeparator } from "about_panel.slint"; import { AboutPanel, AboutPage, AboutComponent, AboutStrings, AboutNavigationRow, AboutNavigationGroup, AboutRowSeparator } from "about_panel.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
@@ -14,7 +15,7 @@ import { GroupFilter } from "group_filter.slint";
import { ScrollView } from "std-widgets.slint"; import { ScrollView } from "std-widgets.slint";
import { RoundedLineEdit } from "rounded_line_edit.slint"; import { RoundedLineEdit } from "rounded_line_edit.slint";
import { AnnouncementItem, MessagePanel, MessageStrings } from "message_panel.slint"; import { AnnouncementItem, MessagePanel, MessageStrings } from "message_panel.slint";
import { FontAwesomeIcons, IconButton, CompactComboBox, ImGuiCheckBox, CompactButton, PrimaryButton, SecondaryButton, ToggleSwitch, SectionTitle, DialogSurface, ImGuiLineStyle, ImGuiFontStyle } from "common.slint"; import { FontAwesomeIcons, IconButton, CompactComboBox, ImGuiCheckBox, CompactButton, PrimaryButton, SecondaryButton, ToggleSwitch, SectionTitle, DialogSurface, ImGuiFontStyle, ControlStyle } from "common.slint";
import "../assets/fonts/message_spacing.ttf"; import "../assets/fonts/message_spacing.ttf";
export struct RecentConnection { export struct RecentConnection {
@@ -206,26 +207,29 @@ component DialogPromptText inherits Rectangle {
} }
} }
// Use the same compact, plain-text field for connection and password changes. // Use the same plain-text field for connection and password changes.
component PasswordInput inherits Rectangle { component PasswordInput inherits ShadowSurface {
in-out property <string> text; in-out property <string> text;
in property <int> max-length: 0; in property <int> max-length: 0;
in property <length> text-size: ImGuiFontStyle.base; in property <length> text-size: ImGuiFontStyle.base;
in property <bool> error: false;
private property <string> previous-text; private property <string> previous-text;
callback accepted; callback accepted;
width: 70px; width: 70px;
height: 23px; height: 23px;
background: white; background: editor.has-focus ? ControlStyle.input-focused : ControlStyle.input-surface;
border-width: 1px; shadow-color: root.error ? UiShadow.error : editor.has-focus ? UiShadow.focus : UiShadow.edge;
border-color: ImGuiLineStyle.border; shadow-focused: editor.has-focus || root.error;
shadow-compact: true;
border-radius: UiRadius.control; border-radius: UiRadius.control;
clip: true;
forward-focus: editor; forward-focus: editor;
init => { root.previous-text = root.text; } init => { root.previous-text = root.text; }
editor := TextInput { editor := TextInput {
x: 5px; y: 1px; width: parent.width - 10px; height: parent.height - 2px; x: 12px; y: 1px; width: parent.width - 24px; height: parent.height - 2px;
text <=> root.text; text <=> root.text;
input-type: InputType.text; input-type: InputType.text;
single-line: true; single-line: true;
@@ -937,10 +941,10 @@ export component MainWindow inherits Window {
clicked => { root.close-main-window(); } clicked => { root.close-main-window(); }
} }
} }
Rectangle { ShadowSeparator {
y: parent.height - 1px; y: parent.height - 1px;
height: 1px; height: 1px;
background: #d5d3d0; shadow-color: UiShadow.separator;
} }
} }
@@ -1024,10 +1028,10 @@ export component MainWindow inherits Window {
} }
close-touch := TouchArea { clicked => { root.close-main-window(); } } close-touch := TouchArea { clicked => { root.close-main-window(); } }
} }
Rectangle { ShadowSeparator {
y: parent.height - 1px; y: parent.height - 1px;
height: 1px; height: 1px;
background: #d4d4d4; shadow-color: UiShadow.separator;
} }
} }
@@ -1090,12 +1094,12 @@ export component MainWindow inherits Window {
// Slint owns the client area below the native macOS title bar. Draw // Slint owns the client area below the native macOS title bar. Draw
// the boundary here because AppKit's titlebar separator is not shown // the boundary here because AppKit's titlebar separator is not shown
// reliably when winit creates a window without an NSToolbar. // reliably when winit creates a window without an NSToolbar.
Rectangle { ShadowSeparator {
x: 0px; x: 0px;
y: 0px; y: 0px;
width: parent.width; width: parent.width;
height: 1px; height: 1px;
background: ImGuiLineStyle.control-separator; shadow-color: UiShadow.separator;
z: 7; z: 7;
} }
@@ -1146,6 +1150,7 @@ export component MainWindow inherits Window {
x: root.ip-direct-enabled ? connection-mode-switch.x + connection-mode-switch.width + 8px : 20px; x: root.ip-direct-enabled ? connection-mode-switch.x + connection-mode-switch.width + 8px : 20px;
y: connection-mode-switch.y; y: connection-mode-switch.y;
width: 80px; height: 28px; width: 80px; height: 28px;
normal-background: white;
text: GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub"); text: GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub");
z: 8; z: 8;
clicked => { root.open-gallery(); } clicked => { root.open-gallery(); }
@@ -1154,20 +1159,20 @@ export component MainWindow inherits Window {
visible: !root.gallery-mode; visible: !root.gallery-mode;
x: parent.width - 56px; y: open-gallery-button.y; x: parent.width - 56px; y: open-gallery-button.y;
width: 36px; height: 28px; width: 36px; height: 28px;
normal-background: white;
icon: FontAwesomeIcons.bars; icon: FontAwesomeIcons.bars;
tooltip: GalleryStrings.tr("更多", "More", "Ещё"); tooltip: GalleryStrings.tr("更多", "More", "Ещё");
z: 8; z: 8;
clicked => { root.gallery-action("release", ""); root.menu-open = !root.menu-open; } clicked => { root.gallery-action("release", ""); root.menu-open = !root.menu-open; }
} }
// Unread messages take precedence so the menu has only one indicator. // Unread messages take precedence so the menu has only one indicator.
if !root.gallery-mode && root.update-available && root.announcement-unread <= 0: Rectangle { if !root.gallery-mode && root.update-available && root.announcement-unread <= 0: ShadowSurface {
x: menu-button.x + menu-button.width - self.width + 4px; x: menu-button.x + menu-button.width - self.width + 4px;
y: menu-button.y - 1px; y: menu-button.y - 1px;
width: 16px; width: 16px;
height: 16px; height: 16px;
border-radius: 8px; border-radius: 8px;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: white;
background: WorkbenchStyle.accent; background: WorkbenchStyle.accent;
opacity: root.menu-open || mod(animation-tick(), 2s) < 1s ? 1 : 0; opacity: root.menu-open || mod(animation-tick(), 2s) < 1s ? 1 : 0;
z: 9; z: 9;
@@ -1201,14 +1206,13 @@ export component MainWindow inherits Window {
clicked => { root.gallery-action("release", ""); root.menu-open = !root.menu-open; } clicked => { root.gallery-action("release", ""); root.menu-open = !root.menu-open; }
} }
} }
if !root.gallery-mode && root.announcement-unread > 0: Rectangle { if !root.gallery-mode && root.announcement-unread > 0: ShadowSurface {
x: menu-button.x + menu-button.width - self.width + 4px; x: menu-button.x + menu-button.width - self.width + 4px;
y: menu-button.y - 1px; y: menu-button.y - 1px;
width: max(16px, unread-badge-label.preferred-width + 8px); width: max(16px, unread-badge-label.preferred-width + 8px);
height: 16px; height: 16px;
border-radius: 8px; border-radius: 8px;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: white;
background: #d92d20; background: #d92d20;
z: 9; z: 9;
accessible-role: button; accessible-role: button;
@@ -1280,7 +1284,7 @@ export component MainWindow inherits Window {
lan-port-editor.select-all(); lan-port-editor.select-all();
} }
} }
local-address-field := Rectangle { local-address-field := ShadowSurface {
x: 16px; y: 90px; width: parent.width - 32px; height: 40px; x: 16px; y: 90px; width: parent.width - 32px; height: 40px;
// Size the endpoint as one line; reserve five port digits so // Size the endpoint as one line; reserve five port digits so
// editing never shifts the IP or changes the font size. // editing never shifts the IP or changes the font size.
@@ -1298,15 +1302,15 @@ export component MainWindow inherits Window {
// Raise the whole field: a child's z cannot escape its // Raise the whole field: a child's z cannot escape its
// parent's stacking order above the following password row. // parent's stacking order above the following password row.
z: 1; z: 1;
background: WorkbenchStyle.field-surface; border-radius: UiRadius.control; background: ControlStyle.input-surface; border-radius: UiRadius.control;
border-width: 1px; shadow-compact: true;
border-color: !root.lan-mode ? WorkbenchStyle.border shadow-color: !root.lan-mode ? UiShadow.edge
: root.lan-endpoint-error != "" ? #d92d20 : root.lan-endpoint-error != "" ? UiShadow.error
: lan-ip-picker.has-focus || root.lan-port-editing ? WorkbenchStyle.accent : lan-ip-picker.has-focus || root.lan-port-editing ? UiShadow.focus
: WorkbenchStyle.border; : UiShadow.edge;
animate border-color { duration: 120ms; } animate shadow-color { duration: 120ms; }
if !root.lan-mode: Text { x: 12px; width: parent.width - 64px; height: parent.height; text: root.local-id; font-size: 24px; font-weight: 600; letter-spacing: 1px; overflow: elide; color: WorkbenchStyle.text; vertical-alignment: center; } if !root.lan-mode: Text { x: 12px; width: parent.width - 64px; height: parent.height; text: root.local-id; font-size: 24px; font-weight: 600; letter-spacing: 1px; overflow: elide; color: WorkbenchStyle.text; vertical-alignment: center; }
Rectangle { x: parent.width - 48px; y: 10px; width: 1px; height: parent.height - 20px; background: WorkbenchStyle.border; } ShadowSeparator { x: parent.width - 48px; y: 10px; width: 1px; height: parent.height - 20px; vertical: true; shadow-color: UiShadow.separator; }
lan-ip-picker := GroupFilter { lan-ip-picker := GroupFilter {
visible: root.lan-mode; visible: root.lan-mode;
x: 4px; y: 2px; x: 4px; y: 2px;
@@ -1319,9 +1323,9 @@ export component MainWindow inherits Window {
model: root.lan-interfaces; system-count: 0; model: root.lan-interfaces; system-count: 0;
current-index <=> root.lan-interface-draft-index; current-index <=> root.lan-interface-draft-index;
enabled: root.lan-mode && !root.lan-password-pending && root.lan-interfaces.length > 0; enabled: root.lan-mode && !root.lan-password-pending && root.lan-interfaces.length > 0;
normal-border-color: WorkbenchStyle.control-border; normal-shadow-color: UiShadow.edge;
hover-border-color: WorkbenchStyle.hover-border; hover-shadow-color: UiShadow.hover;
focus-border-color: WorkbenchStyle.accent; focus-shadow-color: WorkbenchStyle.accent;
accessible-label: GalleryStrings.tr("切换本机 IP", "Switch local IP", "Сменить локальный IP"); accessible-label: GalleryStrings.tr("切换本机 IP", "Switch local IP", "Сменить локальный IP");
selected(index) => { selected(index) => {
root.copy-feedback = CopyFeedback.idle; root.copy-feedback = CopyFeedback.idle;
@@ -1346,17 +1350,16 @@ export component MainWindow inherits Window {
font-weight: 600; color: WorkbenchStyle.text; font-weight: 600; color: WorkbenchStyle.text;
horizontal-alignment: center; vertical-alignment: center; horizontal-alignment: center; vertical-alignment: center;
} }
lan-port-field := Rectangle { lan-port-field := ShadowSurface {
visible: root.lan-mode; visible: root.lan-mode;
x: lan-ip-picker.x + lan-ip-picker.width + 2px; y: 2px; x: lan-ip-picker.x + lan-ip-picker.width + 2px; y: 2px;
width: lan-port-metrics.preferred-width * local-address-field.lan-font-scale + 4px; width: lan-port-metrics.preferred-width * local-address-field.lan-font-scale + 4px;
height: parent.height - 4px; height: parent.height - 4px;
background: lan-port-editor.has-focus ? WorkbenchStyle.surface background: lan-port-editor.has-focus ? ControlStyle.input-focused
: lan-port-hover.has-hover ? WorkbenchStyle.hover-surface : transparent; : lan-port-hover.has-hover ? ControlStyle.hover : transparent;
border-radius: UiRadius.control; border-radius: UiRadius.control;
border-width: 1px; shadow-color: lan-port-editor.has-focus ? UiShadow.focus : transparent;
border-color: lan-port-editor.has-focus ? WorkbenchStyle.accent : transparent; animate background, shadow-color { duration: 120ms; }
animate background, border-color { duration: 120ms; }
lan-port-hover := TouchArea { lan-port-hover := TouchArea {
enabled: root.lan-mode && !root.lan-password-pending; enabled: root.lan-mode && !root.lan-password-pending;
mouse-cursor: text; mouse-cursor: text;
@@ -1439,12 +1442,13 @@ export component MainWindow inherits Window {
} }
} }
Text { x: 16px; y: 138px; height: 18px; text: UiStrings.password; font-size: 12px; color: WorkbenchStyle.muted; } Text { x: 16px; y: 138px; height: 18px; text: UiStrings.password; font-size: 12px; color: WorkbenchStyle.muted; }
Rectangle { ShadowSurface {
x: 16px; y: 158px; width: parent.width - 32px; height: 36px; x: 16px; y: 158px; width: parent.width - 32px; height: 36px;
background: WorkbenchStyle.field-surface; border-radius: UiRadius.control; background: ControlStyle.input-surface; border-radius: UiRadius.control;
border-width: 1px; border-color: WorkbenchStyle.border; shadow-color: UiShadow.edge;
shadow-compact: true;
Text { x: 12px; width: parent.width - 132px; height: parent.height; text: root.active-password == "" ? "—" : root.active-password-visible ? root.active-password : "••••••"; font-size: 20px; font-weight: 500; letter-spacing: 2px; color: WorkbenchStyle.text; vertical-alignment: center; } Text { x: 12px; width: parent.width - 132px; height: parent.height; text: root.active-password == "" ? "—" : root.active-password-visible ? root.active-password : "••••••"; font-size: 20px; font-weight: 500; letter-spacing: 2px; color: WorkbenchStyle.text; vertical-alignment: center; }
Rectangle { x: parent.width - 118px; y: 8px; width: 1px; height: parent.height - 16px; background: WorkbenchStyle.border; } ShadowSeparator { x: parent.width - 118px; y: 8px; width: 1px; height: parent.height - 16px; vertical: true; shadow-color: UiShadow.separator; }
WorkbenchButton { WorkbenchButton {
x: parent.width - 112px; y: 0px; width: 36px; quiet: true; x: parent.width - 112px; y: 0px; width: 36px; quiet: true;
icon: root.active-password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash; icon: root.active-password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash;
@@ -1471,15 +1475,18 @@ export component MainWindow inherits Window {
Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.remote-desktop; font-size: 16px; font-weight: 600; color: WorkbenchStyle.text; } Text { x: 16px; y: 14px; width: parent.width - 32px; height: 24px; overflow: elide; text: UiStrings.remote-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:端口,通过局域网连接", "Enter IP:port to connect over LAN", "Введите IP:порт для подключения по LAN") : GalleryStrings.tr("输入设备 ID,连接远程桌面", "Enter a device ID to start a remote session", "Введите 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:端口,通过局域网连接", "Enter IP:port to connect over LAN", "Введите IP:порт для подключения по LAN") : GalleryStrings.tr("输入设备 ID,连接远程桌面", "Enter a device ID to start a remote session", "Введите ID для удалённого подключения"); font-size: 11px; color: WorkbenchStyle.muted; overflow: elide; }
Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("远端 IP / 端口", "Remote IP / Port", "Удалённый IP / Порт") : UiStrings.remote-id; font-size: 12px; color: WorkbenchStyle.muted; } Text { x: 16px; y: 68px; height: 18px; text: root.lan-mode ? GalleryStrings.tr("远端 IP / 端口", "Remote IP / Port", "Удалённый IP / Порт") : UiStrings.remote-id; font-size: 12px; color: WorkbenchStyle.muted; }
Rectangle { ShadowSurface {
x: 16px; y: 90px; width: parent.width - 32px; height: 40px; x: 16px; y: 90px; width: parent.width - 32px; height: 40px;
background: white; border-radius: UiRadius.control; border-width: 1px; background: remote-id-editor.has-focus ? ControlStyle.input-focused : ControlStyle.input-surface;
border-color: root.connection-input-error != "" ? #d65252 : remote-id-editor.has-focus ? WorkbenchStyle.accent : WorkbenchStyle.control-border; shadow-compact: true;
animate border-color { duration: 120ms; } border-radius: UiRadius.control;
shadow-color: root.connection-input-error != "" ? UiShadow.error : remote-id-editor.has-focus ? UiShadow.focus : UiShadow.edge;
shadow-focused: remote-id-editor.has-focus || root.connection-input-error != "";
animate shadow-color { duration: 120ms; }
if root.remote-id-input == "": Text { if root.remote-id-input == "": Text {
x: 12px; width: parent.width - 24px; height: parent.height; x: 12px; width: parent.width - 24px; height: parent.height;
text: root.lan-mode ? "192.168.1.20:52888" : GalleryStrings.tr("请输入 9 位设备 ID", "Enter a 9-digit device ID", "Введите ID из 9 цифр"); text: root.lan-mode ? "192.168.1.20:52888" : GalleryStrings.tr("请输入 9 位设备 ID", "Enter a 9-digit device ID", "Введите ID из 9 цифр");
font-size: 13px; color: #8290a4; vertical-alignment: center; font-size: 13px; color: ControlStyle.input-placeholder; vertical-alignment: center;
} }
remote-id-editor := TextInput { remote-id-editor := TextInput {
x: 12px; width: parent.width - 24px; height: parent.height; x: 12px; width: parent.width - 24px; height: parent.height;
@@ -1528,15 +1535,16 @@ export component MainWindow inherits Window {
visible: !root.lan-mode; visible: !root.lan-mode;
x: parent.width - self.width - 20px; y: 0px; x: parent.width - self.width - 20px; y: 0px;
width: 155px; height: 32px; width: 155px; height: 32px;
normal-border-color: WorkbenchStyle.control-border; normal-background: white;
hover-border-color: WorkbenchStyle.hover-border; normal-shadow-color: UiShadow.edge;
focus-border-color: WorkbenchStyle.accent; hover-shadow-color: UiShadow.hover;
focus-shadow-color: WorkbenchStyle.accent;
model: root.recent-groups; model: root.recent-groups;
current-index <=> root.recent-group-index; current-index <=> root.recent-group-index;
selected(index) => { root.recent-group-selected(index); } selected(index) => { root.recent-group-selected(index); }
} }
recent-list := Rectangle { recent-list := ShadowSurface {
x: 20px; x: 20px;
y: 36px; y: 36px;
width: parent.width - 40px; width: parent.width - 40px;
@@ -1544,8 +1552,8 @@ export component MainWindow inherits Window {
// A continuous inset surface groups the cards into one // A continuous inset surface groups the cards into one
// horizontal scrolling lane, including its scrollbar. // horizontal scrolling lane, including its scrollbar.
background: root.recent-connections.length > 0 ? #e6edf6 : transparent; background: root.recent-connections.length > 0 ? #e6edf6 : transparent;
border-width: root.recent-connections.length > 0 ? 1px : 0px; shadow-enabled: root.recent-connections.length > 0;
border-color: #c9d4e3; shadow-color: UiShadow.edge;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
clip: true; clip: true;
@@ -1668,7 +1676,7 @@ export component MainWindow inherits Window {
width: parent.width; width: parent.width;
height: 44px; height: 44px;
background: white; background: white;
Rectangle { x: 0px; y: 0px; width: parent.width; height: 1px; background: WorkbenchStyle.separator; } ShadowSeparator { x: 0px; y: 0px; width: parent.width; height: 1px; shadow-color: UiShadow.separator; }
Text { Text {
x: 12px; y: 5px; height: 19px; x: 12px; y: 5px; height: 19px;
width: parent.width - 56px; width: parent.width - 56px;
@@ -1802,11 +1810,11 @@ export component MainWindow inherits Window {
} }
} }
} }
Rectangle { ShadowSurface {
width: parent.width; height: 124px; width: parent.width; height: 124px;
border-width: 1px; border-radius: UiRadius.surface; border-radius: UiRadius.surface;
border-color: card-touch.has-hover || card-menu.menu-open ? WorkbenchStyle.hover-border : WorkbenchStyle.border; shadow-color: card-touch.has-hover || card-menu.menu-open ? UiShadow.hover : UiShadow.edge;
animate border-color { duration: 120ms; } animate shadow-color { duration: 120ms; }
} }
} }
} }
@@ -1863,10 +1871,10 @@ export component MainWindow inherits Window {
y: parent.height - 28px; y: parent.height - 28px;
height: 28px; height: 28px;
background: white; background: white;
Rectangle { ShadowSeparator {
y: 0px; y: 0px;
height: 1px; height: 1px;
background: WorkbenchStyle.border; shadow-color: UiShadow.separator;
} }
Rectangle { Rectangle {
x: 20px; y: 11px; width: 6px; height: 6px; border-radius: 3px; x: 20px; y: 11px; width: 6px; height: 6px; border-radius: 3px;
@@ -1917,7 +1925,7 @@ export component MainWindow inherits Window {
// Shared device details sit outside the clipped recent list and wall rail, // Shared device details sit outside the clipped recent list and wall rail,
// below the modal layer (z >= 39). // below the modal layer (z >= 39).
if (root.gallery-mode ? gallery.hovered-device.id != "" : root.recent-tooltip-visible) if (root.gallery-mode ? gallery.hovered-device.id != "" : root.recent-tooltip-visible)
&& !root.connection-dialog-open && !root.lan-open: device-tip := Rectangle { && !root.connection-dialog-open && !root.lan-open: device-tip := ShadowSurface {
private property <string> device-name: root.gallery-mode ? gallery.hovered-device.name : root.recent-tooltip-name; private property <string> device-name: root.gallery-mode ? gallery.hovered-device.name : root.recent-tooltip-name;
private property <string> host-name: root.gallery-mode ? gallery.hovered-device.host-name : root.recent-tooltip-host; 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> device-id: root.gallery-mode ? gallery.hovered-device.id : root.recent-tooltip-id;
@@ -1944,8 +1952,7 @@ export component MainWindow inherits Window {
self.label-width + max(tooltip-id.preferred-width, tooltip-groups.preferred-width) + 32px)); self.label-width + max(tooltip-id.preferred-width, tooltip-groups.preferred-width) + 32px));
height: tooltip-groups.y + tooltip-groups.height + 10px; height: tooltip-groups.y + tooltip-groups.height + 10px;
background: WorkbenchStyle.surface; background: WorkbenchStyle.surface;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: #d5dfea;
border-radius: UiRadius.menu; border-radius: UiRadius.menu;
drop-shadow-color: #14294424; drop-shadow-color: #14294424;
drop-shadow-blur: 14px; drop-shadow-blur: 14px;
@@ -1988,11 +1995,11 @@ export component MainWindow inherits Window {
color: WorkbenchStyle.muted; font-size: 10px; color: WorkbenchStyle.muted; font-size: 10px;
wrap: word-wrap; wrap: word-wrap;
} }
tooltip-divider := Rectangle { tooltip-divider := ShadowSeparator {
x: 12px; y: tooltip-name.y + tooltip-name.height x: 12px; y: tooltip-name.y + tooltip-name.height
+ (device-tip.has-host-name ? tooltip-host.height + 3px : 0px) + 8px; + (device-tip.has-host-name ? tooltip-host.height + 3px : 0px) + 8px;
width: parent.width - 24px; height: 1px; width: parent.width - 24px; height: 1px;
background: WorkbenchStyle.separator; shadow-color: UiShadow.separator;
} }
tooltip-id-label := Text { tooltip-id-label := Text {
x: 12px; y: tooltip-divider.y + 7px; x: 12px; y: tooltip-divider.y + 7px;
@@ -2039,7 +2046,7 @@ export component MainWindow inherits Window {
} }
// Expand downward from the application toolbar. // Expand downward from the application toolbar.
if !root.gallery-mode && root.menu-open: Rectangle { if !root.gallery-mode && root.menu-open: ShadowSurface {
x: menu-button.x + menu-button.width - self.width; x: menu-button.x + menu-button.width - self.width;
y: menu-button.y + menu-button.height + 6px; y: menu-button.y + menu-button.height + 6px;
width: max(128px, max(menu-settings-label.preferred-width, width: max(128px, max(menu-settings-label.preferred-width,
@@ -2047,8 +2054,7 @@ export component MainWindow inherits Window {
menu-about-label.preferred-width + (root.update-available ? menu-update-badge.width + 8px : 0px))) + 28px); menu-about-label.preferred-width + (root.update-available ? menu-update-badge.width + 8px : 0px))) + 28px);
height: application-menu-content.preferred-height; height: application-menu-content.preferred-height;
background: WorkbenchStyle.surface; background: WorkbenchStyle.surface;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: WorkbenchStyle.control-border;
border-radius: UiRadius.menu; border-radius: UiRadius.menu;
z: 10; z: 10;
TouchArea { } TouchArea { }
@@ -2081,9 +2087,9 @@ export component MainWindow inherits Window {
} }
Rectangle { Rectangle {
height: 7px; height: 7px;
Rectangle { ShadowSeparator {
x: 8px; y: 3px; width: parent.width - 16px; height: 1px; x: 8px; y: 3px; width: parent.width - 16px; height: 1px;
background: WorkbenchStyle.border; shadow-color: UiShadow.separator;
} }
} }
menu-messages := Rectangle { menu-messages := Rectangle {
@@ -2130,9 +2136,9 @@ export component MainWindow inherits Window {
} }
Rectangle { Rectangle {
height: 7px; height: 7px;
Rectangle { ShadowSeparator {
x: 8px; y: 3px; width: parent.width - 16px; height: 1px; x: 8px; y: 3px; width: parent.width - 16px; height: 1px;
background: WorkbenchStyle.border; shadow-color: UiShadow.separator;
} }
} }
menu-about := Rectangle { menu-about := Rectangle {
@@ -2232,24 +2238,29 @@ export component MainWindow inherits Window {
DialogSurface { DialogSurface {
x: (parent.width - self.width) / 2; x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: root.compact-language ? 230px : 320px; width: root.compact-language ? 260px : 320px;
height: 170px; height: reset-content.preferred-height;
VerticalLayout { reset-content := VerticalLayout {
padding: 20px; padding: 24px;
spacing: 12px; spacing: 16px;
DialogPromptText { prompt: UiStrings.new-password; } Text {
HorizontalLayout { text: UiStrings.new-password; color: WorkbenchStyle.text;
alignment: center; font-size: 13px; font-weight: 500; wrap: word-wrap;
PasswordInput { min-height: 22px; vertical-alignment: center;
text <=> root.new-password-input; }
max-length: 6; PasswordInput {
} width: parent.width - 48px; height: 38px; text-size: 16px;
text <=> root.new-password-input;
max-length: 6;
error: root.reset-password-invalid;
}
if root.reset-password-invalid: Text {
text: UiStrings.invalid-password; color: #b42318; font-size: 12px; wrap: word-wrap;
} }
Text { height: root.reset-password-invalid ? 18px : 0px; visible: root.reset-password-invalid; text: UiStrings.invalid-password; color: #d92d20; font-size: ImGuiFontStyle.small; horizontal-alignment: center; }
HorizontalLayout { HorizontalLayout {
alignment: center; spacing: 8px; spacing: 12px; height: 34px;
PrimaryButton { PrimaryButton {
text: UiStrings.ok; text: UiStrings.ok; height: 34px; horizontal-stretch: 1;
enabled: root.lan-mode ? !root.lan-password-pending : !root.password-change-pending; enabled: root.lan-mode ? !root.lan-password-pending : !root.password-change-pending;
clicked => { clicked => {
if (root.reset-password(root.new-password-input)) { if (root.reset-password(root.new-password-input)) {
@@ -2259,7 +2270,7 @@ export component MainWindow inherits Window {
} else { root.reset-password-invalid = true; } } else { root.reset-password-invalid = true; }
} }
} }
SecondaryButton { text: UiStrings.cancel; clicked => { root.new-password-input = ""; root.reset-password-invalid = false; root.reset-password-open = false; } } SecondaryButton { text: UiStrings.cancel; height: 34px; horizontal-stretch: 1; clicked => { root.new-password-input = ""; root.reset-password-invalid = false; root.reset-password-open = false; } }
} }
} }
} }
@@ -2298,7 +2309,7 @@ export component MainWindow inherits Window {
DialogPromptText { prompt: UiStrings.edit-alias; } DialogPromptText { prompt: UiStrings.edit-alias; }
RoundedLineEdit { RoundedLineEdit {
height: 28px; height: 28px;
normal-border-color: ImGuiLineStyle.border; normal-shadow-color: UiShadow.edge;
text <=> root.alias-input; text <=> root.alias-input;
accepted => { accepted => {
root.recent-edit-alias(root.alias-remote-id, root.alias-input); root.recent-edit-alias(root.alias-remote-id, root.alias-input);
@@ -2373,7 +2384,7 @@ export component MainWindow inherits Window {
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: min(420px, parent.width - 40px); width: min(420px, parent.width - 40px);
height: disconnect-content.preferred-height; height: disconnect-content.preferred-height;
border-color: WorkbenchStyle.border; shadow-color: UiShadow.edge;
TouchArea { scroll-event(event) => { accept } } TouchArea { scroll-event(event) => { accept } }
disconnect-content := VerticalLayout { disconnect-content := VerticalLayout {
padding: 24px; padding: 24px;
@@ -2463,14 +2474,14 @@ export component MainWindow inherits Window {
if !root.lan-open && root.connection-password-required && !root.connection-validating: PasswordInput { if !root.lan-open && root.connection-password-required && !root.connection-validating: PasswordInput {
x: (parent.width - self.width) / 2; y: connection-prompt-text.y + connection-prompt-text.height + 16px; x: (parent.width - self.width) / 2; y: connection-prompt-text.y + connection-prompt-text.height + 16px;
width: 164px; height: 36px; text-size: 16px; width: 164px; height: 36px; text-size: 16px;
border-radius: UiRadius.control; border-color: WorkbenchStyle.control-border; border-radius: UiRadius.control;
text <=> root.connection-password; text <=> root.connection-password;
accepted => { root.submit-connection-password(); } accepted => { root.submit-connection-password(); }
} }
if root.lan-open && !root.lan-connecting: PasswordInput { if root.lan-open && !root.lan-connecting: PasswordInput {
x: (parent.width - self.width) / 2; y: connection-prompt-text.y + connection-prompt-text.height + 16px; x: (parent.width - self.width) / 2; y: connection-prompt-text.y + connection-prompt-text.height + 16px;
width: 164px; height: 36px; text-size: 16px; max-length: 6; width: 164px; height: 36px; text-size: 16px; max-length: 6;
border-radius: UiRadius.control; border-color: WorkbenchStyle.control-border; border-radius: UiRadius.control;
text <=> root.lan-access-password; text <=> root.lan-access-password;
accepted => { root.submit-connection-password(); } accepted => { root.submit-connection-password(); }
} }
@@ -2522,7 +2533,7 @@ export component MainWindow inherits Window {
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: min(400px, parent.width - 40px); width: min(400px, parent.width - 40px);
height: permission-content.preferred-height; height: permission-content.preferred-height;
background: #f8fafc; border-color: WorkbenchStyle.separator; background: #f8fafc; shadow-color: UiShadow.edge;
drop-shadow-color: #14294424; drop-shadow-blur: 24px; drop-shadow-offset-y: 8px; drop-shadow-color: #14294424; drop-shadow-blur: 24px; drop-shadow-offset-y: 8px;
TouchArea { clicked => { focus-sink.focus(); } } TouchArea { clicked => { focus-sink.focus(); } }
permission-content := VerticalLayout { permission-content := VerticalLayout {
@@ -2594,7 +2605,7 @@ export component MainWindow inherits Window {
} }
VerticalLayout { VerticalLayout {
spacing: 12px; spacing: 12px;
Rectangle { height: 1px; background: WorkbenchStyle.separator; } ShadowSeparator { height: 1px; shadow-color: UiShadow.separator; }
Text { Text {
text: GalleryStrings.tr("请在系统设置中允许 CrossDesk 访问。如系统提示重新打开应用,请按提示操作。", text: GalleryStrings.tr("请在系统设置中允许 CrossDesk 访问。如系统提示重新打开应用,请按提示操作。",
"Allow CrossDesk access in System Settings. Reopen the app if macOS asks you to.", "Allow CrossDesk access in System Settings. Reopen the app if macOS asks you to.",
@@ -2619,7 +2630,7 @@ export component MainWindow inherits Window {
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: min(400px, parent.width - 40px); width: min(400px, parent.width - 40px);
height: min(446px, parent.height - 16px); height: min(446px, parent.height - 16px);
background: #f8fafc; border-radius: UiRadius.surface; border-color: #e1e7ef; background: #f8fafc; border-radius: UiRadius.surface; shadow-color: UiShadow.edge;
drop-shadow-color: #14294424; drop-shadow-blur: 24px; drop-shadow-offset-y: 8px; drop-shadow-color: #14294424; drop-shadow-blur: 24px; drop-shadow-offset-y: 8px;
TouchArea { clicked => { focus-sink.focus(); } } TouchArea { clicked => { focus-sink.focus(); } }
Text { Text {
@@ -2627,16 +2638,16 @@ export component MainWindow inherits Window {
width: parent.width - 2 * settings-dialog.content-inset; height: 27px; width: parent.width - 2 * settings-dialog.content-inset; height: 27px;
text: UiStrings.settings; font-size: 20px; font-weight: 600; color: #22324a; text: UiStrings.settings; font-size: 20px; font-weight: 600; color: #22324a;
} }
Rectangle { ShadowSurface {
x: settings-dialog.content-inset; y: 56px; x: settings-dialog.content-inset; y: 56px;
width: parent.width - 2 * settings-dialog.content-inset; height: 40px; width: parent.width - 2 * settings-dialog.content-inset; height: 40px;
background: WorkbenchStyle.canvas; border-radius: UiRadius.menu; background: #dfe7f2; border-radius: UiRadius.menu;
border-width: 1px; border-color: WorkbenchStyle.border; shadow-color: UiShadow.edge;
Rectangle { ShadowSurface {
x: 4px + root.settings-tab * (parent.width - 4px) / 2; x: 4px + root.settings-tab * (parent.width - 4px) / 2;
y: 4px; width: (parent.width - 12px) / 2; height: parent.height - 8px; y: 4px; width: (parent.width - 12px) / 2; height: parent.height - 8px;
background: WorkbenchStyle.surface; border-radius: UiRadius.control; background: WorkbenchStyle.surface; border-radius: UiRadius.control;
border-width: 1px; border-color: WorkbenchStyle.control-border; shadow-color: UiShadow.edge;
animate x { duration: 140ms; easing: ease-out; } animate x { duration: 140ms; easing: ease-out; }
} }
for label[index] in [GalleryStrings.tr("常规", "General", "Общие"), GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub")]: Rectangle { for label[index] in [GalleryStrings.tr("常规", "General", "Общие"), GalleryStrings.tr("桌面墙", "DeskHub", "DeskHub")]: Rectangle {
@@ -2770,9 +2781,9 @@ export component MainWindow inherits Window {
width: parent.width - 2 * settings-dialog.content-inset; height: 24px; width: parent.width - 2 * settings-dialog.content-inset; height: 24px;
text: root.settings-save-error; font-size: 11px; color: #c53e3e; wrap: word-wrap; text: root.settings-save-error; font-size: 11px; color: #c53e3e; wrap: word-wrap;
} }
Rectangle { ShadowSeparator {
x: settings-dialog.content-inset; y: parent.height - 64px; x: settings-dialog.content-inset; y: parent.height - 64px;
width: parent.width - 2 * settings-dialog.content-inset; height: 1px; background: #e6ebf2; width: parent.width - 2 * settings-dialog.content-inset; height: 1px; shadow-color: UiShadow.separator;
} }
HorizontalLayout { HorizontalLayout {
x: settings-dialog.content-inset; y: parent.height - 50px; x: settings-dialog.content-inset; y: parent.height - 50px;
@@ -2798,7 +2809,7 @@ export component MainWindow inherits Window {
x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2; x: (parent.width - self.width) / 2; y: (parent.height - self.height) / 2;
width: min(460px, parent.width - 32px); width: min(460px, parent.width - 32px);
height: min(380px, parent.height - 24px); height: min(380px, parent.height - 24px);
background: #f8fafc; border-color: #e1e7ef; background: #f8fafc; shadow-color: UiShadow.edge;
drop-shadow-color: #14294424; drop-shadow-blur: 18px; drop-shadow-offset-y: 6px; drop-shadow-color: #14294424; drop-shadow-blur: 18px; drop-shadow-offset-y: 6px;
TouchArea { clicked => { focus-sink.focus(); } } TouchArea { clicked => { focus-sink.focus(); } }
Text { Text {
@@ -2852,7 +2863,7 @@ export component MainWindow inherits Window {
x: (parent.width - self.width) / 2; x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2; y: (parent.height - self.height) / 2;
width: 360px; height: root.self-host-settings-error == "" ? 266px : 322px; width: 360px; height: root.self-host-settings-error == "" ? 266px : 322px;
border-radius: UiRadius.surface; border-color: #e1e7ef; background: #f8fafc; border-radius: UiRadius.surface; shadow-color: UiShadow.edge; background: #f8fafc;
drop-shadow-color: #14294424; drop-shadow-blur: 18px; drop-shadow-offset-y: 6px; drop-shadow-color: #14294424; drop-shadow-blur: 18px; drop-shadow-offset-y: 6px;
TouchArea { clicked => { focus-sink.focus(); } } TouchArea { clicked => { focus-sink.focus(); } }
Text { Text {
@@ -2863,21 +2874,21 @@ export component MainWindow inherits Window {
RoundedLineEdit { RoundedLineEdit {
x: 18px; y: 80px; width: parent.width - 36px; height: 30px; x: 18px; y: 80px; width: parent.width - 36px; height: 30px;
surrounding-background: #f8fafc; surrounding-background: #f8fafc;
normal-border-color: WorkbenchStyle.control-border; normal-shadow-color: UiShadow.edge;
text <=> root.server-host; text <=> root.server-host;
} }
Text { x: 18px; y: 120px; width: parent.width - 36px; height: 20px; text: UiStrings.server-port; color: #34445a; font-size: 12px; overflow: elide; vertical-alignment: center; } Text { x: 18px; y: 120px; width: parent.width - 36px; height: 20px; text: UiStrings.server-port; color: #34445a; font-size: 12px; overflow: elide; vertical-alignment: center; }
RoundedLineEdit { RoundedLineEdit {
x: 18px; y: 144px; width: parent.width - 36px; height: 30px; x: 18px; y: 144px; width: parent.width - 36px; height: 30px;
surrounding-background: #f8fafc; surrounding-background: #f8fafc;
normal-border-color: WorkbenchStyle.control-border; normal-shadow-color: UiShadow.edge;
text <=> root.server-port; input-type: InputType.number; text <=> root.server-port; input-type: InputType.number;
} }
if root.self-host-settings-error != "": Text { if root.self-host-settings-error != "": Text {
x: 18px; y: 186px; width: parent.width - 36px; height: 48px; x: 18px; y: 186px; width: parent.width - 36px; height: 48px;
text: root.self-host-settings-error; color: #c53e3e; font-size: 11px; wrap: word-wrap; text: root.self-host-settings-error; color: #c53e3e; font-size: 11px; wrap: word-wrap;
} }
Rectangle { x: 18px; y: parent.height - 66px; width: parent.width - 36px; height: 1px; background: #e6ebf2; } ShadowSeparator { x: 18px; y: parent.height - 66px; width: parent.width - 36px; height: 1px; shadow-color: UiShadow.separator; }
SettingsButton { x: parent.width - 114px; y: parent.height - 50px; width: 92px; primary: true; text: UiStrings.ok; clicked => { if root.save-self-hosted-settings() { root.self-host-settings-open = false; } } } SettingsButton { x: parent.width - 114px; y: parent.height - 50px; width: 92px; primary: true; text: UiStrings.ok; clicked => { if root.save-self-hosted-settings() { root.self-host-settings-open = false; } } }
SettingsButton { x: parent.width - 204px; y: parent.height - 50px; width: 80px; text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } } SettingsButton { x: parent.width - 204px; y: parent.height - 50px; width: 80px; text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } }
} }
@@ -2915,7 +2926,7 @@ export component MainWindow inherits Window {
color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap; color: WorkbenchStyle.muted; font-size: 12px; wrap: word-wrap;
font-family: "CrossDesk Message Spacing"; font-family: "CrossDesk Message Spacing";
} }
Rectangle { height: 1px; background: WorkbenchStyle.separator; } ShadowSeparator { height: 1px; shadow-color: UiShadow.separator; }
HorizontalLayout { HorizontalLayout {
alignment: end; alignment: end;
SettingsButton { SettingsButton {
@@ -2983,11 +2994,11 @@ export component MainWindow inherits Window {
width: about-scroll.width; height: self.preferred-height; width: about-scroll.width; height: self.preferred-height;
// Keep the expanding scrollbar outside the content. // Keep the expanding scrollbar outside the content.
padding: 0px; padding-right: 22px; spacing: 16px; padding: 0px; padding-right: 22px; spacing: 16px;
Rectangle { ShadowSurface {
height: max(80px, product-info.preferred-height + 32px); height: max(80px, product-info.preferred-height + 32px);
background: WorkbenchStyle.surface; background: WorkbenchStyle.surface;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
border-width: 1px; border-color: WorkbenchStyle.border; shadow-color: UiShadow.edge;
Image { Image {
x: 16px; y: (parent.height - self.height) / 2; x: 16px; y: (parent.height - self.height) / 2;
width: 56px; height: 56px; width: 56px; height: 56px;
@@ -3004,12 +3015,11 @@ export component MainWindow inherits Window {
} }
} }
} }
if root.update-available: Rectangle { if root.update-available: ShadowSurface {
height: update-summary.preferred-height + 24px; height: update-summary.preferred-height + 24px;
background: WorkbenchStyle.field-surface; background: WorkbenchStyle.field-surface;
border-radius: UiRadius.control; border-radius: UiRadius.control;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: WorkbenchStyle.separator;
update-summary := VerticalLayout { update-summary := VerticalLayout {
x: 12px; y: 12px; width: parent.width - 24px; x: 12px; y: 12px; width: parent.width - 24px;
height: self.preferred-height; height: self.preferred-height;
@@ -3079,7 +3089,7 @@ export component MainWindow inherits Window {
x: 24px; y: parent.height - self.height - 24px; x: 24px; y: parent.height - self.height - 24px;
width: parent.width - 48px; height: self.preferred-height; width: parent.width - 48px; height: self.preferred-height;
spacing: 16px; spacing: 16px;
Rectangle { height: 1px; background: WorkbenchStyle.separator; } ShadowSeparator { height: 1px; shadow-color: UiShadow.separator; }
if root.update-status-text != "": Text { if root.update-status-text != "": Text {
text: root.update-status-text; text: root.update-status-text;
color: root.update-check-status == UpdateCheckStatus.failed ? #b3261e color: root.update-check-status == UpdateCheckStatus.failed ? #b3261e
@@ -3123,7 +3133,7 @@ export component MainWindow inherits Window {
// Use the same content area for sizing and centering the dialog. // Use the same content area for sizing and centering the dialog.
height: min(488px, parent.height - 32px); height: min(488px, parent.height - 32px);
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
border-color: WorkbenchStyle.border; shadow-color: UiShadow.edge;
drop-shadow-color: #07142b24; drop-shadow-color: #07142b24;
drop-shadow-blur: 24px; drop-shadow-blur: 24px;
drop-shadow-offset-y: 8px; drop-shadow-offset-y: 8px;
@@ -3179,10 +3189,9 @@ export component MainWindow inherits Window {
font-weight: 600; font-weight: 600;
vertical-alignment: center; vertical-alignment: center;
} }
Rectangle { ShadowSurface {
background: WorkbenchStyle.field-surface; background: WorkbenchStyle.field-surface;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: WorkbenchStyle.separator;
border-radius: UiRadius.control; border-radius: UiRadius.control;
// Inset the viewport so text and the scrollbar stay clear // Inset the viewport so text and the scrollbar stay clear
// of the rounded frame, including at either scroll limit. // of the rounded frame, including at either scroll limit.
@@ -3229,7 +3238,7 @@ export component MainWindow inherits Window {
VerticalLayout { VerticalLayout {
vertical-stretch: 0; vertical-stretch: 0;
spacing: 16px; spacing: 16px;
Rectangle { height: 1px; background: WorkbenchStyle.separator; } ShadowSeparator { height: 1px; shadow-color: UiShadow.separator; }
if root.update-install-status != "": Text { if root.update-install-status != "": Text {
text: root.update-install-status; text: root.update-install-status;
font-size: 12px; font-size: 12px;
@@ -3302,7 +3311,7 @@ export component MainWindow inherits Window {
width: min(440px, parent.width - 40px); width: min(440px, parent.width - 40px);
height: update-confirm-content.preferred-height; height: update-confirm-content.preferred-height;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
border-color: WorkbenchStyle.border; shadow-color: UiShadow.edge;
TouchArea {} TouchArea {}
update-confirm-content := VerticalLayout { update-confirm-content := VerticalLayout {
padding: 24px; padding: 24px;
@@ -3356,7 +3365,7 @@ export component MainWindow inherits Window {
width: 512px; width: 512px;
height: wayland-notice-content.preferred-height; height: wayland-notice-content.preferred-height;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
border-color: #dce3ee; shadow-color: UiShadow.edge;
drop-shadow-color: #07142b30; drop-shadow-color: #07142b30;
drop-shadow-blur: 28px; drop-shadow-blur: 28px;
drop-shadow-offset-y: 10px; drop-shadow-offset-y: 10px;
@@ -3380,10 +3389,10 @@ export component MainWindow inherits Window {
} }
} }
Rectangle { ShadowSurface {
background: #fff8eb; background: #fff8eb;
border-color: #f0dfba; shadow-color: UiShadow.edge;
border-width: 1px;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
VerticalLayout { VerticalLayout {
padding: 14px; spacing: 8px; padding: 14px; spacing: 8px;
@@ -3403,10 +3412,10 @@ export component MainWindow inherits Window {
} }
} }
Rectangle { ShadowSurface {
background: #f3f7fd; background: #f3f7fd;
border-color: #dfe8f5; shadow-color: UiShadow.edge;
border-width: 1px;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
VerticalLayout { VerticalLayout {
padding: 14px; spacing: 8px; padding: 14px; spacing: 8px;
@@ -3426,7 +3435,7 @@ export component MainWindow inherits Window {
} }
} }
Rectangle { height: 1px; background: #e9edf3; } ShadowSeparator { height: 1px; shadow-color: UiShadow.separator; }
HorizontalLayout { HorizontalLayout {
height: 34px; alignment: end; height: 34px; alignment: end;
PrimaryButton { PrimaryButton {
@@ -3459,8 +3468,8 @@ export component MainWindow inherits Window {
text-size: ImGuiFontStyle.prominent; text-size: ImGuiFontStyle.prominent;
text-weight: 600; text-weight: 600;
} }
Rectangle { ShadowSurface {
background: #f7f8fa; border-color: #d9dde4; border-width: 1px; border-radius: UiRadius.surface; background: #f7f8fa; shadow-color: UiShadow.edge; border-radius: UiRadius.surface;
Text { x: 14px; y: 22px; width: parent.width - 28px; height: parent.height - 24px; text: UiStrings.service-setup-message; color: #3d434d; wrap: word-wrap; vertical-alignment: center; } Text { x: 14px; y: 22px; width: parent.width - 28px; height: parent.height - 24px; text: UiStrings.service-setup-message; color: #3d434d; wrap: word-wrap; vertical-alignment: center; }
} }
if root.portable-service-status != "": Text { if root.portable-service-status != "": Text {
@@ -3531,14 +3540,13 @@ export component MainWindow inherits Window {
z: 250; z: 250;
} }
if root.custom-titlebar: Rectangle { if root.custom-titlebar: ShadowSurface {
x: 0px; x: 0px;
y: 0px; y: 0px;
width: root.width; width: root.width;
height: root.height; height: root.height;
background: transparent; background: transparent;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: root.wayland-titlebar ? #aaa8a6 : #8b8b8b;
border-radius: border-radius:
root.wayland-titlebar ? root.wayland-corner-radius : 0px; root.wayland-titlebar ? root.wayland-corner-radius : 0px;
z: 200; z: 200;
+13 -11
View File
@@ -1,6 +1,7 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { ScrollView } from "std-widgets.slint"; import { ScrollView } from "std-widgets.slint";
import { IconButton, FontAwesomeIcons } from "common.slint"; import { IconButton, FontAwesomeIcons, ControlStyle } from "common.slint";
import { SettingsDialog, SettingsButton } from "settings_controls.slint"; import { SettingsDialog, SettingsButton } from "settings_controls.slint";
import { WorkbenchStyle } from "workbench_controls.slint"; import { WorkbenchStyle } from "workbench_controls.slint";
import "../assets/fonts/message_spacing.ttf"; import "../assets/fonts/message_spacing.ttf";
@@ -90,11 +91,11 @@ export component MessagePanel inherits SettingsDialog {
text: root.notice-text; font-size: 12px; wrap: word-wrap; color: #b3261e; text: root.notice-text; font-size: 12px; wrap: word-wrap; color: #b3261e;
} }
} }
message-content := Rectangle { message-content := ShadowSurface {
x: 24px; y: root.content-top + (root.notice-text == "" ? 0px : notice.height + 12px); x: 24px; y: root.content-top + (root.notice-text == "" ? 0px : notice.height + 12px);
width: parent.width - 48px; height: parent.height - self.y - 24px; width: parent.width - 48px; height: parent.height - self.y - 24px;
background: WorkbenchStyle.surface; background: WorkbenchStyle.surface;
border-radius: UiRadius.surface; border-width: 1px; border-color: WorkbenchStyle.border; border-radius: UiRadius.surface; shadow-color: UiShadow.edge;
if root.items.length > 0 || root.selected-id != "": Rectangle { if root.items.length > 0 || root.selected-id != "": Rectangle {
x: 1px; y: 1px; width: content-divider.x - 1px; height: parent.height - 2px; x: 1px; y: 1px; width: content-divider.x - 1px; height: parent.height - 2px;
background: WorkbenchStyle.field-surface; background: WorkbenchStyle.field-surface;
@@ -112,19 +113,19 @@ export component MessagePanel inherits SettingsDialog {
announcement-entries := VerticalLayout { announcement-entries := VerticalLayout {
width: announcements-scroll.viewport-width; spacing: 10px; padding: 0px; width: announcements-scroll.viewport-width; spacing: 10px; padding: 0px;
alignment: start; alignment: start;
for item in root.items: Rectangle { for item in root.items: ShadowSurface {
height: 64px; height: 64px;
background: root.selected-id == item.id ? #e8efff background: root.selected-id == item.id
? (entry-touch.pressed ? ControlStyle.selected-pressed : entry-touch.has-hover ? ControlStyle.selected-hover : ControlStyle.selected)
: entry-touch.pressed ? WorkbenchStyle.pressed-surface : entry-touch.pressed ? WorkbenchStyle.pressed-surface
: entry-touch.has-hover ? WorkbenchStyle.hover-surface : WorkbenchStyle.surface; : entry-touch.has-hover ? WorkbenchStyle.hover-surface : WorkbenchStyle.surface;
border-radius: UiRadius.control; border-radius: UiRadius.control;
border-width: 1px; shadow-color: root.selected-id == item.id ? UiShadow.focus : UiShadow.edge;
border-color: root.selected-id == item.id ? #b5c9f5 : WorkbenchStyle.border;
Text { Text {
x: 10px; y: 9px; width: parent.width - 30px; height: 20px; x: 10px; y: 9px; width: parent.width - 30px; height: 20px;
text: item.title; overflow: elide; font-size: 13px; text: item.title; overflow: elide; font-size: 13px;
font-weight: item.read ? 400 : 600; font-weight: item.read ? 400 : 600;
color: root.selected-id == item.id ? WorkbenchStyle.accent : WorkbenchStyle.text; color: root.selected-id == item.id ? ControlStyle.selected-text : WorkbenchStyle.text;
vertical-alignment: center; vertical-alignment: center;
} }
if !item.read: Rectangle { if !item.read: Rectangle {
@@ -154,10 +155,10 @@ export component MessagePanel inherits SettingsDialog {
color: WorkbenchStyle.muted; font-size: 13px; color: WorkbenchStyle.muted; font-size: 13px;
horizontal-alignment: center; vertical-alignment: center; horizontal-alignment: center; vertical-alignment: center;
} }
content-divider := Rectangle { content-divider := ShadowSeparator {
x: announcement-list.x + announcement-list.width + 8px; x: announcement-list.x + announcement-list.width + 8px;
y: 1px; width: 1px; height: parent.height - 2px; y: 1px; width: 1px; height: parent.height - 2px;
background: WorkbenchStyle.separator; vertical: true; shadow-color: UiShadow.separator;
visible: root.items.length > 0 || root.selected-id != ""; visible: root.items.length > 0 || root.selected-id != "";
accessible-role: none; accessible-role: none;
} }
@@ -199,13 +200,14 @@ export component MessagePanel inherits SettingsDialog {
x: parent.width - self.width; y: 0px; width: 28px; height: 28px; x: parent.width - self.width; y: 0px; width: 28px; height: 28px;
icon: FontAwesomeIcons.trash-can; icon-size: 12px; icon: FontAwesomeIcons.trash-can; icon-size: 12px;
icon-color: WorkbenchStyle.muted; danger: true; icon-color: WorkbenchStyle.muted; danger: true;
normal-background: ControlStyle.surface;
accessible-role: button; accessible-role: button;
accessible-label: MessageStrings.delete-message + " " + root.selected-title; accessible-label: MessageStrings.delete-message + " " + root.selected-title;
accessible-action-default => { root.request-delete(root.selected-id, root.selected-revision); } accessible-action-default => { root.request-delete(root.selected-id, root.selected-revision); }
clicked => { root.request-delete(root.selected-id, root.selected-revision); } clicked => { root.request-delete(root.selected-id, root.selected-revision); }
} }
} }
Rectangle { height: 1px; background: WorkbenchStyle.separator; } ShadowSeparator { height: 1px; shadow-color: UiShadow.separator; }
StyledText { StyledText {
vertical-stretch: 0; vertical-stretch: 0;
text: root.selected-body; text: root.selected-body;
@@ -1,15 +1,17 @@
import { ShadowSurface, UiShadow } from "ui_shadow.slint";
import { LineEdit } from "std-widgets.slint"; import { LineEdit } from "std-widgets.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { ControlStyle } from "common.slint";
// Retain the standard editor's selection, clipboard, clear button and input // Retain the standard editor's selection, clipboard, clear button and input
// method support. Only its outer frame is replaced by the shared shape. // method support. Its frame receives the shared field tint and rounded shape.
export component RoundedLineEdit inherits Rectangle { export component RoundedLineEdit inherits Rectangle {
in-out property <string> text; in-out property <string> text;
in property <string> placeholder-text; in property <string> placeholder-text;
in property <InputType> input-type: InputType.text; in property <InputType> input-type: InputType.text;
in property <bool> enabled: true; in property <bool> enabled: true;
in property <length> font-size: 12px; in property <length> font-size: 12px;
in property <color> normal-border-color: #dfe6ef; in property <color> normal-shadow-color: UiShadow.edge;
in property <brush> surrounding-background: white; in property <brush> surrounding-background: white;
callback accepted(); callback accepted();
callback edited(); callback edited();
@@ -20,8 +22,8 @@ export component RoundedLineEdit inherits Rectangle {
forward-focus: editor; forward-focus: editor;
editor := LineEdit { editor := LineEdit {
x: -1px; y: -1px; x: -2px; y: -2px;
width: parent.width + 2px; height: parent.height + 2px; width: parent.width + 4px; height: parent.height + 4px;
text <=> root.text; text <=> root.text;
placeholder-text: root.placeholder-text; placeholder-text: root.placeholder-text;
input-type: root.input-type; input-type: root.input-type;
@@ -30,8 +32,15 @@ export component RoundedLineEdit inherits Rectangle {
accepted => { root.accepted(); } accepted => { root.accepted(); }
edited => { root.edited(); } edited => { root.edited(); }
} }
// Fluent's LineEdit paints its own background and exposes no fill override.
// A visual-only tint preserves the standard editor and its built-in actions.
Rectangle {
width: parent.width; height: parent.height;
background: !root.enabled ? transparent
: editor.has-focus ? ControlStyle.input-focus-tint : ControlStyle.input-tint;
}
// The software renderer clips children to rectangular bounds. Mask the // The software renderer clips children to rectangular bounds. Mask the
// standard editor's corners before drawing our rounded border on top. // standard editor's corners before drawing our soft focus edge on top.
Rectangle { Rectangle {
x: -root.border-radius; y: -root.border-radius; x: -root.border-radius; y: -root.border-radius;
width: parent.width + 2 * root.border-radius; width: parent.width + 2 * root.border-radius;
@@ -40,11 +49,13 @@ export component RoundedLineEdit inherits Rectangle {
border-radius: 2 * root.border-radius; border-radius: 2 * root.border-radius;
border-color: root.surrounding-background; border-color: root.surrounding-background;
} }
Rectangle { ShadowSurface {
width: parent.width; height: parent.height; width: parent.width; height: parent.height;
border-radius: root.border-radius; border-radius: root.border-radius;
border-width: editor.has-focus ? 2px : 1px; clip: true;
border-color: editor.has-focus ? #3564e3 : root.normal-border-color; shadow-color: editor.has-focus ? UiShadow.focus : root.normal-shadow-color;
shadow-focused: editor.has-focus;
shadow-compact: true;
background: transparent; background: transparent;
} }
} }
+14 -13
View File
@@ -1,6 +1,7 @@
import { ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { ScrollView } from "std-widgets.slint"; import { ScrollView } from "std-widgets.slint";
import { FontAwesomeIcons, ImGuiLineStyle, FloatingBarStyle } from "common.slint"; import { FontAwesomeIcons, FloatingBarStyle, ControlStyle } from "common.slint";
export struct ControllerEntry { export struct ControllerEntry {
remote-id: string, remote-id: string,
@@ -19,7 +20,7 @@ export global ServerStrings {
in property <string> cancel: "取消"; in property <string> cancel: "取消";
} }
component SessionButton inherits Rectangle { component SessionButton inherits ShadowSurface {
in property <string> text; in property <string> text;
in property <bool> danger: false; in property <bool> danger: false;
in property <bool> enabled: true; in property <bool> enabled: true;
@@ -29,10 +30,10 @@ component SessionButton inherits Rectangle {
width: label.preferred-width + 20px; width: label.preferred-width + 20px;
height: 28px; height: 28px;
border-radius: UiRadius.control; border-radius: UiRadius.control;
border-width: 1px; shadow-color: root.danger ? UiShadow.error : UiShadow.edge;
border-color: root.danger ? #d92d20 : #c9ced7;
background: root.danger ? (root.pointer-feedback && touch.pressed ? #b42318 : root.pointer-feedback && touch.has-hover ? #f04438 : #d92d20) background: root.danger ? (root.pointer-feedback && touch.pressed ? #b42318 : root.pointer-feedback && touch.has-hover ? #f04438 : #d92d20)
: (root.pointer-feedback && touch.pressed ? #dfe3e9 : root.pointer-feedback && touch.has-hover ? #f3f4f6 : white); : (root.pointer-feedback && touch.pressed ? ControlStyle.pressed
: root.pointer-feedback && touch.has-hover ? ControlStyle.hover : ControlStyle.surface);
opacity: root.enabled ? 1 : 0.5; opacity: root.enabled ? 1 : 0.5;
accessible-role: button; accessible-role: button;
accessible-label: root.text; accessible-label: root.text;
@@ -72,9 +73,9 @@ component SessionButton inherits Rectangle {
// Restore hover feedback only after a fresh pointer move or press. // Restore hover feedback only after a fresh pointer move or press.
clicked => { root.pointer-feedback = false; root.keyboard-focus = false; keyboard.focus(); root.clicked(); } clicked => { root.pointer-feedback = false; root.keyboard-focus = false; keyboard.focus(); root.clicked(); }
} }
if root.enabled && keyboard.has-focus && root.keyboard-focus: Rectangle { if root.enabled && keyboard.has-focus && root.keyboard-focus: ShadowSurface {
x: 2px; y: 2px; width: parent.width - 4px; height: parent.height - 4px; x: 2px; y: 2px; width: parent.width - 4px; height: parent.height - 4px;
border-radius: UiRadius.control - 2px; border-width: 1px; border-color: root.danger ? white : #2463c7; border-radius: UiRadius.control - 2px; shadow-color: root.danger ? white : UiShadow.focus;
} }
} }
@@ -107,10 +108,10 @@ export component ServerStatusBar inherits Window {
return reject; return reject;
} }
} }
Rectangle { ShadowSurface {
x: 0px; y: 0px; width: parent.width; height: parent.height; x: 0px; y: 0px; width: parent.width; height: parent.height;
background: touch.pressed ? #dfe3e9 : touch.has-hover ? #f3f4f6 : white; background: touch.pressed ? #dfe3e9 : touch.has-hover ? #f3f4f6 : white;
border-width: 1px; border-color: ImGuiLineStyle.border; shadow-color: UiShadow.edge;
border-radius: FloatingBarStyle.corner-radius; border-radius: FloatingBarStyle.corner-radius;
border-top-right-radius: 0px; border-top-right-radius: 0px;
border-bottom-right-radius: 0px; border-bottom-right-radius: 0px;
@@ -170,9 +171,9 @@ export component ServerWindow inherits Window {
if event.text == Key.Escape { root.dismiss-panel(); return accept; } if event.text == Key.Escape { root.dismiss-panel(); return accept; }
return reject; return reject;
} }
Rectangle { ShadowSurface {
width: root.width; height: root.height; width: root.width; height: root.height;
background: white; border-width: 1px; border-color: ImGuiLineStyle.border; background: white; shadow-color: UiShadow.edge;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
Text { Text {
x: 10px; y: 10px; width: parent.width - 20px; height: 24px; x: 10px; y: 10px; width: parent.width - 20px; height: 24px;
@@ -192,9 +193,9 @@ export component ServerWindow inherits Window {
width: parent.width - (root.controllers.length > 1 ? disconnect.width + 4px : status.preferred-width + 8px); width: parent.width - (root.controllers.length > 1 ? disconnect.width + 4px : status.preferred-width + 8px);
height: parent.height; border-radius: UiRadius.control; height: parent.height; border-radius: UiRadius.control;
background: root.controllers.length > 1 && index == root.selected-controller ? #eef5fd : select-touch.has-hover ? #f3f4f6 : transparent; background: root.controllers.length > 1 && index == root.selected-controller ? #eef5fd : select-touch.has-hover ? #f3f4f6 : transparent;
if root.controllers.length > 1: Rectangle { if root.controllers.length > 1: ShadowSurface {
x: 4px; y: (parent.height - 13px) / 2; width: 13px; height: 13px; x: 4px; y: (parent.height - 13px) / 2; width: 13px; height: 13px;
border-radius: 7px; border-width: 1px; border-color: index == root.selected-controller ? #2463c7 : #8c939e; border-radius: 7px; shadow-color: index == root.selected-controller ? UiShadow.focus : UiShadow.edge;
if index == root.selected-controller: Rectangle { x: 3px; y: 3px; width: 7px; height: 7px; border-radius: 4px; background: #2463c7; } if index == root.selected-controller: Rectangle { x: 3px; y: 3px; width: 7px; height: 7px; border-radius: 4px; background: #2463c7; }
} }
name := Text { name := Text {
+18 -14
View File
@@ -1,9 +1,11 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { CompactComboBox, ToggleSwitch, DialogSurface, IconButton, FontAwesomeIcons } from "common.slint"; import { CompactComboBox, ToggleSwitch, DialogSurface, IconButton, FontAwesomeIcons, ControlStyle } from "common.slint";
import { WorkbenchStyle } from "workbench_controls.slint"; import { WorkbenchStyle } from "workbench_controls.slint";
// Shared visual treatment for the global settings pages. // Shared visual treatment for the global settings pages.
export component SettingsButton inherits Rectangle { export component SettingsButton inherits ShadowSurface {
shadow-compact: true;
in property <string> text; in property <string> text;
in property <image> icon; in property <image> icon;
in property <bool> primary: false; in property <bool> primary: false;
@@ -14,12 +16,13 @@ export component SettingsButton inherits Rectangle {
private property <length> content-padding: root.has-icon ? 8px : 12px; private property <length> content-padding: root.has-icon ? 8px : 12px;
callback clicked(); callback clicked();
width: 88px; height: 32px; border-radius: UiRadius.control; width: 88px; height: 32px; border-radius: UiRadius.control;
background: !root.enabled ? #f0f3f7 background: !root.enabled ? ControlStyle.disabled
: root.primary ? (touch.pressed ? #1e4fa3 : touch.has-hover ? #2866c9 : #3275dc) : root.primary ? (touch.pressed ? #1e4fa3 : touch.has-hover ? #2866c9 : #3275dc)
: touch.pressed ? #e9eef5 : touch.has-hover ? #f0f4fa : root.quiet ? transparent : white; : touch.pressed ? ControlStyle.pressed : touch.has-hover ? ControlStyle.hover
border-width: root.quiet || root.primary ? 0px : 1px; : root.quiet ? transparent : ControlStyle.surface;
border-color: !root.enabled ? WorkbenchStyle.border shadow-enabled: !root.quiet && !root.primary;
: touch.has-hover ? WorkbenchStyle.hover-border : WorkbenchStyle.control-border; shadow-color: !root.enabled ? UiShadow.edge
: touch.has-hover ? UiShadow.hover : UiShadow.edge;
accessible-role: button; accessible-label: root.text; accessible-role: button; accessible-label: root.text;
accessible-action-default => { if root.enabled { root.clicked(); } } accessible-action-default => { if root.enabled { root.clicked(); } }
Rectangle { Rectangle {
@@ -59,7 +62,7 @@ export component SettingsDialog inherits DialogSurface {
callback action(); callback action();
callback close(); callback close();
background: #f8fafc; background: #f8fafc;
border-color: WorkbenchStyle.separator; shadow-color: UiShadow.edge;
drop-shadow-color: #14294424; drop-shadow-color: #14294424;
drop-shadow-blur: 24px; drop-shadow-blur: 24px;
drop-shadow-offset-y: 8px; drop-shadow-offset-y: 8px;
@@ -97,6 +100,7 @@ export component SettingsDialog inherits DialogSurface {
width: 28px; height: 28px; width: 28px; height: 28px;
icon: FontAwesomeIcons.xmark; icon-size: 14px; icon: FontAwesomeIcons.xmark; icon-size: 14px;
icon-color: WorkbenchStyle.muted; icon-color: WorkbenchStyle.muted;
normal-background: ControlStyle.surface;
hover-background: WorkbenchStyle.hover-surface; hover-background: WorkbenchStyle.hover-surface;
pressed-background: WorkbenchStyle.pressed-surface; pressed-background: WorkbenchStyle.pressed-surface;
accessible-role: button; accessible-label: root.close-label; accessible-role: button; accessible-label: root.close-label;
@@ -110,8 +114,8 @@ export component SettingsDialog inherits DialogSurface {
export component SettingsSelect inherits CompactComboBox { export component SettingsSelect inherits CompactComboBox {
subtle: true; subtle: true;
show-arrow: false; show-arrow: false;
normal-border-color: WorkbenchStyle.control-border; normal-shadow-color: UiShadow.edge;
hover-border-color: WorkbenchStyle.hover-border; hover-shadow-color: UiShadow.hover;
height: 28px; height: 28px;
} }
@@ -123,9 +127,9 @@ export component SettingsGroup inherits Rectangle {
x: 2px; y: 0px; width: parent.width - 4px; height: 18px; text: root.title; x: 2px; y: 0px; width: parent.width - 4px; height: 18px; text: root.title;
font-size: 11px; font-weight: 600; color: #718096; vertical-alignment: center; font-size: 11px; font-weight: 600; color: #718096; vertical-alignment: center;
} }
Rectangle { ShadowSurface {
y: 24px; width: parent.width; height: parent.height - self.y; y: 24px; width: parent.width; height: parent.height - self.y;
background: white; border-radius: UiRadius.surface; border-color: WorkbenchStyle.border; border-width: 1px; background: white; border-radius: UiRadius.surface; shadow-color: UiShadow.edge;
VerticalLayout { padding: 0px; spacing: 0px; @children } VerticalLayout { padding: 0px; spacing: 0px; @children }
} }
} }
@@ -141,8 +145,8 @@ export component SettingsRow inherits Rectangle {
text: root.label; color: root.enabled ? #34445a : #9ba5b3; text: root.label; color: root.enabled ? #34445a : #9ba5b3;
font-size: 12px; overflow: elide; vertical-alignment: center; font-size: 12px; overflow: elide; vertical-alignment: center;
} }
if root.divider: Rectangle { if root.divider: ShadowSeparator {
x: 12px; y: parent.height - 1px; width: parent.width - 24px; height: 1px; background: #d9e1ec; x: 12px; y: parent.height - 1px; width: parent.width - 24px; height: 1px; shadow-color: UiShadow.separator;
} }
@children @children
} }
+48 -43
View File
@@ -1,7 +1,8 @@
import { ShadowSeparator, ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { ComboBox, ScrollView } from "std-widgets.slint"; import { ComboBox, ScrollView } from "std-widgets.slint";
import { RemoteCursor } from "remote_cursor.slint"; import { RemoteCursor } from "remote_cursor.slint";
import { FontAwesomeIcons, IconButton, PrimaryButton, SecondaryButton, DialogSurface, ImGuiLineStyle, ImGuiFontStyle } from "common.slint"; import { FontAwesomeIcons, IconButton, PrimaryButton, SecondaryButton, DialogSurface, ImGuiFontStyle, ControlStyle } from "common.slint";
export struct StreamTab { export struct StreamTab {
remote-id: string, remote-id: string,
@@ -80,10 +81,9 @@ struct ControlBarTooltip {
} }
// Shared surface treatment for the capsule and its detached detail cards. // Shared surface treatment for the capsule and its detached detail cards.
component StreamControlSurface inherits Rectangle { component StreamControlSurface inherits ShadowSurface {
background: white; background: white;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: #dbe5f1;
border-radius: 16px; border-radius: 16px;
drop-shadow-color: #1d35571a; drop-shadow-color: #1d35571a;
drop-shadow-blur: 10px; drop-shadow-blur: 10px;
@@ -121,14 +121,14 @@ component ControlBarButton inherits Rectangle {
!root.show-logo && root.action-enabled && !root.muted && touch.pressed; !root.show-logo && root.action-enabled && !root.muted && touch.pressed;
// Share the fill with the slash cutout so every icon stays legible while // Share the fill with the slash cutout so every icon stays legible while
// transitioning between idle, hover and pressed states. // transitioning between idle, hover and pressed states.
private property <color> button-fill: root.muted ? #eef0f3 private property <color> button-fill: root.muted ? ControlStyle.disabled
: root.button-pressed ? (root.danger ? #7a271a : #65a5f2) : root.button-pressed ? (root.danger ? #7a271a : root.selected ? ControlStyle.selected-pressed : ControlStyle.pressed)
: root.button-hovered ? (root.danger ? (root.selected ? #912018 : #b42318) : root.button-hovered ? (root.danger ? (root.selected ? #912018 : #b42318)
: root.selected ? #86baff : #a7ceff) : root.selected ? ControlStyle.selected-hover : ControlStyle.hover)
: root.danger ? (root.selected ? #b42318 : #d92d20) : root.danger ? (root.selected ? #b42318 : #d92d20)
: root.selected ? #b3d5fd : #deecff; : root.selected ? ControlStyle.selected : ControlStyle.surface;
private property <color> button-foreground: root.muted ? #8a8f96 private property <color> button-foreground: root.muted ? #8a8f96
: root.danger ? white : #24272d; : root.danger ? white : root.selected ? ControlStyle.selected-text : #303f55;
animate button-fill, button-foreground { animate button-fill, button-foreground {
duration: 90ms; duration: 90ms;
easing: ease-out; easing: ease-out;
@@ -242,14 +242,17 @@ component VideoSettingRow inherits VerticalLayout {
Text { text: root.label; color: #5c626c; font-size: 11px; height: 18px; } Text { text: root.label; color: #5c626c; font-size: 11px; height: 18px; }
HorizontalLayout { HorizontalLayout {
spacing: 4px; spacing: 4px;
for option[index] in root.options: Rectangle { for option[index] in root.options: ShadowSurface {
height: 27px; height: 27px;
background: !root.enabled ? #f1f1f1 : root.selected == index ? #d7eaff background: !root.enabled ? ControlStyle.disabled
: option-touch.has-hover ? #eaf3ff : #f5f6f8; : 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;
border-radius: UiRadius.control; border-radius: UiRadius.control;
border-width: 1px; shadow-color: root.enabled && root.selected == index ? UiShadow.focus : UiShadow.edge;
border-color: root.enabled && root.selected == index ? #4296fa : #dce0e6; Text { text: option; color: !root.enabled ? #8b8f95
Text { text: option; color: root.enabled ? #30343b : #8b8f95; : root.selected == index ? ControlStyle.selected-text : #30343b;
font-weight: root.selected == index ? 600 : 400;
font-size: 11px; horizontal-alignment: center; vertical-alignment: center; } font-size: 11px; horizontal-alignment: center; vertical-alignment: center; }
option-touch := TouchArea { option-touch := TouchArea {
mouse-cursor: root.enabled ? pointer : default; mouse-cursor: root.enabled ? pointer : default;
@@ -899,10 +902,10 @@ export component StreamWindow inherits Window {
clicked => { root.close-stream-window(); } clicked => { root.close-stream-window(); }
} }
} }
Rectangle { ShadowSeparator {
y: parent.height - 1px; y: parent.height - 1px;
height: 1px; height: 1px;
background: #d5d3d0; shadow-color: UiShadow.separator;
} }
} }
@@ -922,11 +925,11 @@ export component StreamWindow inherits Window {
z: 10; z: 10;
HorizontalLayout { HorizontalLayout {
spacing: 1px; spacing: 1px;
for tab[index] in root.tabs: tab-item := Rectangle { for tab[index] in root.tabs: tab-item := ShadowSurface {
width: min(190px, max(110px, (parent.width - 12px) / root.tabs.length)); width: min(190px, max(110px, (parent.width - 12px) / root.tabs.length));
background: index == root.selected-tab ? white : tab-touch.has-hover || root.dragging-tab == index ? #e5e7eb : #d8dbe0; background: index == root.selected-tab ? white : tab-touch.has-hover || root.dragging-tab == index ? #e5e7eb : #d8dbe0;
border-width: root.dragging-tab == index ? 1px : 0px; shadow-enabled: root.dragging-tab == index;
border-color: #2463c7; shadow-color: UiShadow.focus;
Rectangle { Rectangle {
width: parent.width - 27px; width: parent.width - 27px;
height: parent.height; height: parent.height;
@@ -1199,11 +1202,11 @@ export component StreamWindow inherits Window {
tooltip: root.fullscreen-enabled ? StreamStrings.exit-fullscreen : StreamStrings.fullscreen; tooltip: root.fullscreen-enabled ? StreamStrings.exit-fullscreen : StreamStrings.fullscreen;
clicked => { root.close-control-menus(); root.toggle-fullscreen(); } clicked => { root.close-control-menus(); root.toggle-fullscreen(); }
} }
if root.control-content-visible: Rectangle { if root.control-content-visible: ShadowSeparator {
opacity: root.control-content-progress; opacity: root.control-content-progress;
x: close-button.x - 6px; y: 12px; x: close-button.x - 6px; y: 12px;
width: 1px; height: 14px; width: 1px; height: 14px;
background: #dbe5f1; vertical: true; shadow-color: UiShadow.separator;
} }
close-button := ControlBarButton { close-button := ControlBarButton {
reveal-progress: root.control-content-progress; reveal-progress: root.control-content-progress;
@@ -1408,12 +1411,12 @@ export component StreamWindow inherits Window {
vertical-alignment: center; vertical-alignment: center;
overflow: elide; overflow: elide;
} }
if index < root.stats-rows.length - 2: Rectangle { if index < root.stats-rows.length - 2: ShadowSeparator {
x: 4px; x: 4px;
y: parent.height - 1px; y: parent.height - 1px;
width: parent.width - 8px; width: parent.width - 8px;
height: 1px; height: 1px;
background: #edf0f4; shadow-color: UiShadow.separator;
} }
} }
@@ -1422,16 +1425,16 @@ export component StreamWindow inherits Window {
text: StreamStrings.stats-encoder + "\n" + StreamStrings.stats-decoder; text: StreamStrings.stats-encoder + "\n" + StreamStrings.stats-decoder;
font-size: ImGuiFontStyle.small; font-size: ImGuiFontStyle.small;
} }
Rectangle { ShadowSurface {
x: 0px; x: 0px;
y: root.stats-header-height + root.stats-rows.length * stats-table.row-height y: root.stats-header-height + root.stats-rows.length * stats-table.row-height
+ 70px; + 70px;
width: parent.width; height: 44px; width: parent.width; height: 44px;
background: #f7f9fc; border-radius: UiRadius.control; background: #f7f9fc; border-radius: UiRadius.control;
border-width: 1px; border-color: #e8edf4; shadow-color: UiShadow.edge;
Rectangle { ShadowSeparator {
x: 8px; y: 22px; width: parent.width - 16px; height: 1px; x: 8px; y: 22px; width: parent.width - 16px; height: 1px;
background: #e8edf4; shadow-color: UiShadow.separator;
} }
for codec[index] in [ for codec[index] in [
{ label: StreamStrings.stats-encoder, value: root.stats-encoder }, { label: StreamStrings.stats-encoder, value: root.stats-encoder },
@@ -1639,16 +1642,16 @@ export component StreamWindow inherits Window {
padding: 8px; spacing: 1px; padding: 8px; spacing: 1px;
for display[index] in root.displays: Rectangle { for display[index] in root.displays: Rectangle {
height: 29px; height: 29px;
background: index == display-menu.hovered-index && display-touch.pressed ? #bfd4f2 background: index == root.selected-display
: index == display-menu.hovered-index ? #d8e6f8 ? (index == display-menu.hovered-index ? (display-touch.pressed ? ControlStyle.selected-pressed : ControlStyle.selected-hover) : ControlStyle.selected)
: index == root.selected-display ? #e8eef9 : index == display-menu.hovered-index ? (display-touch.pressed ? ControlStyle.pressed : ControlStyle.hover) : transparent;
: transparent;
border-radius: UiRadius.control; border-radius: UiRadius.control;
Text { Text {
x: 8px; x: 8px;
width: parent.width - 34px; width: parent.width - 34px;
text: display; text: display;
color: #30343b; color: index == root.selected-display ? ControlStyle.selected-text : #30343b;
font-weight: index == root.selected-display ? 600 : 400;
font-size: ImGuiFontStyle.body; font-size: ImGuiFontStyle.body;
overflow: elide; overflow: elide;
vertical-alignment: center; vertical-alignment: center;
@@ -1657,7 +1660,7 @@ export component StreamWindow inherits Window {
x: parent.width - 24px; x: parent.width - 24px;
width: 16px; width: 16px;
text: FontAwesomeIcons.check; text: FontAwesomeIcons.check;
color: #2463c7; color: ControlStyle.selected-text;
font-family: "Font Awesome 6 Free"; font-family: "Font Awesome 6 Free";
font-weight: 900; font-weight: 900;
font-size: 10px; font-size: 10px;
@@ -1695,7 +1698,9 @@ export component StreamWindow inherits Window {
VerticalLayout { VerticalLayout {
padding: 8px; spacing: 1px; padding: 8px; spacing: 1px;
for shortcut in root.shortcut-items: Rectangle { for shortcut in root.shortcut-items: Rectangle {
height: 29px; background: shortcut-touch.has-hover ? #e8eef9 : transparent; height: 29px;
border-radius: UiRadius.control;
background: shortcut-touch.pressed ? ControlStyle.pressed : shortcut-touch.has-hover ? ControlStyle.hover : transparent;
Text { x: 8px; text: shortcut; color: #30343b; font-size: ImGuiFontStyle.body; vertical-alignment: center; } Text { x: 8px; text: shortcut; color: #30343b; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
shortcut-touch := TouchArea { shortcut-touch := TouchArea {
mouse-cursor: MouseCursor.pointer; mouse-cursor: MouseCursor.pointer;
@@ -1745,7 +1750,7 @@ export component StreamWindow inherits Window {
y: min(max(64px, parent.height * 0.18), max(16px, parent.height - self.height - 16px)); y: min(max(64px, parent.height * 0.18), max(16px, parent.height - self.height - 16px));
width: min(400px, parent.width - 48px); width: min(400px, parent.width - 48px);
height: remote-update-message.y + remote-update-message.height + 84px; height: remote-update-message.y + remote-update-message.height + 84px;
border-color: #e5e9f0; shadow-color: UiShadow.edge;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
drop-shadow-color: #07142b30; drop-shadow-color: #07142b30;
drop-shadow-blur: 32px; drop-shadow-blur: 32px;
@@ -1774,8 +1779,8 @@ export component StreamWindow inherits Window {
IconButton { IconButton {
x: parent.width - 44px; y: 12px; width: 32px; height: 32px; x: parent.width - 44px; y: 12px; width: 32px; height: 32px;
icon: FontAwesomeIcons.xmark; icon-size: 14px; icon: FontAwesomeIcons.xmark; icon-size: 14px;
icon-color: #8a93a3; icon-color: ControlStyle.icon;
hover-background: #f2f4f7; normal-background: ControlStyle.surface;
clicked => { root.dismiss-remote-update(); } clicked => { root.dismiss-remote-update(); }
} }
PrimaryButton { PrimaryButton {
@@ -1789,10 +1794,10 @@ export component StreamWindow inherits Window {
} }
// Transfer progress overlay at bottom-left. // Transfer progress overlay at bottom-left.
if root.file-transfer-visible: Rectangle { if root.file-transfer-visible: ShadowSurface {
x: root.file-panel-x; y: root.file-panel-y; x: root.file-panel-x; y: root.file-panel-y;
width: root.file-panel-width; height: root.file-panel-height; width: root.file-panel-width; height: root.file-panel-height;
background: #fffffff0; border-width: 1px; border-color: ImGuiLineStyle.border; border-radius: UiRadius.surface; z: 7; background: #fffffff0; shadow-color: UiShadow.edge; border-radius: UiRadius.surface; z: 7;
// Header/padding and drags leaving the panel belong to local UI. // Header/padding and drags leaving the panel belong to local UI.
TouchArea { TouchArea {
mouse-cursor: default; mouse-cursor: default;
@@ -1801,6 +1806,7 @@ export component StreamWindow inherits Window {
Text { x: 12px; y: 8px; text: StreamStrings.file-transfer; color: #2d3138; font-weight: 600; } Text { x: 12px; y: 8px; text: StreamStrings.file-transfer; color: #2d3138; font-weight: 600; }
IconButton { IconButton {
x: parent.width - 32px; y: 1px; icon: FontAwesomeIcons.xmark; icon-size: 13px; x: parent.width - 32px; y: 1px; icon: FontAwesomeIcons.xmark; icon-size: 13px;
normal-background: ControlStyle.surface;
clicked => { root.close-file-transfer(); } clicked => { root.close-file-transfer(); }
} }
ScrollView { ScrollView {
@@ -1840,12 +1846,11 @@ export component StreamWindow inherits Window {
} }
} }
if root.custom-titlebar && !root.fullscreen-enabled: Rectangle { if root.custom-titlebar && !root.fullscreen-enabled: ShadowSurface {
width: parent.width; width: parent.width;
height: parent.height; height: parent.height;
background: transparent; background: transparent;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: #aaa8a6;
border-radius: border-radius:
root.window-maximized ? 0px : root.window-corner-radius; root.window-maximized ? 0px : root.window-corner-radius;
z: 200; z: 200;
+63
View File
@@ -0,0 +1,63 @@
// Shared surface shadows use ordinary rectangles and gradients because Slint's
// software renderer does not draw drop-shadow-* effects. Keep layout and hit
// areas unchanged; only the decorative shadow extends beyond the surface.
export global UiShadow {
out property <color> edge: #20334e;
out property <color> hover: #315d92;
out property <color> focus: #3564e3;
out property <color> error: #d65252;
out property <color> separator: #20334e14;
}
export component ShadowSurface inherits Rectangle {
in property <color> shadow-color: UiShadow.edge;
in property <bool> shadow-enabled: true;
in property <bool> shadow-focused: false;
// Small controls need a tighter falloff than cards and dialogs.
in property <bool> shadow-compact: false;
border-width: 0px;
border-top-left-radius: root.border-radius;
border-top-right-radius: root.border-radius;
border-bottom-left-radius: root.border-radius;
border-bottom-right-radius: root.border-radius;
// Diffuse outwards, with more weight below the surface. A clipped surface
// keeps a faint inner falloff so native popup edges remain distinguishable.
for layer in (root.shadow-compact ? 3 : 6): Rectangle {
visible: root.shadow-enabled;
private property <length> inset: root.clip ? layer * 0.5px : -(layer + 1) * 1px;
private property <float> weight: (root.shadow-compact
? (layer == 0 ? 0.09 : layer == 1 ? 0.035 : 0.01)
: (layer == 0 ? 0.09 : layer == 1 ? 0.07
: layer == 2 ? 0.05 : layer == 3 ? 0.03 : layer == 4 ? 0.015 : 0.005))
* (root.shadow-focused ? 2.5 : 1);
x: self.inset; y: self.inset;
width: max(0px, root.width - 2 * self.inset);
height: max(0px, root.height - 2 * self.inset);
border-top-left-radius: max(0px, root.border-top-left-radius - self.inset);
border-top-right-radius: max(0px, root.border-top-right-radius - self.inset);
border-bottom-left-radius: max(0px, root.border-bottom-left-radius - self.inset);
border-bottom-right-radius: max(0px, root.border-bottom-right-radius - self.inset);
border-width: 1px;
border-color: @linear-gradient(180deg,
root.shadow-color.mix(transparent, self.weight * 0.45) 0%,
root.shadow-color.mix(transparent, self.weight) 100%);
}
@children
}
export component ShadowSeparator inherits Rectangle {
in property <bool> vertical: false;
in property <color> shadow-color: UiShadow.separator;
height: 1px;
Rectangle {
x: root.vertical ? -2px : 0px;
y: root.vertical ? 0px : -1px;
width: root.vertical ? 5px : root.width;
height: root.vertical ? root.height : 6px;
background: root.vertical
? @linear-gradient(90deg, transparent 0%, root.shadow-color 50%, transparent 100%)
: @linear-gradient(180deg, transparent 0%, root.shadow-color 50%, transparent 100%);
}
}
@@ -1,14 +1,13 @@
import { ShadowSurface, UiShadow } from "ui_shadow.slint";
import { UiRadius } from "ui_radius.slint"; import { UiRadius } from "ui_radius.slint";
import { ControlStyle } from "common.slint";
// Colors and spacing for the connection workbench; shapes are shared app-wide. // Colors and spacing for the connection workbench; shapes are shared app-wide.
export global WorkbenchStyle { export global WorkbenchStyle {
out property <color> canvas: #f2f5f9; out property <color> canvas: #edf2f8;
out property <color> surface: #ffffff; out property <color> surface: #ffffff;
out property <color> border: #c6cfdd;
out property <color> control-border: #b2bfd1;
out property <color> hover-border: #7f97b8;
out property <color> field-surface: #f5f7fb; out property <color> field-surface: #f5f7fb;
out property <color> hover-surface: #eef3fb; out property <color> hover-surface: ControlStyle.hover;
out property <color> pressed-surface: #e4ebf6; out property <color> pressed-surface: ControlStyle.pressed;
out property <color> separator: #e1e7ef; out property <color> separator: #e1e7ef;
out property <color> text: #1c2940; out property <color> text: #1c2940;
out property <color> muted: #69788e; out property <color> muted: #69788e;
@@ -16,18 +15,20 @@ export global WorkbenchStyle {
out property <color> success: #18835a; out property <color> success: #18835a;
} }
export component WorkbenchButton inherits Rectangle { export component WorkbenchButton inherits ShadowSurface {
shadow-compact: true;
in property <string> text; in property <string> text;
in property <length> text-size: 13px; in property <length> text-size: 13px;
in property <color> text-color: root.destructive || root.primary ? white : WorkbenchStyle.text; in property <color> text-color: root.destructive || root.primary ? white : WorkbenchStyle.text;
in property <color> normal-border-color: WorkbenchStyle.control-border; in property <color> normal-shadow-color: UiShadow.edge;
in property <color> normal-background: ControlStyle.surface;
in property <string> icon; in property <string> icon;
in property <string> tooltip; in property <string> tooltip;
in property <bool> tooltip-above: false; in property <bool> tooltip-above: false;
in property <bool> tooltip-suppressed: false; in property <bool> tooltip-suppressed: false;
in property <color> icon-color: root.destructive ? white : root.primary in property <color> icon-color: root.destructive ? white : root.primary
? (root.soft && !touch.has-hover && !root.show-focus ? WorkbenchStyle.accent : white) ? (root.soft && !touch.has-hover && !root.show-focus ? WorkbenchStyle.accent : white)
: WorkbenchStyle.muted; : ControlStyle.icon;
in property <bool> primary: false; in property <bool> primary: false;
in property <bool> destructive: false; in property <bool> destructive: false;
in property <bool> soft: false; in property <bool> soft: false;
@@ -45,24 +46,23 @@ export component WorkbenchButton inherits Rectangle {
width: 112px; width: 112px;
height: 36px; height: 36px;
border-radius: UiRadius.control; border-radius: UiRadius.control;
border-width: root.show-focus ? 2px : root.soft ? 0px : 1px; shadow-enabled: root.show-focus || !root.soft;
border-color: root.destructive shadow-focused: root.show-focus;
? (root.show-focus ? #7a271a : touch.pressed ? #912018 : touch.has-hover ? #b42318 : #d92d20) shadow-color: root.destructive ? UiShadow.error
: root.show-focus ? (root.primary ? #193da0 : WorkbenchStyle.accent) : root.show-focus || root.primary ? UiShadow.focus
: root.primary ? WorkbenchStyle.accent : touch.has-hover ? UiShadow.hover
: touch.has-hover ? WorkbenchStyle.hover-border : root.quiet ? transparent : root.normal-shadow-color;
: root.quiet ? transparent : root.normal-border-color;
background: root.destructive background: root.destructive
? (touch.pressed ? #912018 : touch.has-hover ? #b42318 : #d92d20) ? (touch.pressed ? #912018 : touch.has-hover ? #b42318 : #d92d20)
: root.soft : root.soft
? (root.primary ? (root.primary
? (touch.pressed ? #254cc0 : touch.has-hover || root.show-focus ? WorkbenchStyle.accent : #edf2ff) ? (touch.pressed ? #254cc0 : touch.has-hover || root.show-focus ? WorkbenchStyle.accent : #dce7ff)
: (touch.pressed ? WorkbenchStyle.pressed-surface : touch.has-hover ? WorkbenchStyle.hover-surface : WorkbenchStyle.field-surface)) : (touch.pressed ? ControlStyle.pressed : touch.has-hover ? ControlStyle.hover : ControlStyle.surface))
: root.primary : root.primary
? (touch.pressed ? #254cc0 : touch.has-hover ? #4574f0 : WorkbenchStyle.accent) ? (touch.pressed ? #254cc0 : touch.has-hover ? #4574f0 : WorkbenchStyle.accent)
: (touch.pressed ? WorkbenchStyle.pressed-surface : touch.has-hover ? WorkbenchStyle.hover-surface : root.quiet ? transparent : white); : (touch.pressed ? ControlStyle.pressed : touch.has-hover ? ControlStyle.hover : root.quiet ? transparent : root.normal-background);
opacity: root.enabled ? 1 : 0.45; opacity: root.enabled ? 1 : 0.45;
animate background, border-color { duration: 120ms; } animate background, shadow-color { duration: 120ms; }
forward-focus: button-focus; forward-focus: button-focus;
accessible-role: button; accessible-role: button;
accessible-label: root.tooltip != "" ? root.tooltip : root.text; accessible-label: root.tooltip != "" ? root.tooltip : root.text;
@@ -150,7 +150,7 @@ export component WorkbenchButton inherits Rectangle {
} }
// A raised surface keeps transient action feedback distinct from the workbench. // A raised surface keeps transient action feedback distinct from the workbench.
export component WorkbenchNotice inherits Rectangle { export component WorkbenchNotice inherits ShadowSurface {
in property <string> text; in property <string> text;
in property <string> icon; in property <string> icon;
in property <bool> error: false; in property <bool> error: false;
@@ -159,8 +159,7 @@ export component WorkbenchNotice inherits Rectangle {
width: min(root.width-limit, max(280px, message.preferred-width + 64px)); width: min(root.width-limit, max(280px, message.preferred-width + 64px));
height: 64px; height: 64px;
background: root.error ? #fff1f0 : #e9f6ef; background: root.error ? #fff1f0 : #e9f6ef;
border-width: 1px; shadow-color: root.error ? UiShadow.error : UiShadow.edge;
border-color: root.error ? #f0c4bf : #b9dfc9;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
drop-shadow-color: #14294424; drop-shadow-color: #14294424;
drop-shadow-blur: 24px; drop-shadow-blur: 24px;
@@ -192,9 +191,8 @@ export component WorkbenchNotice inherits Rectangle {
} }
} }
export component WorkbenchCard inherits Rectangle { export component WorkbenchCard inherits ShadowSurface {
background: WorkbenchStyle.surface; background: WorkbenchStyle.surface;
border-width: 1px; shadow-color: UiShadow.edge;
border-color: WorkbenchStyle.border;
border-radius: UiRadius.surface; border-radius: UiRadius.surface;
} }