import { ComboBox, HorizontalBox, VerticalBox } from "std-widgets.slint"; import { LineEdit, Switch } from "controls.slint"; import { FocusVisibleScope } from "focus-visible-scope.slint"; import { TypeScale } from "../typography.slint"; import { SidebarStyle } from "sidebar-row.slint"; import { KbdShortcut, KeyboardKeys } from "kbd.slint"; // Settings navigation is intentionally separate from MailSidebar: the latter // owns mailbox models, compose actions, and tree expansion state. export component SettingsNavItem inherits Rectangle { in property text; in property icon; in property selected: false; in property accent; in property accent-soft; in property body-text; in property muted-text; in property icon-color: root.selected ? root.accent : root.muted-text; callback clicked; height: 36px; border-radius: 7px; background: root.selected ? root.accent-soft : nav-hit.has-hover ? root.accent-soft : transparent; Image { x: 10px; y: (parent.height - self.height) / 2; width: SidebarStyle.icon-size; height: SidebarStyle.icon-size; source: root.icon; colorize: root.icon-color; image-fit: contain; accessible-role: none; } Text { x: 37px; width: parent.width - 47px; height: parent.height; text: root.text; color: root.body-text; font-size: SidebarStyle.label-size; font-weight: root.selected ? SidebarStyle.selected-weight : SidebarStyle.normal-weight; vertical-alignment: center; overflow: elide; } nav-hit := TouchArea { accessible-role: button; accessible-label: root.text; accessible-checked: root.selected; accessible-action-default => { root.clicked(); } clicked => { root.clicked(); } mouse-cursor: pointer; } } export component SettingsCard inherits Rectangle { in property title; in property description; in property surface; in property card-border-color; in property heading; in property secondary-text; // Where content placed below the header starts. Larger platform fonts // make the header taller than its 58px design, so callers offset from it. out property content-y: description-text.y + description-text.height; background: root.surface; border-radius: 10px; border-width: 1px; border-color: root.card-border-color; title-text := Text { x: 20px; y: 17px; width: parent.width - 40px; height: max(20px, self.preferred-height); text: root.title; color: root.heading; font-size: TypeScale.subtitle; font-weight: 600; letter-spacing: -0.3px; vertical-alignment: center; overflow: elide; } description-text := Text { x: 20px; y: title-text.y + title-text.height + 4px; width: parent.width - 40px; height: max(17px, self.preferred-height); text: root.description; color: root.secondary-text; font-size: TypeScale.caption; vertical-alignment: center; overflow: elide; } } // Shared preference row for boolean appearance and behavior settings. Keeping // the label, description, touch target, and spacing here prevents individual // settings cards from slowly drifting apart. export component SettingsSwitchRow inherits Rectangle { in property compact: false; in property compact-title-height: 17px; in property compact-description-height: 34px; in property desktop-height: 48px; in property title; in property description; in property checked: false; in property enabled: true; in property title-color; in property description-color; callback toggled(bool); // Both texts grow past their designed heights with larger platform fonts. height: root.compact ? 27px + title-text.height + description-text.height : root.desktop-height + title-text.height + description-text.height - 34px; opacity: root.enabled ? 1 : 0.5; HorizontalBox { spacing: 16px; VerticalBox { horizontal-stretch: 1; min-width: 0px; spacing: 2px; title-text := Text { height: max(root.compact ? root.compact-title-height : 17px, self.preferred-height); text: root.title; color: root.title-color; font-size: TypeScale.body; font-weight: 600; wrap: root.compact ? word-wrap : no-wrap; overflow: root.compact ? clip : elide; } description-text := Text { height: max(root.compact ? root.compact-description-height : 17px, self.preferred-height); text: root.description; color: root.description-color; font-size: TypeScale.caption; wrap: word-wrap; } } Switch { checked: root.checked; enabled: root.enabled; control-label: root.title; toggled => { root.toggled(self.checked); } } } } // A small app-level wrapper keeps select geometry consistent while retaining // the platform widget's keyboard navigation, popup behavior, and accessibility. export component SettingsSelect inherits ComboBox { in property <[string]> choices; in-out property selected-index: 0; callback selection-changed(int, string); model: root.choices; current-index <=> root.selected-index; height: 36px; min-width: 180px; selected(value) => { root.selection-changed(root.selected-index, value); } } export component SettingsSelectRow inherits Rectangle { in property compact: false; in property compact-description-height: 34px; in property title; in property description; in property <[string]> choices; in-out property selected-index: 0; in property title-color; in property description-color; callback selected(int, string); // Desktop: the texts overhang the box padding by design. Compact: two 8px // insets, two 7px gaps and the 36px selector around the stacked texts. // Both layouts stay instantiated so the row can measure its texts. height: root.compact ? 66px + compact-title.height + compact-description.height : 23px + desktop-title.height + desktop-description.height; vertical-stretch: 0; HorizontalBox { visible: !root.compact; spacing: 20px; VerticalBox { spacing: 2px; desktop-title := Text { height: max(18px, self.preferred-height); text: root.title; color: root.title-color; font-size: TypeScale.body; font-weight: 600; overflow: elide; } desktop-description := Text { height: max(17px, self.preferred-height); text: root.description; color: root.description-color; font-size: TypeScale.caption; overflow: elide; } } Rectangle { horizontal-stretch: 1; } SettingsSelect { width: 210px; choices: root.choices; selected-index <=> root.selected-index; selection-changed(index, value) => { root.selected(index, value); } } } VerticalBox { visible: root.compact; padding: 8px; spacing: 7px; compact-title := Text { height: max(20px, self.preferred-height); text: root.title; color: root.title-color; font-size: TypeScale.body; font-weight: 600; wrap: word-wrap; } compact-description := Text { height: max(root.compact-description-height, self.preferred-height); text: root.description; color: root.description-color; font-size: TypeScale.caption; wrap: word-wrap; } SettingsSelect { horizontal-stretch: 1; choices: root.choices; selected-index <=> root.selected-index; selection-changed(index, value) => { root.selected(index, value); } } } } export component SettingsLinkRow inherits Rectangle { in property text; in property description; in property icon; in property colorize-icon: true; in property accent; in property body-text; in property muted-text; in property hover-background; out property focus-visible: keyboard.focus-visible; callback clicked; height: 18px + link-title.height + link-description.height; border-radius: 7px; forward-focus: keyboard; border-width: keyboard.focus-visible ? 2px : 0px; border-color: root.accent; background: link-hit.has-hover ? root.hover-background : transparent; accessible-role: button; accessible-label: root.text; accessible-description: root.description; accessible-action-default => { root.clicked(); } Image { x: 10px; y: (parent.height - self.height) / 2; width: 18px; height: 18px; source: root.icon; colorize: root.colorize-icon ? root.accent : transparent; image-fit: contain; accessible-role: none; } VerticalBox { x: 40px; y: 7px; width: parent.width - 80px; height: parent.height - 14px; spacing: 1px; link-title := Text { height: max(18px, self.preferred-height); text: root.text; color: root.body-text; font-size: TypeScale.body; font-weight: 600; overflow: elide; } link-description := Text { height: max(16px, self.preferred-height); text: root.description; color: root.muted-text; font-size: TypeScale.caption; overflow: elide; } } Image { x: parent.width - 27px; y: (parent.height - self.height) / 2; width: 16px; height: 16px; source: @image-url("../icons/lucide/outline/external-link.svg"); colorize: root.accent; image-fit: contain; accessible-role: none; } keyboard := FocusVisibleScope { accessible-role: none; key-pressed(event) => { if event.text == Key.Return || event.text == Key.Space { root.clicked(); return accept; } reject } } link-hit := TouchArea { clicked => { keyboard.focus(); keyboard.pointer-activated(); root.clicked(); } mouse-cursor: pointer; } } // A card of shortcut rows under a small heading. export component ShortcutSection inherits Rectangle { in property title; in property surface; in property card-border-color; in property heading; background: root.surface; border-radius: 8px; border-width: 1px; border-color: root.card-border-color; VerticalBox { padding-top: 12px; padding-right: 18px; padding-bottom: 12px; padding-left: 18px; spacing: 0px; Text { height: max(20px, self.preferred-height); text: root.title; color: root.heading; font-size: TypeScale.caption; font-weight: 700; vertical-alignment: center; } @children } } // One shortcut with its keys; a second binding is shown as an alternative. export component ShortcutRow inherits Rectangle { in property title; in property <[keys]> keys; in property separator: @tr("or"); in property title-color; in property separator-color; height: max(40px, keys-row.preferred-height + 12px); accessible-role: text; accessible-label: @tr("{}, {}", root.title, root.keys.length > 1 ? KeyboardKeys.text(root.keys[0]) + " " + root.separator + " " + KeyboardKeys.text(root.keys[1]) : KeyboardKeys.text(root.keys[0])); HorizontalLayout { spacing: 16px; Text { horizontal-stretch: 1; text: root.title; color: root.title-color; font-size: TypeScale.body; vertical-alignment: center; overflow: elide; accessible-role: none; } keys-row := HorizontalLayout { spacing: 6px; alignment: end; cross-axis-alignment: center; for binding[index] in root.keys : HorizontalLayout { spacing: 6px; cross-axis-alignment: center; if index > 0 : Text { text: root.separator; color: root.separator-color; font-size: TypeScale.caption; vertical-alignment: center; accessible-role: none; } KbdShortcut { keys: binding; } } } } } export component FormField inherits VerticalBox { in property enabled: true; in property label; // Bind with `<=>`: typing replaces a one-way binding, after which the // field would keep showing its old text when the form is reset or filled. in-out property value; in property placeholder; in property input-type: InputType.text; in property label-color; callback edited(string); forward-focus: field; padding: 0px; spacing: 5px; // Long translated labels wrap on narrow screens instead of overflowing. Text { height: max(16px, self.preferred-height); text: root.label; color: root.label-color; font-size: TypeScale.caption; font-weight: 600; vertical-alignment: center; wrap: word-wrap; } field := LineEdit { enabled: root.enabled; height: 36px; text <=> root.value; placeholder-text: root.placeholder; input-type: root.input-type; edited(value) => { root.edited(value); } } }