mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 00:09:39 +08:00
[ui] refine floating notices and dismiss them on interaction
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { UiRadius } from "ui_radius.slint";
|
import { UiRadius } from "ui_radius.slint";
|
||||||
import { WorkbenchStyle, WorkbenchButton, WorkbenchCard } from "workbench_controls.slint";
|
import { WorkbenchStyle, WorkbenchButton, WorkbenchCard, WorkbenchNotice } from "workbench_controls.slint";
|
||||||
import { GalleryStrings } from "device_strings.slint";
|
import { GalleryStrings } from "device_strings.slint";
|
||||||
import { DeviceCardActions, DeviceCardStyle } from "device_card_actions.slint";
|
import { DeviceCardActions, DeviceCardStyle } from "device_card_actions.slint";
|
||||||
import { DeviceGroupDialog, DeviceGroupOption } from "device_group_dialog.slint";
|
import { DeviceGroupDialog, DeviceGroupOption } from "device_group_dialog.slint";
|
||||||
@@ -32,6 +32,7 @@ export struct ReleaseNoteBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum UpdateCheckStatus { idle, checking, up-to-date, available, failed }
|
export enum UpdateCheckStatus { idle, checking, up-to-date, available, failed }
|
||||||
|
enum CopyFeedback { idle, success, failed }
|
||||||
|
|
||||||
export global UiStrings {
|
export global UiStrings {
|
||||||
in-out property <string> local-desktop: "Local Desktop";
|
in-out property <string> local-desktop: "Local Desktop";
|
||||||
@@ -467,7 +468,7 @@ export component MainWindow inherits Window {
|
|||||||
in-out property <bool> delete-open: false;
|
in-out property <bool> delete-open: false;
|
||||||
private property <string> delete-remote-id: "";
|
private property <string> delete-remote-id: "";
|
||||||
private property <bool> delete-from-gallery: false;
|
private property <bool> delete-from-gallery: false;
|
||||||
private property <bool> copy-toast: false;
|
private property <CopyFeedback> copy-feedback: idle;
|
||||||
private property <bool> compact-language: root.language-index == 0;
|
private property <bool> compact-language: root.language-index == 0;
|
||||||
private property <bool> recent-tooltip-visible: false;
|
private property <bool> recent-tooltip-visible: false;
|
||||||
private property <string> recent-tooltip-name: "";
|
private property <string> recent-tooltip-name: "";
|
||||||
@@ -478,7 +479,9 @@ export component MainWindow inherits Window {
|
|||||||
private property <length> recent-tooltip-pointer-x: 0px;
|
private property <length> recent-tooltip-pointer-x: 0px;
|
||||||
private property <length> recent-tooltip-pointer-y: 0px;
|
private property <length> recent-tooltip-pointer-y: 0px;
|
||||||
|
|
||||||
changed menu-open => { if root.menu-open { root.gallery-action("release", ""); } }
|
changed menu-open => {
|
||||||
|
if root.menu-open { root.copy-feedback = CopyFeedback.idle; root.gallery-action("release", ""); }
|
||||||
|
}
|
||||||
changed connection-dialog-open => { if root.connection-dialog-open { root.gallery-action("release", ""); } }
|
changed connection-dialog-open => { if root.connection-dialog-open { root.gallery-action("release", ""); } }
|
||||||
changed permission-dialog-open => { if root.permission-dialog-open { root.gallery-action("release", ""); } }
|
changed permission-dialog-open => { if root.permission-dialog-open { root.gallery-action("release", ""); } }
|
||||||
changed settings-open => {
|
changed settings-open => {
|
||||||
@@ -544,6 +547,7 @@ export component MainWindow inherits Window {
|
|||||||
width: parent.width - 80px;
|
width: parent.width - 80px;
|
||||||
property <int> phase: self.pressed ? 1 : 0;
|
property <int> phase: self.pressed ? 1 : 0;
|
||||||
changed phase => {
|
changed phase => {
|
||||||
|
if self.pressed { root.copy-feedback = CopyFeedback.idle; }
|
||||||
root.main-title-drag(phase, self.mouse-x, self.mouse-y);
|
root.main-title-drag(phase, self.mouse-x, self.mouse-y);
|
||||||
}
|
}
|
||||||
moved => {
|
moved => {
|
||||||
@@ -643,7 +647,10 @@ export component MainWindow inherits Window {
|
|||||||
x: 0px;
|
x: 0px;
|
||||||
width: parent.width - 92px;
|
width: parent.width - 92px;
|
||||||
property <int> phase: self.pressed ? 1 : 0;
|
property <int> phase: self.pressed ? 1 : 0;
|
||||||
changed phase => { root.main-title-drag(phase, self.mouse-x, self.mouse-y); }
|
changed phase => {
|
||||||
|
if self.pressed { root.copy-feedback = CopyFeedback.idle; }
|
||||||
|
root.main-title-drag(phase, self.mouse-x, self.mouse-y);
|
||||||
|
}
|
||||||
moved => { if (self.pressed) { root.main-title-drag(2, self.mouse-x, self.mouse-y); } }
|
moved => { if (self.pressed) { root.main-title-drag(2, self.mouse-x, self.mouse-y); } }
|
||||||
}
|
}
|
||||||
minimize-button := Rectangle {
|
minimize-button := Rectangle {
|
||||||
@@ -735,7 +742,12 @@ export component MainWindow inherits Window {
|
|||||||
return reject;
|
return reject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TouchArea { clicked => { focus-sink.focus(); } }
|
TouchArea {
|
||||||
|
pointer-event(event) => {
|
||||||
|
if event.kind == PointerEventKind.down { root.copy-feedback = CopyFeedback.idle; }
|
||||||
|
}
|
||||||
|
clicked => { focus-sink.focus(); }
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -860,11 +872,21 @@ export component MainWindow inherits Window {
|
|||||||
border-width: 1px; border-color: WorkbenchStyle.border;
|
border-width: 1px; border-color: WorkbenchStyle.border;
|
||||||
Text { x: 12px; width: parent.width - 64px; height: parent.height; text: root.local-id; font-size: 24px; font-weight: 600; letter-spacing: 1px; color: WorkbenchStyle.text; vertical-alignment: center; }
|
Text { x: 12px; width: parent.width - 64px; height: parent.height; text: root.local-id; font-size: 24px; font-weight: 600; letter-spacing: 1px; color: WorkbenchStyle.text; vertical-alignment: center; }
|
||||||
Rectangle { x: parent.width - 48px; y: 10px; width: 1px; height: parent.height - 20px; background: WorkbenchStyle.border; }
|
Rectangle { x: parent.width - 48px; y: 10px; width: 1px; height: parent.height - 20px; background: WorkbenchStyle.border; }
|
||||||
WorkbenchButton {
|
copy-button := WorkbenchButton {
|
||||||
x: parent.width - 42px; y: 2px; width: 36px; quiet: true;
|
x: parent.width - 42px; y: 2px; width: 36px; quiet: true;
|
||||||
icon: root.copy-toast ? FontAwesomeIcons.check : FontAwesomeIcons.copy;
|
icon: root.copy-feedback == CopyFeedback.success ? FontAwesomeIcons.check : FontAwesomeIcons.copy;
|
||||||
tooltip: root.copy-toast ? UiStrings.copied : GalleryStrings.tr("复制本机 ID", "Copy local ID", "Копировать ID");
|
icon-color: root.copy-feedback == CopyFeedback.success ? WorkbenchStyle.success : WorkbenchStyle.muted;
|
||||||
clicked => { if root.copy-local-id() { root.copy-toast = true; copy-timer.restart(); } }
|
tooltip: GalleryStrings.tr("复制本机 ID", "Copy local ID", "Копировать ID");
|
||||||
|
tooltip-suppressed: root.copy-feedback != CopyFeedback.idle;
|
||||||
|
// Let other controls receive the original click; moving
|
||||||
|
// focus away from Copy dismisses its transient feedback.
|
||||||
|
changed has-focus => {
|
||||||
|
if !self.has-focus { root.copy-feedback = CopyFeedback.idle; }
|
||||||
|
}
|
||||||
|
clicked => {
|
||||||
|
root.copy-feedback = root.copy-local-id() ? CopyFeedback.success : CopyFeedback.failed;
|
||||||
|
copy-timer.restart();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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; }
|
||||||
@@ -994,7 +1016,10 @@ export component MainWindow inherits Window {
|
|||||||
width: parent.width - 12px;
|
width: parent.width - 12px;
|
||||||
height: parent.height - 6px;
|
height: parent.height - 6px;
|
||||||
horizontal-stretch: 1;
|
horizontal-stretch: 1;
|
||||||
changed viewport-x => { root.recent-tooltip-visible = false; }
|
changed viewport-x => {
|
||||||
|
root.recent-tooltip-visible = false;
|
||||||
|
root.copy-feedback = CopyFeedback.idle;
|
||||||
|
}
|
||||||
|
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
@@ -1094,7 +1119,7 @@ export component MainWindow inherits Window {
|
|||||||
width: connection-card.content-width;
|
width: connection-card.content-width;
|
||||||
height: 124px;
|
height: 124px;
|
||||||
mouse-cursor: pointer;
|
mouse-cursor: pointer;
|
||||||
clicked => { root.recent-connect(connection.remote-id); }
|
clicked => { root.copy-feedback = CopyFeedback.idle; root.recent-connect(connection.remote-id); }
|
||||||
image-touch := TouchArea {
|
image-touch := TouchArea {
|
||||||
x: 0px; y: 0px;
|
x: 0px; y: 0px;
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
@@ -1111,7 +1136,7 @@ export component MainWindow inherits Window {
|
|||||||
root.recent-tooltip-groups = self.group-names;
|
root.recent-tooltip-groups = self.group-names;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clicked => { root.recent-connect(connection.remote-id); }
|
clicked => { root.copy-feedback = CopyFeedback.idle; root.recent-connect(connection.remote-id); }
|
||||||
function show-details() {
|
function show-details() {
|
||||||
if !self.has-hover { return; }
|
if !self.has-hover { return; }
|
||||||
root.recent-tooltip-name = connection.display-name;
|
root.recent-tooltip-name = connection.display-name;
|
||||||
@@ -1145,18 +1170,20 @@ export component MainWindow inherits Window {
|
|||||||
remove-label: GalleryStrings.tr("删除记录", "Delete record", "Удалить запись");
|
remove-label: GalleryStrings.tr("删除记录", "Delete record", "Удалить запись");
|
||||||
connected: connection.connected;
|
connected: connection.connected;
|
||||||
edit-alias => {
|
edit-alias => {
|
||||||
|
root.copy-feedback = CopyFeedback.idle;
|
||||||
root.alias-remote-id = connection.remote-id;
|
root.alias-remote-id = connection.remote-id;
|
||||||
root.alias-input = connection.display-name;
|
root.alias-input = connection.display-name;
|
||||||
root.alias-open = true;
|
root.alias-open = true;
|
||||||
}
|
}
|
||||||
edit-groups => { root.open-device-groups(connection.remote-id, connection.display-name); }
|
edit-groups => { root.copy-feedback = CopyFeedback.idle; root.open-device-groups(connection.remote-id, connection.display-name); }
|
||||||
remove-device => {
|
remove-device => {
|
||||||
|
root.copy-feedback = CopyFeedback.idle;
|
||||||
root.delete-remote-id = connection.remote-id;
|
root.delete-remote-id = connection.remote-id;
|
||||||
root.delete-from-gallery = false;
|
root.delete-from-gallery = false;
|
||||||
root.delete-open = true;
|
root.delete-open = true;
|
||||||
}
|
}
|
||||||
connect-device => { root.recent-connect(connection.remote-id); }
|
connect-device => { root.copy-feedback = CopyFeedback.idle; root.recent-connect(connection.remote-id); }
|
||||||
disconnect-device => { root.recent-disconnect(connection.remote-id); }
|
disconnect-device => { root.copy-feedback = CopyFeedback.idle; root.recent-disconnect(connection.remote-id); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -1261,6 +1288,7 @@ export component MainWindow inherits Window {
|
|||||||
TouchArea {
|
TouchArea {
|
||||||
mouse-cursor: pointer;
|
mouse-cursor: pointer;
|
||||||
clicked => {
|
clicked => {
|
||||||
|
root.copy-feedback = CopyFeedback.idle;
|
||||||
root.signal-reconnect-requested();
|
root.signal-reconnect-requested();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1516,8 +1544,9 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copy-timer := Timer {
|
copy-timer := Timer {
|
||||||
interval: 1000ms;
|
interval: root.copy-feedback == CopyFeedback.failed ? 4s : 2500ms;
|
||||||
triggered => { root.copy-toast = false; }
|
running: root.copy-feedback != CopyFeedback.idle;
|
||||||
|
triggered => { root.copy-feedback = CopyFeedback.idle; }
|
||||||
}
|
}
|
||||||
remote-id-format-timer := Timer {
|
remote-id-format-timer := Timer {
|
||||||
interval: 1ms;
|
interval: 1ms;
|
||||||
@@ -1530,14 +1559,17 @@ export component MainWindow inherits Window {
|
|||||||
formatted.character-count, formatted.character-count);
|
formatted.character-count, formatted.character-count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if root.copy-toast: Rectangle {
|
// Float above the workbench while keeping modal dialogs in front.
|
||||||
x: (parent.width - self.width) / 2; y: parent.height - 92px; width: 280px; height: 40px;
|
if !root.gallery-mode && root.copy-feedback != CopyFeedback.idle: WorkbenchNotice {
|
||||||
background: #fffffff2;
|
x: (parent.width - self.width) / 2;
|
||||||
border-radius: UiRadius.surface;
|
y: (parent.height - self.height) / 2 - 24px;
|
||||||
border-width: 1px;
|
width-limit: min(440px, parent.width - 40px);
|
||||||
border-color: ImGuiLineStyle.border;
|
error: root.copy-feedback == CopyFeedback.failed;
|
||||||
|
icon: self.error ? FontAwesomeIcons.xmark : FontAwesomeIcons.check;
|
||||||
|
text: self.error
|
||||||
|
? GalleryStrings.tr("复制失败,请重试", "Couldn’t copy. Try again", "Не удалось скопировать. Повторите попытку")
|
||||||
|
: UiStrings.copied;
|
||||||
z: 30;
|
z: 30;
|
||||||
Text { text: UiStrings.copied; color: #333840; horizontal-alignment: center; vertical-alignment: center; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset password modal.
|
// Reset password modal.
|
||||||
|
|||||||
@@ -20,11 +20,19 @@ export component WorkbenchButton inherits Rectangle {
|
|||||||
in property <string> text;
|
in property <string> text;
|
||||||
in property <string> icon;
|
in property <string> icon;
|
||||||
in property <string> tooltip;
|
in property <string> tooltip;
|
||||||
|
in property <bool> tooltip-suppressed: false;
|
||||||
|
in property <color> icon-color: root.primary ? white : WorkbenchStyle.muted;
|
||||||
in property <bool> primary: false;
|
in property <bool> primary: false;
|
||||||
in property <bool> quiet: false;
|
in property <bool> quiet: false;
|
||||||
in property <bool> enabled: true;
|
in property <bool> enabled: true;
|
||||||
|
out property <bool> has-focus: button-focus.has-focus;
|
||||||
private property <bool> keyboard-focus: false;
|
private property <bool> keyboard-focus: false;
|
||||||
private property <bool> show-focus: button-focus.has-focus && root.keyboard-focus;
|
private property <bool> show-focus: button-focus.has-focus && root.keyboard-focus;
|
||||||
|
private property <bool> tooltip-active: root.enabled && root.tooltip != ""
|
||||||
|
&& !root.tooltip-suppressed && !touch.pressed
|
||||||
|
&& (touch.has-hover || root.show-focus);
|
||||||
|
private property <bool> tooltip-visible: false;
|
||||||
|
changed tooltip-active => { root.tooltip-visible = false; }
|
||||||
callback clicked;
|
callback clicked;
|
||||||
width: 112px;
|
width: 112px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
@@ -55,7 +63,7 @@ export component WorkbenchButton inherits Rectangle {
|
|||||||
font-family: "Font Awesome 6 Free";
|
font-family: "Font Awesome 6 Free";
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: root.primary ? white : WorkbenchStyle.muted;
|
color: root.icon-color;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
accessible-role: none;
|
accessible-role: none;
|
||||||
}
|
}
|
||||||
@@ -98,24 +106,76 @@ export component WorkbenchButton inherits Rectangle {
|
|||||||
clicked => { root.keyboard-focus = false; button-focus.focus(); root.clicked(); }
|
clicked => { root.keyboard-focus = false; button-focus.focus(); root.clicked(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if touch.has-hover && root.tooltip != "": Rectangle {
|
tooltip-timer := Timer {
|
||||||
|
interval: 450ms;
|
||||||
|
running: root.tooltip-active && !root.tooltip-visible;
|
||||||
|
triggered => { root.tooltip-visible = true; }
|
||||||
|
}
|
||||||
|
if root.tooltip-active && root.tooltip-visible: Rectangle {
|
||||||
x: min(0px, root.width - self.width);
|
x: min(0px, root.width - self.width);
|
||||||
y: root.height + 6px;
|
y: root.height + 6px;
|
||||||
width: tip.preferred-width + 16px;
|
width: min(260px, tip.preferred-width + 20px);
|
||||||
height: 28px;
|
height: max(28px, tip.preferred-height + 12px);
|
||||||
background: #24324a;
|
background: #24324a;
|
||||||
border-radius: UiRadius.control;
|
border-radius: UiRadius.control;
|
||||||
z: 20;
|
z: 20;
|
||||||
tip := Text {
|
tip := Text {
|
||||||
|
x: 10px; y: 6px;
|
||||||
|
width: parent.width - 20px;
|
||||||
|
height: parent.height - 12px;
|
||||||
text: root.tooltip;
|
text: root.tooltip;
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
color: white;
|
color: white;
|
||||||
|
wrap: word-wrap;
|
||||||
horizontal-alignment: center;
|
horizontal-alignment: center;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A raised surface keeps transient action feedback distinct from the workbench.
|
||||||
|
export component WorkbenchNotice inherits Rectangle {
|
||||||
|
in property <string> text;
|
||||||
|
in property <string> icon;
|
||||||
|
in property <bool> error: false;
|
||||||
|
in property <length> width-limit: 360px;
|
||||||
|
private property <color> foreground: root.error ? #b42318 : #146c49;
|
||||||
|
width: min(root.width-limit, max(280px, message.preferred-width + 64px));
|
||||||
|
height: 64px;
|
||||||
|
background: root.error ? #fff1f0 : #e9f6ef;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: root.error ? #f0c4bf : #b9dfc9;
|
||||||
|
border-radius: UiRadius.surface;
|
||||||
|
drop-shadow-color: #14294424;
|
||||||
|
drop-shadow-blur: 24px;
|
||||||
|
drop-shadow-offset-y: 6px;
|
||||||
|
accessible-role: AccessibleRole.text;
|
||||||
|
accessible-label: root.text;
|
||||||
|
Text {
|
||||||
|
x: message.x - 28px; width: 18px; height: parent.height;
|
||||||
|
text: root.icon;
|
||||||
|
font-family: "Font Awesome 6 Free";
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 16px;
|
||||||
|
color: root.foreground;
|
||||||
|
horizontal-alignment: center;
|
||||||
|
vertical-alignment: center;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
message := Text {
|
||||||
|
x: (parent.width - self.width + 28px) / 2;
|
||||||
|
width: min(self.preferred-width, parent.width - 64px);
|
||||||
|
height: parent.height;
|
||||||
|
text: root.text;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: root.foreground;
|
||||||
|
overflow: elide;
|
||||||
|
vertical-alignment: center;
|
||||||
|
accessible-role: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export component WorkbenchCard inherits Rectangle {
|
export component WorkbenchCard inherits Rectangle {
|
||||||
background: WorkbenchStyle.surface;
|
background: WorkbenchStyle.surface;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
|
|||||||
Reference in New Issue
Block a user