Files
crossdesk/apps/desktop/src/gui/ui/main_window.slint
T

2256 lines
103 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { LineEdit, ScrollView } from "std-widgets.slint";
import { AnnouncementItem, MessagePanel, MessageStrings } from "message_panel.slint";
import { FontAwesomeIcons, IconButton, CompactComboBox, ImGuiCheckBox, MiniToggleSwitch, CompactButton, PrimaryButton, SecondaryButton, ToggleSwitch, SectionTitle, DialogSurface, ImGuiLineStyle, ImGuiFontStyle } from "common.slint";
export struct RecentConnection {
remote-id: string,
display-name: string,
host-name: string,
online: bool,
thumbnail: image,
has-thumbnail: bool,
platform: string,
}
export struct ReleaseNoteBlock {
content: styled-text,
section-gap: bool,
}
export enum UpdateCheckStatus { idle, checking, up-to-date, available, failed }
export global UiStrings {
in-out property <string> local-desktop: "Local Desktop";
in-out property <string> remote-desktop: "Remote Desktop";
in-out property <string> recent-connections: "Recent Connections";
in-out property <string> local-id: "Local ID";
in-out property <string> device-name: "Device Name";
in-out property <string> remote-id: "Remote ID";
in-out property <string> online: "Online";
in-out property <string> offline: "Offline";
in-out property <string> password: "Password";
in-out property <string> refresh-password: "Refresh random password";
in-out property <string> copied: "Local ID copied to clipboard";
in-out property <string> connect: "Connect";
in-out property <string> settings: "Settings";
in-out property <string> about: "About";
in-out property <string> ok: "OK";
in-out property <string> cancel: "Cancel";
in-out property <string> save: "Save";
in-out property <string> new-password: "New password (6 characters)";
in-out property <string> invalid-password: "Password must contain exactly 6 characters";
in-out property <string> edit-alias: "Connection alias";
in-out property <string> delete-connection: "Delete connection";
in-out property <string> confirm-delete: "Delete this recent connection?";
in-out property <string> language: "Language";
in-out property <string> capture-method: "Capture method";
in-out property <string> capture-method-auto: "Auto";
in-out property <string> codec: "Video codec";
in-out property <string> hardware-codec: "Hardware video codec";
in-out property <string> turn-relay: "TURN relay";
in-out property <string> force-relay: "Force relay";
in-out property <string> self-hosted: "Self-hosted server";
in-out property <string> autostart: "Start with system";
in-out property <string> daemon: "Run in background";
in-out property <string> save-remote-previews: "Save remote screen previews:";
in-out property <string> unknown-platform: "Unknown platform";
in-out property <string> privacy-on-connect: "Privacy screen on connect";
in-out property <string> file-save-path: "File save path";
in-out property <string> default-desktop: "Desktop";
in-out property <string> server-host: "Server host";
in-out property <string> server-port: "Signal port";
in-out property <string> version: "Version";
in-out property <string> copyright: "© 2026 by JUNKUN DI. All right reserved.";
in-out property <string> license: "Licensed under GNU GPL v3.";
in-out property <string> signal-connected: "Signal server connected";
in-out property <string> signal-disconnected: "Signal server disconnected";
in-out property <string> tls-error: "Signal server TLS certificate error";
in-out property <string> update-available: "A new version is available";
in-out property <string> new-version: "New version";
in-out property <string> check-for-updates: "Check for updates";
in-out property <string> checking-for-updates: "Checking for updates…";
in-out property <string> up-to-date: "You’re up to date";
in-out property <string> update-check-failed: "Check failed. Please try again later.";
in-out property <string> release-notes: "Release notes";
in-out property <string> view-release-notes: "Release notes";
in-out property <string> later: "Later";
in-out property <string> download: "Download";
in-out property <string> input-password: "Input password";
in-out property <string> reinput-password: "Incorrect password, please try again";
in-out property <string> remember-password: "Remember password";
in-out property <string> validate-password: "Validating password";
in-out property <string> request-permissions: "Request permissions";
in-out property <string> permission-required: "The application requires the following permissions:";
in-out property <string> screen-recording-permission: "Screen recording permission";
in-out property <string> accessibility-permission: "Accessibility permission";
in-out property <string> wayland-notice-title: "Wayland Session Notice";
in-out property <string> wayland-notice-subtitle;
in-out property <string> wayland-permission-title;
in-out property <string> wayland-notice-message;
in-out property <string> wayland-recommendation-title;
in-out property <string> wayland-recommendation-message;
in-out property <string> wayland-notice-acknowledge: "Got it";
in-out property <string> service-setup-title: "Install CrossDesk Service";
in-out property <string> service-setup-message: "Administrator permission is required to control this device while it is locked.";
in-out property <string> service-settings-label: "Lock Screen Service:";
in-out property <string> install-service: "Install";
in-out property <string> service-installed: "Installed";
in-out property <string> do-not-remind: "Do not remind again";
in-out property <string> notification: "Notification";
in-out property <string> service-suppressed-message: "You will not be reminded again. You can install the service later in Settings.";
in-out property <string> codec-h264: "H.264";
in-out property <string> codec-av1: "AV1";
in-out property <string> self-hosted-settings: "Self-hosted server settings";
in-out property <string> access-website: "Website: ";
in-out property <string> release-date-label: "Release Date: ";
}
// Fluent LineEdit draws a blue 2px focus underline. The ImGui-style field
// already has its own border, so cover that underline and restore its bottom
// edge without changing the editor's focus, selection, or clear-button logic.
component LineEditFocusUnderlineMask inherits Rectangle {
height: 3px;
background: white;
z: 2;
Rectangle {
y: parent.height - 1px;
height: 1px;
background: ImGuiLineStyle.border;
}
}
// Prompt copy in modal dialogs sits slightly low in the legacy ImGui layout.
// Offset only the rendered text so input fields and action buttons keep their
// established geometry.
component DialogPromptText inherits Rectangle {
in property <string> prompt;
in property <color> text-color: #30343b;
in property <length> text-size: ImGuiFontStyle.base;
in property <int> text-weight: 400;
// Mirror Text's intrinsic layout constraints exactly. This makes the
// wrapper invisible to VerticalLayout: siblings retain the same geometry
// they had when the Text item was a direct child.
min-height: prompt-label.min-height;
preferred-height: prompt-label.preferred-height;
max-height: prompt-label.max-height;
vertical-stretch: prompt-label.vertical-stretch;
prompt-label := Text {
y: 10px;
width: parent.width;
text: root.prompt;
color: root.text-color;
font-size: root.text-size;
font-weight: root.text-weight;
wrap: word-wrap;
horizontal-alignment: center;
}
}
// Explicit device labels make state readable without interpreting a dot.
// Opaque surfaces keep the badge legible over both dark and light previews.
component ConnectionStatusBadge inherits Rectangle {
in property <string> label;
in property <bool> online: false;
preferred-width: badge-label.preferred-width + 16px;
width: self.preferred-width;
height: 20px;
border-radius: self.height / 2;
background: root.online ? #eaf8f0 : #f2f4f7;
border-width: 1px;
border-color: root.online ? #bce3cd : #d5dbe3;
badge-label := Text {
x: 8px;
width: max(0px, parent.width - 16px);
height: parent.height;
text: root.label;
color: root.online ? #16a34a : #596575;
font-size: ImGuiFontStyle.small;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
overflow: elide;
}
}
// Keep the green center and fade the surrounding glow into the status bar.
component OnlineStatusIndicator inherits Rectangle {
in property <length> dot-diameter: 8px;
width: root.dot-diameter * 2.2;
height: self.width;
border-radius: self.width / 2;
// Slint's default radial radius is half the bounding-box diagonal.
// Fade out at the circle's edge (1 / sqrt(2)), before rounded clipping.
background: @radial-gradient(
circle,
#00ff0040 0%,
#00ff0038 32%,
#00ff0018 50%,
#00ff0007 62%,
#00ff0000 70.7%
);
Rectangle {
x: (parent.width - root.dot-diameter) / 2;
y: (parent.height - root.dot-diameter) / 2;
width: root.dot-diameter;
height: root.dot-diameter;
border-radius: root.dot-diameter / 2;
background: #00ff00;
}
}
// ImGui::Button under StyleColorsLight(), used by both actions in the legacy
// connection-password dialog. The old dialog does not distinguish primary and
// secondary actions visually.
component ConnectionDialogButton inherits Rectangle {
in property <string> text;
callback clicked;
width: max(40px, button-label.preferred-width + 8px);
height: 24px;
background: button-touch.pressed ? #0f87fa
: button-touch.has-hover ? #4296fa
: #4296fa66;
border-radius: 3px;
button-label := Text {
text: root.text;
color: #000000de;
font-size: ImGuiFontStyle.base;
horizontal-alignment: center;
vertical-alignment: center;
}
button-touch := TouchArea { clicked => { root.clicked(); } }
}
// Use the same compact, plain-text field for connection and password changes.
component PasswordInput inherits Rectangle {
in-out property <string> text;
in property <int> max-length: 0;
private property <string> previous-text;
callback accepted;
width: 70px;
height: 23px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 3px;
forward-focus: editor;
init => { root.previous-text = root.text; }
editor := TextInput {
x: 5px; y: 1px; width: parent.width - 10px; height: parent.height - 2px;
text <=> root.text;
input-type: InputType.text;
single-line: true;
color: #202124;
font-size: ImGuiFontStyle.base;
vertical-alignment: center;
selection-foreground-color: white;
selection-background-color: #2463c7;
edited => {
// Validate the resulting text so typing, paste, and replacement
// all obey the limit while deletion and selection still work.
if (root.max-length > 0 && self.text.character-count > root.max-length) {
self.text = root.previous-text;
} else {
root.previous-text = self.text;
}
}
accepted => { root.accepted(); }
}
Timer {
interval: 1ms;
running: true;
triggered => {
self.running = false;
editor.focus();
// Place the caret after any restored password, including UTF-8.
editor.select-all();
editor.clear-selection();
}
}
}
export component MainWindow inherits Window {
title: "CrossDesk";
icon: @image-url("../../../resources/linux/crossdesk_256x256.png");
preferred-width: 640px;
preferred-height: 450px + root.titlebar-height;
min-width: 640px;
max-width: 640px;
min-height: 450px + root.titlebar-height;
max-height: 450px + root.titlebar-height;
no-frame: root.custom-titlebar;
background: root.wayland-titlebar ? transparent : white;
default-font-size: ImGuiFontStyle.base;
in property <bool> custom-titlebar: false;
in property <bool> wayland-titlebar: false;
in property <bool> window-active: true;
private property <length> titlebar-height:
root.custom-titlebar ? (root.wayland-titlebar ? 32px : 31px) : 0px;
private property <length> wayland-corner-radius: 12px;
in property <string> local-id: "";
in property <string> local-password: "";
in-out property <string> remote-id-input: "";
in property <bool> password-visible: true;
in property <bool> password-change-pending: false;
in property <bool> signal-connected: false;
in property <bool> signal-tls-error: false;
in property <[RecentConnection]> recent-connections;
in property <[AnnouncementItem]> announcements;
in property <int> announcement-unread;
private property <string> announcement-unread-label:
root.announcement-unread > 99 ? "99+" : "" + root.announcement-unread;
in property <int> announcement-total;
in property <bool> announcement-loading;
in property <bool> announcement-failed;
in property <bool> announcement-read-save-failed;
in property <bool> announcement-delete-failed;
in property <bool> announcement-loaded;
in-out property <bool> messages-open: false;
in property <string> announcement-selected-id;
in property <string> announcement-selected-title;
in property <styled-text> announcement-selected-body;
in property <string> announcement-selected-date;
in property <bool> announcement-selected-read;
callback open-announcement(string, string);
callback open-announcement-link(string);
callback delete-announcement(string, string);
callback refresh-announcements;
callback load-more-announcements;
in property <bool> update-available: false;
in property <UpdateCheckStatus> update-check-status: idle;
private property <string> update-status-text:
root.update-check-status == UpdateCheckStatus.checking ? UiStrings.checking-for-updates :
root.update-check-status == UpdateCheckStatus.up-to-date ? UiStrings.up-to-date :
root.update-check-status == UpdateCheckStatus.failed ? UiStrings.update-check-failed : "";
in property <string> current-version: "";
in property <string> latest-version: "";
in property <string> release-name: "";
in property <[ReleaseNoteBlock]> release-note-blocks;
in property <string> release-date: "";
in-out property <string> offline-warning: "";
in property <bool> connection-dialog-open: false;
in property <string> connection-status-text: "";
in property <bool> connection-pending: false;
in property <bool> connection-password-required: false;
in property <bool> connection-validating: false;
in-out property <string> connection-password: "";
in-out property <bool> connection-remember-password: false;
in property <bool> permission-dialog-open: false;
in property <bool> screen-recording-granted: true;
in property <bool> accessibility-granted: true;
in-out property <bool> wayland-notice-open: false;
in property <bool> portable-service-settings-visible: false;
in property <bool> portable-service-dialog-open: false;
in property <bool> portable-service-suppressed-notice-open: false;
in property <bool> portable-service-installed: false;
in property <bool> portable-service-installing: false;
in property <bool> portable-service-succeeded: false;
in property <string> portable-service-status: "";
in-out property <bool> portable-service-do-not-remind: false;
in-out property <int> language-index: 0;
in property <bool> capture-method-visible: false;
in-out property <int> capture-method-index: 0;
in-out property <int> codec-index: 0;
in-out property <bool> hardware-codec-enabled: true;
in-out property <bool> turn-enabled: true;
in-out property <bool> force-relay-enabled: false;
changed turn-enabled => {
if !root.turn-enabled { root.force-relay-enabled = false; }
}
in-out property <bool> self-hosted-enabled: false;
in-out property <bool> autostart-enabled: false;
in-out property <bool> daemon-enabled: false;
in-out property <bool> save-remote-previews: true;
// Show the preference on every platform; only adapted hosts can change it.
in property <bool> privacy-setting-available: false;
in-out property <bool> privacy-on-connect-enabled: true;
in-out property <string> file-save-path: "";
in-out property <string> server-host: "";
in-out property <string> server-port: "";
in-out property <string> self-host-settings-error: "";
in property <bool> settings-session-active: false;
// Keep the setting visible on every platform. Unsupported builds expose it
// as disabled so the fixed settings layout never turns into a blank row.
in property <bool> hardware-codec-available: true;
callback copy-local-id;
callback toggle-password-visibility;
callback refresh-password;
callback reset-password(string) -> bool;
callback format-remote-id(string) -> string;
callback connect-requested(string);
callback recent-connect(string);
callback recent-edit-alias(string, string);
callback recent-delete(string);
callback save-settings() -> bool;
callback cancel-settings;
callback save-self-hosted-settings() -> bool;
callback cancel-self-hosted-settings;
callback browse-save-path;
callback open-download;
callback check-for-updates;
callback connection-cancel;
callback connection-acknowledge;
callback connection-submit-password(string, bool);
callback reset-remote-id;
callback request-screen-recording-permission;
callback request-accessibility-permission;
callback install-portable-service;
callback cancel-portable-service;
callback acknowledge-portable-service;
callback acknowledge-portable-service-suppressed;
callback main-title-drag(int, length, length);
callback minimize-main-window;
callback close-main-window;
private property <bool> menu-open: false;
in-out property <bool> settings-open: false;
private property <length> settings-scroll-y: 0px;
in-out property <bool> self-host-settings-open: false;
changed self-host-settings-open => {
if !root.self-host-settings-open { root.self-host-settings-error = ""; }
}
in-out property <bool> about-open: false;
// Follow the first available update. Dismissing the dialog replaces this
// binding, so background checks do not reopen it during this launch.
in-out property <bool> update-open: root.update-available;
in-out property <bool> reset-password-open: false;
in-out property <bool> reset-password-invalid: false;
in-out property <string> new-password-input: "";
in-out property <bool> alias-open: false;
private property <string> alias-remote-id: "";
in-out property <string> alias-input: "";
in-out property <bool> delete-open: false;
private property <string> delete-remote-id: "";
private property <bool> copy-toast: false;
private property <bool> compact-language: root.language-index == 0;
private property <bool> recent-tooltip-visible: false;
private property <string> recent-tooltip-name: "";
private property <string> recent-tooltip-host: "";
private property <string> recent-tooltip-id: "";
private property <bool> recent-tooltip-online: false;
private property <length> recent-tooltip-pointer-x: 0px;
private property <length> recent-tooltip-pointer-y: 0px;
private property <length> recent-tooltip-width: 260px;
private property <length> recent-tooltip-height:
root.recent-tooltip-host != "" && root.recent-tooltip-host != root.recent-tooltip-name
? 80px : 64px;
private property <length> recent-tooltip-x: min(
root.width - root.recent-tooltip-width - 8px,
max(8px,
root.recent-tooltip-pointer-x > root.width * 0.7
? root.recent-tooltip-pointer-x - root.recent-tooltip-width - 12px
: root.recent-tooltip-pointer-x + 12px));
private property <length> recent-tooltip-y: min(
root.height - root.titlebar-height - root.recent-tooltip-height - 8px,
max(8px, root.recent-tooltip-pointer-y - root.recent-tooltip-height - 8px));
changed settings-open => {
if root.settings-open {
root.settings-scroll-y = 0px;
focus-sink.focus();
}
}
changed wayland-notice-open => {
if root.wayland-notice-open { focus-sink.focus(); }
}
changed messages-open => {
if root.messages-open { focus-sink.focus(); }
}
reset-remote-id => {
root.remote-id-input = "";
remote-id-editor.focus();
}
window-surface := Rectangle {
width: root.width;
height: root.height;
background: root.wayland-titlebar ? #f6f5f4 : transparent;
border-radius:
root.wayland-titlebar ? root.wayland-corner-radius : 0px;
clip: root.wayland-titlebar;
if root.custom-titlebar && root.wayland-titlebar: Rectangle {
x: 0px;
y: 0px;
width: parent.width;
height: root.titlebar-height;
background: #f6f5f4;
z: 100;
Text {
x: 80px;
width: parent.width - 160px;
height: parent.height;
text: "CrossDesk";
color: root.window-active ? #3d3d3d : #747474;
font-size: 12px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
overflow: elide;
}
wayland-title-drag := TouchArea {
width: parent.width - 80px;
property <int> phase: self.pressed ? 1 : 0;
changed phase => {
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);
}
}
}
wayland-minimize-button := Rectangle {
x: parent.width - 74px;
y: 4px;
width: 24px;
height: 24px;
border-radius: 12px;
background: wayland-minimize-touch.pressed ? #c0bfbc
: wayland-minimize-touch.has-hover ? #deddda
: root.window-active ? #e6e6e6
: transparent;
accessible-role: button;
accessible-label: "Minimize";
accessible-action-default => { wayland-minimize-touch.clicked(); }
Rectangle {
x: 8px;
y: 13px;
width: 8px;
height: 0.75px;
background: root.window-active
|| wayland-minimize-touch.has-hover
|| wayland-minimize-touch.pressed ? #454545 : #898989;
}
wayland-minimize-touch := TouchArea {
clicked => { root.minimize-main-window(); }
}
}
wayland-close-button := Rectangle {
x: parent.width - 34px;
y: 4px;
width: 24px;
height: 24px;
border-radius: 12px;
background: wayland-close-touch.pressed ? #b2161d
: wayland-close-touch.has-hover ? #e01b24
: root.window-active ? #e6e6e6
: transparent;
accessible-role: button;
accessible-label: "Close";
accessible-action-default => { wayland-close-touch.clicked(); }
Path {
width: parent.width;
height: parent.height;
viewbox-width: 24;
viewbox-height: 24;
commands: "M 14.75 14.75 L 8.25 8.25 M 14.75 8.25 L 8.25 14.75";
stroke: wayland-close-touch.has-hover
|| wayland-close-touch.pressed ? white
: root.window-active ? #454545 : #898989;
stroke-width: 1px;
}
wayland-close-touch := TouchArea {
clicked => { root.close-main-window(); }
}
}
Rectangle {
y: parent.height - 1px;
height: 1px;
background: #d5d3d0;
}
}
if root.custom-titlebar && !root.wayland-titlebar: Rectangle {
x: 0px;
y: 0px;
width: root.width;
height: 31px;
background: #f9f9f9;
z: 100;
Image {
x: 8px;
y: 7px;
width: 16px;
height: 16px;
source: @image-url("../../../resources/linux/crossdesk_16x16.png");
image-fit: contain;
}
Text {
x: 30px;
width: parent.width - 122px;
height: parent.height;
text: "CrossDesk";
color: #202124;
font-size: 12px;
vertical-alignment: center;
overflow: elide;
}
title-drag := TouchArea {
x: 0px;
width: parent.width - 92px;
property <int> phase: self.pressed ? 1 : 0;
changed phase => { 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); } }
}
minimize-button := Rectangle {
x: parent.width - 92px;
width: 46px;
height: parent.height;
background: minimize-touch.pressed ? #c7c7c7
: minimize-touch.has-hover ? #e5e5e5
: transparent;
accessible-role: button;
accessible-label: "Minimize";
accessible-action-default => { minimize-touch.clicked(); }
Text {
text: FontAwesomeIcons.minus;
color: #202124;
font-family: "Font Awesome 6 Free";
font-weight: 900;
font-size: 10px;
horizontal-alignment: center;
vertical-alignment: center;
accessible-role: none;
}
minimize-touch := TouchArea { clicked => { root.minimize-main-window(); } }
}
close-button := Rectangle {
x: parent.width - 46px;
width: 46px;
height: parent.height;
background: close-touch.pressed ? #b3251b
: close-touch.has-hover ? #c42b1c
: transparent;
accessible-role: button;
accessible-label: "Close";
accessible-action-default => { close-touch.clicked(); }
Text {
text: FontAwesomeIcons.xmark;
color: close-touch.has-hover || close-touch.pressed ? white : #202124;
font-family: "Font Awesome 6 Free";
font-weight: 900;
font-size: 12px;
horizontal-alignment: center;
vertical-alignment: center;
accessible-role: none;
}
close-touch := TouchArea { clicked => { root.close-main-window(); } }
}
Rectangle {
y: parent.height - 1px;
height: 1px;
background: #d4d4d4;
}
}
content-layer := Rectangle {
x: 0px;
y: root.titlebar-height;
width: root.width;
height: root.height - self.y;
clip: true;
Rectangle {
width: parent.width;
height: parent.height;
background: white;
border-width: 0px;
border-radius: 0px;
focus-sink := FocusScope {
x: 0px; y: 0px; width: 0px; height: 0px;
key-pressed(event) => {
if root.wayland-notice-open {
if event.text == Key.Return || event.text == Key.Escape {
root.wayland-notice-open = false;
}
return accept;
}
if root.messages-open {
if event.text == Key.Escape { root.messages-open = false; }
return accept;
}
return reject;
}
}
TouchArea { clicked => { focus-sink.focus(); } }
// Slint owns the client area below the native macOS title bar. Draw
// the boundary here because AppKit's titlebar separator is not shown
// reliably when winit creates a window without an NSToolbar.
Rectangle {
x: 0px;
y: 0px;
width: parent.width;
height: 1px;
background: ImGuiLineStyle.control-separator;
z: 7;
}
// App actions live in the content header; window controls and dragging
// remain in the native or Windows-specific title bar above it.
menu-button := IconButton {
x: parent.width - 40px;
y: 4px;
width: 32px;
height: 32px;
icon: FontAwesomeIcons.bars;
icon-size: 14px;
hover-background: #e1e5eb;
pressed-background: #cbd1d9;
tooltip: root.announcement-unread > 0
? MessageStrings.unread + " " + root.announcement-unread + (root.update-available ? " · " + UiStrings.update-available : "")
: root.update-available ? UiStrings.update-available : "";
z: 8;
clicked => { root.menu-open = !root.menu-open; }
}
// Unread messages take precedence so the menu has only one indicator.
if root.update-available && root.announcement-unread <= 0: Rectangle {
x: menu-button.x + menu-button.width - self.width + 4px;
y: menu-button.y - 1px;
width: 16px;
height: 16px;
border-radius: 8px;
border-width: 1px;
border-color: white;
background: #2563eb;
opacity: root.menu-open || mod(animation-tick(), 2s) < 1s ? 1 : 0;
z: 9;
accessible-role: button;
accessible-label: UiStrings.update-available;
accessible-action-default => { root.menu-open = !root.menu-open; }
// Center the visible mark independently of font baseline metrics.
Rectangle {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 2px;
height: 10px;
accessible-role: none;
Rectangle {
y: 0px;
width: parent.width;
height: 6px;
border-radius: 1px;
background: white;
}
Rectangle {
y: 8px;
width: parent.width;
height: 2px;
border-radius: 1px;
background: white;
}
}
TouchArea {
mouse-cursor: pointer;
clicked => { root.menu-open = !root.menu-open; }
}
}
if root.announcement-unread > 0: Rectangle {
x: menu-button.x + menu-button.width - self.width + 4px;
y: menu-button.y - 1px;
width: max(16px, unread-badge-label.preferred-width + 8px);
height: 16px;
border-radius: 8px;
border-width: 1px;
border-color: white;
background: #d92d20;
z: 9;
accessible-role: button;
accessible-label: MessageStrings.messages + " · " + MessageStrings.unread + " " + root.announcement-unread;
accessible-action-default => { root.menu-open = !root.menu-open; }
unread-badge-label := Text {
text: root.announcement-unread-label;
color: white;
font-size: 10px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
accessible-role: none;
}
TouchArea {
mouse-cursor: pointer;
clicked => { root.menu-open = !root.menu-open; }
}
}
// Local and remote desktop panels.
Rectangle {
y: 0px;
height: 234px;
background: white;
SectionTitle { x: 29px; y: 9px; width: 256px; height: 31px; text: UiStrings.local-desktop; }
Rectangle {
x: 29px;
y: 42px;
width: 256px;
height: 183px;
background: #eff0f2;
border-radius: 9px;
border-width: 1px;
border-color: ImGuiLineStyle.border;
Text { x: 8px; y: 10px; width: 160px; height: 26px; text: UiStrings.local-id; color: #111111; font-size: ImGuiFontStyle.panel-label; }
Rectangle {
x: 8px;
y: 42px;
width: 160px;
height: 38px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 4px;
// Measure the ID using the preferred field size.
// The visible text can then scale only on systems
// whose font metrics would otherwise clip it.
local-id-width-probe := Text {
visible: false;
text: root.local-id;
font-size: ImGuiFontStyle.field-value;
letter-spacing: 1px;
}
Text {
x: 8px;
// Compensate for the font's ascender-heavy
// metrics so digits sit at the optical center.
y: 4px;
width: parent.width - 16px;
height: parent.height - 4px;
text: root.local-id;
color: #20242a;
font-size: max(18px,
ImGuiFontStyle.field-value *
(self.width / max(self.width, local-id-width-probe.preferred-width)));
letter-spacing: 1px;
horizontal-alignment: left;
vertical-alignment: center;
}
}
IconButton {
x: 174px;
y: 46px;
width: 29px;
height: 30px;
icon: FontAwesomeIcons.copy;
icon-size: 16px;
tooltip: UiStrings.copied;
clicked => {
root.copy-local-id();
root.copy-toast = true;
copy-timer.restart();
}
}
Rectangle { x: 8px; y: 88px; width: 240px; height: 1px; background: ImGuiLineStyle.separator; }
Text { x: 8px; y: 101px; width: 160px; height: 26px; text: UiStrings.password; color: #111111; font-size: ImGuiFontStyle.panel-label; }
Rectangle {
x: 8px;
y: 129px;
width: 160px;
height: 39px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 4px;
Text {
x: 8px;
y: 4px;
width: parent.width - 16px;
height: parent.height - 4px;
text: root.password-visible ? root.local-password : "••••••";
color: #20242a;
font-size: ImGuiFontStyle.field-value;
letter-spacing: 2px;
horizontal-alignment: left;
vertical-alignment: center;
}
}
IconButton {
x: 174px;
y: 133px;
width: 24px;
height: 30px;
icon: root.password-visible ? FontAwesomeIcons.eye : FontAwesomeIcons.eye-slash;
icon-size: 17px;
clicked => { root.toggle-password-visibility(); }
}
IconButton {
x: 198px;
y: 133px;
width: 24px;
height: 30px;
icon: FontAwesomeIcons.rotate;
icon-size: 15px;
tooltip: UiStrings.refresh-password;
enabled: !root.password-change-pending;
clicked => { root.refresh-password(); }
}
IconButton {
x: 222px;
y: 133px;
width: 24px;
height: 30px;
icon: FontAwesomeIcons.pen;
icon-size: 15px;
enabled: !root.password-change-pending;
clicked => { root.reset-password-open = true; }
}
}
Rectangle { x: parent.width / 2; y: 18px; width: 1px; height: 206px; background: ImGuiLineStyle.divider; }
SectionTitle { x: 356px; y: 9px; width: 256px; height: 31px; text: UiStrings.remote-desktop; }
Rectangle {
x: 356px;
y: 42px;
width: 256px;
height: 183px;
background: #eff0f2;
border-radius: 9px;
border-width: 1px;
border-color: ImGuiLineStyle.border;
Text { x: 8px; y: 10px; width: 160px; height: 26px; text: UiStrings.remote-id; color: #111111; font-size: ImGuiFontStyle.panel-label; }
Rectangle {
x: 8px;
y: 42px;
width: 160px;
height: 38px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 4px;
remote-id-editor := TextInput {
x: 10px;
y: 1.5px;
width: parent.width - 20px;
height: parent.height - 3px;
text <=> root.remote-id-input;
font-size: ImGuiFontStyle.field-value;
input-type: InputType.number;
color: #202124;
vertical-alignment: center;
selection-foreground-color: white;
selection-background-color: #2463c7;
// Run formatting after the input event has
// finished so the caret state isn't reset by
// a synchronous two-way binding rewrite.
edited => { remote-id-format-timer.restart(); }
accepted => { root.connect-requested(root.remote-id-input); }
}
}
IconButton {
x: 175px;
y: 42px;
width: 64px;
height: 38px;
normal-background: #a7cefc;
hover-background: #b8d8fc;
pressed-background: #76aeea;
border-radius: 4px;
icon: FontAwesomeIcons.arrow-right-long;
icon-size: 25px;
clicked => { root.connect-requested(root.remote-id-input); }
}
}
}
// Recent connections: horizontal cards with thumbnail, state and actions.
recent-panel := Rectangle {
y: 234px;
height: 192px;
background: white;
border-width: 0px;
border-color: ImGuiLineStyle.border;
Rectangle { y: 0px; height: 1px; background: ImGuiLineStyle.border; }
SectionTitle { x: 29px; y: 7px; height: 28px; text: UiStrings.recent-connections; }
recent-list := Rectangle {
x: 29px;
y: 42px;
width: parent.width - 58px;
height: 139px;
background: #eff0f2;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 10px;
clip: true;
recent-scroll := ScrollView {
width: parent.width;
height: parent.height;
horizontal-stretch: 1;
HorizontalLayout {
padding-left: 8px;
padding-top: 8px;
padding-bottom: 14px;
spacing: 24px;
alignment: start;
for connection[index] in root.recent-connections: connection-card := Rectangle {
width: 178px;
height: 117px;
background: transparent;
private property <length> content-width: self.width - 30px;
private property <length> toolbar-width: 79px;
private property <length> toolbar-right-padding: 3px;
Rectangle {
x: 7px;
y: 7px;
width: connection-card.content-width;
height: 109px;
border-radius: 4px;
clip: true;
Rectangle {
x: 0px;
y: 0px;
width: parent.width;
height: 84px;
background: connection.platform == "windows" ? @linear-gradient(135deg, #1f61b3, #337ac4) :
connection.platform == "macos" ? @linear-gradient(135deg, #6e4aad, #8c63c4) :
connection.platform == "linux" ? @linear-gradient(135deg, #a14f14, #ba631f) :
connection.platform == "ios" ? @linear-gradient(135deg, #146e66, #1f8278) :
@linear-gradient(135deg, #596675, #737d8c);
if connection.has-thumbnail: Image {
width: parent.width;
height: parent.height;
source: connection.thumbnail;
image-fit: cover;
}
if !connection.has-thumbnail: Text {
x: 8px;
width: parent.width - 16px;
height: parent.height;
text: connection.platform == "windows" ? "Windows" :
connection.platform == "macos" ? "macOS" :
connection.platform == "linux" ? "Linux" :
connection.platform == "ios" ? "iOS" : UiStrings.unknown-platform;
color: white;
font-size: 20px;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
overflow: elide;
}
}
Rectangle {
x: 0px;
y: 84px;
width: parent.width;
height: 25px;
background: #00000018;
Text {
x: 4px;
y: 1px;
height: parent.height - self.y;
width: max(0px, parent.width - self.x - 4px
- (card-touch.has-hover
? connection-card.toolbar-width + connection-card.toolbar-right-padding
: 0px));
text: connection.display-name;
color: #30343b;
// The ImGui card footer uses its own 0.52 font
// scale and renders slightly larger than the
// compact body text used in dialogs.
font-size: ImGuiFontStyle.base;
overflow: elide;
vertical-alignment: center;
}
}
ConnectionStatusBadge {
x: 6px;
y: 6px;
width: min(self.preferred-width, parent.width - 12px);
label: connection.online ? UiStrings.online : UiStrings.offline;
online: connection.online;
}
// The software renderer only clips rectangular bounds.
// This outer border masks the four corners there too.
Rectangle {
x: -parent.border-radius;
y: -parent.border-radius;
width: parent.width + 2 * parent.border-radius;
height: parent.height + 2 * parent.border-radius;
border-width: parent.border-radius;
border-radius: 2 * parent.border-radius;
border-color: recent-list.background;
}
}
card-touch := TouchArea {
x: 7px;
y: 7px;
width: connection-card.content-width;
height: 109px;
clicked => { root.recent-connect(connection.remote-id); }
image-touch := TouchArea {
width: parent.width;
height: 84px;
clicked => { root.recent-connect(connection.remote-id); }
changed has-hover => {
if self.has-hover {
root.recent-tooltip-name = connection.display-name;
root.recent-tooltip-host = connection.host-name;
root.recent-tooltip-id = connection.remote-id;
root.recent-tooltip-online = connection.online;
root.recent-tooltip-pointer-x = recent-panel.x + recent-list.x
+ recent-scroll.x + recent-scroll.viewport-x
+ connection-card.x + card-touch.x + self.mouse-x;
root.recent-tooltip-pointer-y = recent-panel.y + recent-list.y
+ recent-scroll.y + recent-scroll.viewport-y
+ connection-card.y + card-touch.y + self.mouse-y;
root.recent-tooltip-visible = true;
} else if root.recent-tooltip-id == connection.remote-id {
root.recent-tooltip-visible = false;
}
}
moved => {
if self.has-hover {
root.recent-tooltip-pointer-x = recent-panel.x + recent-list.x
+ recent-scroll.x + recent-scroll.viewport-x
+ connection-card.x + card-touch.x + self.mouse-x;
root.recent-tooltip-pointer-y = recent-panel.y + recent-list.y
+ recent-scroll.y + recent-scroll.viewport-y
+ connection-card.y + card-touch.y + self.mouse-y;
}
}
}
if card-touch.has-hover: Rectangle {
x: parent.width - self.width - connection-card.toolbar-right-padding;
y: 85px;
width: connection-card.toolbar-width;
height: 23px;
background: #4a4e55dc;
border-radius: 5px;
HorizontalLayout {
spacing: 0px;
IconButton {
width: 26px; height: 23px; icon: FontAwesomeIcons.pen; icon-size: 12px; icon-color: white;
hover-background: #ffffff2e;
pressed-background: #598cf273;
clicked => {
root.alias-remote-id = connection.remote-id;
root.alias-input = connection.display-name;
root.alias-open = true;
}
}
IconButton {
width: 26px; height: 23px; icon: FontAwesomeIcons.trash-can; icon-size: 12px; icon-color: white;
hover-background: #ffffff2e;
pressed-background: #598cf273;
clicked => {
root.delete-remote-id = connection.remote-id;
root.delete-open = true;
}
}
IconButton {
width: 27px; height: 23px; icon: FontAwesomeIcons.arrow-right-long; icon-size: 13px; icon-color: white;
hover-background: #ffffff2e;
pressed-background: #598cf273;
clicked => { root.recent-connect(connection.remote-id); }
}
}
}
}
if index < root.recent-connections.length - 1: Rectangle {
x: 183px;
y: 7px;
width: 1px;
height: 109px;
background: #0000007a;
}
}
}
}
}
}
// Status bar.
Rectangle {
y: 426px;
height: 24px;
background: #fafbfc;
Rectangle {
y: 0px;
height: 1px;
background: ImGuiLineStyle.border;
}
if root.signal-connected && !root.signal-tls-error: OnlineStatusIndicator {
x: 15px - self.width / 2;
y: 12px - self.height / 2;
dot-diameter: 8px;
}
if !root.signal-connected || root.signal-tls-error: Rectangle {
x: 11px;
y: 8px;
width: 8px;
height: 8px;
border-radius: 4px;
background: root.signal-tls-error ? #f59e0b : #ef4444;
}
Text {
x: 28px;
y: 3px;
height: 18px;
text: root.signal-tls-error ? UiStrings.tls-error
: root.signal-connected ? UiStrings.signal-connected
: UiStrings.signal-disconnected;
color: #343941;
font-size: ImGuiFontStyle.base;
vertical-alignment: center;
}
}
}
// Match the ImGui recent-connection tooltip while keeping it outside the
// clipped ScrollView. It is deliberately limited to the thumbnail area.
if root.recent-tooltip-visible: Rectangle {
x: root.recent-tooltip-x;
y: root.recent-tooltip-y;
width: root.recent-tooltip-width;
height: root.recent-tooltip-height;
background: #22252bf2;
border-width: 1px;
border-color: #ffffff38;
border-radius: 5px;
z: 50;
VerticalLayout {
padding: 8px;
spacing: 2px;
Text {
text: UiStrings.device-name + ": " + root.recent-tooltip-name;
color: white;
font-size: ImGuiFontStyle.small;
overflow: elide;
}
if root.recent-tooltip-host != "" && root.recent-tooltip-host != root.recent-tooltip-name: Text {
text: root.recent-tooltip-host;
color: #e1e4e8;
font-size: ImGuiFontStyle.small;
overflow: elide;
}
Text {
text: UiStrings.remote-id + ": " + root.recent-tooltip-id;
color: #e1e4e8;
font-size: ImGuiFontStyle.small;
overflow: elide;
}
Text {
text: root.recent-tooltip-online ? UiStrings.online : UiStrings.offline;
color: root.recent-tooltip-online ? #62d98b : #d1d5db;
font-size: ImGuiFontStyle.small;
}
}
}
// Dismiss the title menu when clicking anywhere outside of it.
if root.menu-open: TouchArea {
x: 0px;
y: 0px;
width: parent.width;
height: parent.height;
z: 9;
clicked => {
root.menu-open = false;
focus-sink.focus();
}
}
// Title menu.
if root.menu-open: Rectangle {
x: menu-button.x + menu-button.width - self.width;
y: menu-button.y + menu-button.height + 2px;
width: max(80px, max(menu-settings-label.preferred-width,
max(menu-messages-label.preferred-width + (root.announcement-unread > 0 ? menu-message-badge.width + 8px : 0px),
menu-about-label.preferred-width + (root.update-available ? menu-update-badge.width + 8px : 0px))) + 24px);
height: 93px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 5px;
z: 10;
TouchArea { }
VerticalLayout {
padding: 4px;
spacing: 2px;
menu-settings := Rectangle {
height: 27px;
background: settings-touch.pressed ? #d8dadd
: settings-touch.has-hover ? #f0f1f2
: white;
border-width: 0px;
border-radius: 3px;
accessible-role: button;
accessible-label: UiStrings.settings;
accessible-action-default => { settings-touch.clicked(); }
menu-settings-label := Text {
x: 7px;
height: parent.height;
text: UiStrings.settings;
color: #2d3138;
vertical-alignment: center;
accessible-role: none;
}
settings-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.menu-open = false; root.settings-open = true; }
}
}
menu-messages := Rectangle {
height: 27px;
background: messages-touch.pressed ? #d8dadd
: messages-touch.has-hover ? #f0f1f2 : white;
border-radius: 3px;
accessible-role: button;
accessible-label: MessageStrings.messages + (root.announcement-unread > 0 ? " · " + MessageStrings.unread + " " + root.announcement-unread : "");
accessible-action-default => { messages-touch.clicked(); }
menu-messages-label := Text {
x: 7px;
height: parent.height;
text: MessageStrings.messages;
color: #2d3138;
vertical-alignment: center;
accessible-role: none;
}
menu-message-badge := Rectangle {
visible: root.announcement-unread > 0;
x: parent.width - self.width - 7px;
y: (parent.height - self.height) / 2;
width: max(18px, menu-message-count.preferred-width + 10px);
height: 18px;
border-radius: 9px;
background: #fee4e2;
accessible-role: none;
menu-message-count := Text {
text: root.announcement-unread-label;
font-size: 10px; font-weight: 600; color: #b42318;
horizontal-alignment: center; vertical-alignment: center;
accessible-role: none;
}
}
messages-touch := TouchArea {
mouse-cursor: pointer;
clicked => {
root.menu-open = false;
root.messages-open = true;
root.refresh-announcements();
}
}
}
menu-about := Rectangle {
height: 27px;
background: about-touch.pressed ? #d8dadd
: about-touch.has-hover ? #f0f1f2
: white;
border-width: 0px;
border-radius: 3px;
accessible-role: button;
accessible-label: UiStrings.about + (root.update-available ? " · " + UiStrings.update-available : "");
accessible-action-default => { about-touch.clicked(); }
menu-about-label := Text {
x: 7px;
height: parent.height;
text: UiStrings.about;
color: #2d3138;
vertical-alignment: center;
accessible-role: none;
}
menu-update-badge := Rectangle {
visible: root.update-available;
x: parent.width - self.width - 7px;
y: (parent.height - self.height) / 2;
width: menu-update-label.preferred-width + 12px;
height: 18px;
border-radius: 9px;
background: #dbeafe;
accessible-role: none;
menu-update-label := Text {
text: UiStrings.new-version;
font-size: 11px; font-weight: 400; color: #1d4ed8;
horizontal-alignment: center; vertical-alignment: center;
accessible-role: none;
}
}
about-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.menu-open = false; root.about-open = true; }
}
}
}
Rectangle {
x: 4px;
y: 31.5px;
width: parent.width - 8px;
height: 1px;
background: ImGuiLineStyle.separator;
z: 1;
}
Rectangle {
x: 4px;
y: 60.5px;
width: parent.width - 8px;
height: 1px;
background: ImGuiLineStyle.separator;
z: 1;
}
}
if root.messages-open: Rectangle {
background: #00000042;
z: 40;
TouchArea { clicked => { focus-sink.focus(); } }
MessagePanel {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 520px;
height: 330px;
items: root.announcements;
unread-count: root.announcement-unread; total: root.announcement-total;
loading: root.announcement-loading; failed: root.announcement-failed; loaded: root.announcement-loaded;
read-save-failed: root.announcement-read-save-failed;
delete-failed: root.announcement-delete-failed;
connected: root.signal-connected;
selected-id: root.announcement-selected-id; selected-title: root.announcement-selected-title;
selected-body: root.announcement-selected-body; selected-date: root.announcement-selected-date;
selected-read: root.announcement-selected-read;
open-message(id, revision) => { root.open-announcement(id, revision); }
open-link(url) => { root.open-announcement-link(url); }
delete-message(id, revision) => { root.delete-announcement(id, revision); }
load-more => { root.load-more-announcements(); }
close => { root.messages-open = false; }
}
}
copy-timer := Timer {
interval: 1000ms;
triggered => { root.copy-toast = false; }
}
remote-id-format-timer := Timer {
interval: 1ms;
triggered => {
let formatted = root.format-remote-id(root.remote-id-input);
root.remote-id-input = formatted;
// IDs are ASCII-only, so character-count is also the UTF-8 offset
// expected by set-selection-offsets().
remote-id-editor.set-selection-offsets(
formatted.character-count, formatted.character-count);
}
}
if root.copy-toast: Rectangle {
x: 180px; y: 170px; width: 280px; height: 68px;
background: #fffffff2;
border-radius: 8px;
border-width: 1px;
border-color: ImGuiLineStyle.border;
z: 30;
Text { text: UiStrings.copied; color: #333840; horizontal-alignment: center; vertical-alignment: center; }
}
// Reset password modal.
if root.reset-password-open: Rectangle {
background: #00000042;
z: 40;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: root.compact-language ? 205px : 160px;
y: 140px;
width: root.compact-language ? 230px : 320px;
height: 170px;
VerticalLayout {
padding: 20px;
spacing: 12px;
DialogPromptText { prompt: UiStrings.new-password; }
HorizontalLayout {
alignment: center;
PasswordInput {
text <=> root.new-password-input;
max-length: 6;
}
}
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 {
alignment: center; spacing: 8px;
PrimaryButton {
text: UiStrings.ok;
clicked => {
if (root.reset-password(root.new-password-input)) {
root.new-password-input = "";
root.reset-password-invalid = false;
root.reset-password-open = false;
} 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; } }
}
}
}
}
// Recent connection alias modal.
if root.alias-open: Rectangle {
background: #00000042;
z: 40;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: root.compact-language ? 205px : 160px;
y: 145px;
width: root.compact-language ? 230px : 320px;
height: 160px;
VerticalLayout {
padding: 20px; spacing: 14px;
DialogPromptText { prompt: UiStrings.edit-alias; }
Rectangle {
height: 28px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 4px;
LineEdit {
x: 1px;
y: 1.5px;
width: parent.width - 2px;
height: parent.height - 2px;
text <=> root.alias-input;
accepted => {
root.recent-edit-alias(root.alias-remote-id, root.alias-input);
root.alias-open = false;
}
}
LineEditFocusUnderlineMask {
x: 5px;
y: parent.height - 3px;
width: parent.width - 10px;
}
}
HorizontalLayout {
alignment: center; spacing: 8px;
PrimaryButton { text: UiStrings.ok; clicked => { root.recent-edit-alias(root.alias-remote-id, root.alias-input); root.alias-open = false; } }
SecondaryButton { text: UiStrings.cancel; clicked => { root.alias-open = false; } }
}
}
}
}
// Delete confirmation modal.
if root.delete-open: Rectangle {
background: #00000042;
z: 40;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: root.compact-language ? 205px : 160px;
y: 155px;
width: root.compact-language ? 230px : 320px;
height: 140px;
VerticalLayout {
padding: 20px; spacing: 18px;
DialogPromptText { prompt: UiStrings.confirm-delete; }
HorizontalLayout {
alignment: center; spacing: 8px;
PrimaryButton { text: UiStrings.ok; clicked => { root.recent-delete(root.delete-remote-id); root.delete-open = false; } }
SecondaryButton { text: UiStrings.cancel; clicked => { root.delete-open = false; } }
}
}
}
}
// Offline/error warning modal.
if root.offline-warning != "": Rectangle {
background: #00000042;
z: 39;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: 185px; y: 150px; width: 270px; height: 150px;
VerticalLayout {
padding: 20px; spacing: 18px;
DialogPromptText { prompt: root.offline-warning; }
PrimaryButton { text: UiStrings.ok; clicked => { root.offline-warning = ""; } }
}
}
}
// Connection progress and password retry modal from the ImGui client.
if root.connection-dialog-open: Rectangle {
background: #00000042;
z: 48;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: root.connection-password-required ? 211px : 190px;
y: root.connection-password-required ? 143px : 130px;
width: root.connection-password-required ? 211px : 260px;
height: root.connection-password-required ? 158px : 145px;
if root.connection-password-required: Rectangle {
Text {
y: 38px;
height: 22px;
text: root.connection-status-text;
color: #30343b;
horizontal-alignment: center;
vertical-alignment: center;
}
if !root.connection-validating: PasswordInput {
x: 71px; y: 63px;
text <=> root.connection-password;
accepted => { root.connection-submit-password(root.connection-password, root.connection-remember-password); }
}
if !root.connection-validating: Rectangle {
x: 72px; y: 89px; width: 120px; height: 21px;
ImGuiCheckBox {
x: 0px; y: 0px;
checked <=> root.connection-remember-password;
}
Text {
x: 27px; y: 0px;
width: parent.width - 27px; height: parent.height;
text: UiStrings.remember-password;
color: #30343b;
font-size: ImGuiFontStyle.body;
vertical-alignment: center;
}
TouchArea {
x: 27px; y: 0px;
width: parent.width - 27px; height: parent.height;
clicked => { root.connection-remember-password = !root.connection-remember-password; }
}
}
if !root.connection-validating: ConnectionDialogButton {
x: 69px; y: 118px;
text: UiStrings.ok;
clicked => { root.connection-submit-password(root.connection-password, root.connection-remember-password); }
}
if !root.connection-validating: ConnectionDialogButton {
x: 117px;
y: 118px;
text: UiStrings.cancel;
clicked => { root.connection-cancel(); }
}
}
if !root.connection-password-required: VerticalLayout {
padding: 20px; spacing: 13px;
DialogPromptText { prompt: root.connection-status-text; }
HorizontalLayout {
alignment: center; spacing: 8px;
if root.connection-pending: SecondaryButton {
text: UiStrings.cancel;
clicked => { root.connection-cancel(); }
}
if !root.connection-pending: PrimaryButton {
text: UiStrings.ok;
clicked => { root.connection-acknowledge(); }
}
}
}
}
}
// macOS permission gate. On other platforms this remains hidden.
if root.permission-dialog-open: Rectangle {
background: transparent;
z: 50;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: root.compact-language ? 255px : 190px;
y: 162px;
width: root.compact-language ? 130px : 260px;
height: 125px;
border-radius: 7px;
Rectangle {
x: 0px;
y: 0px;
width: parent.width;
height: 21px;
background: #d3d3d3;
border-radius: 7px;
z: 5;
Rectangle { y: parent.height - 7px; height: 7px; background: #d3d3d3; }
Text { x: 6px; text: UiStrings.request-permissions; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
}
Text { x: 10px; y: 32px; width: root.compact-language ? 110px : 240px; height: 24px; text: UiStrings.permission-required; color: #202124; font-size: 8px; wrap: word-wrap; }
Text { x: 10px; y: 61px; width: root.compact-language ? 78px : 198px; height: 18px; text: "1. " + UiStrings.accessibility-permission + ":"; color: #202124; font-size: 8px; overflow: elide; vertical-alignment: center; }
MiniToggleSwitch {
x: root.compact-language ? 90px : 210px;
y: 61px;
checked: root.accessibility-granted;
enabled: !root.accessibility-granted;
toggled(value) => { if (value) { root.request-accessibility-permission(); } }
}
Text { x: 10px; y: 85px; width: root.compact-language ? 78px : 198px; height: 18px; text: "2. " + UiStrings.screen-recording-permission + ":"; color: #202124; font-size: 8px; overflow: elide; vertical-alignment: center; }
MiniToggleSwitch {
x: root.compact-language ? 90px : 210px;
y: 85px;
checked: root.screen-recording-granted;
enabled: !root.screen-recording-granted;
toggled(value) => { if (value) { root.request-screen-recording-permission(); } }
}
}
}
// Settings modal.
if root.settings-open: Rectangle {
background: transparent;
z: 45;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: root.compact-language ? 219px : 190px;
y: 60px;
width: root.compact-language ? 202px : 269px;
height: 330px;
border-radius: 7px;
Rectangle {
x: 0px;
y: 0px;
width: parent.width;
height: 21px;
background: #d3d3d3;
border-radius: 7px;
z: 5;
Rectangle { y: parent.height - 7px; height: 7px; background: #d3d3d3; }
Text { x: 6px; text: UiStrings.settings; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
}
settings-scroll := ScrollView {
x: 0px;
y: 21px;
width: parent.width;
height: 265px;
viewport-y <=> root.settings-scroll-y;
settings-content := VerticalLayout {
// Reserve space for the overlaid vertical scrollbar.
width: settings-scroll.width;
padding-top: 8px;
padding-bottom: 8px;
spacing: 0px;
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 94px : 162px; height: 24px; text: UiStrings.language; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
CompactComboBox { x: root.compact-language ? 106px : 174px; y: 2px; model: ["中文", "English", "Русский"]; current-index <=> root.language-index; }
}
if root.capture-method-visible: Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 94px : 162px; height: 24px; text: UiStrings.capture-method; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
CompactComboBox { x: root.compact-language ? 106px : 174px; y: 2px; enabled: !root.settings-session-active; model: [UiStrings.capture-method-auto, "DXGI", "WGC", "GDI"]; current-index <=> root.capture-method-index; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 94px : 162px; height: 24px; text: UiStrings.codec; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
CompactComboBox { x: root.compact-language ? 106px : 174px; y: 2px; enabled: !root.settings-session-active; model: [UiStrings.codec-h264, UiStrings.codec-av1]; current-index <=> root.codec-index; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.hardware-codec; color: root.hardware-codec-available ? #202124 : #8b8f95; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.hardware-codec-enabled; enabled: root.hardware-codec-available && !root.settings-session-active; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.turn-relay; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.turn-enabled; enabled: !root.settings-session-active; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.force-relay; color: root.turn-enabled ? #202124 : #8b8f95; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.force-relay-enabled; enabled: root.turn-enabled && !root.settings-session-active; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.save-remote-previews; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.save-remote-previews; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.privacy-on-connect; color: root.privacy-setting-available ? #202124 : #8b8f95; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.privacy-on-connect-enabled; enabled: root.privacy-setting-available; }
}
Rectangle {
height: 30px;
CompactButton { x: 8px; y: 3px; width: root.compact-language ? 70px : 206px; text: UiStrings.self-hosted; enabled: !root.settings-session-active; clicked => { root.self-host-settings-open = true; } }
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.self-hosted-enabled; enabled: !root.settings-session-active; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.autostart; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.autostart-enabled; enabled: !root.settings-session-active; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 145px : 212px; height: 24px; text: UiStrings.daemon; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
ImGuiCheckBox { x: root.compact-language ? 157px : 224px; y: 3px; checked <=> root.daemon-enabled; enabled: !root.settings-session-active; }
}
Rectangle {
height: 30px;
Text { x: 8px; y: 3px; width: root.compact-language ? 94px : 154px; height: 24px; text: UiStrings.file-save-path; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
CompactButton { x: root.compact-language ? 106px : 166px; y: 3px; width: root.compact-language ? 73px : 80px; text: root.file-save-path == "" ? UiStrings.default-desktop : root.file-save-path; enabled: !root.settings-session-active; clicked => { root.browse-save-path(); } }
}
}
}
Rectangle { x: 7px; y: 286px; width: parent.width - 14px; height: 1px; background: ImGuiLineStyle.separator; }
CompactButton { x: root.compact-language ? 63px : 89px; y: 297px; primary: true; text: UiStrings.ok; clicked => { if root.save-settings() { root.settings-open = false; root.self-host-settings-open = false; } } }
CompactButton { x: root.compact-language ? 104px : 130px; y: 297px; width: root.compact-language ? 34px : 56px; text: UiStrings.cancel; clicked => { root.cancel-settings(); root.settings-open = false; root.self-host-settings-open = false; } }
}
}
if root.self-host-settings-open: Rectangle {
background: transparent;
z: 49;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: root.compact-language ? 190px : 140px;
y: 105px;
width: root.compact-language ? 260px : 360px;
height: root.self-host-settings-error == "" ? 138px : 202px;
border-radius: 7px;
Rectangle {
x: 0px;
y: 0px;
width: parent.width;
height: 21px;
background: #d3d3d3;
border-radius: 7px;
z: 5;
Rectangle { y: parent.height - 7px; height: 7px; background: #d3d3d3; }
Text { x: 6px; text: UiStrings.self-hosted-settings; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
}
Text { x: 8px; y: 31px; width: root.compact-language ? 88px : 165px; height: 24px; text: UiStrings.server-host; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
Rectangle {
x: root.compact-language ? 100px : 173px;
y: 30px;
width: root.compact-language ? 152px : 179px;
height: 24px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 4px;
LineEdit { x: 1px; y: 1.5px; width: parent.width - 2px; height: parent.height - 2px; text <=> root.server-host; input-type: InputType.text; font-size: ImGuiFontStyle.body; }
LineEditFocusUnderlineMask { x: 5px; y: parent.height - 3px; width: parent.width - 10px; }
}
Text { x: 8px; y: 61px; width: root.compact-language ? 88px : 165px; height: 24px; text: UiStrings.server-port; font-size: ImGuiFontStyle.body; overflow: elide; vertical-alignment: center; }
Rectangle {
x: root.compact-language ? 100px : 173px;
y: 60px;
width: root.compact-language ? 152px : 179px;
height: 24px;
background: white;
border-width: 1px;
border-color: ImGuiLineStyle.border;
border-radius: 4px;
LineEdit { x: 1px; y: 1.5px; width: parent.width - 2px; height: parent.height - 2px; text <=> root.server-port; input-type: InputType.number; font-size: ImGuiFontStyle.body; }
LineEditFocusUnderlineMask { x: 5px; y: parent.height - 3px; width: parent.width - 10px; }
}
if root.self-host-settings-error != "": Text {
x: 8px; y: 89px; width: parent.width - 16px; height: 64px;
text: root.self-host-settings-error; color: #b3261e;
font-size: ImGuiFontStyle.body; wrap: word-wrap;
}
CompactButton { x: root.compact-language ? 93px : 135px; y: parent.height - 38px; primary: true; text: UiStrings.ok; clicked => { if root.save-self-hosted-settings() { root.self-host-settings-open = false; } } }
CompactButton { x: root.compact-language ? 134px : 176px; y: parent.height - 38px; width: root.compact-language ? 34px : 56px; text: UiStrings.cancel; clicked => { root.cancel-self-hosted-settings(); root.self-host-settings-open = false; } }
}
}
// About modal.
if root.about-open: Rectangle {
background: transparent;
z: 46;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: (root.width - self.width) / 2;
y: (root.height - self.height) / 2;
width: 320px;
height: max(216px, about-content.preferred-height);
border-radius: 7px;
Rectangle {
x: 0px;
y: 0px;
width: parent.width;
height: 21px;
background: #d3d3d3;
border-radius: 7px;
z: 5;
Rectangle { y: parent.height - 7px; height: 7px; background: #d3d3d3; }
Text { x: 6px; text: UiStrings.about; font-size: ImGuiFontStyle.base; font-weight: 600; color: #202124; vertical-alignment: center; }
}
// Grow for update actions and wrapped translations while keeping
// the footer below the version and check result.
about-content := VerticalLayout {
padding: 16px;
padding-top: 35px;
spacing: 12px;
alignment: space-between;
VerticalLayout {
vertical-stretch: 0;
spacing: 4px;
Text {
text: "CrossDesk " + root.current-version;
color: #202124;
font-size: ImGuiFontStyle.base;
font-weight: 600;
min-height: 18px;
overflow: elide;
}
if root.update-available: Rectangle {
height: max(18px, version-link.preferred-height);
accessible-role: button;
accessible-label: UiStrings.release-notes + " " + root.latest-version;
accessible-action-default => { version-touch.clicked(); }
version-link := Text {
x: 0px;
y: 0px;
width: parent.width;
height: parent.height;
text: UiStrings.update-available + ": " + root.latest-version;
color: version-touch.has-hover ? #174ea6 : #334fd1;
font-size: ImGuiFontStyle.body;
vertical-alignment: center;
horizontal-alignment: left;
wrap: word-wrap;
}
version-touch := TouchArea {
mouse-cursor: pointer;
clicked => { root.about-open = false; root.update-open = true; }
}
}
if root.update-available: HorizontalLayout {
alignment: start;
padding-top: 4px;
CompactButton {
width: max(76px, release-notes-button-metrics.preferred-width + 20px);
text: UiStrings.view-release-notes;
accessible-role: button;
accessible-label: self.text;
accessible-action-default => { self.clicked(); }
clicked => { root.about-open = false; root.update-open = true; }
release-notes-button-metrics := Text { visible: false; text: UiStrings.view-release-notes; font-size: ImGuiFontStyle.body; }
}
}
if root.update-status-text != "": Text {
text: root.update-status-text;
color: root.update-check-status == UpdateCheckStatus.failed ? #b3261e : #61656c;
font-size: ImGuiFontStyle.body;
min-height: 18px;
wrap: word-wrap;
}
}
VerticalLayout {
vertical-stretch: 0;
spacing: 3px;
Rectangle { height: 1px; background: #e6e8eb; }
Text { height: 16px; text: UiStrings.copyright; color: #61656c; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
Text { height: 16px; text: UiStrings.license; color: #61656c; font-size: ImGuiFontStyle.body; vertical-alignment: center; }
}
HorizontalLayout {
vertical-stretch: 0;
alignment: end;
spacing: 8px;
CompactButton {
width: max(76px, check-button-metrics.preferred-width + 20px);
text: UiStrings.check-for-updates;
enabled: root.update-check-status != UpdateCheckStatus.checking;
accessible-role: button;
accessible-label: self.text;
accessible-action-default => { if self.enabled { root.check-for-updates(); } }
clicked => { root.check-for-updates(); }
check-button-metrics := Text { visible: false; text: UiStrings.check-for-updates; font-size: ImGuiFontStyle.body; }
}
CompactButton { width: 48px; primary: true; text: UiStrings.ok; clicked => { root.about-open = false; } }
}
}
}
}
// Update notification modal.
if root.update-open && root.update-available: Rectangle {
background: transparent;
z: 47;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: 120px;
y: 52px;
width: 400px;
height: 346px;
border-radius: 7px;
Text { x: 30px; y: 22px; width: 320px; height: 24px; text: UiStrings.update-available + ": " + root.latest-version; color: #202124; font-size: ImGuiFontStyle.prominent; vertical-alignment: center; }
Rectangle {
x: 30px;
y: 47px;
width: 320px;
height: 22px;
Text {
x: 0px;
width: parent.width;
height: parent.height;
text: UiStrings.access-website + "https://crossdesk.cn";
color: website-touch.has-hover ? #174ea6 : #334fd1;
font-size: ImGuiFontStyle.body;
horizontal-alignment: left;
vertical-alignment: center;
}
website-touch := TouchArea { mouse-cursor: pointer; clicked => { root.open-download(); } }
}
Text {
x: 30px;
y: 75px;
width: 320px;
height: 20px;
text: UiStrings.release-notes;
color: #202124;
font-size: 12px;
vertical-alignment: center;
}
Rectangle {
x: 30px;
y: 105px;
width: 360px;
height: 190px;
background: white;
border-width: 1px;
border-color: #b9bdc3;
ScrollView {
x: 1px;
y: 1px;
width: parent.width - 2px;
height: parent.height - 2px;
VerticalLayout {
width: 340px;
padding-left: 8px;
padding-right: 8px;
padding-top: 8px;
padding-bottom: 8px;
spacing: 7px;
VerticalLayout {
width: 312px;
spacing: 10px;
for block in root.release-note-blocks: VerticalLayout {
padding-top: block.section-gap ? 10px : 0px;
StyledText { width: 312px; text: block.content; default-color: #202124; default-font-size: ImGuiFontStyle.body; }
}
}
}
}
}
CompactButton { x: root.compact-language ? 163px : 130px; y: 309px; width: root.compact-language ? 34px : 70px; primary: true; text: UiStrings.download; clicked => { root.open-download(); root.update-open = false; } }
CompactButton { x: root.compact-language ? 204px : 207px; y: 309px; width: root.compact-language ? 34px : 56px; text: UiStrings.later; clicked => { root.update-open = false; } }
}
}
// Linux Wayland sessions show this advisory once per application launch.
if root.wayland-notice-open: Rectangle {
background: #14233b66;
z: 50;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: 512px;
height: wayland-notice-content.preferred-height;
border-radius: 14px;
border-color: #dce3ee;
drop-shadow-color: #07142b30;
drop-shadow-blur: 28px;
drop-shadow-offset-y: 10px;
wayland-notice-content := VerticalLayout {
padding: 24px; spacing: 18px;
VerticalLayout {
spacing: 7px;
Text {
text: UiStrings.wayland-notice-title;
color: #1c2b43;
font-size: 20px;
font-weight: 700;
wrap: word-wrap;
}
Text {
text: UiStrings.wayland-notice-subtitle;
color: #68758a;
font-size: ImGuiFontStyle.base;
wrap: word-wrap;
}
}
Rectangle {
background: #fff8eb;
border-color: #f0dfba;
border-width: 1px;
border-radius: 9px;
VerticalLayout {
padding: 14px; spacing: 8px;
Text {
text: UiStrings.wayland-permission-title;
color: #805719;
font-size: 13px;
font-weight: 600;
wrap: word-wrap;
}
Text {
text: UiStrings.wayland-notice-message;
color: #6c5a3d;
font-size: ImGuiFontStyle.base;
wrap: word-wrap;
}
}
}
Rectangle {
background: #f3f7fd;
border-color: #dfe8f5;
border-width: 1px;
border-radius: 9px;
VerticalLayout {
padding: 14px; spacing: 8px;
Text {
text: UiStrings.wayland-recommendation-title;
color: #244c87;
font-size: 13px;
font-weight: 600;
wrap: word-wrap;
}
Text {
text: UiStrings.wayland-recommendation-message;
color: #59677c;
font-size: ImGuiFontStyle.base;
wrap: word-wrap;
}
}
}
Rectangle { height: 1px; background: #e9edf3; }
HorizontalLayout {
height: 34px; alignment: end;
PrimaryButton {
width: 104px; height: 34px;
border-radius: 7px;
text-size: ImGuiFontStyle.base;
text: UiStrings.wayland-notice-acknowledge;
accessible-role: button;
accessible-label: UiStrings.wayland-notice-acknowledge;
accessible-action-default => { root.wayland-notice-open = false; }
clicked => { root.wayland-notice-open = false; }
}
}
}
}
}
// Portable Windows builds prompt for the optional lock-screen service.
if root.portable-service-dialog-open: Rectangle {
background: #00000052;
z: 50;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: 80px; y: 75px; width: 480px; height: 300px;
VerticalLayout {
padding: 24px; spacing: 14px;
DialogPromptText {
prompt: UiStrings.service-setup-title;
text-color: #242832;
text-size: ImGuiFontStyle.prominent;
text-weight: 600;
}
Rectangle {
background: #f7f8fa; border-color: #d9dde4; border-width: 1px; border-radius: 6px;
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 {
text: root.portable-service-status;
color: root.portable-service-succeeded ? #25874b : root.portable-service-installing ? #68707d : #b23b3b;
horizontal-alignment: center;
wrap: word-wrap;
}
if !root.portable-service-succeeded: HorizontalLayout {
height: 26px; alignment: center; spacing: 8px;
Text { text: UiStrings.do-not-remind; vertical-alignment: center; color: #4a505a; }
ToggleSwitch { checked <=> root.portable-service-do-not-remind; enabled: !root.portable-service-installing; }
}
HorizontalLayout {
height: 34px; alignment: center; spacing: 8px;
if root.portable-service-succeeded: PrimaryButton { text: UiStrings.ok; clicked => { root.acknowledge-portable-service(); } }
if !root.portable-service-succeeded: PrimaryButton {
text: UiStrings.install-service;
enabled: !root.portable-service-installing;
clicked => { root.install-portable-service(); }
}
if !root.portable-service-succeeded: SecondaryButton {
text: UiStrings.cancel;
enabled: !root.portable-service-installing;
clicked => { root.cancel-portable-service(); }
}
}
}
}
}
if root.portable-service-suppressed-notice-open: Rectangle {
background: #00000052;
z: 51;
TouchArea { clicked => { focus-sink.focus(); } }
DialogSurface {
x: 100px; y: 130px; width: 440px; height: 190px;
VerticalLayout {
padding: 24px; spacing: 16px;
DialogPromptText {
prompt: UiStrings.notification;
text-color: #242832;
text-size: ImGuiFontStyle.prominent;
text-weight: 600;
}
DialogPromptText {
prompt: UiStrings.service-suppressed-message;
text-color: #3d434d;
}
PrimaryButton { text: UiStrings.ok; clicked => { root.acknowledge-portable-service-suppressed(); } }
}
}
}
}
if root.custom-titlebar: Rectangle {
x: 0px;
y: 0px;
width: root.width;
height: root.height;
background: transparent;
border-width: 1px;
border-color: root.wayland-titlebar ? #aaa8a6 : #8b8b8b;
border-radius:
root.wayland-titlebar ? root.wayland-corner-radius : 0px;
z: 200;
}
}
}