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

556 lines
20 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 { FlatSurface, UiSurface } from "ui_surface.slint";
import { UiRadius } from "ui_radius.slint";
export global FloatingBarStyle {
out property <length> corner-radius: UiRadius.menu;
}
// Fields and selection controls share a muted fill.
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;
}
// Action buttons use a light surface and a crisp edge, distinct from fields.
export global ButtonStyle {
out property <color> surface: #ffffff;
out property <color> hover: #f0f5ff;
out property <color> pressed: #dfe9fa;
out property <color> border: #b8c7db;
out property <color> hover-border: #7e9fd1;
out property <color> pressed-border: #6488bf;
out property <color> disabled-border: #dce2eb;
out property <color> selected: #3564e3;
out property <color> selected-hover: #4574f0;
out property <color> selected-pressed: #254cc0;
}
// ImGui loads a 32px font atlas and applies per-window font scales. These
// logical sizes reproduce those visual tiers on Slint's HiDPI renderer.
export global ImGuiFontStyle {
out property <length> base: 12px;
out property <length> section-title: 18px;
out property <length> panel-label: 17px;
out property <length> field-value: 22px;
out property <length> prominent: 16px;
out property <length> body: 11px;
out property <length> small: 10px;
}
// Font Awesome 6 Free Solid glyphs used by the legacy ImGui client. The font
// data is registered for every Slint window by GuiApplication before show().
export global FontAwesomeIcons {
out property <string> angle-down: "\u{f107}";
out property <string> angle-left: "\u{f104}";
out property <string> angle-right: "\u{f105}";
out property <string> angle-up: "\u{f106}";
out property <string> arrow-right-long: "\u{f178}";
out property <string> arrow-up-right-from-square: "\u{f35d}";
out property <string> bars: "\u{f0c9}";
out property <string> check: "\u{f00c}";
out property <string> circle-arrow-up: "\u{f0aa}";
out property <string> code: "\u{f121}";
out property <string> compress: "\u{f066}";
out property <string> computer-mouse: "\u{f8cc}";
out property <string> copy: "\u{f0c5}";
out property <string> display: "\u{e163}";
out property <string> expand: "\u{f065}";
out property <string> eye: "\u{f06e}";
out property <string> eye-slash: "\u{f070}";
out property <string> file-lines: "\u{f15c}";
out property <string> folder-open: "\u{f07c}";
out property <string> keyboard: "\u{f11c}";
out property <string> layer-group: "\u{f5fd}";
out property <string> minus: "\u{f068}";
out property <string> pen: "\u{f304}";
out property <string> rotate: "\u{f2f1}";
out property <string> signal: "\u{f012}";
out property <string> shield-halved: "\u{f3ed}";
out property <string> square-full: "\u{f45c}";
out property <string> tags: "\u{f02c}";
out property <string> trash-can: "\u{f2ed}";
out property <string> user-secret: "\u{f21b}";
out property <string> volume-high: "\u{f028}";
out property <string> volume-xmark: "\u{f6a9}";
out property <string> xmark: "\u{f00d}";
}
export component IconButton inherits Rectangle {
in property <string> icon;
in property <string> tooltip;
in property <bool> tooltip-above: false;
in property <bool> enabled: true;
in property <bool> danger: false;
in property <length> icon-size: 16px;
in property <color> icon-color: ControlStyle.icon;
in property <color> normal-background: transparent;
in property <color> hover-background: ControlStyle.hover;
in property <color> pressed-background: ControlStyle.pressed;
in property <bool> slashed: false;
callback clicked;
width: 30px;
height: 30px;
background: touch.pressed ? (danger ? #d92d20 : root.pressed-background)
: touch.has-hover ? (danger ? #f04438 : root.hover-background)
: root.normal-background;
border-radius: UiRadius.control;
opacity: root.enabled ? 1 : 0.4;
Text {
text: root.icon;
color: touch.has-hover && root.danger ? white : root.icon-color;
font-family: "Font Awesome 6 Free";
font-weight: 900;
font-size: root.icon-size;
horizontal-alignment: center;
vertical-alignment: center;
}
if root.slashed: Text {
text: "/";
color: touch.has-hover && root.danger ? white : root.icon-color;
font-size: root.icon-size + 7px;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
touch := TouchArea {
enabled: root.enabled;
clicked => { root.clicked(); }
}
if touch.has-hover && root.tooltip != "": Rectangle {
x: min(root.width - self.width, 0px);
y: root.tooltip-above ? -self.height - 3px : root.height + 3px;
width: tip.preferred-width + 12px;
height: 24px;
background: #22252be8;
border-radius: UiRadius.control;
z: 20;
tip := Text {
text: root.tooltip;
color: white;
font-size: ImGuiFontStyle.small;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
// Retained for non-font artwork. UI action icons use IconButton so the Slint
// and ImGui clients share Font Awesome's glyph geometry.
export component ImageButton inherits Rectangle {
in property <image> icon;
in property <string> tooltip;
in property <bool> danger: false;
in property <length> icon-width: 16px;
in property <length> icon-height: 16px;
callback clicked;
width: 30px;
height: 30px;
background: touch.pressed ? (danger ? #d92d20 : ControlStyle.pressed)
: touch.has-hover ? (danger ? #f04438 : ControlStyle.hover)
: transparent;
border-radius: UiRadius.control;
Image {
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
width: root.icon-width;
height: root.icon-height;
source: root.icon;
image-fit: contain;
}
touch := TouchArea {
clicked => { root.clicked(); }
}
if touch.has-hover && root.tooltip != "": Rectangle {
x: min(root.width - self.width, 0px);
y: root.height + 3px;
width: tip.preferred-width + 12px;
height: 24px;
background: #22252be8;
border-radius: UiRadius.control;
z: 20;
tip := Text {
text: root.tooltip;
color: white;
font-size: ImGuiFontStyle.small;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
export component CompactComboBox inherits FlatSurface {
in property <[string]> model;
in-out property <int> current-index: 0;
in property <bool> enabled: true;
in property <bool> subtle: false;
in property <bool> show-arrow: true;
in property <color> normal-outline-color: ButtonStyle.border;
in property <color> hover-outline-color: ButtonStyle.hover-border;
property <bool> popup-animation-enabled: false;
property <float> popup-progress: 0.0;
callback selected(int);
// Session state can disable a setting while its popup is already open.
// Remove the stale options and stop a pending reveal on every backend.
changed enabled => {
if !root.enabled {
popup-open-timer.running = false;
root.popup-animation-enabled = false;
root.popup-progress = 0.0;
combo-popup.close();
}
}
animate popup-progress {
duration: 160ms;
easing: ease-out;
enabled: root.popup-animation-enabled;
}
width: 73px;
height: 24px;
background: !root.enabled ? ControlStyle.disabled
: combo-touch.pressed ? ButtonStyle.pressed
: combo-popup.is-open || combo-touch.has-hover ? ButtonStyle.hover : ButtonStyle.surface;
outline-focused: root.enabled && combo-popup.is-open;
outline-color: !root.enabled ? ButtonStyle.disabled-border
: combo-popup.is-open ? UiSurface.focus
: combo-touch.pressed ? ButtonStyle.pressed-border
: combo-touch.has-hover ? root.hover-outline-color : root.normal-outline-color;
border-radius: UiRadius.control;
animate background, outline-color { duration: 120ms; }
accessible-role: combobox;
accessible-enabled: root.enabled;
accessible-expandable: true;
accessible-expanded: combo-popup.is-open;
accessible-value: root.model[root.current-index];
Text {
x: root.subtle ? 10px : 8px;
width: parent.width - self.x - (root.show-arrow ? 28px : self.x);
height: parent.height;
text: root.model[root.current-index];
color: root.enabled ? #202124 : #8b8f95;
font-size: root.subtle ? 12px : ImGuiFontStyle.body;
overflow: elide;
vertical-alignment: center;
accessible-role: none;
}
if root.show-arrow: Text {
x: parent.width - 26px;
width: 18px; height: parent.height;
// Switch glyphs so the software renderer also shows the open state.
text: combo-popup.is-open ? FontAwesomeIcons.angle-up : FontAwesomeIcons.angle-down;
color: !root.enabled ? #98a2b3 : combo-popup.is-open ? UiSurface.focus : ControlStyle.icon;
font-family: "Font Awesome 6 Free";
font-weight: 900;
font-size: 10px;
horizontal-alignment: center;
vertical-alignment: center;
accessible-role: none;
}
combo-touch := TouchArea {
enabled: root.enabled; mouse-cursor: pointer;
clicked => {
root.popup-animation-enabled = false;
root.popup-progress = 0.0;
combo-popup.show();
popup-open-timer.running = true;
}
}
// Start on the next frame so the native popup first renders at zero
// height, making the downward expansion visible on every backend.
popup-open-timer := Timer {
interval: 16ms;
running: false;
triggered => {
self.running = false;
if combo-popup.is-open {
root.popup-animation-enabled = true;
root.popup-progress = 1.0;
}
}
}
combo-popup := PopupWindow {
x: 0px;
y: root.height + 4px;
width: root.width;
height: root.model.length * 23px + 8px;
close-policy: close-on-click-outside;
FlatSurface {
// Keep the reveal anchored to the popup's top edge while the
// content settles downward into its final position.
y: (1.0 - root.popup-progress) * -8px;
width: parent.width;
height: parent.height * root.popup-progress;
opacity: root.popup-progress;
background: white;
outline-color: root.normal-outline-color;
border-radius: UiRadius.menu;
clip: true;
for choice[index] in root.model: Rectangle {
x: 4px; y: 4px + index * 23px;
width: parent.width - 8px;
height: 23px;
border-radius: UiRadius.control;
background: index == root.current-index
? (option-touch.pressed ? ButtonStyle.pressed : ButtonStyle.hover)
: option-touch.pressed ? ButtonStyle.pressed : option-touch.has-hover ? ButtonStyle.hover : white;
Text {
x: 6px; width: parent.width - 28px; height: parent.height; text: choice;
color: index == root.current-index ? ControlStyle.selected-text : #202124;
font-weight: index == root.current-index ? 600 : 400;
font-size: root.subtle ? 12px : ImGuiFontStyle.body; overflow: elide; vertical-alignment: center;
}
if index == root.current-index: Text {
x: parent.width - 20px; width: 14px; height: parent.height;
text: FontAwesomeIcons.check; color: UiSurface.focus;
font-family: "Font Awesome 6 Free"; font-weight: 900; font-size: 10px;
horizontal-alignment: center; vertical-alignment: center;
accessible-role: none;
}
option-touch := TouchArea {
enabled: root.enabled;
mouse-cursor: pointer;
clicked => {
popup-open-timer.running = false;
root.popup-animation-enabled = false;
root.popup-progress = 0.0;
if root.enabled && index >= 0 && index < root.model.length {
root.current-index = index;
root.selected(index);
}
combo-popup.close();
}
}
}
FlatSurface {
width: parent.width;
height: parent.height;
background: transparent;
clip: true;
outline-color: root.normal-outline-color;
border-radius: UiRadius.menu;
z: 2;
}
}
}
}
export component ImGuiCheckBox inherits FlatSurface {
in-out property <bool> checked;
in property <bool> enabled: true;
width: 21px;
height: 21px;
background: !root.enabled ? ControlStyle.disabled
: root.checked ? (check-touch.pressed ? #254cc0 : check-touch.has-hover ? #4574f0 : #3564e3)
: check-touch.pressed ? ControlStyle.pressed : check-touch.has-hover ? ControlStyle.hover : ControlStyle.surface;
outline-color: check-touch.has-hover && root.enabled ? UiSurface.focus : UiSurface.edge;
border-radius: 4px;
if root.checked: Text {
text: FontAwesomeIcons.check;
color: root.enabled ? white : #8b8f95;
font-family: "Font Awesome 6 Free";
font-weight: 900;
font-size: 16px;
horizontal-alignment: center;
vertical-alignment: center;
}
check-touch := TouchArea {
enabled: root.enabled;
clicked => { root.checked = !root.checked; }
}
}
export component MiniToggleSwitch inherits Rectangle {
in-out property <bool> checked;
in property <bool> enabled: true;
callback toggled(bool);
width: 29px;
height: 16px;
background: !root.enabled ? (root.checked ? #6e9ddacc : #9a9a9acc)
: root.checked ? #2463c7 : #9a9a9a;
border-radius: 8px;
Rectangle {
x: root.checked ? parent.width - self.width - 2px : 2px;
y: 2px;
width: 12px;
height: 12px;
border-radius: 6px;
background: white;
}
TouchArea {
enabled: root.enabled;
clicked => { root.checked = !root.checked; root.toggled(root.checked); }
}
}
export component CompactButton inherits FlatSurface {
in property <string> text;
in property <length> text-size: ImGuiFontStyle.body;
in property <bool> primary: false;
in property <bool> quiet: false;
in property <bool> enabled: true;
callback clicked;
width: 34px;
height: 24px;
background: !root.enabled ? (root.quiet ? transparent : ControlStyle.disabled)
: button-touch.pressed ? (root.primary ? #82b8f2 : ButtonStyle.pressed)
: button-touch.has-hover ? (root.primary ? #b8d9fb : ButtonStyle.hover)
: root.primary ? #a8d0fa : root.quiet ? transparent : ButtonStyle.surface;
outline-enabled: !root.quiet;
outline-color: !root.enabled ? ButtonStyle.disabled-border
: root.primary ? UiSurface.focus
: button-touch.pressed ? ButtonStyle.pressed-border
: button-touch.has-hover ? ButtonStyle.hover-border : ButtonStyle.border;
border-radius: UiRadius.control;
animate background, outline-color { duration: 100ms; }
Text {
text: root.text;
color: root.enabled ? #24272d : #8e9298;
font-size: root.text-size;
overflow: elide;
horizontal-alignment: center;
vertical-alignment: center;
}
button-touch := TouchArea { enabled: root.enabled; mouse-cursor: pointer; clicked => { root.clicked(); } }
}
export component PrimaryButton inherits FlatSurface {
in property <string> text;
in property <length> text-size: 12px;
in property <bool> enabled: true;
callback clicked;
min-width: 72px;
height: 32px;
background: !root.enabled ? #b8c1d1
: touch.pressed ? #254cc0
: touch.has-hover ? #4574f0
: #3564e3;
outline-enabled: false;
border-radius: UiRadius.control;
animate background { duration: 100ms; }
Text {
text: root.text;
color: white;
font-size: root.text-size;
font-weight: 600;
horizontal-alignment: center;
vertical-alignment: center;
}
touch := TouchArea {
enabled: root.enabled;
clicked => { root.clicked(); }
}
}
export component SecondaryButton inherits FlatSurface {
in property <string> text;
in property <length> text-size: 12px;
in property <bool> enabled: true;
callback clicked;
min-width: 72px;
height: 32px;
background: !root.enabled ? ControlStyle.disabled
: touch.pressed ? ButtonStyle.pressed
: touch.has-hover ? ButtonStyle.hover : ButtonStyle.surface;
outline-color: !root.enabled ? ButtonStyle.disabled-border
: touch.pressed ? ButtonStyle.pressed-border
: touch.has-hover ? ButtonStyle.hover-border : ButtonStyle.border;
border-radius: UiRadius.control;
animate background, outline-color { duration: 100ms; }
Text {
text: root.text;
color: root.enabled ? #303f55 : #949aa5;
font-size: root.text-size;
font-weight: 500;
horizontal-alignment: center;
vertical-alignment: center;
}
touch := TouchArea {
enabled: root.enabled;
mouse-cursor: pointer;
clicked => { root.clicked(); }
}
}
export component ToggleSwitch inherits Rectangle {
in-out property <bool> checked;
in property <bool> enabled: true;
in property <bool> toggle-on-click: true;
callback clicked();
callback toggled(bool);
width: 36px;
height: 20px;
background: !root.enabled ? #d0d5dd : root.checked ? #2463c7 : #98a2b3;
border-radius: self.height / 2;
Rectangle {
x: root.checked ? parent.width - self.width - 3px : 3px;
y: 3px;
width: 14px;
height: 14px;
border-radius: 7px;
background: white;
animate x { duration: 100ms; easing: ease-in-out; }
}
TouchArea {
enabled: root.enabled;
clicked => {
if root.toggle-on-click {
root.checked = !root.checked;
root.toggled(root.checked);
}
root.clicked();
}
}
}
export component SectionTitle inherits Text {
color: #00000080;
font-size: ImGuiFontStyle.section-title;
font-weight: 500;
vertical-alignment: center;
}
export component DialogSurface inherits FlatSurface {
background: white;
outline-color: UiSurface.edge;
border-radius: UiRadius.surface;
}