Files
mail/ui/components/settings-components.slint

369 lines
12 KiB
Plaintext

import { ComboBox, HorizontalBox, VerticalBox } from "std-widgets.slint";
import { LineEdit, Switch } from "controls.slint";
import { TypeScale } from "../typography.slint";
import { SidebarStyle } from "sidebar-row.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 <string> text;
in property <image> icon;
in property <bool> selected: false;
in property <color> accent;
in property <color> accent-soft;
in property <color> body-text;
in property <color> muted-text;
in property <color> 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 <string> title;
in property <string> description;
in property <color> surface;
in property <color> card-border-color;
in property <color> heading;
in property <color> 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 <length> 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 <bool> compact: false;
in property <length> compact-title-height: 17px;
in property <length> compact-description-height: 34px;
in property <length> desktop-height: 48px;
in property <string> title;
in property <string> description;
in property <bool> checked: false;
in property <bool> enabled: true;
in property <color> title-color;
in property <color> 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 <int> 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 <bool> compact: false;
in property <length> compact-description-height: 34px;
in property <string> title;
in property <string> description;
in property <[string]> choices;
in-out property <int> selected-index: 0;
in property <color> title-color;
in property <color> 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 <string> text;
in property <string> description;
in property <image> icon;
in property <bool> colorize-icon: true;
in property <color> accent;
in property <color> body-text;
in property <color> muted-text;
in property <color> hover-background;
callback clicked;
height: 18px + link-title.height + link-description.height;
border-radius: 7px;
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; }
}
Text {
x: parent.width - 28px;
width: 18px;
height: parent.height;
text: "↗";
color: root.accent;
font-size: TypeScale.body;
horizontal-alignment: center;
vertical-alignment: center;
}
link-hit := TouchArea {
clicked => { root.clicked(); }
mouse-cursor: pointer;
}
}
export component ShortcutRow inherits Rectangle {
in property <string> title;
in property <string> shortcut;
in property <color> title-color;
in property <color> shortcut-color;
in property <color> key-background;
in property <color> key-border;
height: 40px;
Text {
x: 0px;
width: parent.width - 52px;
height: parent.height;
text: root.title;
color: root.title-color;
font-size: TypeScale.body;
vertical-alignment: center;
overflow: elide;
}
Rectangle {
x: parent.width - self.width;
y: (parent.height - self.height) / 2;
width: max(32px, key-label.preferred-width + 16px);
height: 26px;
border-radius: 5px;
border-width: 1px;
border-color: root.key-border;
background: root.key-background;
key-label := Text {
text: root.shortcut;
color: root.shortcut-color;
font-size: TypeScale.caption;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
}
}
}
export component FormField inherits VerticalBox {
in property <bool> enabled: true;
in property <string> label;
in-out property <string> value;
in property <string> placeholder;
in property <InputType> input-type: InputType.text;
in property <color> label-color;
callback edited(string);
forward-focus: field;
padding: 0px;
spacing: 5px;
Text {
height: max(16px, self.preferred-height);
text: root.label;
color: root.label-color;
font-size: TypeScale.caption;
font-weight: 600;
vertical-alignment: center;
}
field := LineEdit {
enabled: root.enabled;
height: 36px;
text: root.value;
placeholder-text: root.placeholder;
input-type: root.input-type;
edited(value) => {
root.value = value;
root.edited(value);
}
}
}