Add support for fluent-light and fluent-dark styles

These variants of the fluent style will use either the light or the dark color scheme,
regardless of the system wide setting.
This commit is contained in:
Simon Hausmann 2022-10-26 12:04:20 +02:00 committed by Simon Hausmann
parent a4313c3e32
commit 871a06224f
12 changed files with 978 additions and 893 deletions

View file

@ -17,6 +17,8 @@ All notable changes to this project are documented in this file.
- Skia renderer: improvements to the TextInput
- Support for input method with pre-edit
- Dark theme for the Fluent style
- Added `fluent-light` and `fluent-dark` as explicit styles to select a light/dark variant,
regardless of the system color scheme setting.
### Fixed

View file

@ -0,0 +1,246 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
import { ColorSchemeSelector } from "color-scheme.slint";
export global Palette := {
property<bool> dark-color-scheme: ColorSchemeSelector.dark-color-scheme;
// The colors in light mode are default palette. In the
// Fluent UI Theme Designer they match the colors produced
// with Primary Color = #0078d4, Text Color = #201f1e and Background Color = #ffffff
// The dark mode colors are produced in the Fluent UI Theme Designer by swapping
// Text Color and Background Color from light mode, applying the variations below
// and darkening the "white" and brightening the "neutralLight" variants for improved
// contrast (esp. on buttons).
property<color> themeDarker: #004578;
property<color> themeDark: #005a9e;
property<color> themeDarkAlt: #106ebe;
property<color> themePrimary: #0078d4;
property<color> themeSecondary: #2b88d8;
property<color> themeTertiary: #71afe5;
property<color> themeLight: #c7e0f4;
property<color> themeLighter: #deecf9;
property<color> themeLighterAlt: #eff6fc;
property<color> black: !dark-color-scheme ? #000000 : #f8f8f8;
property<color> blackTranslucent40: rgba(0,0,0,0.4);
property<color> neutralDark: !dark-color-scheme ? #201f1e : #f4f4f4;
property<color> neutralPrimary: !dark-color-scheme ? #323130 : #ffffff;
property<color> neutralPrimaryAlt: !dark-color-scheme ? #3b3a39 : #dadada;
property<color> neutralSecondary: !dark-color-scheme ? #605e5c : #d0d0d0;
property<color> neutralSecondaryAlt: #8a8886;
property<color> neutralTertiary: !dark-color-scheme ? #a19f9d : #c8c8c8;
property<color> neutralTertiaryAlt: !dark-color-scheme ? #c8c6c4 : #6d6d6d;
property<color> neutralQuaternary: #d2d0ce;
property<color> neutralQuaternaryAlt: !dark-color-scheme ? #e1dfdd : #484848;
property<color> neutralLight: !dark-color-scheme ? #edebe9 : #3f3f3f;
property<color> neutralLighter: !dark-color-scheme ? #f3f2f1 : #313131;
property<color> neutralLighterAlt: !dark-color-scheme ? #faf9f8 : #282828;
property<color> accent: #0078d4;
property<color> white: !dark-color-scheme ? #ffffff : #1f1f1f;
property<color> whiteTranslucent40: rgba(255,255,255,0.4);
property<color> yellowDark: #d29200;
property<color> yellow: #ffb900;
property<color> yellowLight: #fff100;
property<color> orange: #d83b01;
property<color> orangeLight: #ea4300;
property<color> orangeLighter: #ff8c00;
property<color> redDark: #a4262c;
property<color> red: #e81123;
property<color> magentaDark: #5c005c;
property<color> magenta: #b4009e;
property<color> magentaLight: #e3008c;
property<color> purpleDark: #32145a;
property<color> purple: #5c2d91;
property<color> purpleLight: #b4a0ff;
property<color> blueDark: #002050;
property<color> blueMid: #00188f;
property<color> blue: #0078d4;
property<color> blueLight: #00bcf2;
property<color> tealDark: #004b50;
property<color> teal: #008272;
property<color> tealLight: #00b294;
property<color> greenDark: #004b1c;
property<color> green: #107c10;
property<color> greenLight: #bad80a;
}
export global StyleMetrics := {
property<length> layout-spacing: 8px;
property<length> layout-padding: 8px;
property<length> text-cursor-width: 2px;
property<brush> window-background: Palette.white;
property<color> default-text-color: Palette.neutralDark;
property<brush> textedit-background: Palette.white;
property<color> textedit-text-color: Palette.neutralPrimary;
property<brush> textedit-background-disabled: Palette.neutralLighter;
property<color> textedit-text-color-disabled: Palette.neutralTertiary;
property<bool> dark-color-scheme: Palette.dark-color-scheme;
}
export Button := Rectangle {
callback clicked;
property<string> text <=> text.text;
property<bool> has-focus <=> fs.has-focus;
property<bool> pressed: self.enabled && touch.pressed;
property<bool> enabled <=> touch.enabled;
property<bool> checkable;
property<bool> checked;
property<image> icon;
property<length> font-size <=> text.font-size;
accessible-role: button;
accessible-label <=> text.text;
border-width: 1px;
border-radius: 2px;
border-color: !enabled ? Palette.neutralLighter : Palette.neutralSecondaryAlt;
background: !enabled ? Palette.neutralLighter
: (touch.pressed || checked) ? Palette.neutralLight
: touch.has-hover ? Palette.neutralLighter
: Palette.white;
horizontal-stretch: 0;
vertical-stretch: 0;
min-height: max(32px, l.min-height);
l := HorizontalLayout {
padding-left: 16px;
padding-right: 16px;
spacing: 8px;
padding-top: 3px;
padding-bottom: 3px;
if (icon.width > 0 && icon.height > 0): Image {
source <=> icon;
width: 24px;
}
text := Text {
color: !enabled ? Palette.neutralTertiary : Palette.neutralDark;
horizontal-alignment: center;
vertical-alignment: center;
font-weight: 600;
}
}
touch := TouchArea {
clicked => {
if (root.checkable) {
root.checked = !root.checked;
}
root.clicked();
}
}
fs := FocusScope {
width: 0px; // Do not react on clicks
enabled <=> root.enabled;
key-pressed(event) => {
if (event.text == " " || event.text == "\n") {
touch.clicked();
return accept;
}
return reject;
}
}
Rectangle { // Focus rectangle
x: 3px;
y: x;
width: parent.width - 2*x;
height: parent.height - 2*y;
border-width: enabled && has-focus? 1px : 0px;
border-color: Palette.black;
}
}
ScrollBar := Rectangle {
background: Palette.white;
// border-color: Palette.button-background;
border-width: 1px;
property <bool> horizontal;
property<length> maximum;
property<length> page-size;
// this is always negative and bigger than -maximum
property<length> value;
handle := Rectangle {
width: !horizontal ? parent.width : maximum <= 0phx ? 0phx : parent.width * (page-size / (maximum + page-size));
height: horizontal ? parent.height : maximum <= 0phx ? 0phx : parent.height * (page-size / (maximum + page-size));
border-radius: (horizontal ? self.height : self.width) / 2;
background: touch-area.pressed ? Palette.themePrimary :
touch-area.has-hover ? Palette.themeSecondary : Palette.neutralTertiary;
x: !horizontal ? 0phx : (root.width - handle.width) * (-value / maximum);
y: horizontal ? 0phx : (root.height - handle.height) * (-value / maximum);
}
touch-area := TouchArea {
width: parent.width;
height: parent.height;
property <length> pressed-value;
pointer-event(event) => {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
pressed-value = -root.value;
}
}
moved => {
if (enabled && pressed) {
value = -max(0px, min(root.maximum, pressed-value + (
horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (maximum / (root.width - handle.width))
: (touch-area.mouse-y - touch-area.pressed-y) * (maximum / (root.height - handle.height))
)));
}
}
}
}
export ScrollView := Rectangle {
property <length> viewport-width <=> fli.viewport-width;
property <length> viewport-height <=> fli.viewport-height;
property <length> viewport-x <=> fli.viewport-x;
property <length> viewport-y <=> fli.viewport-y;
property <length> visible-width <=> fli.width;
property <length> visible-height <=> fli.height;
property <bool> enabled: true;
property <bool> has-focus;
min-height: 50px;
min-width: 50px;
horizontal-stretch: 1;
vertical-stretch: 1;
border-radius: 2px;
border-width: !enabled ? 0px : has-focus ? 2px : 1px;
border-color: !enabled ? Palette.neutralLighter
: has-focus ? Palette.themeSecondary
: Palette.neutralPrimary;
fli := Flickable {
@children
x: 2px;
y: 2px;
interactive: false;
viewport-y <=> vbar.value;
viewport-x <=> hbar.value;
width: parent.width - vbar.width - 4px;
height: parent.height - hbar.height - 4px;
}
vbar := ScrollBar {
width: 16px;
x: fli.width + fli.x;
y: fli.y;
height: fli.height;
horizontal: false;
maximum: fli.viewport-height - fli.height;
page-size: fli.height;
}
hbar := ScrollBar {
height: 16px;
y: fli.height + fli.y;
x: fli.x;
width: fli.width;
horizontal: true;
maximum: fli.viewport-width - fli.width;
page-size: fli.width;
}
}

View file

@ -0,0 +1,657 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
// cSpell: ignore combobox spinbox standardbutton
import { LineEditInner, TextEdit, AboutSlint } from "../common/common.slint";
import { StandardButton } from "../common/standardbutton.slint";
import { StyleMetrics, ScrollView, Button, Palette } from "std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, StandardButton, TextEdit, AboutSlint, AboutSlint as AboutSixtyFPS }
export CheckBox := Rectangle {
callback toggled;
property <string> text <=> text.text;
property <bool> checked;
property <bool> has-focus;
property<bool> enabled: true;
min-height: 20px;
horizontal-stretch: 0;
vertical-stretch: 0;
accessible-label <=> text.text;
accessible-checkable: true;
accessible-checked <=> checked;
accessible-role: checkbox;
HorizontalLayout {
spacing: 8px;
VerticalLayout {
alignment: center;
Rectangle {
border-width: 1px;
border-radius: 2px;
/* border-color: !enabled ? Palette.neutralLighter : Palette.neutralSecondaryAlt;
background: !enabled ? Palette.white
: touch.pressed ? Palette.neutralLight
: touch.has-hover ? Palette.neutralLighter
: Palette.themePrimary;*/
border-color: checked ? background : !enabled ? Palette.neutralTertiaryAlt : Palette.neutralSecondaryAlt;
background: !checked ? Palette.white
: !enabled ? Palette.neutralTertiaryAlt
: touch.has-hover || touch.pressed ? Palette.themeDark
: Palette.themePrimary;
animate background { duration: 250ms; easing: ease; }
//width: height;
vertical-stretch: 0;
width: 20px;
height: 20px;
if (checked || touch.has-hover || touch.pressed) : Path {
width: 66%;
height: 66%;
x: (parent.width - width) / 2;
y: (parent.height - height) / 2;
commands: "M.22.5.42.7.78.34.74.3.42.62.26.54z";
fill: checked ? Palette.white : Palette.neutralSecondaryAlt;
}
}
}
text := Text {
color: !enabled ? Palette.neutralTertiary : Palette.neutralDark;
horizontal-alignment: left;
vertical-alignment: center;
vertical-stretch: 1;
}
}
touch := TouchArea {
enabled <=> root.enabled;
clicked => {
if (root.enabled) {
root.checked = !root.checked;
root.toggled();
}
}
}
fs := FocusScope {
width: 0px; // Do not react on clicks
enabled <=> root.enabled;
has_focus <=> root.has-focus;
key-pressed(event) => {
if (event.text == " " || event.text == "\n") {
touch.clicked();
return accept;
}
return reject;
}
}
Rectangle { // Focus rectangle
x: -3px;
y: x;
width: parent.width - 2*x;
height: parent.height - 2*y;
border-width: enabled && has-focus ? 1px : 0px;
border-color: Palette.black;
}
}
SpinBoxButton := Rectangle {
callback clicked <=> touch.clicked;
property<string> text; // text and font-size are not used, but present in the other styles
property <length> font-size;
property<bool> enabled <=> touch.enabled;
background: !enabled ? transparent
: touch.pressed ? Palette.neutralLight
: touch.has-hover ? Palette.neutralLighter
: Palette.white;
property <color> symbol-color: !enabled ? Palette.neutralTertiary
: touch.pressed || touch.has-hover ? Palette.neutralPrimary
: Palette.neutralSecondary;
touch := TouchArea { }
}
export SpinBox := FocusScope {
property <bool> checked;
property <int> value;
property <int> minimum;
property <int> maximum: 100;
property <image> icon;
property <length> font-size <=> button.font-size;
min-height: max(32px, l.min-height);
horizontal-stretch: 1;
vertical-stretch: 0;
accessible-role: spinbox;
accessible-value: value;
accessible-value-minimum: minimum;
accessible-value-maximum: maximum;
accessible-value-step: (maximum - minimum) / 100;
Rectangle {
background: !enabled ? Palette.neutralLighter : Palette.white;
}
l := GridLayout {
padding-left: 8px;
padding-top: 3px;
padding-bottom: 3px;
text := Text {
rowspan: 2;
text: value;
color: !enabled ? Palette.neutralTertiary : Palette.neutralDark;
horizontal-alignment: left;
vertical-alignment: center;
}
Rectangle { width: 8px; }
button := SpinBoxButton {
width: 25px;
enabled: root.enabled;
Path {
commands: "M978.2,688.9l-84.2,82.1c-15.7,15.3-41.1,15.3-56.7,0l-341-304.2L162.6,764.5c-15.5,15.1-41,15.1-56.6,0l-84.3-82.1c-15.6-15.2-15.6-39.9,0-55.2l446.6-398.2c15.7-15.3,41-15.3,56.7,0l6.9,6.7l446.3,398.1C993.9,649,993.9,673.7,978.2,688.9z";
fill: parent.symbol-color;
height: 33%;
x: (parent.width - width) / 2;
y: (parent.height - height) / 2;
}
clicked => {
if (root.value < root.maximum) {
root.value += 1;
}
}
}
SpinBoxButton {
row: 1; col: 2;
enabled: root.enabled;
Path {
commands: "M21.8,311.1l84.2-82.1c15.7-15.2,41-15.2,56.7,0l341.1,304.1l333.7-297.5c15.5-15.2,41-15.2,56.6,0l84.3,82.1c15.6,15.2,15.6,40,0,55.2L531.7,771c-15.7,15.3-41,15.3-56.7,0l-6.9-6.7L21.8,366.3C6.1,351,6.1,326.3,21.8,311.1z";
fill: parent.symbol-color;
height: 33%;
x: (parent.width - width) / 2;
y: (parent.height - height) / 2;
}
clicked => {
if (root.value > root.minimum) {
root.value -= 1;
}
}
}
}
Rectangle {
x: enabled && has-focus ? -2px : 0px;
y: x;
width: parent.width - 2*x;
height: parent.height - 2*y;
border-radius: 2px;
border-width: !enabled ? 0px : has-focus ? 3px : 1px;
border-color: !enabled ? Palette.neutralLighter
: has-focus ? Palette.themeSecondary
: Palette.neutralDark;
}
key-pressed(event) => {
if (enabled && event.text == Keys.UpArrow && value < maximum) {
value += 1;
accept
} else if (enabled && event.text == Keys.DownArrow && value > minimum) {
value -= 1;
accept
} else {
reject
}
}
}
export Slider := Rectangle {
property<float> maximum: 100;
property<float> minimum: 0;
property<float> value;
property<bool> has-focus <=> fs.has-focus;
property<bool> enabled <=> touch.enabled;
callback changed(float);
min-height: 24px;
min-width: 100px;
horizontal-stretch: 1;
vertical-stretch: 0;
accessible-role: slider;
accessible-value: value;
accessible-value-minimum: minimum;
accessible-value-maximum: maximum;
accessible-value-step: (maximum - minimum) / 100;
Rectangle {
width: parent.width - parent.min-height;
x: parent.height / 2;
height: parent.min-height / 4;
y: (parent.height - height) / 2;
border-radius: height/2;
background: !root.enabled ? Palette.neutralLighter
: touch.has-hover ? Palette.themeLight
: Palette.neutralTertiaryAlt;
}
Rectangle {
width: (parent.width - parent.min-height) * ((value - minimum) / (maximum - minimum));
x: parent.height / 2;
height: parent.min-height / 4;
y: (parent.height - height) / 2;
border-radius: height/2;
background: !root.enabled ? Palette.neutralTertiary
: touch.has-hover ? Palette.themeSecondary
: Palette.neutralSecondary;
}
handle := Rectangle {
property<length> border: 3px;
width: height;
height: parent.height - 2 * border;
border-width: 3px;
border-radius: height / 2;
border-color: !root.enabled ? Palette.neutralTertiaryAlt
: touch.has-hover ? Palette.themePrimary
: Palette.neutralSecondary;
background: Palette.white;
x: (root.width - handle.width) * (value - minimum)/(maximum - minimum);
y: border;
}
touch := TouchArea {
width: parent.width;
height: parent.height;
property <float> pressed-value;
pointer-event(event) => {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
pressed-value = root.value;
}
}
moved => {
if (enabled && pressed) {
value = max(root.minimum, min(root.maximum,
pressed-value + (touch.mouse-x - touch.pressed-x) * (maximum - minimum) / (root.width - handle.width)));
root.changed(value);
}
}
}
fs := FocusScope {
width: 0px;
key-pressed(event) => {
if (enabled && event.text == Keys.RightArrow) {
value = Math.min(value + 1, maximum);
accept
} else if (enabled && event.text == Keys.LeftArrow) {
value = Math.max(value - 1, minimum);
accept
} else {
reject
}
}
}
Rectangle { // Focus rectangle
border-width: enabled && has-focus ? 1px : 0px;
border-color: Palette.black;
}
}
export GroupBox := VerticalLayout {
property <string> title <=> label.text;
property<bool> enabled: true;
spacing: 8px;
padding-top: 16px;
padding-bottom: 8px;
label := Text {
vertical-stretch: 0;
color: !enabled ? Palette.neutralTertiary : Palette.neutralDark;
font-weight: 600;
}
Rectangle {
vertical-stretch: 1;
GridLayout {
@children
}
}
}
export TabWidgetImpl := Rectangle {
property <length> content-x: 0;
property <length> content-y: tabbar-preferred-height;
property <length> content-height: height - tabbar-preferred-height;
property <length> content-width: width;
property <length> tabbar-x: 0;
property <length> tabbar-y: 0;
property <length> tabbar-height: tabbar-preferred-height;
property <length> tabbar-width: width;
property <length> tabbar-preferred-height;
property <length> tabbar-preferred-width;
property <length> content-min-height;
property <length> content-min-width;
property <int> current-index;
property <int> current-focused;
preferred-width: content-min-width;
min-width: max(content-min-width, tabbar-preferred-width);
preferred-height: content-min-height + tabbar-preferred-height;
min-height: content-min-height + tabbar-preferred-height;
}
export TabImpl := Rectangle {
property<string> title <=> t.text;
//property<image> icon;
property<bool> enabled: true;
property<bool> has-focus: current-focused == tab-index;
property<bool> pressed;
property<int> current; // The currently selected tab
property<int> current-focused; // The currently focused tab
property<int> tab-index; // The index of this tab
property<int> num-tabs; // The total number of tabs
min-height: t.preferred-height + 16px;
preferred-width: t.preferred-width + 16px;
background: !enabled ? Palette.neutralLighter
: touch.pressed ? Palette.neutralLight
: touch.has-hover ? Palette.neutralLighter
: Palette.white;
horizontal-stretch: 0;
vertical-stretch: 0;
accessible-role: tab;
accessible-label <=> title;
touch := TouchArea {
enabled <=> root.enabled;
clicked => {
current = tab-index;
}
}
t := Text {
width: parent.width;
height: parent.height;
vertical-alignment: center;
horizontal-alignment: center;
color: !enabled ? Palette.neutralTertiary : Palette.neutralPrimary;
font-weight: root.current == root.tab-index ? 600 : 500;
}
Rectangle {
height: 3px;
width: touch.has-hover && root.current == root.tab-index ? parent.width : parent.width - 16px;
animate width { duration: 250ms; easing: ease-out; }
background: root.current == root.tab-index ? Palette.themeSecondary : transparent;
y: parent.height - height;
x: (parent.width - width) / 2;
}
Rectangle { // Focus Rectangle
border-width: enabled && has-focus ? 1px : 0px;
border-color: Palette.black;
}
}
export TabBarImpl := Rectangle {
// injected properties:
property<int> current; // The currently selected tab
property<int> current-focused: fs.has-focus ? fs.focused-tab : -1; // The currently focused tab
property<int> num-tabs; // The total number of tabs
HorizontalLayout {
spacing: 8px;
alignment: start;
@children
}
accessible-role: tab;
accessible-delegate-focus: current-focused >= 0 ? current-focused : current;
fs := FocusScope {
width: 0px; // Do not react on clicks
property<int> focused-tab: 0;
key-pressed(event) => {
if (event.text == "\n") {
current = current-focused;
return accept;
}
if (event.text == Keys.LeftArrow) {
focused-tab = Math.max(focused-tab - 1, 0);
return accept;
}
if (event.text == Keys.RightArrow) {
focused-tab = Math.min(focused-tab + 1, num-tabs - 1);
return accept;
}
return reject;
}
key-released(event) => {
if (event.text == " ") {
current = current-focused;
return accept;
}
return reject;
}
}
}
export TabWidget := TabWidget {}
export LineEdit := Rectangle {
property <length> font-size <=> inner.font-size;
property <string> text <=> inner.text;
property <string> placeholder-text <=> inner.placeholder-text;
property <bool> has-focus: inner.has-focus;
property <bool> enabled <=> inner.enabled;
property input-type <=> inner.input-type;
property horizontal-alignment <=> inner.horizontal-alignment;
property read-only <=> inner.read-only;
callback accepted <=> inner.accepted;
callback edited <=> inner.edited;
forward-focus: inner;
// border-color: root.has-focus ? Palette.highlight-background : #ffffff;
horizontal-stretch: 1;
vertical-stretch: 0;
min-height: max(32px, l.min-height);
background: !enabled ? Palette.neutralLighter : Palette.white;
border-radius: 2px;
border-width: !enabled ? 0px : has-focus ? 2px : 1px;
border-color: !enabled ? Palette.neutralLighter
: has-focus ? Palette.themeSecondary
: Palette.neutralPrimary;
l := HorizontalLayout {
padding-left: 8px;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 3px;
inner := LineEditInner {
placeholder-color: !enabled ? Palette.neutralTertiary : Palette.neutralSecondary;
}
}
}
export ListView := ScrollView {
@children
}
export StandardListView := ListView {
property<[StandardListViewItem]> model;
property<int> current-item: -1;
for item[idx] in model : Rectangle {
l := HorizontalLayout {
padding: 8px;
spacing: 0px;
t := Text {
text: item.text;
color: Palette.neutralPrimary;
}
}
background: idx == root.current-item ? Palette.neutralLighter
: touch.has-hover ? Palette.neutralLighterAlt : transparent;
touch := TouchArea {
width: parent.width;
height: parent.height;
clicked => { current-item = idx; }
}
}
FocusScope {
key-pressed(event) => {
if (event.text == Keys.UpArrow && current-item > 0) {
current-item -= 1;
return accept;
} else if (event.text == Keys.DownArrow && current-item + 1 < model.length) {
current-item += 1;
return accept;
}
reject
}
}
}
export ComboBox := FocusScope {
property <[string]> model;
property <int> current-index : 0;
property <string> current-value: model[current-index];
//property <bool> is-open: false;
callback selected(string);
accessible-role: combobox;
accessible-value <=> current-value;
key-pressed(event) => {
if (event.text == Keys.UpArrow) {
current-index = Math.max(current-index - 1, 0);
current-value = model[current-index];
return accept;
} else if (event.text == Keys.DownArrow) {
current-index = Math.min(current-index + 1, model.length - 1);
current-value = model[current-index];
return accept;
// PopupWindow can not get hidden again at this time, so do not allow to pop that up.
// } else if (event.text == Keys.Return) {
// touch.clicked()
// return accept;
}
return reject;
}
Rectangle {
background: !enabled ? Palette.neutralLighter : Palette.white;
border-radius: 2px;
border-width: !enabled ? 0px : has-focus ? 3px : 1px;
border-color: !enabled ? Palette.neutralLighter
: has-focus ? Palette.themeSecondary
: Palette.neutralPrimary;
}
horizontal-stretch: 1;
vertical-stretch: 0;
min-width: 170px;
min-height: max(32px, l.min-height);
l := HorizontalLayout {
padding-left: 8px;
padding-right: 8px;
padding-bottom: 3px;
padding-top: 3px;
spacing: 8px;
t := Text {
text <=> root.current-value;
horizontal-alignment: left;
vertical-alignment: center;
horizontal-stretch: 1;
color: !enabled ? Palette.neutralTertiary
: root.has-focus || touch.has-hover ? Palette.neutralPrimary
: Palette.neutralSecondary;
min-width: 0;
}
Rectangle {
width: 25px;
Path {
x: (parent.width - width) / 2;
y: (parent.height - height) / 2;
height: 8px;
width: 25px;
commands: "M.22.4.5.64.78.4.74.36.5.6.26.36z";
fill: t.color;
}
}
}
touch := TouchArea {
enabled <=> root.enabled;
clicked => {
root.focus();
popup.show();
}
}
popup := PopupWindow {
y: root.height;
width: root.width;
Rectangle {
border-color: Palette.neutralLighter;
border-width: 1px;
/*drop-shadow-color: Palette.neutralTertiary;
drop-shadow-blur: 5px;*/
background: Palette.white;
}
VerticalLayout {
for value[idx] in root.model: Rectangle {
background: idx == root.current-index ? Palette.neutralLighter
: item-area.has-hover ? Palette.neutralLighterAlt : transparent;
VerticalLayout {
padding: 10px;
Text {
text: value;
}
}
item-area := TouchArea {
width: 100%;
height: 100%;
clicked => {
if (root.enabled) {
root.current-index = idx;
root.current-value = value;
root.selected(root.current-value);
}
}
}
}
}
}
}
export VerticalBox := VerticalLayout {
spacing: StyleMetrics.layout-spacing;
padding: StyleMetrics.layout-padding;
}
export HorizontalBox := HorizontalLayout {
spacing: StyleMetrics.layout-spacing;
padding: StyleMetrics.layout-padding;
}
export GridBox := GridLayout {
spacing: StyleMetrics.layout-spacing;
padding: StyleMetrics.layout-padding;
}

View file

@ -0,0 +1,6 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
export global ColorSchemeSelector := {
property<bool> dark-color-scheme: true;
}

View file

@ -0,0 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }

View file

@ -0,0 +1,16 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
import {
StyleMetrics, ScrollView, Button, StandardButton, TextEdit,
AboutSlint, AboutSlint as AboutSixtyFPS, CheckBox, SpinBox, Slider, GroupBox, TabWidgetImpl,
TabImpl, TabBarImpl, TabWidget, LineEdit, ListView, StandardListView, ComboBox,
VerticalBox, HorizontalBox, GridBox
} from "../fluent-base/std-widgets.slint";
export {
StyleMetrics, ScrollView, Button, StandardButton, TextEdit,
AboutSlint, AboutSlint as AboutSixtyFPS, CheckBox, SpinBox, Slider, GroupBox, TabWidgetImpl,
TabImpl, TabBarImpl, TabWidget, LineEdit, ListView, StandardListView, ComboBox,
VerticalBox, HorizontalBox, GridBox
}

View file

@ -0,0 +1,6 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
export global ColorSchemeSelector := {
property<bool> dark-color-scheme: false;
}

View file

@ -0,0 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
import { StyleMetrics, ScrollView, Button } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }

View file

@ -0,0 +1,16 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
import {
StyleMetrics, ScrollView, Button, StandardButton, TextEdit,
AboutSlint, AboutSlint as AboutSixtyFPS, CheckBox, SpinBox, Slider, GroupBox, TabWidgetImpl,
TabImpl, TabBarImpl, TabWidget, LineEdit, ListView, StandardListView, ComboBox,
VerticalBox, HorizontalBox, GridBox
} from "../fluent-base/std-widgets.slint";
export {
StyleMetrics, ScrollView, Button, StandardButton, TextEdit,
AboutSlint, AboutSlint as AboutSixtyFPS, CheckBox, SpinBox, Slider, GroupBox, TabWidgetImpl,
TabImpl, TabBarImpl, TabWidget, LineEdit, ListView, StandardListView, ComboBox,
VerticalBox, HorizontalBox, GridBox
}

View file

@ -0,0 +1,6 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
export global ColorSchemeSelector := {
property<bool> dark-color-scheme: SlintInternal.dark-color-scheme();
}

View file

@ -1,244 +1,5 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
export global Palette := {
property<bool> dark-color-scheme: SlintInternal.dark-color-scheme();
// The colors in light mode are default palette. In the
// Fluent UI Theme Designer they match the colors produced
// with Primary Color = #0078d4, Text Color = #201f1e and Background Color = #ffffff
// The dark mode colors are produced in the Fluent UI Theme Designer by swapping
// Text Color and Background Color from light mode, applying the variations below
// and darkening the "white" and brightening the "neutralLight" variants for improved
// contrast (esp. on buttons).
property<color> themeDarker: #004578;
property<color> themeDark: #005a9e;
property<color> themeDarkAlt: #106ebe;
property<color> themePrimary: #0078d4;
property<color> themeSecondary: #2b88d8;
property<color> themeTertiary: #71afe5;
property<color> themeLight: #c7e0f4;
property<color> themeLighter: #deecf9;
property<color> themeLighterAlt: #eff6fc;
property<color> black: !dark-color-scheme ? #000000 : #f8f8f8;
property<color> blackTranslucent40: rgba(0,0,0,0.4);
property<color> neutralDark: !dark-color-scheme ? #201f1e : #f4f4f4;
property<color> neutralPrimary: !dark-color-scheme ? #323130 : #ffffff;
property<color> neutralPrimaryAlt: !dark-color-scheme ? #3b3a39 : #dadada;
property<color> neutralSecondary: !dark-color-scheme ? #605e5c : #d0d0d0;
property<color> neutralSecondaryAlt: #8a8886;
property<color> neutralTertiary: !dark-color-scheme ? #a19f9d : #c8c8c8;
property<color> neutralTertiaryAlt: !dark-color-scheme ? #c8c6c4 : #6d6d6d;
property<color> neutralQuaternary: #d2d0ce;
property<color> neutralQuaternaryAlt: !dark-color-scheme ? #e1dfdd : #484848;
property<color> neutralLight: !dark-color-scheme ? #edebe9 : #3f3f3f;
property<color> neutralLighter: !dark-color-scheme ? #f3f2f1 : #313131;
property<color> neutralLighterAlt: !dark-color-scheme ? #faf9f8 : #282828;
property<color> accent: #0078d4;
property<color> white: !dark-color-scheme ? #ffffff : #1f1f1f;
property<color> whiteTranslucent40: rgba(255,255,255,0.4);
property<color> yellowDark: #d29200;
property<color> yellow: #ffb900;
property<color> yellowLight: #fff100;
property<color> orange: #d83b01;
property<color> orangeLight: #ea4300;
property<color> orangeLighter: #ff8c00;
property<color> redDark: #a4262c;
property<color> red: #e81123;
property<color> magentaDark: #5c005c;
property<color> magenta: #b4009e;
property<color> magentaLight: #e3008c;
property<color> purpleDark: #32145a;
property<color> purple: #5c2d91;
property<color> purpleLight: #b4a0ff;
property<color> blueDark: #002050;
property<color> blueMid: #00188f;
property<color> blue: #0078d4;
property<color> blueLight: #00bcf2;
property<color> tealDark: #004b50;
property<color> teal: #008272;
property<color> tealLight: #00b294;
property<color> greenDark: #004b1c;
property<color> green: #107c10;
property<color> greenLight: #bad80a;
}
export global StyleMetrics := {
property<length> layout-spacing: 8px;
property<length> layout-padding: 8px;
property<length> text-cursor-width: 2px;
property<brush> window-background: Palette.white;
property<color> default-text-color: Palette.neutralDark;
property<brush> textedit-background: Palette.white;
property<color> textedit-text-color: Palette.neutralPrimary;
property<brush> textedit-background-disabled: Palette.neutralLighter;
property<color> textedit-text-color-disabled: Palette.neutralTertiary;
property<bool> dark-color-scheme: Palette.dark-color-scheme;
}
export Button := Rectangle {
callback clicked;
property<string> text <=> text.text;
property<bool> has-focus <=> fs.has-focus;
property<bool> pressed: self.enabled && touch.pressed;
property<bool> enabled <=> touch.enabled;
property<bool> checkable;
property<bool> checked;
property<image> icon;
property<length> font-size <=> text.font-size;
accessible-role: button;
accessible-label <=> text.text;
border-width: 1px;
border-radius: 2px;
border-color: !enabled ? Palette.neutralLighter : Palette.neutralSecondaryAlt;
background: !enabled ? Palette.neutralLighter
: (touch.pressed || checked) ? Palette.neutralLight
: touch.has-hover ? Palette.neutralLighter
: Palette.white;
horizontal-stretch: 0;
vertical-stretch: 0;
min-height: max(32px, l.min-height);
l := HorizontalLayout {
padding-left: 16px;
padding-right: 16px;
spacing: 8px;
padding-top: 3px;
padding-bottom: 3px;
if (icon.width > 0 && icon.height > 0): Image {
source <=> icon;
width: 24px;
}
text := Text {
color: !enabled ? Palette.neutralTertiary : Palette.neutralDark;
horizontal-alignment: center;
vertical-alignment: center;
font-weight: 600;
}
}
touch := TouchArea {
clicked => {
if (root.checkable) {
root.checked = !root.checked;
}
root.clicked();
}
}
fs := FocusScope {
width: 0px; // Do not react on clicks
enabled <=> root.enabled;
key-pressed(event) => {
if (event.text == " " || event.text == "\n") {
touch.clicked();
return accept;
}
return reject;
}
}
Rectangle { // Focus rectangle
x: 3px;
y: x;
width: parent.width - 2*x;
height: parent.height - 2*y;
border-width: enabled && has-focus? 1px : 0px;
border-color: Palette.black;
}
}
ScrollBar := Rectangle {
background: Palette.white;
// border-color: Palette.button-background;
border-width: 1px;
property <bool> horizontal;
property<length> maximum;
property<length> page-size;
// this is always negative and bigger than -maximum
property<length> value;
handle := Rectangle {
width: !horizontal ? parent.width : maximum <= 0phx ? 0phx : parent.width * (page-size / (maximum + page-size));
height: horizontal ? parent.height : maximum <= 0phx ? 0phx : parent.height * (page-size / (maximum + page-size));
border-radius: (horizontal ? self.height : self.width) / 2;
background: touch-area.pressed ? Palette.themePrimary :
touch-area.has-hover ? Palette.themeSecondary : Palette.neutralTertiary;
x: !horizontal ? 0phx : (root.width - handle.width) * (-value / maximum);
y: horizontal ? 0phx : (root.height - handle.height) * (-value / maximum);
}
touch-area := TouchArea {
width: parent.width;
height: parent.height;
property <length> pressed-value;
pointer-event(event) => {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
pressed-value = -root.value;
}
}
moved => {
if (enabled && pressed) {
value = -max(0px, min(root.maximum, pressed-value + (
horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (maximum / (root.width - handle.width))
: (touch-area.mouse-y - touch-area.pressed-y) * (maximum / (root.height - handle.height))
)));
}
}
}
}
export ScrollView := Rectangle {
property <length> viewport-width <=> fli.viewport-width;
property <length> viewport-height <=> fli.viewport-height;
property <length> viewport-x <=> fli.viewport-x;
property <length> viewport-y <=> fli.viewport-y;
property <length> visible-width <=> fli.width;
property <length> visible-height <=> fli.height;
property <bool> enabled: true;
property <bool> has-focus;
min-height: 50px;
min-width: 50px;
horizontal-stretch: 1;
vertical-stretch: 1;
border-radius: 2px;
border-width: !enabled ? 0px : has-focus ? 2px : 1px;
border-color: !enabled ? Palette.neutralLighter
: has-focus ? Palette.themeSecondary
: Palette.neutralPrimary;
fli := Flickable {
@children
x: 2px;
y: 2px;
interactive: false;
viewport-y <=> vbar.value;
viewport-x <=> hbar.value;
width: parent.width - vbar.width - 4px;
height: parent.height - hbar.height - 4px;
}
vbar := ScrollBar {
width: 16px;
x: fli.width + fli.x;
y: fli.y;
height: fli.height;
horizontal: false;
maximum: fli.viewport-height - fli.height;
page-size: fli.height;
}
hbar := ScrollBar {
height: 16px;
y: fli.height + fli.y;
x: fli.x;
width: fli.width;
horizontal: true;
maximum: fli.viewport-width - fli.width;
page-size: fli.width;
}
}
import { StyleMetrics, ScrollView, Button } from "../fluent-base/std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button }

View file

@ -1,657 +1,16 @@
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
// cSpell: ignore combobox spinbox standardbutton
import {
StyleMetrics, ScrollView, Button, StandardButton, TextEdit,
AboutSlint, AboutSlint as AboutSixtyFPS, CheckBox, SpinBox, Slider, GroupBox, TabWidgetImpl,
TabImpl, TabBarImpl, TabWidget, LineEdit, ListView, StandardListView, ComboBox,
VerticalBox, HorizontalBox, GridBox
} from "../fluent-base/std-widgets.slint";
import { LineEditInner, TextEdit, AboutSlint } from "../common/common.slint";
import { StandardButton } from "../common/standardbutton.slint";
import { StyleMetrics, ScrollView, Button, Palette } from "std-widgets-impl.slint";
export { StyleMetrics, ScrollView, Button, StandardButton, TextEdit, AboutSlint, AboutSlint as AboutSixtyFPS }
export CheckBox := Rectangle {
callback toggled;
property <string> text <=> text.text;
property <bool> checked;
property <bool> has-focus;
property<bool> enabled: true;
min-height: 20px;
horizontal-stretch: 0;
vertical-stretch: 0;
accessible-label <=> text.text;
accessible-checkable: true;
accessible-checked <=> checked;
accessible-role: checkbox;
HorizontalLayout {
spacing: 8px;
VerticalLayout {
alignment: center;
Rectangle {
border-width: 1px;
border-radius: 2px;
/* border-color: !enabled ? Palette.neutralLighter : Palette.neutralSecondaryAlt;
background: !enabled ? Palette.white
: touch.pressed ? Palette.neutralLight
: touch.has-hover ? Palette.neutralLighter
: Palette.themePrimary;*/
border-color: checked ? background : !enabled ? Palette.neutralTertiaryAlt : Palette.neutralSecondaryAlt;
background: !checked ? Palette.white
: !enabled ? Palette.neutralTertiaryAlt
: touch.has-hover || touch.pressed ? Palette.themeDark
: Palette.themePrimary;
animate background { duration: 250ms; easing: ease; }
//width: height;
vertical-stretch: 0;
width: 20px;
height: 20px;
if (checked || touch.has-hover || touch.pressed) : Path {
width: 66%;
height: 66%;
x: (parent.width - width) / 2;
y: (parent.height - height) / 2;
commands: "M.22.5.42.7.78.34.74.3.42.62.26.54z";
fill: checked ? Palette.white : Palette.neutralSecondaryAlt;
}
}
}
text := Text {
color: !enabled ? Palette.neutralTertiary : Palette.neutralDark;
horizontal-alignment: left;
vertical-alignment: center;
vertical-stretch: 1;
}
}
touch := TouchArea {
enabled <=> root.enabled;
clicked => {
if (root.enabled) {
root.checked = !root.checked;
root.toggled();
}
}
}
fs := FocusScope {
width: 0px; // Do not react on clicks
enabled <=> root.enabled;
has_focus <=> root.has-focus;
key-pressed(event) => {
if (event.text == " " || event.text == "\n") {
touch.clicked();
return accept;
}
return reject;
}
}
Rectangle { // Focus rectangle
x: -3px;
y: x;
width: parent.width - 2*x;
height: parent.height - 2*y;
border-width: enabled && has-focus ? 1px : 0px;
border-color: Palette.black;
}
}
SpinBoxButton := Rectangle {
callback clicked <=> touch.clicked;
property<string> text; // text and font-size are not used, but present in the other styles
property <length> font-size;
property<bool> enabled <=> touch.enabled;
background: !enabled ? transparent
: touch.pressed ? Palette.neutralLight
: touch.has-hover ? Palette.neutralLighter
: Palette.white;
property <color> symbol-color: !enabled ? Palette.neutralTertiary
: touch.pressed || touch.has-hover ? Palette.neutralPrimary
: Palette.neutralSecondary;
touch := TouchArea { }
}
export SpinBox := FocusScope {
property <bool> checked;
property <int> value;
property <int> minimum;
property <int> maximum: 100;
property <image> icon;
property <length> font-size <=> button.font-size;
min-height: max(32px, l.min-height);
horizontal-stretch: 1;
vertical-stretch: 0;
accessible-role: spinbox;
accessible-value: value;
accessible-value-minimum: minimum;
accessible-value-maximum: maximum;
accessible-value-step: (maximum - minimum) / 100;
Rectangle {
background: !enabled ? Palette.neutralLighter : Palette.white;
}
l := GridLayout {
padding-left: 8px;
padding-top: 3px;
padding-bottom: 3px;
text := Text {
rowspan: 2;
text: value;
color: !enabled ? Palette.neutralTertiary : Palette.neutralDark;
horizontal-alignment: left;
vertical-alignment: center;
}
Rectangle { width: 8px; }
button := SpinBoxButton {
width: 25px;
enabled: root.enabled;
Path {
commands: "M978.2,688.9l-84.2,82.1c-15.7,15.3-41.1,15.3-56.7,0l-341-304.2L162.6,764.5c-15.5,15.1-41,15.1-56.6,0l-84.3-82.1c-15.6-15.2-15.6-39.9,0-55.2l446.6-398.2c15.7-15.3,41-15.3,56.7,0l6.9,6.7l446.3,398.1C993.9,649,993.9,673.7,978.2,688.9z";
fill: parent.symbol-color;
height: 33%;
x: (parent.width - width) / 2;
y: (parent.height - height) / 2;
}
clicked => {
if (root.value < root.maximum) {
root.value += 1;
}
}
}
SpinBoxButton {
row: 1; col: 2;
enabled: root.enabled;
Path {
commands: "M21.8,311.1l84.2-82.1c15.7-15.2,41-15.2,56.7,0l341.1,304.1l333.7-297.5c15.5-15.2,41-15.2,56.6,0l84.3,82.1c15.6,15.2,15.6,40,0,55.2L531.7,771c-15.7,15.3-41,15.3-56.7,0l-6.9-6.7L21.8,366.3C6.1,351,6.1,326.3,21.8,311.1z";
fill: parent.symbol-color;
height: 33%;
x: (parent.width - width) / 2;
y: (parent.height - height) / 2;
}
clicked => {
if (root.value > root.minimum) {
root.value -= 1;
}
}
}
}
Rectangle {
x: enabled && has-focus ? -2px : 0px;
y: x;
width: parent.width - 2*x;
height: parent.height - 2*y;
border-radius: 2px;
border-width: !enabled ? 0px : has-focus ? 3px : 1px;
border-color: !enabled ? Palette.neutralLighter
: has-focus ? Palette.themeSecondary
: Palette.neutralDark;
}
key-pressed(event) => {
if (enabled && event.text == Keys.UpArrow && value < maximum) {
value += 1;
accept
} else if (enabled && event.text == Keys.DownArrow && value > minimum) {
value -= 1;
accept
} else {
reject
}
}
}
export Slider := Rectangle {
property<float> maximum: 100;
property<float> minimum: 0;
property<float> value;
property<bool> has-focus <=> fs.has-focus;
property<bool> enabled <=> touch.enabled;
callback changed(float);
min-height: 24px;
min-width: 100px;
horizontal-stretch: 1;
vertical-stretch: 0;
accessible-role: slider;
accessible-value: value;
accessible-value-minimum: minimum;
accessible-value-maximum: maximum;
accessible-value-step: (maximum - minimum) / 100;
Rectangle {
width: parent.width - parent.min-height;
x: parent.height / 2;
height: parent.min-height / 4;
y: (parent.height - height) / 2;
border-radius: height/2;
background: !root.enabled ? Palette.neutralLighter
: touch.has-hover ? Palette.themeLight
: Palette.neutralTertiaryAlt;
}
Rectangle {
width: (parent.width - parent.min-height) * ((value - minimum) / (maximum - minimum));
x: parent.height / 2;
height: parent.min-height / 4;
y: (parent.height - height) / 2;
border-radius: height/2;
background: !root.enabled ? Palette.neutralTertiary
: touch.has-hover ? Palette.themeSecondary
: Palette.neutralSecondary;
}
handle := Rectangle {
property<length> border: 3px;
width: height;
height: parent.height - 2 * border;
border-width: 3px;
border-radius: height / 2;
border-color: !root.enabled ? Palette.neutralTertiaryAlt
: touch.has-hover ? Palette.themePrimary
: Palette.neutralSecondary;
background: Palette.white;
x: (root.width - handle.width) * (value - minimum)/(maximum - minimum);
y: border;
}
touch := TouchArea {
width: parent.width;
height: parent.height;
property <float> pressed-value;
pointer-event(event) => {
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
pressed-value = root.value;
}
}
moved => {
if (enabled && pressed) {
value = max(root.minimum, min(root.maximum,
pressed-value + (touch.mouse-x - touch.pressed-x) * (maximum - minimum) / (root.width - handle.width)));
root.changed(value);
}
}
}
fs := FocusScope {
width: 0px;
key-pressed(event) => {
if (enabled && event.text == Keys.RightArrow) {
value = Math.min(value + 1, maximum);
accept
} else if (enabled && event.text == Keys.LeftArrow) {
value = Math.max(value - 1, minimum);
accept
} else {
reject
}
}
}
Rectangle { // Focus rectangle
border-width: enabled && has-focus ? 1px : 0px;
border-color: Palette.black;
}
}
export GroupBox := VerticalLayout {
property <string> title <=> label.text;
property<bool> enabled: true;
spacing: 8px;
padding-top: 16px;
padding-bottom: 8px;
label := Text {
vertical-stretch: 0;
color: !enabled ? Palette.neutralTertiary : Palette.neutralDark;
font-weight: 600;
}
Rectangle {
vertical-stretch: 1;
GridLayout {
@children
}
}
}
export TabWidgetImpl := Rectangle {
property <length> content-x: 0;
property <length> content-y: tabbar-preferred-height;
property <length> content-height: height - tabbar-preferred-height;
property <length> content-width: width;
property <length> tabbar-x: 0;
property <length> tabbar-y: 0;
property <length> tabbar-height: tabbar-preferred-height;
property <length> tabbar-width: width;
property <length> tabbar-preferred-height;
property <length> tabbar-preferred-width;
property <length> content-min-height;
property <length> content-min-width;
property <int> current-index;
property <int> current-focused;
preferred-width: content-min-width;
min-width: max(content-min-width, tabbar-preferred-width);
preferred-height: content-min-height + tabbar-preferred-height;
min-height: content-min-height + tabbar-preferred-height;
}
export TabImpl := Rectangle {
property<string> title <=> t.text;
//property<image> icon;
property<bool> enabled: true;
property<bool> has-focus: current-focused == tab-index;
property<bool> pressed;
property<int> current; // The currently selected tab
property<int> current-focused; // The currently focused tab
property<int> tab-index; // The index of this tab
property<int> num-tabs; // The total number of tabs
min-height: t.preferred-height + 16px;
preferred-width: t.preferred-width + 16px;
background: !enabled ? Palette.neutralLighter
: touch.pressed ? Palette.neutralLight
: touch.has-hover ? Palette.neutralLighter
: Palette.white;
horizontal-stretch: 0;
vertical-stretch: 0;
accessible-role: tab;
accessible-label <=> title;
touch := TouchArea {
enabled <=> root.enabled;
clicked => {
current = tab-index;
}
}
t := Text {
width: parent.width;
height: parent.height;
vertical-alignment: center;
horizontal-alignment: center;
color: !enabled ? Palette.neutralTertiary : Palette.neutralPrimary;
font-weight: root.current == root.tab-index ? 600 : 500;
}
Rectangle {
height: 3px;
width: touch.has-hover && root.current == root.tab-index ? parent.width : parent.width - 16px;
animate width { duration: 250ms; easing: ease-out; }
background: root.current == root.tab-index ? Palette.themeSecondary : transparent;
y: parent.height - height;
x: (parent.width - width) / 2;
}
Rectangle { // Focus Rectangle
border-width: enabled && has-focus ? 1px : 0px;
border-color: Palette.black;
}
}
export TabBarImpl := Rectangle {
// injected properties:
property<int> current; // The currently selected tab
property<int> current-focused: fs.has-focus ? fs.focused-tab : -1; // The currently focused tab
property<int> num-tabs; // The total number of tabs
HorizontalLayout {
spacing: 8px;
alignment: start;
@children
}
accessible-role: tab;
accessible-delegate-focus: current-focused >= 0 ? current-focused : current;
fs := FocusScope {
width: 0px; // Do not react on clicks
property<int> focused-tab: 0;
key-pressed(event) => {
if (event.text == "\n") {
current = current-focused;
return accept;
}
if (event.text == Keys.LeftArrow) {
focused-tab = Math.max(focused-tab - 1, 0);
return accept;
}
if (event.text == Keys.RightArrow) {
focused-tab = Math.min(focused-tab + 1, num-tabs - 1);
return accept;
}
return reject;
}
key-released(event) => {
if (event.text == " ") {
current = current-focused;
return accept;
}
return reject;
}
}
}
export TabWidget := TabWidget {}
export LineEdit := Rectangle {
property <length> font-size <=> inner.font-size;
property <string> text <=> inner.text;
property <string> placeholder-text <=> inner.placeholder-text;
property <bool> has-focus: inner.has-focus;
property <bool> enabled <=> inner.enabled;
property input-type <=> inner.input-type;
property horizontal-alignment <=> inner.horizontal-alignment;
property read-only <=> inner.read-only;
callback accepted <=> inner.accepted;
callback edited <=> inner.edited;
forward-focus: inner;
// border-color: root.has-focus ? Palette.highlight-background : #ffffff;
horizontal-stretch: 1;
vertical-stretch: 0;
min-height: max(32px, l.min-height);
background: !enabled ? Palette.neutralLighter : Palette.white;
border-radius: 2px;
border-width: !enabled ? 0px : has-focus ? 2px : 1px;
border-color: !enabled ? Palette.neutralLighter
: has-focus ? Palette.themeSecondary
: Palette.neutralPrimary;
l := HorizontalLayout {
padding-left: 8px;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 3px;
inner := LineEditInner {
placeholder-color: !enabled ? Palette.neutralTertiary : Palette.neutralSecondary;
}
}
}
export ListView := ScrollView {
@children
}
export StandardListView := ListView {
property<[StandardListViewItem]> model;
property<int> current-item: -1;
for item[idx] in model : Rectangle {
l := HorizontalLayout {
padding: 8px;
spacing: 0px;
t := Text {
text: item.text;
color: Palette.neutralPrimary;
}
}
background: idx == root.current-item ? Palette.neutralLighter
: touch.has-hover ? Palette.neutralLighterAlt : transparent;
touch := TouchArea {
width: parent.width;
height: parent.height;
clicked => { current-item = idx; }
}
}
FocusScope {
key-pressed(event) => {
if (event.text == Keys.UpArrow && current-item > 0) {
current-item -= 1;
return accept;
} else if (event.text == Keys.DownArrow && current-item + 1 < model.length) {
current-item += 1;
return accept;
}
reject
}
}
}
export ComboBox := FocusScope {
property <[string]> model;
property <int> current-index : 0;
property <string> current-value: model[current-index];
//property <bool> is-open: false;
callback selected(string);
accessible-role: combobox;
accessible-value <=> current-value;
key-pressed(event) => {
if (event.text == Keys.UpArrow) {
current-index = Math.max(current-index - 1, 0);
current-value = model[current-index];
return accept;
} else if (event.text == Keys.DownArrow) {
current-index = Math.min(current-index + 1, model.length - 1);
current-value = model[current-index];
return accept;
// PopupWindow can not get hidden again at this time, so do not allow to pop that up.
// } else if (event.text == Keys.Return) {
// touch.clicked()
// return accept;
}
return reject;
}
Rectangle {
background: !enabled ? Palette.neutralLighter : Palette.white;
border-radius: 2px;
border-width: !enabled ? 0px : has-focus ? 3px : 1px;
border-color: !enabled ? Palette.neutralLighter
: has-focus ? Palette.themeSecondary
: Palette.neutralPrimary;
}
horizontal-stretch: 1;
vertical-stretch: 0;
min-width: 170px;
min-height: max(32px, l.min-height);
l := HorizontalLayout {
padding-left: 8px;
padding-right: 8px;
padding-bottom: 3px;
padding-top: 3px;
spacing: 8px;
t := Text {
text <=> root.current-value;
horizontal-alignment: left;
vertical-alignment: center;
horizontal-stretch: 1;
color: !enabled ? Palette.neutralTertiary
: root.has-focus || touch.has-hover ? Palette.neutralPrimary
: Palette.neutralSecondary;
min-width: 0;
}
Rectangle {
width: 25px;
Path {
x: (parent.width - width) / 2;
y: (parent.height - height) / 2;
height: 8px;
width: 25px;
commands: "M.22.4.5.64.78.4.74.36.5.6.26.36z";
fill: t.color;
}
}
}
touch := TouchArea {
enabled <=> root.enabled;
clicked => {
root.focus();
popup.show();
}
}
popup := PopupWindow {
y: root.height;
width: root.width;
Rectangle {
border-color: Palette.neutralLighter;
border-width: 1px;
/*drop-shadow-color: Palette.neutralTertiary;
drop-shadow-blur: 5px;*/
background: Palette.white;
}
VerticalLayout {
for value[idx] in root.model: Rectangle {
background: idx == root.current-index ? Palette.neutralLighter
: item-area.has-hover ? Palette.neutralLighterAlt : transparent;
VerticalLayout {
padding: 10px;
Text {
text: value;
}
}
item-area := TouchArea {
width: 100%;
height: 100%;
clicked => {
if (root.enabled) {
root.current-index = idx;
root.current-value = value;
root.selected(root.current-value);
}
}
}
}
}
}
}
export VerticalBox := VerticalLayout {
spacing: StyleMetrics.layout-spacing;
padding: StyleMetrics.layout-padding;
}
export HorizontalBox := HorizontalLayout {
spacing: StyleMetrics.layout-spacing;
padding: StyleMetrics.layout-padding;
}
export GridBox := GridLayout {
spacing: StyleMetrics.layout-spacing;
padding: StyleMetrics.layout-padding;
export {
StyleMetrics, ScrollView, Button, StandardButton, TextEdit,
AboutSlint, AboutSlint as AboutSixtyFPS, CheckBox, SpinBox, Slider, GroupBox, TabWidgetImpl,
TabImpl, TabBarImpl, TabWidget, LineEdit, ListView, StandardListView, ComboBox,
VerticalBox, HorizontalBox, GridBox
}