1786205236
@@ -1,3 +1,5 @@
|
||||
import { BottomAppBar } from "./material/material.slint";
|
||||
|
||||
import {
|
||||
Palette,
|
||||
StyleMetrics,
|
||||
@@ -11,6 +13,8 @@ import {
|
||||
import { Climate, WeatherData } from "./climate.slint";
|
||||
import { Calendar } from "calendar.slint";
|
||||
import { Energy } from "energy.slint";
|
||||
import { TabBar, Switch, MaterialStyleMetrics } from "material/material.slint";
|
||||
import { Icons } from "material/ui/icons/icons.slint";
|
||||
|
||||
export struct LightData {
|
||||
id: string,
|
||||
@@ -84,6 +88,36 @@ component Thermometer inherits Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
component LightTile inherits Rectangle {
|
||||
in property <LightData> light;
|
||||
callback clicked();
|
||||
|
||||
TouchArea {
|
||||
clicked => {
|
||||
clicked();
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: MaterialStyleMetrics.padding_8;
|
||||
Text {
|
||||
text: light.name;
|
||||
height: 32px;
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Rectangle { }
|
||||
|
||||
sw := Switch {
|
||||
width: 48px;
|
||||
height: 32px;
|
||||
enabled: light.available;
|
||||
checked: light.on;
|
||||
checked_state_changed(checked) => { root.clicked() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component AppWindow inherits Window {
|
||||
width: 1024px;
|
||||
height: 600px;
|
||||
@@ -134,34 +168,23 @@ export component AppWindow inherits Window {
|
||||
Rectangle {
|
||||
preferred-width: root.width;
|
||||
VerticalLayout {
|
||||
if root.page == 0: Climate {
|
||||
if tab-bar.current-index == 0: Climate {
|
||||
is-night: is-night;
|
||||
weather: weather;
|
||||
}
|
||||
if root.page == 1: Calendar { }
|
||||
if root.page == 2: Energy { }
|
||||
if tab-bar.current-index == 1: Calendar { }
|
||||
if tab-bar.current-index == 2: Energy { }
|
||||
|
||||
Rectangle {
|
||||
height: 64px;
|
||||
HorizontalBox {
|
||||
padding-top: 0;
|
||||
Button {
|
||||
text: "Climate";
|
||||
clicked => { root.page = 0; }
|
||||
primary: root.page == 0;
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Calendar";
|
||||
clicked => { root.page = 1; }
|
||||
primary: root.page == 1;
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Energy";
|
||||
clicked => { root.page = 2; }
|
||||
primary: root.page == 2;
|
||||
}
|
||||
tab-bar := TabBar {
|
||||
width: parent.width;
|
||||
items: [
|
||||
{ icon: Icons.heat_pump, text: "Climate" },
|
||||
{ icon: Icons.edit_calendar, text: "Calendar" },
|
||||
{ icon: Icons.energy_savings_leaf, text: "Energy" }
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,10 +192,8 @@ export component AppWindow inherits Window {
|
||||
|
||||
VerticalBox {
|
||||
preferred-width: 0;
|
||||
for light[index] in root.lights: Button {
|
||||
text: light.name;
|
||||
primary: light.on;
|
||||
enabled: light.available;
|
||||
for light[index] in root.lights: LightTile {
|
||||
light: light;
|
||||
clicked => { toggle-light(light.id) }
|
||||
}
|
||||
}
|
||||
|
||||
11
dashboard/ui/material/LICENSE.md
Normal file
@@ -0,0 +1,11 @@
|
||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 -->
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 SixtyFPS GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
30
dashboard/ui/material/README.md
Normal file
@@ -0,0 +1,30 @@
|
||||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Material Design 3 component set for Slint
|
||||
|
||||
[](examples/gallery)
|
||||
|
||||
Welcome to the official Material Design 3 component set for [Slint](https://slint.dev). These UI components adhere to [Material Design 3 guidelines](https://m3.material.io/).
|
||||
The components are intended to use for development of user interfaces with Slint for Android apps, touch friendly interfaces for embedded devices and even for desktop application development.
|
||||
Contributions and feedback from the community are welcome.
|
||||
|
||||
|
||||
## Demos
|
||||
|
||||
[WebAssembly build in the web browser](https://material.slint.dev/wasm/)
|
||||
|
||||
Download and install the [APK for android](https://material.slint.dev/apk/slint_material.apk)
|
||||
|
||||
## Documentation
|
||||
|
||||
View the documentation online at https://material.slint.dev/getting-started/
|
||||
|
||||
### Get Started
|
||||
|
||||
Clone one of our Material Components for Slint templates and follow the instruction from their README:
|
||||
|
||||
- Rust: https://github.com/slint-ui/material-rust-template
|
||||
- C++: https://github.com/slint-ui/material-cpp-template
|
||||
- Node.js/Deno: https://github.com/slint-ui/material-nodejs-template
|
||||
- Python: https://github.com/slint-ui/material-python-template
|
||||
|
||||
66
dashboard/ui/material/material.slint
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// components
|
||||
export { AppBar, SmallAppBar, MediumAppBar, LargeAppBar } from "./ui/components/app_bar.slint";
|
||||
export { Badge } from "./ui/components/badge.slint";
|
||||
export { BottomAppBar } from "./ui/components/bottom_app_bar.slint";
|
||||
export { CheckState, CheckBox, CheckBoxTile } from "./ui/components/check_box.slint";
|
||||
export { ActionChip, FilterChip, InputChip } from "./ui/components/chip.slint";
|
||||
export { DatePickerPopup, DatePickerAdapter } from "./ui/components/date_picker.slint";
|
||||
export { Dialog, FullscreenDialog } from "./ui/components/dialog.slint";
|
||||
export { Drawer, ModalDrawer } from "./ui/components/drawer.slint";
|
||||
export { DropDownMenu } from "./ui/components/drop_down_menu.slint";
|
||||
export { VerticalDivider, HorizontalDivider } from "./ui/components/divider.slint";
|
||||
export { ModalBottomSheet } from "./ui/components/bottom_sheet.slint";
|
||||
export { ElevatedCard, FilledCard, OutlinedCard } from "./ui/components/card.slint";
|
||||
export { ElevatedButton } from "./ui/components/elevated_button.slint";
|
||||
export { Elevation } from "./ui/components/elevation.slint";
|
||||
export { ExtendedTouchArea } from "./ui/components/extended_touch_area.slint";
|
||||
export { FilledButton } from "./ui/components/filled_button.slint";
|
||||
export { FilledIconButton } from "./ui/components/filled_icon_button.slint";
|
||||
export { FloatingActionButton, FABStyle } from "./ui/components/floating_action_button.slint";
|
||||
export { Grid } from "./ui/components/grid.slint";
|
||||
export { Horizontal } from "./ui/components/horizontal.slint";
|
||||
export { Icon } from "./ui/components/icon.slint";
|
||||
export { IconButton } from "./ui/components/icon_button.slint";
|
||||
export { OutlineButton } from "./ui/components/outline_button.slint";
|
||||
export { OutlineIconButton } from "./ui/components/outline_icon_button.slint";
|
||||
export { Avatar, ListTile } from "./ui/components/list.slint";
|
||||
export { ListView } from "./ui/components/list_view.slint";
|
||||
export { MaterialText } from "./ui/components/material_text.slint";
|
||||
export { MaterialWindow, MaterialWindowAdapter } from "./ui/components/material_window.slint";
|
||||
export { PopupMenu } from "./ui/components/menu.slint";
|
||||
export { NavigationBar } from "./ui/components/navigation_bar.slint";
|
||||
export { NavigationDrawer, ModalNavigationDrawer } from "./ui/components/navigation_drawer.slint";
|
||||
export { NavigationRail } from "./ui/components/navigation_rail.slint";
|
||||
export { CircularProgressIndicator, LinearProgressIndicator } from "./ui/components/progress_indicator.slint";
|
||||
export { RadioButton, RadioButtonTile } from "./ui/components/radio_button.slint";
|
||||
export { SearchBar } from "./ui/components/search_bar.slint";
|
||||
export { ScrollView } from "./ui/components/scroll_view.slint";
|
||||
export { Slider } from "./ui/components/slider.slint";
|
||||
export { SnackBar } from "./ui/components/snack_bar.slint";
|
||||
export { StateLayerArea, StateLayer, Ripple } from "./ui/components/state_layer.slint";
|
||||
export { SegmentedButton } from "./ui/components/segmented_button.slint";
|
||||
export { Switch } from "./ui/components/switch.slint";
|
||||
export { TabBar, SecondaryTabBar } from "./ui/components/tab_bar.slint";
|
||||
export { TextButton } from "./ui/components/text_button.slint";
|
||||
export { TextField } from "./ui/components/text_field.slint";
|
||||
export { TimePickerPopup, Time } from "./ui/components/time_picker.slint";
|
||||
export { TonalButton } from "./ui/components/tonal_button.slint";
|
||||
export { TonalIconButton } from "./ui/components/tonal_icon_button.slint";
|
||||
export { ToolTip } from "./ui/components/tooltip.slint";
|
||||
export { Vertical } from "./ui/components/vertical.slint";
|
||||
export { Modal } from "./ui/components/modal.slint";
|
||||
|
||||
// items
|
||||
export { ListItem } from "./ui/items/list_item.slint";
|
||||
export { NavigationItem, NavigationGroup } from "./ui/items/navigation_item.slint";
|
||||
export { MenuItem } from "./ui/items/menu_item.slint";
|
||||
|
||||
// styles
|
||||
export { MaterialAnimations } from "./ui/styling/material_animations.slint";
|
||||
export { MaterialScheme, MaterialSchemes } from "./ui/styling/material_schemes.slint";
|
||||
export { MaterialStyleMetrics } from "./ui/styling/material_style_metrics.slint";
|
||||
export { MaterialPalette } from "./ui/styling/material_palette.slint";
|
||||
export { MaterialTypography } from "./ui/styling/material_typography.slint";
|
||||
256
dashboard/ui/material/ui/components/app_bar.slint
Normal file
@@ -0,0 +1,256 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialTypography, TextStyle } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { IconButtonItem } from "./bottom_app_bar.slint";
|
||||
|
||||
component BaseAppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <bool> show_background;
|
||||
in property <[IconButtonItem]> trailing_buttons;
|
||||
in property <TextHorizontalAlignment> horizontal_alignment;
|
||||
in property <length> spacing;
|
||||
in property <length> vertical_spacing;
|
||||
in property <bool> two_rows;
|
||||
in property <TextStyle> text_style;
|
||||
in property <length> text_padding;
|
||||
in property <length> appbar_padding_right: 0;
|
||||
in property <length> appbar_padding_top: 0;
|
||||
in property <length> appbar_padding_bottom: 0;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked(index: int);
|
||||
|
||||
min_width: layout.min_width;
|
||||
min_height: layout.min_height;
|
||||
|
||||
Rectangle {
|
||||
background: root.show_background ? MaterialPalette.surface_container : MaterialPalette.surface;
|
||||
|
||||
layout := VerticalLayout {
|
||||
padding_top: root.appbar_padding_top;
|
||||
padding_bottom: root.appbar_padding_bottom;
|
||||
min_width: top_layout.min_width;
|
||||
spacing: root.vertical_spacing;
|
||||
|
||||
top_layout := HorizontalLayout {
|
||||
padding_left: root.spacing;
|
||||
padding_right: root.appbar_padding_right;
|
||||
spacing: MaterialStyleMetrics.spacing_6;
|
||||
|
||||
if root.leading_button.icon.width > 0 && root.leading_button.icon.height > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
IconButton {
|
||||
icon: root.leading_button.icon;
|
||||
enabled: root.leading_button.enabled;
|
||||
tooltip: root.leading_button.tooltip;
|
||||
|
||||
clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !root.two_rows : MaterialText {
|
||||
horizontal_stretch: 1;
|
||||
text: root.title;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: root.horizontal_alignment;
|
||||
color: MaterialPalette.on_surface;
|
||||
overflow: elide;
|
||||
style: root.text_style;
|
||||
}
|
||||
|
||||
// spacer
|
||||
if root.two_rows : Rectangle {
|
||||
horizontal_stretch: 1;
|
||||
}
|
||||
|
||||
if root.trailing_buttons.length > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: top_layout.spacing;
|
||||
|
||||
for item[index] in root.trailing_buttons : IconButton {
|
||||
icon: item.icon;
|
||||
enabled: item.enabled;
|
||||
tooltip: item.tooltip;
|
||||
|
||||
clicked => {
|
||||
root.trailing_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.two_rows && root.title != "" : HorizontalLayout {
|
||||
padding_left: root.text_padding;
|
||||
padding_right: self.padding_left;
|
||||
|
||||
MaterialText {
|
||||
text: root.title;
|
||||
horizontal_alignment: root.horizontal_alignment;
|
||||
color: MaterialPalette.on_surface;
|
||||
overflow: elide;
|
||||
style: root.text_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component AppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <IconButtonItem> trailing_button;
|
||||
in property <bool> show_background;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked();
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_64, base.min_height);
|
||||
|
||||
HorizontalLayout {
|
||||
base := BaseAppBar {
|
||||
title: root.title;
|
||||
horizontal_alignment: center;
|
||||
|
||||
leading_button: root.leading_button;
|
||||
trailing_buttons: [root.trailing_button];
|
||||
show_background: root.show_background;
|
||||
|
||||
appbar_padding_right: self.padding_left;
|
||||
appbar_padding_top: MaterialStyleMetrics.padding_8;
|
||||
appbar_padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_6;
|
||||
text_style: MaterialTypography.title_large;
|
||||
|
||||
leading_button_clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
|
||||
trailing_button_clicked(index) => {
|
||||
root.trailing_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component SmallAppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <[IconButtonItem]> trailing_buttons;
|
||||
in property <bool> show_background;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked(index: int);
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_64, base.min_height);
|
||||
|
||||
HorizontalLayout {
|
||||
base := BaseAppBar {
|
||||
title: root.title;
|
||||
horizontal_alignment: left;
|
||||
leading_button: root.leading_button;
|
||||
trailing_buttons: root.trailing_buttons;
|
||||
show_background: root.show_background;
|
||||
appbar_padding_right: self.padding_left;
|
||||
appbar_padding_top: MaterialStyleMetrics.padding_8;
|
||||
appbar_padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
text_style: MaterialTypography.title_large;
|
||||
|
||||
leading_button_clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
|
||||
trailing_button_clicked(index) => {
|
||||
root.trailing_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component MediumAppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <[IconButtonItem]> trailing_buttons;
|
||||
in property <bool> show_background;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked(index: int);
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_64, base.min_height);
|
||||
|
||||
HorizontalLayout {
|
||||
base := BaseAppBar {
|
||||
title: root.title;
|
||||
horizontal_alignment: left;
|
||||
leading_button: root.leading_button;
|
||||
trailing_buttons: root.trailing_buttons;
|
||||
show_background: root.show_background;
|
||||
appbar_padding_right: self.padding_left;
|
||||
appbar_padding_top: MaterialStyleMetrics.padding_8;
|
||||
appbar_padding_bottom: MaterialStyleMetrics.padding_24;
|
||||
vertical_spacing: MaterialStyleMetrics.spacing_4;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
two_rows: true;
|
||||
text_style: MaterialTypography.headline_small;
|
||||
text_padding: MaterialStyleMetrics.padding_16;
|
||||
|
||||
leading_button_clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
|
||||
trailing_button_clicked(index) => {
|
||||
root.trailing_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component LargeAppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <[IconButtonItem]> trailing_buttons;
|
||||
in property <bool> show_background;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked(index: int);
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_64, base.min_height);
|
||||
|
||||
HorizontalLayout {
|
||||
base := BaseAppBar {
|
||||
title: root.title;
|
||||
horizontal_alignment: left;
|
||||
leading_button: root.leading_button;
|
||||
trailing_buttons: root.trailing_buttons;
|
||||
show_background: root.show_background;
|
||||
appbar_padding_right: self.padding_left;
|
||||
appbar_padding_top: MaterialStyleMetrics.padding_8;
|
||||
appbar_padding_bottom: MaterialStyleMetrics.padding_28;
|
||||
vertical_spacing: MaterialStyleMetrics.spacing_40;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
two_rows: true;
|
||||
text_style: MaterialTypography.headline_medium;
|
||||
text_padding: MaterialStyleMetrics.padding_16;
|
||||
|
||||
leading_button_clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
|
||||
trailing_button_clicked(index) => {
|
||||
root.trailing_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
dashboard/ui/material/ui/components/badge.slint
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialText } from "material_text.slint";
|
||||
|
||||
export component Badge {
|
||||
in property <string> text;
|
||||
|
||||
width: max(MaterialStyleMetrics.size_16, label.width + 2 * MaterialStyleMetrics.padding_4);
|
||||
height: max(MaterialStyleMetrics.size_16, label.height);
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
border_radius: self.height / 2;
|
||||
background: MaterialPalette.error;
|
||||
}
|
||||
|
||||
label := MaterialText {
|
||||
text: root.text;
|
||||
color: MaterialPalette.on_error;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
style: MaterialTypography.label_small;
|
||||
}
|
||||
|
||||
states [
|
||||
small when root.text == "" : {
|
||||
background_layer.width: MaterialStyleMetrics.size_6;
|
||||
background_layer.height: MaterialStyleMetrics.size_6;
|
||||
}
|
||||
]
|
||||
}
|
||||
72
dashboard/ui/material/ui/components/base_button.slint
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Avatar } from "./list.slint";
|
||||
|
||||
export component BaseButton inherits StateLayerArea {
|
||||
in property <image> icon;
|
||||
in property <color> icon_color: self.color;
|
||||
in property <string> text;
|
||||
in property <length> button_horizontal_padding: MaterialStyleMetrics.padding_24;
|
||||
in property <length> button_vertical_padding: MaterialStyleMetrics.padding_10;
|
||||
in property <length> button_padding_left: self.button_horizontal_padding;
|
||||
in property <length> button_padding_right: self.button_horizontal_padding;
|
||||
in property <length> button_padding_top: self.button_vertical_padding;
|
||||
in property <length> button_padding_bottom: self.button_vertical_padding;
|
||||
in property <length> spacing: MaterialStyleMetrics.spacing_8;
|
||||
in property <length> min_layout_width: MaterialStyleMetrics.size_40;
|
||||
in property <length> min_layout_height: MaterialStyleMetrics.size_40;
|
||||
in property <length> icon_size: MaterialStyleMetrics.icon_size_18;
|
||||
in property <image> avatar_icon;
|
||||
in property <length> avatar_size;
|
||||
in property <color> avatar_background: #00000000;
|
||||
out property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
out property <bool> has_avatar: root.avatar_icon.width > 0 && root.avatar_icon.height > 0;
|
||||
|
||||
min_width: max(root.min_layout_width, layout.min_width);
|
||||
min_height: max(root.min_layout_height, layout.min_height);
|
||||
tooltip_offset: root.height / 2 + root.icon_size / 2 + MaterialStyleMetrics.padding_14;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: root.button_padding_left;
|
||||
padding_right: root.button_padding_right;
|
||||
padding_top: root.button_padding_top;
|
||||
padding_bottom: root.button_padding_bottom;
|
||||
spacing: root.spacing;
|
||||
alignment: center;
|
||||
|
||||
if root.has_icon || root.has_avatar : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
if root.has_avatar && root.avatar_size > 0 : Avatar {
|
||||
width: root.avatar_size;
|
||||
height: self.width;
|
||||
image: root.avatar_icon;
|
||||
background: root.avatar_background;
|
||||
}
|
||||
|
||||
if root.has_icon : Icon {
|
||||
source: root.icon;
|
||||
colorize: root.icon_color;
|
||||
opacity: root.enabled ? 100% : 38%;
|
||||
width: root.icon_size;
|
||||
}
|
||||
}
|
||||
|
||||
if root.text != "" : MaterialText {
|
||||
text: root.text;
|
||||
style: MaterialTypography.label_large;
|
||||
color: root.color;
|
||||
opacity: root.enabled ? 100% : 38%;
|
||||
overflow: clip;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
51
dashboard/ui/material/ui/components/base_navigation.slint
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { NavigationItem } from "../items/navigation_item.slint";
|
||||
|
||||
export component BaseNavigationItemTemplate {
|
||||
in property <image> icon;
|
||||
in property <image> selected_icon;
|
||||
in property <string> text;
|
||||
in property <int> index;
|
||||
in property <bool> selected;
|
||||
in property <bool> show_badge;
|
||||
in property <string> badge;
|
||||
|
||||
callback clicked;
|
||||
callback pointer_event(event: PointerEvent, position: Point);
|
||||
|
||||
accessible-role: tab;
|
||||
accessible-label: root.text;
|
||||
accessible-item-index: root.index;
|
||||
accessible-item-selectable: true;
|
||||
accessible-item-selected: root.selected;
|
||||
accessible-action-default => { self.clicked(); }
|
||||
|
||||
@children
|
||||
|
||||
}
|
||||
|
||||
export component BaseNavigation {
|
||||
in property <[NavigationItem]> items;
|
||||
in_out property <int> current_index;
|
||||
|
||||
callback index_changed(index: int);
|
||||
|
||||
accessible-role: tab-list;
|
||||
// accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index;
|
||||
// accessible-label: root.title;
|
||||
accessible-item-count: root.items.length;
|
||||
|
||||
@children
|
||||
|
||||
protected function select(index: int) {
|
||||
if index < 0 || index >= root.items.length {
|
||||
return;
|
||||
}
|
||||
|
||||
root.current_index = index;
|
||||
root.index_changed(index);
|
||||
}
|
||||
|
||||
}
|
||||
64
dashboard/ui/material/ui/components/bottom_app_bar.slint
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { FloatingActionButton, FABStyle } from "./floating_action_button.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export struct IconButtonItem {
|
||||
icon: image,
|
||||
tooltip: string,
|
||||
enabled: bool
|
||||
}
|
||||
|
||||
export component BottomAppBar {
|
||||
in property <[IconButtonItem]> icon_buttons;
|
||||
in property <image> fab_icon;
|
||||
|
||||
callback fab_clicked();
|
||||
callback icon_button_clicked(index: int);
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_80, layout.min_height);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_4;
|
||||
padding_right: MaterialStyleMetrics.padding_16;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
HorizontalLayout {
|
||||
for button[index] in root.icon_buttons : IconButton {
|
||||
icon: button.icon;
|
||||
enabled: button.enabled;
|
||||
tooltip: button.tooltip;
|
||||
|
||||
clicked => {
|
||||
root.icon_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spacer
|
||||
Rectangle {}
|
||||
|
||||
if root.fab_icon.width > 0 && root.fab_icon.height > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
FloatingActionButton {
|
||||
icon: root.fab_icon;
|
||||
style: FABStyle.standard;
|
||||
|
||||
clicked => {
|
||||
root.fab_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
dashboard/ui/material/ui/components/bottom_sheet.slint
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Horizontal } from "./horizontal.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Modal } from "./modal.slint";
|
||||
|
||||
component BottomSheet {
|
||||
callback drag_moved(offset_y: length);
|
||||
callback pressed();
|
||||
callback released();
|
||||
|
||||
elevation := Elevation {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
level: 3;
|
||||
border_radius: MaterialStyleMetrics.border_radius_16;
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container_low;
|
||||
border_top_left_radius: MaterialStyleMetrics.border_radius_16;
|
||||
border_top_right_radius: self.border_top_left_radius;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
drag_handle := TouchArea {
|
||||
height: MaterialStyleMetrics.size_36;
|
||||
|
||||
Rectangle {
|
||||
width: MaterialStyleMetrics.size_32;
|
||||
height: MaterialStyleMetrics.size_4;
|
||||
background: MaterialPalette.outline;
|
||||
border_radius: self.height / 2;
|
||||
}
|
||||
|
||||
moved => {
|
||||
root.drag_moved(self.pressed_y - self.mouse_y);
|
||||
}
|
||||
|
||||
changed pressed => {
|
||||
if self.pressed {
|
||||
root.pressed();
|
||||
return;
|
||||
}
|
||||
|
||||
root.released();
|
||||
}
|
||||
}
|
||||
|
||||
Horizontal {
|
||||
@children
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ModalBottomSheet inherits PopupWindow {
|
||||
close_policy: no_auto_close;
|
||||
|
||||
property <length> drag_margin: MaterialStyleMetrics.padding_56;
|
||||
|
||||
modal := Modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
sheet := BottomSheet {
|
||||
property <duration> y_duration: MaterialAnimations.standard_accelerate_duration;
|
||||
property <length> y_drag_start;
|
||||
property <length> height_drag_start;
|
||||
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: parent.height;
|
||||
width: min(MaterialStyleMetrics.size_640, parent.width - 2 * MaterialStyleMetrics.padding_56);
|
||||
|
||||
@children
|
||||
|
||||
drag_moved(offset_y) => {
|
||||
self.y_duration = 0;
|
||||
self.y = max(self.y - offset_y, self.y_drag_start);
|
||||
self.height += offset_y;
|
||||
}
|
||||
|
||||
pressed => {
|
||||
self.y_drag_start = self.y;
|
||||
self.height_drag_start = self.height;
|
||||
}
|
||||
|
||||
released => {
|
||||
if self.y >= root.height - root.drag_margin {
|
||||
root.close();
|
||||
return;
|
||||
}
|
||||
|
||||
self.y_duration = MaterialAnimations.standard_accelerate_duration;
|
||||
self.height = self.height_drag_start;
|
||||
self.y = self.y_drag_start;
|
||||
}
|
||||
|
||||
animate y, height {
|
||||
duration: self.y_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
sheet.y = modal.height - sheet.height;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
112
dashboard/ui/material/ui/components/card.slint
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
|
||||
component BaseCard {
|
||||
in property <bool> clickable;
|
||||
in property <bool> has_elevation;
|
||||
in property <color> background;
|
||||
in property <color> border_color;
|
||||
in property <length> border_width;
|
||||
|
||||
callback clicked();
|
||||
|
||||
forward_focus: state_layer;
|
||||
|
||||
if root.has_elevation : Elevation {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: background_layer.border_radius;
|
||||
level: 1;
|
||||
}
|
||||
|
||||
background_layer := Rectangle {
|
||||
background: root.background;
|
||||
border_radius: MaterialStyleMetrics.border_radius_12;
|
||||
border_width: root.border_width;
|
||||
border_color: root.border_color;
|
||||
}
|
||||
|
||||
state_layer := StateLayerArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: background_layer.border_radius;
|
||||
visible: root.clickable;
|
||||
enabled: root.visible;
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
border_radius: MaterialStyleMetrics.border_radius_12;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
export component ElevatedCard {
|
||||
in property <bool> clickable <=> base.clickable;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
forward_focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.clickable;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseCard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
has_elevation: true;
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
export component FilledCard {
|
||||
in property <bool> clickable <=> base.clickable;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
forward_focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.clickable;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseCard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
export component OutlinedCard {
|
||||
in property <bool> clickable <=> base.clickable;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
forward_focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.clickable;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseCard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: MaterialPalette.surface_container_low;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
130
dashboard/ui/material/ui/components/check_box.slint
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { ListTile } from "./list.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
export enum CheckState {
|
||||
unchecked,
|
||||
partially_checked,
|
||||
checked,
|
||||
}
|
||||
|
||||
export component CheckBox {
|
||||
|
||||
in_out property <CheckState> check_state;
|
||||
in_out property <bool> tristate;
|
||||
in property <bool> has_error;
|
||||
in property <bool> enabled <=> state_area.enabled;
|
||||
|
||||
property <bool> checked: root.check_state == CheckState.checked || root.check_state == CheckState.partially_checked;
|
||||
|
||||
callback checked_state_changed(check_state: CheckState);
|
||||
|
||||
min_width: MaterialStyleMetrics.size_48;
|
||||
min_height: self.min_width;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-checkable: true;
|
||||
accessible-checked <=> root.checked;
|
||||
accessible-role: checkbox;
|
||||
accessible-action-default => { state_area.clicked(); }
|
||||
forward_focus: state_area;
|
||||
|
||||
state_area := StateLayerArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
color: MaterialPalette.on_surface;
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: MaterialStyleMetrics.size_18;
|
||||
height: self.width;
|
||||
border_radius: MaterialStyleMetrics.border_radius_2;
|
||||
border_width: root.checked ? 0 : 2px;
|
||||
border_color: root.has_error ? MaterialPalette.error : MaterialPalette.on_surface_variant;
|
||||
|
||||
if root.checked : Icon {
|
||||
colorize: MaterialPalette.on_primary;
|
||||
source: Icons.check;
|
||||
|
||||
states [
|
||||
partial_checked when root.check_state == CheckState.partially-checked : {
|
||||
source: Icons.remove;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
animate background, border_width {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
changed check_state => {
|
||||
root.checked_state_changed(root.check_state);
|
||||
}
|
||||
|
||||
public function toggle() {
|
||||
if !root.tristate {
|
||||
root.check_state = root.check_state != CheckState.checked ? CheckState.checked : CheckState.unchecked;
|
||||
return;
|
||||
}
|
||||
|
||||
root.check_state = root.check_state == CheckState.checked ? CheckState.partially_checked :
|
||||
root.check_state == CheckState.partially_checked ? CheckState.unchecked : CheckState.checked;
|
||||
}
|
||||
|
||||
public function set_check_state(check_state: CheckState) {
|
||||
if check_state == CheckState.partially-checked {
|
||||
root.tristate = true;
|
||||
}
|
||||
root.check_state = check_state;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
state_area.display_background: false;
|
||||
background_layer.border_color: root.checked ? transparent : MaterialPalette.on_surface.with_alpha(38%);
|
||||
background_layer.background: root.checked ? MaterialPalette.on_surface.with_alpha(38%) : transparent;
|
||||
}
|
||||
checked when root.checked : {
|
||||
background_layer.border_width: 0;
|
||||
background_layer.background: root.has_error ? MaterialPalette.error : MaterialPalette.primary;
|
||||
state_area.color: background_layer.background;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component CheckBoxTile inherits ListTile {
|
||||
in_out property <CheckState> check_state <=> check_box.check_state;
|
||||
in_out property <bool> tristate <=> check_box.tristate;
|
||||
in property <bool> has_error <=> check_box.has_error;
|
||||
|
||||
callback checked_state_changed <=> check_box.checked_state_changed;
|
||||
|
||||
Rectangle {
|
||||
horizontal-stretch: 0;
|
||||
|
||||
check_box := CheckBox {
|
||||
enabled: root.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
check_box.toggle();
|
||||
}
|
||||
|
||||
public function set_check_state(check-state: CheckState) {
|
||||
check_box.set_check_state(check_state);
|
||||
}
|
||||
}
|
||||
205
dashboard/ui/material/ui/components/chip.slint
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { IconButton } from "icon_button.slint";
|
||||
|
||||
export component ActionChip {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <image> avatar_icon <=> base.avatar-icon;
|
||||
in property <color> avatar_background <=> base.avatar_background;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
forward-focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: base.border_radius;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
base := BaseButton {
|
||||
min_layout_height: MaterialStyleMetrics.size_32;
|
||||
border_radius: MaterialStyleMetrics.border_radius_8;
|
||||
color: MaterialPalette.on_surface;
|
||||
icon_color: MaterialPalette.primary;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
button_padding_left: self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_16;
|
||||
button_padding_right:MaterialStyleMetrics.padding_16;
|
||||
button_vertical_padding: MaterialStyleMetrics.padding_6;
|
||||
avatar_size: MaterialStyleMetrics.size_18;
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.display_background: false;
|
||||
base.icon_color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component FilterChip {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in_out property <bool> checked;
|
||||
|
||||
forward-focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-checkable: true;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: base.border_radius;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
|
||||
base := BaseButton {
|
||||
min_layout_height: MaterialStyleMetrics.size_32;
|
||||
border_radius: MaterialStyleMetrics.border_radius_8;
|
||||
color: MaterialPalette.on_surface;
|
||||
icon_color: MaterialPalette.primary;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
button_padding_left: self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_16;
|
||||
button_padding_right:MaterialStyleMetrics.padding_16;
|
||||
button_vertical_padding: MaterialStyleMetrics.padding_6;
|
||||
avatar_size: MaterialStyleMetrics.size_18;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.checked : {
|
||||
base.icon: Icons.check;
|
||||
base.icon_color: MaterialPalette.on_secondary_container;
|
||||
base.color: MaterialPalette.on_secondary_container;
|
||||
border.border_width: 0;
|
||||
border.background: MaterialPalette.secondary_container;
|
||||
}
|
||||
]
|
||||
|
||||
animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; }
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.display_background: false;
|
||||
base.icon_color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component InputChip {
|
||||
in property <image> leading_icon <=> base.icon;
|
||||
in property <image> trailing_icon;
|
||||
in property <image> avatar <=> base.avatar-icon;
|
||||
in property <color> avatar_background <=> base.avatar_background;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
|
||||
property <bool> has_avatar: root.avatar.width > 0 && root.avatar.height > 0;
|
||||
|
||||
callback clicked();
|
||||
callback trailing_icon_clicked();
|
||||
|
||||
forward-focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: base.border_radius;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
clip: true;
|
||||
|
||||
base := BaseButton {
|
||||
min_layout_height: MaterialStyleMetrics.size_32;
|
||||
border_radius: root.has_avatar ? self.height / 2 : MaterialStyleMetrics.border_radius_8;
|
||||
color: MaterialPalette.on_surface;
|
||||
icon_color: MaterialPalette.primary;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
button_padding_left: root.has_avatar ? MaterialStyleMetrics.padding_4 : self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_12;
|
||||
button_padding_right: root.trailing_icon.width > 0 && root.trailing_icon.height > 0 ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_12;
|
||||
button_vertical_padding: MaterialStyleMetrics.padding_6;
|
||||
avatar_size: MaterialStyleMetrics.size_18;
|
||||
|
||||
if root.trailing_icon.width > 0 && root.trailing_icon.height > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
IconButton {
|
||||
icon: root.trailing_icon;
|
||||
inline: true;
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
root.trailing_icon_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
root.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.checked : {
|
||||
base.icon_color: MaterialPalette.on_secondary_container;
|
||||
base.color: MaterialPalette.on_secondary_container;
|
||||
border.border_width: 0;
|
||||
border.background: MaterialPalette.secondary_container;
|
||||
}
|
||||
]
|
||||
|
||||
animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; }
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.display_background: false;
|
||||
base.icon_color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
585
dashboard/ui/material/ui/components/date_picker.slint
Normal file
@@ -0,0 +1,585 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseDialog } from "./dialog.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { StateLayer } from "./state_layer.slint";
|
||||
import { ScrollView } from "./scroll_view.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { TextField } from "./text_field.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { ExtendedTouchArea } from "./extended_touch_area.slint";
|
||||
|
||||
// defines the interface to get the data for the DatePicker from native code.
|
||||
export global DatePickerAdapter {
|
||||
// returns the number of days for the given month in the given year.
|
||||
pure callback month_day_count(month: int, year: int) -> int;
|
||||
|
||||
// return the numbers of day to the first monday of the month.
|
||||
pure callback month_offset(month: int, year: int) -> int;
|
||||
|
||||
// used to format a date that is defined by day month and year.
|
||||
pure callback format_date(format: string, day: int, month: int, year: int) -> string;
|
||||
|
||||
// parses the given date string and returns a list of day, month and year.
|
||||
pure callback parse_date(date: string, format: string) -> [int];
|
||||
|
||||
// returns true if the given date is valid.
|
||||
pure callback valid_date(date: string, format: string) -> bool;
|
||||
|
||||
// returns the current date as list of day, month and year.
|
||||
pure callback date_now() -> [int];
|
||||
}
|
||||
|
||||
export struct Date {
|
||||
year: int,
|
||||
month: int,
|
||||
day: int,
|
||||
}
|
||||
|
||||
component CalendarHeaderDelegate {
|
||||
in property <string> text <=> text_label.text;
|
||||
|
||||
min_width: max(MaterialStyleMetrics.size_40, content_layer.min_width);
|
||||
min_height: max(MaterialStyleMetrics.size_40, content_layer.min_height);
|
||||
|
||||
content_layer := VerticalLayout {
|
||||
text_label := MaterialText {
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
style: MaterialTypography.body_large;
|
||||
color: MaterialPalette.on_surface;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component CalendarDelegate {
|
||||
in property <bool> selected;
|
||||
in property <bool> today;
|
||||
in property <string> text <=> text_label.text;
|
||||
in property <bool> enabled: true;
|
||||
|
||||
callback clicked <=> touch_area.clicked;
|
||||
|
||||
min_width: max(MaterialStyleMetrics.size_40, content_layer.min_width);
|
||||
min_height: max(MaterialStyleMetrics.size_40, content_layer.min_height);
|
||||
forward_focus: focus_scope;
|
||||
|
||||
accessible_role: button;
|
||||
accessible_checkable: true;
|
||||
accessible_checked: root.selected;
|
||||
accessible_label: root.text;
|
||||
accessible_action_default => { touch_area.clicked(); }
|
||||
|
||||
touch_area := TouchArea {
|
||||
enabled: root.enabled;
|
||||
}
|
||||
|
||||
focus_scope := FocusScope {
|
||||
width: 0px;
|
||||
enabled: root.enabled;
|
||||
|
||||
key_pressed(event) => {
|
||||
if (event.text == " " || event.text == "\n") {
|
||||
touch_area.clicked();
|
||||
return accept;
|
||||
}
|
||||
return reject;
|
||||
}
|
||||
}
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: self.height / 2;
|
||||
}
|
||||
|
||||
content_layer := HorizontalLayout {
|
||||
text_label := MaterialText {
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
style: MaterialTypography.body_large;
|
||||
color: MaterialPalette.on_surface;
|
||||
}
|
||||
}
|
||||
|
||||
state_layer := StateLayer {
|
||||
enabled: root.enabled;
|
||||
border_radius: background_layer.border_radius;
|
||||
pressed: touch_area.pressed;
|
||||
has_hover: touch_area.has_hover;
|
||||
has_focus: focus_scope.has_focus;
|
||||
}
|
||||
|
||||
states [
|
||||
// FIXME: states
|
||||
disabled when !root.enabled: {
|
||||
root.opacity: 0.38;
|
||||
}
|
||||
selected when root.selected: {
|
||||
background_layer.background: MaterialPalette.primary;
|
||||
text_label.color: MaterialPalette.on_primary;
|
||||
}
|
||||
today when root.today: {
|
||||
background_layer.border_width: 1px;
|
||||
background_layer.border_color: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component Calendar {
|
||||
in property <int> column_count;
|
||||
in property <int> row_count;
|
||||
in property <length> delegate_size;
|
||||
in property <int> start_column;
|
||||
in property <[string]> header_model;
|
||||
in property <int> month_count;
|
||||
in property <Date> today;
|
||||
in property <Date> selected_date;
|
||||
in property <int> display_month;
|
||||
in property <int> display_year;
|
||||
|
||||
callback select_date(date: Date);
|
||||
|
||||
// header
|
||||
for day[index] in root.header_model: CalendarHeaderDelegate {
|
||||
x: root.delegate_x(index);
|
||||
y: root.delegate_y(index);
|
||||
text: day;
|
||||
}
|
||||
|
||||
// items
|
||||
for index in root.month_count: CalendarDelegate {
|
||||
property <Date> d: { day: index + 1, month: root.display_month, year: root.display_year };
|
||||
|
||||
x: root.delegate_x(root.index_on_calendar(index));
|
||||
y: root.delegate_y(root.index_on_calendar(index));
|
||||
width: root.delegate_size;
|
||||
height: root.delegate_size;
|
||||
text: index + 1;
|
||||
selected: root.selected_date == self.d;
|
||||
today: root.today == self.d;
|
||||
|
||||
clicked => {
|
||||
root.select_date(self.d);
|
||||
}
|
||||
}
|
||||
|
||||
function index_on_calendar(index: int) -> int {
|
||||
// add column count because items starts after header row
|
||||
root.column_count + root.start_column + index
|
||||
}
|
||||
|
||||
function row_for_index(index: int) -> int {
|
||||
floor(index / root.column_count)
|
||||
}
|
||||
|
||||
function column_for_index(index: int) -> int {
|
||||
mod(index, root.column_count)
|
||||
}
|
||||
|
||||
function delegate_x(index: int) -> length {
|
||||
root.column_for_index(index) * root.delegate_size + root.column_for_index(index) * 1px /* * root.style.spacing */
|
||||
}
|
||||
|
||||
function delegate_y(index: int) -> length {
|
||||
root.row_for_index(index) * root.delegate_size + root.row_for_index(index) * 1px /* * root.style.spacing */
|
||||
}
|
||||
}
|
||||
|
||||
component YearSelection {
|
||||
in property <[int]> model;
|
||||
in property <length> spacing;
|
||||
in property <int> visible_row_count;
|
||||
in property <int> column_count;
|
||||
in property <length> delegate_width;
|
||||
in property <length> delegate_height;
|
||||
in property <int> selected_year;
|
||||
in property <int> today_year;
|
||||
|
||||
callback select_year(year: int);
|
||||
|
||||
property <length> row_height: root.height / root.visible_row_count;
|
||||
property <int> row_count: root.model.length / root.column_count;
|
||||
property <length> viewport_height: root.row_count * root.row_height;
|
||||
property <length> start_x: root.width / (root.column_count + 1);
|
||||
property <length> start_y: root.height / (root.visible_row_count + 1);
|
||||
|
||||
ScrollView {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
viewport_width: root.width;
|
||||
viewport_height: root.viewport_height;
|
||||
|
||||
for year[index] in root.model: CalendarDelegate {
|
||||
x: root.delegate_center_x(index) - self.width / 2;
|
||||
y: root.delegate_center_y(index) - self.height / 2;
|
||||
width: root.delegate_width;
|
||||
height: root.delegate_height;
|
||||
text: year;
|
||||
selected: year == root.selected_year;
|
||||
today: year == root.today_year;
|
||||
|
||||
clicked => {
|
||||
root.select_year(year);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function delegate_center_x(index: int) -> length {
|
||||
root.start_x * (root.column_for_index(index) + 1)
|
||||
}
|
||||
|
||||
function delegate_center_y(index: int) -> length {
|
||||
root.start_y * (root.row_for_index(index) + 1)
|
||||
}
|
||||
|
||||
function row_for_index(index: int) -> int {
|
||||
floor(index / root.column_count)
|
||||
}
|
||||
|
||||
function column_for_index(index: int) -> int {
|
||||
mod(index, root.column_count)
|
||||
}
|
||||
}
|
||||
|
||||
export component SelectionButton {
|
||||
in property <string> text <=> text-label.text;
|
||||
in property <image> icon <=> icon-image.source;
|
||||
in property <bool> enabled: true;
|
||||
in-out property <bool> checked;
|
||||
|
||||
callback clicked();
|
||||
|
||||
min-width: content-layer.min-width;
|
||||
min-height: max(40px, content-layer.min-height);
|
||||
accessible-label: root.text;
|
||||
accessible-role: button;
|
||||
accessible-checkable: true;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { touch-area.clicked(); }
|
||||
forward-focus: touch-area;
|
||||
|
||||
touch-area := ExtendedTouchArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
root.checked = !root.checked;
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
state-layer := StateLayer {
|
||||
enabled: root.enabled;
|
||||
pressed: touch-area.pressed;
|
||||
has-hover: touch-area.has-hover;
|
||||
has-focus: touch-area.has-focus;
|
||||
background: text_label.color;
|
||||
}
|
||||
|
||||
content-layer := HorizontalLayout {
|
||||
alignment: center;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
spacing: 8px;
|
||||
|
||||
text-label := MaterialText {
|
||||
vertical-alignment: center;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
style: MaterialTypography.label_large;
|
||||
}
|
||||
|
||||
icon-image := Image {
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: MaterialStyleMetrics.size_18;
|
||||
colorize: MaterialPalette.on_surface_variant;
|
||||
transform-rotation: root.checked ? 180deg : 0;
|
||||
rotation-origin-x: self.width / 2;
|
||||
rotation-origin-y: self.height / 2;
|
||||
|
||||
animate transform-rotation { duration: 250ms; }
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled: {
|
||||
root.opacity: 0.38;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
export component DatePickerPopup inherits PopupWindow {
|
||||
in property <string> title;
|
||||
in property <Date> date: { day: today[0], month: today[1], year: today[2] };
|
||||
|
||||
callback canceled();
|
||||
callback accepted(date: Date);
|
||||
|
||||
// this is used for the navigation between months
|
||||
property <string> input_title: @tr("Enter date");
|
||||
property <string> input_placeholder_text: "mm/dd/yyyy";
|
||||
property <string> input_format: "%m/%d/%Y";
|
||||
property <string> cancel_text: @tr("Cancel");
|
||||
property <string> ok_text: @tr("Ok");
|
||||
property <Date> display_date: root.date;
|
||||
property <Date> current_date: root.date;
|
||||
property <length> delegate_size: 40px;
|
||||
property <length> year_delegate_width: 72px;
|
||||
property <int> calendar_column_count: 7;
|
||||
property <int> calendar_row_count: 6;
|
||||
property <length> calendar_min_width: root.delegate_size * root.calendar_column_count + (root.calendar_column_count - 1) * 1px;
|
||||
property <length> calendar_min_height: root.delegate_size * (root.calendar_row_count + 1) + (root.calendar_row_count - 1) * 1px;
|
||||
property <int> year_selection_column_count: 3;
|
||||
property <int> year_selection_row_count: 5;
|
||||
property <bool> year_selection;
|
||||
property <bool> selection_mode: true;
|
||||
property <string> current_input;
|
||||
|
||||
property <int> calendar_month_count: DatePickerAdapter.month_day_count(root.display_date.month, root.display_date.year);
|
||||
property <[string]> calendar_header_model: [
|
||||
@tr("One-letter abbrev for Sunday" => "S"),
|
||||
@tr("One-letter abbrev for Monday" => "M"),
|
||||
@tr("One-letter abbrev for Tuesday" => "T"),
|
||||
@tr("One-letter abbrev for Wednesday" => "W"),
|
||||
@tr("One-letter abbrev for Thursday" => "T"),
|
||||
@tr("One-letter abbrev for Friday" => "F"),
|
||||
@tr("One-letter abbrev for Saturday" => "S"),
|
||||
];
|
||||
property <[int]> today: DatePickerAdapter.date_now();
|
||||
property <int> start_column: DatePickerAdapter.month_offset(root.display_date.month, root.display_date.year);
|
||||
property <string> current_month: DatePickerAdapter.format_date("%B %Y", root.display_date.day, root.display_date.month, root.display_date.year);
|
||||
property <[int]> year_model: [
|
||||
2024,
|
||||
2025,
|
||||
2026,
|
||||
2027,
|
||||
2028,
|
||||
2029,
|
||||
2031,
|
||||
2032,
|
||||
2033,
|
||||
2034,
|
||||
2035,
|
||||
2036,
|
||||
2037,
|
||||
2038,
|
||||
2039,
|
||||
2040,
|
||||
2041,
|
||||
2042,
|
||||
2043
|
||||
];
|
||||
property <int> selected_year: root.display_date.year;
|
||||
property <int> today_year: root.today[2];
|
||||
property <string> current_day: DatePickerAdapter.format_date("%a, %b %d", root.current_date.day, root.current_date.month, root.current_date.year);
|
||||
property <[int]> input_formatted: DatePickerAdapter.parse_date(root.current_input, root.input_format);
|
||||
|
||||
close_policy: no_auto_close;
|
||||
forward_focus: base;
|
||||
|
||||
base := BaseDialog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
title: root.title;
|
||||
actions: [root.cancel_text, root.ok_text];
|
||||
|
||||
content_layer := VerticalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_12;
|
||||
|
||||
header := HorizontalLayout {
|
||||
MaterialText {
|
||||
text: root.selection_mode ? root.current_day : root.input_title;
|
||||
horizontal_alignment: left;
|
||||
vertical_alignment: center;
|
||||
style: MaterialTypography.headline_large;
|
||||
}
|
||||
|
||||
if root.selection_mode: IconButton {
|
||||
icon: Icons.edit;
|
||||
accessible_label: "Toggle selection mode";
|
||||
|
||||
clicked => {
|
||||
root.toggle_selection_mode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: MaterialPalette.outline;
|
||||
}
|
||||
|
||||
if root.selection_mode: HorizontalLayout {
|
||||
// FIXME: spacing
|
||||
|
||||
VerticalLayout {
|
||||
horizontal_stretch: 0;
|
||||
alignment: center;
|
||||
|
||||
SelectionButton {
|
||||
text: root.current_month;
|
||||
icon: Icons.arrow_drop_down;
|
||||
checked <=> root.year_selection;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { }
|
||||
|
||||
IconButton {
|
||||
icon: Icons.chevron_backward;
|
||||
accessible_label: "Previous month";
|
||||
|
||||
clicked => {
|
||||
root.show_previous();
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: Icons.chevron_forward;
|
||||
accessible_label: "Next month";
|
||||
|
||||
clicked => {
|
||||
root.show_next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.selection_mode: VerticalLayout {
|
||||
if !root.year_selection: Calendar {
|
||||
min_width: root.calendar_min_width;
|
||||
min_height: root.calendar_min_height;
|
||||
column_count: root.calendar_column_count;
|
||||
row_count: root.calendar_row_count;
|
||||
delegate_size: root.delegate_size;
|
||||
header_model: root.calendar_header_model;
|
||||
month_count: root.calendar_month_count;
|
||||
today: { day: root.today[0], month: root.today[1], year: root.today[2] };
|
||||
selected_date <=> root.current_date;
|
||||
start_column: root.start_column;
|
||||
display_month: root.display_date.month;
|
||||
display_year: root.display_date.year;
|
||||
|
||||
select_date(date) => {
|
||||
root.select_date(date);
|
||||
}
|
||||
}
|
||||
|
||||
if root.year_selection: YearSelection {
|
||||
min_width: root.calendar_min_width;
|
||||
min_height: root.calendar_min_height;
|
||||
column_count: root.year_selection_column_count;
|
||||
visible_row_count: root.year_selection_row_count;
|
||||
delegate_width: root.year_delegate_width;
|
||||
delegate_height: root.delegate_size;
|
||||
model: root.year_model;
|
||||
selected_year: root.selected_year;
|
||||
today_year: root.today_year;
|
||||
|
||||
select_year(year) => {
|
||||
root.select_year(year);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
visible: root.year_selection;
|
||||
background: MaterialPalette.outline;
|
||||
}
|
||||
|
||||
if !root.selection_mode: TextField {
|
||||
text <=> root.current_input;
|
||||
placeholder_text: root.input_placeholder_text;
|
||||
trailing_icon: Icons.calendar_today;
|
||||
|
||||
trailing_icon_clicked => {
|
||||
root.toggle_selection_mode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action_clicked(index) => {
|
||||
// cancel
|
||||
if index == 0 {
|
||||
root.close();
|
||||
root.canceled();
|
||||
} else if index == 1 {
|
||||
root.accepted(root.date);
|
||||
}
|
||||
}
|
||||
|
||||
close => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
changed date => {
|
||||
root.display_date = root.date;
|
||||
root.current_date = root.date;
|
||||
}
|
||||
|
||||
changed selection_mode => {
|
||||
// check switch from input mode if input is valid
|
||||
if root.selection_mode && root.current_input_valid() {
|
||||
root.current_date = root.input_as_date();
|
||||
root.display_date = root.current_date;
|
||||
}
|
||||
}
|
||||
|
||||
pure public function ok_enabled() -> bool {
|
||||
root.selection_mode || root.current_input_valid()
|
||||
}
|
||||
|
||||
public function get_current_date() -> Date {
|
||||
if root.selection_mode {
|
||||
return root.current_date;
|
||||
}
|
||||
root.input_as_date()
|
||||
}
|
||||
|
||||
pure function current_input_valid() -> bool {
|
||||
DatePickerAdapter.valid_date(root.current_input, root.input_format)
|
||||
}
|
||||
|
||||
pure function input_as_date() -> Date {
|
||||
{
|
||||
day: root.input_formatted[0],
|
||||
month: root.input_formatted[1],
|
||||
year: root.input_formatted[2]
|
||||
}
|
||||
}
|
||||
|
||||
function select_date(date: Date) {
|
||||
root.current_date = date;
|
||||
}
|
||||
|
||||
function select_year(year: int) {
|
||||
root.current_date = { day: 1, month: 1, year: year };
|
||||
root.display_date = root.current_date;
|
||||
root.year_selection = false;
|
||||
}
|
||||
|
||||
function show_next() {
|
||||
if root.display_date.month >= 12 {
|
||||
root.display_date = { day: 1, month: 1, year: root.display_date.year + 1 };
|
||||
return;
|
||||
}
|
||||
root.display_date = { day: 1, month: root.display_date.month + 1, year: root.display_date.year };
|
||||
}
|
||||
|
||||
function show_previous() {
|
||||
if root.display_date.month <= 1 {
|
||||
root.display_date = { day: 1, month: 12, year: root.display_date.year - 1 };
|
||||
return;
|
||||
}
|
||||
root.display_date = { day: 1, month: root.display_date.month - 1, year: root.display_date.year };
|
||||
}
|
||||
|
||||
function toggle_selection_mode() {
|
||||
root.selection_mode = !root.selection_mode;
|
||||
}
|
||||
}
|
||||
233
dashboard/ui/material/ui/components/dialog.slint
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { TextButton } from "./text_button.slint";
|
||||
import { FilledButton } from "./filled_button.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Modal } from "./modal.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
|
||||
export component FullscreenDialog inherits PopupWindow {
|
||||
in property <string> title;
|
||||
in property <[string]> actions;
|
||||
|
||||
callback action_clicked(index: int);
|
||||
|
||||
close_policy: no_auto_close;
|
||||
forward_focus: focus_scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
x: 0;
|
||||
width: 0;
|
||||
|
||||
key_pressed(event) => {
|
||||
if event.text == Key.Escape {
|
||||
root.close();
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_16;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_24;
|
||||
spacing: MaterialStyleMetrics.spacing_16;
|
||||
vertical_stretch: 0;
|
||||
|
||||
IconButton {
|
||||
icon: Icons.close;
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
text: root.title;
|
||||
style: MaterialTypography.headline_small;
|
||||
color: MaterialPalette.on_surface;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
|
||||
for action[index] in root.actions : TextButton {
|
||||
text: action;
|
||||
|
||||
clicked => {
|
||||
root.action_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
background_layer.opacity = 1;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component BaseDialog {
|
||||
in property <string> title;
|
||||
in property <image> icon;
|
||||
in property <[image]> action_button_icons;
|
||||
in property <string> default_action_text;
|
||||
in property <[string]> actions;
|
||||
|
||||
property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
|
||||
callback default_action_clicked();
|
||||
callback action_button_clicked(index: int);
|
||||
callback action_clicked(index: int);
|
||||
callback close();
|
||||
|
||||
forward_focus: focus_scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
x: 0;
|
||||
width: 0;
|
||||
|
||||
key_pressed(event) => {
|
||||
if event.text == Key.Return && root.default_action_text != "" {
|
||||
root.default_action_clicked();
|
||||
return accept;
|
||||
}
|
||||
|
||||
if event.text == Key.Escape {
|
||||
root.close();
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
modal := Modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background_layer := Rectangle {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: layout.min_width;
|
||||
height: layout.min_height;
|
||||
opacity: 0;
|
||||
border_radius: MaterialStyleMetrics.border_radius_28;
|
||||
background: MaterialPalette.surface_container_high;
|
||||
|
||||
TouchArea {
|
||||
layout := VerticalLayout {
|
||||
padding: MaterialStyleMetrics.padding_24;
|
||||
spacing: MaterialStyleMetrics.spacing_16;
|
||||
|
||||
if root.has_icon : Icon {
|
||||
x: (parent.width - self.width) / 2;
|
||||
colorize: MaterialPalette.on_surface;
|
||||
source: root.icon;
|
||||
}
|
||||
|
||||
if root.title != "" : MaterialText {
|
||||
horizontal_alignment: root.has_icon ? center : left;
|
||||
text: root.title;
|
||||
style: MaterialTypography.headline_small;
|
||||
color: MaterialPalette.on_surface;
|
||||
}
|
||||
|
||||
@children
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
|
||||
for action_button[index] in root.action_button_icons : IconButton {
|
||||
icon: action_button;
|
||||
|
||||
clicked => {
|
||||
root.action_button_clicked(index);
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Rectangle {}
|
||||
|
||||
for action[index] in root.actions : TextButton {
|
||||
text: action;
|
||||
|
||||
clicked => {
|
||||
root.action_clicked(index);
|
||||
}
|
||||
}
|
||||
|
||||
if root.default_action_text != "" : FilledButton {
|
||||
text: root.default_action_text;
|
||||
|
||||
clicked => {
|
||||
root.default_action_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animate opacity { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
background_layer.opacity = 1;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component Dialog inherits PopupWindow {
|
||||
in property <string> title <=> base.title;
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> default_action_text <=> base.default_action_text;
|
||||
in property <[string]> actions <=> base.actions;
|
||||
|
||||
callback default_action_clicked <=> base.default_action_clicked;
|
||||
callback action_clicked <=> base.action_clicked;
|
||||
|
||||
close_policy: no_auto_close;
|
||||
forward_focus: base;
|
||||
|
||||
base := BaseDialog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@children
|
||||
|
||||
close => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
dashboard/ui/material/ui/components/divider.slint
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
component BaseDivider {
|
||||
Rectangle {
|
||||
background: MaterialPalette.outline_variant;
|
||||
}
|
||||
}
|
||||
|
||||
export component VerticalDivider {
|
||||
width: 1px;
|
||||
|
||||
BaseDivider {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
export component HorizontalDivider {
|
||||
height: 1px;
|
||||
|
||||
BaseDivider {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
89
dashboard/ui/material/ui/components/drawer.slint
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Modal } from "./modal.slint";
|
||||
|
||||
export component DrawerHeader {
|
||||
in property <string> title;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_56, title_label.min_width);
|
||||
|
||||
title_label := MaterialText {
|
||||
x: MaterialStyleMetrics.padding_16;
|
||||
width: root.width - MaterialStyleMetrics.padding_16;
|
||||
text: root.title;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
vertical_alignment: center;
|
||||
style: MaterialTypography.title_small;
|
||||
}
|
||||
}
|
||||
|
||||
export component Drawer {
|
||||
in property <string> title;
|
||||
|
||||
min_width: max(MaterialStyleMetrics.size_360, layout.min_width);
|
||||
|
||||
TouchArea {
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container_low;
|
||||
border_radius: MaterialStyleMetrics.border_radius_16;
|
||||
|
||||
layout := VerticalLayout {
|
||||
alignment: start;
|
||||
padding: MaterialStyleMetrics.padding_12;
|
||||
|
||||
if root.title != "" : DrawerHeader {
|
||||
width: 100%;
|
||||
title: root.title;
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ModalDrawer inherits PopupWindow {
|
||||
in property <bool> position_right;
|
||||
in property <string> title;
|
||||
|
||||
close_policy: no_auto_close;
|
||||
|
||||
modal := Modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
drawer := Drawer {
|
||||
x: root.position_right ? parent.width : -self.width;
|
||||
y: 0;
|
||||
height: 100%;
|
||||
title: root.title;
|
||||
|
||||
@children
|
||||
|
||||
animate x {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
drawer.x = root.position_right ? root.width - drawer.width : 0;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
dashboard/ui/material/ui/components/drop_down_menu.slint
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { TextField } from "./text_field.slint";
|
||||
import { MenuItem } from "../items/menu_item.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MenuInner } from "./menu.slint";
|
||||
import { StateLayer } from "./state_layer.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { ScrollView } from "./scroll_view.slint";
|
||||
|
||||
export component DropDownMenu {
|
||||
in property <string> label <=> text_field.label;
|
||||
in property <bool> enabled <=> text_field.enabled;
|
||||
in property <image> leading_icon <=> text_field.leading_icon;
|
||||
in property <[MenuItem]> items;
|
||||
in_out property <int> current_index: -1;
|
||||
|
||||
callback selected(index: int);
|
||||
|
||||
property <length> item_height: MaterialStyleMetrics.size_56;
|
||||
property <int> max_displayed_items: 6;
|
||||
property <string> text <=> text_field.text;
|
||||
|
||||
min_width: text_field.min_width;
|
||||
min_height: text_field.min_height;
|
||||
forward_focus: text_field; accessible-role: combobox;
|
||||
accessible_enabled: root.enabled;
|
||||
accessible_expandable: true;
|
||||
accessible_value <=> text_field.text;
|
||||
accessible_action_expand => { menu.show(); }
|
||||
|
||||
text_field := TextField {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
trailing_icon: Icons.arrow_drop_down;
|
||||
read_only: true;
|
||||
}
|
||||
|
||||
state_layer := StateLayer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: MaterialPalette.on_surface;
|
||||
has_hover: touch_area.has_hover;
|
||||
enabled: root.enabled;
|
||||
}
|
||||
|
||||
touch_area := TouchArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
menu.show();
|
||||
}
|
||||
}
|
||||
|
||||
menu := PopupWindow {
|
||||
x: 0;
|
||||
width: root.width;
|
||||
close_policy: close_on_click_outside;
|
||||
forward_focus: inner_text_field;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
inner_text_field := TextField {
|
||||
width: root.width;
|
||||
leading_icon: root.leading_icon;
|
||||
trailing_icon: Icons.arrow_drop_up;
|
||||
text: root.text;
|
||||
label: root.label;
|
||||
read_only: true;
|
||||
|
||||
leading_icon_clicked => {
|
||||
menu.close();
|
||||
}
|
||||
|
||||
trailing_icon_clicked => {
|
||||
menu.close();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: min(max(root.item_height, items.length * root.item_height), root.max_displayed_items * root.item_height);
|
||||
background: MaterialPalette.surface_container;
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
menu_inner := MenuInner {
|
||||
current_index: root.current_index;
|
||||
items: root.items;
|
||||
|
||||
activated(index) => {
|
||||
root.update_selection(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function update_selection(index: int) {
|
||||
root.update_text(index);
|
||||
root.current_index = index;
|
||||
menu.close();
|
||||
root.selected(index);
|
||||
}
|
||||
|
||||
function update_text(index: int) {
|
||||
if index < 0 || root.current_index >= root.items.length {
|
||||
text_field.text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
text_field.text = root.items[index].text;
|
||||
}
|
||||
|
||||
changed current_index => {
|
||||
root.update_text(root.current_index);
|
||||
}
|
||||
}
|
||||
45
dashboard/ui/material/ui/components/elevated_button.slint
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component ElevatedButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
elevation := Elevation {
|
||||
border_radius: root.height / 2;
|
||||
background: MaterialPalette.surface_container_low;
|
||||
level: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: elevation.border_radius;
|
||||
color: MaterialPalette.primary;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
elevation.background: transparent;
|
||||
elevation.level: 0;
|
||||
base.color: MaterialPalette.on_surface;
|
||||
}
|
||||
hover when base.has_hover && !base.pressed && !base.enter_pressed : {
|
||||
elevation.level: 3;
|
||||
}
|
||||
]
|
||||
}
|
||||
108
dashboard/ui/material/ui/components/elevation.slint
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Palette } from "std-widgets.slint";
|
||||
import { MaterialWindow } from "material_window.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component Elevation {
|
||||
in property <brush> background;
|
||||
in property <length> border_radius;
|
||||
in property <int> level;
|
||||
|
||||
property <bool> dark: Palette.color_scheme == ColorScheme.dark;
|
||||
|
||||
outer_shadow := Rectangle {
|
||||
border_radius: root.border_radius;
|
||||
|
||||
inner_shadow := Rectangle {
|
||||
border_radius: root.border_radius;
|
||||
background: root.background;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
level_1_light when root.level == 1 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 1px;
|
||||
outer_shadow.drop_shadow_blur: 2px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 2px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
}
|
||||
level_2_light when root.level == 2 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 1px;
|
||||
outer_shadow.drop_shadow_blur: 2px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
inner_shadow.drop_shadow_offset_y: 2px;
|
||||
inner_shadow.drop_shadow_blur: 6px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
}
|
||||
level_3_light when root.level == 3 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 4px;
|
||||
outer_shadow.drop_shadow_blur: 8px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 3px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_4_light when root.level == 4 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 6px;
|
||||
outer_shadow.drop_shadow_blur: 10px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 2px;
|
||||
inner_shadow.drop_shadow_blur: 3px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_5_light when root.level == 5 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 8px;
|
||||
outer_shadow.drop_shadow_blur: 12px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 4px;
|
||||
inner_shadow.drop_shadow_blur: 4px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_1_dark when root.level == 1 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 1px;
|
||||
outer_shadow.drop_shadow_blur: 3px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 2px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_2_dark when root.level == 2 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 2px;
|
||||
outer_shadow.drop_shadow_blur: 6px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 2px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_3_dark when root.level == 3 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 4px;
|
||||
outer_shadow.drop_shadow_blur: 8px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 3px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_4_dark when root.level == 4 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 6px;
|
||||
outer_shadow.drop_shadow_blur: 10px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 2px;
|
||||
inner_shadow.drop_shadow_blur: 3px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_5_dark when root.level == 5 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 8px;
|
||||
outer_shadow.drop_shadow_blur: 12px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 4px;
|
||||
inner_shadow.drop_shadow_blur: 4px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { ToolTip } from "tooltip.slint";
|
||||
|
||||
export component ExtendedTouchArea inherits TouchArea {
|
||||
in property <string> tooltip;
|
||||
in property <length> tooltip_offset;
|
||||
out property <bool> has_focus <=> focus_scope.has_focus;
|
||||
out property <bool> enter_pressed;
|
||||
|
||||
callback key_pressed(KeyEvent) -> EventResult;
|
||||
|
||||
forward-focus: focus-scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
x: 0;
|
||||
width: 0px;
|
||||
enabled: root.enabled;
|
||||
|
||||
key_pressed(event) => {
|
||||
if !root.enabled {
|
||||
return reject;
|
||||
}
|
||||
|
||||
if (event.text == " " || event.text == "\n") && !root.enter_pressed {
|
||||
root.enter_pressed = true;
|
||||
root.clicked();
|
||||
return accept;
|
||||
}
|
||||
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
if !root.enabled {
|
||||
return reject;
|
||||
}
|
||||
|
||||
if (event.text == " " || event.text == "\n") && root.enter_pressed {
|
||||
root.enter_pressed = false;
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
|
||||
if root.tooltip != "" && root.has-hover && !root.pressed : ToolTip {
|
||||
y: root.tooltip_offset;
|
||||
text: root.tooltip;
|
||||
}
|
||||
}
|
||||
43
dashboard/ui/material/ui/components/filled_button.slint
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component FilledButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
elevation := Elevation {
|
||||
border_radius: root.height / 2;
|
||||
background: MaterialPalette.primary;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: elevation.border_radius;
|
||||
color: MaterialPalette.on_primary;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
elevation.background: transparent;
|
||||
base.color: MaterialPalette.on_surface;
|
||||
}
|
||||
hover when base.has_hover && !base.pressed && !base.enter_pressed : {
|
||||
elevation.level: 1;
|
||||
}
|
||||
]
|
||||
}
|
||||
64
dashboard/ui/material/ui/components/filled_icon_button.slint
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component FilledIconButton {
|
||||
in property <image> icon;
|
||||
in property <image> checked_icon: root.icon;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked();
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: true;
|
||||
accessible-label: root.tooltip;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: base.border_radius;
|
||||
background: root.checkable ? MaterialPalette.surface_container_highest : MaterialPalette.primary;
|
||||
visible: root.enabled;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
icon: root.checked ? root.checked_icon : root.icon;
|
||||
border_radius: self.height / 2;
|
||||
color: !root.enabled ? MaterialPalette.on_surface_variant : root.checkable ? MaterialPalette.primary : MaterialPalette.on_primary;
|
||||
button_horizontal_padding: 0;
|
||||
button_vertical_padding: 0;
|
||||
width: self.height;
|
||||
icon_size: MaterialStyleMetrics.icon_size_24;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.enabled && root.checked : {
|
||||
base.color: MaterialPalette.on_primary;
|
||||
background_layer.background: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export enum FABStyle {
|
||||
small,
|
||||
standard,
|
||||
large
|
||||
}
|
||||
|
||||
export component FloatingActionButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <FABStyle> style;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: true;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
elevation := Elevation {
|
||||
border_radius: root.style == FABStyle.small ? MaterialStyleMetrics.border_radius_12 :
|
||||
root.style == FABStyle.standard ? MaterialStyleMetrics.border_radius_16 : MaterialStyleMetrics.border_radius_28;
|
||||
background: MaterialPalette.primary;
|
||||
level: 3;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: elevation.border_radius;
|
||||
color: MaterialPalette.on_primary;
|
||||
button_vertical_padding: root.style == FABStyle.small ? MaterialStyleMetrics.padding_10 :
|
||||
root.style == FABStyle.standard ? MaterialStyleMetrics.padding_14 : MaterialStyleMetrics.padding_30;
|
||||
button_horizontal_padding: self.button_vertical_padding;
|
||||
min_layout_width: self.min_layout_height;
|
||||
min_layout_height: root.style == FABStyle.small ? MaterialStyleMetrics.size_40 :
|
||||
root.style == FABStyle.standard ? MaterialStyleMetrics.size_56 : MaterialStyleMetrics.size_90;
|
||||
icon_size: root.style != FABStyle.large ? MaterialStyleMetrics.icon_size_24 : MaterialStyleMetrics.icon_size_36;
|
||||
}
|
||||
|
||||
states [
|
||||
hover when base.has_hover : {
|
||||
elevation.level: 4;
|
||||
}
|
||||
]
|
||||
}
|
||||
10
dashboard/ui/material/ui/components/grid.slint
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component Grid inherits GridLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
spacing_vertical: MaterialStyleMetrics.spacing_8;
|
||||
spacing_horizontal: MaterialStyleMetrics.spacing_8;
|
||||
}
|
||||
9
dashboard/ui/material/ui/components/horizontal.slint
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component Horizontal inherits HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
}
|
||||
11
dashboard/ui/material/ui/components/icon.slint
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component Icon inherits Image {
|
||||
width: MaterialStyleMetrics.icon_size_18;
|
||||
height: self.width;
|
||||
colorize: MaterialPalette.on_background;
|
||||
}
|
||||
62
dashboard/ui/material/ui/components/icon_button.slint
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component IconButton {
|
||||
in property <image> icon;
|
||||
in property <image> checked_icon: root.icon;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <bool> inverse;
|
||||
in property <bool> inline: false;
|
||||
in property <bool> has_error;
|
||||
|
||||
callback clicked();
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: true;
|
||||
accessible-label: root.tooltip;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseButton {
|
||||
icon: root.checked ? root.checked_icon : root.icon;
|
||||
border_radius: self.height / 2;
|
||||
color: root.has_error ? MaterialPalette.error : root.inverse ? MaterialPalette.inverse_on_surface : MaterialPalette.on_surface_variant;
|
||||
button_horizontal_padding: 0;
|
||||
button_vertical_padding: 0;
|
||||
width: self.height;
|
||||
display_background: false;
|
||||
icon_size: root.inline ? MaterialStyleMetrics.icon_size_18 : MaterialStyleMetrics.icon_size_24;
|
||||
min_layout_width: root.inline ? MaterialStyleMetrics.icon_size_18 : MaterialStyleMetrics.size_40;
|
||||
min_layout_height: self.min_layout_width;
|
||||
clip_ripple: !root.inline;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.enabled && root.checked : {
|
||||
base.color: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
107
dashboard/ui/material/ui/components/list.slint
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
export component Avatar {
|
||||
in property <image> image;
|
||||
in property <color> background: MaterialPalette.primary;
|
||||
|
||||
width: MaterialStyleMetrics.size_32;
|
||||
height: self.width;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: self.width / 2;
|
||||
background: root.background;
|
||||
clip: true;
|
||||
|
||||
Image {
|
||||
source: root.image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ListTile {
|
||||
in property <string> text;
|
||||
in property <string> supporting_text;
|
||||
in property <image> avatar_icon;
|
||||
in property <string> avatar_text;
|
||||
in property <color> avatar_background;
|
||||
in property <color> avatar_foreground;
|
||||
in property <bool> enabled <=> state_layer.enabled;
|
||||
|
||||
property <color> color: MaterialPalette.on_surface;
|
||||
|
||||
callback clicked <=> state_layer.clicked;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_72, layout.min_height);
|
||||
|
||||
state_layer := StateLayerArea {
|
||||
color: root.color;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_16;
|
||||
padding_right: MaterialStyleMetrics.padding_24;
|
||||
padding_top: MaterialStyleMetrics.padding_8;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_16;
|
||||
|
||||
if root.avatar_background != #00000000 || root.avatar_text != "" || (root.avatar_icon.width > 0 && root.avatar_icon.height > 0) : Rectangle {
|
||||
width: self.height;
|
||||
border_radius: self.height / 2;
|
||||
background: root.avatar_background;
|
||||
clip: true;
|
||||
|
||||
if root.avatar_text != "" : MaterialText {
|
||||
text: root.avatar_text;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
color: root.avatar_foreground;
|
||||
style: MaterialTypography.title_medium;
|
||||
}
|
||||
|
||||
if root.avatar_icon.width > 0 && root.avatar_icon.height > 0 : Icon {
|
||||
width: parent.width;
|
||||
source: root.avatar_icon;
|
||||
colorize: root.avatar_foreground;
|
||||
}
|
||||
}
|
||||
|
||||
title_layout := VerticalLayout {
|
||||
alignment: center;
|
||||
horizontal_stretch: 1;
|
||||
|
||||
MaterialText {
|
||||
text: root.text;
|
||||
color: root.color;
|
||||
overflow: elide;
|
||||
style: MaterialTypography.body_large;
|
||||
}
|
||||
|
||||
if root.supporting_text != "" : MaterialText {
|
||||
text: root.supporting_text;
|
||||
color: root.color;
|
||||
overflow: elide;
|
||||
style: MaterialTypography.body_medium;
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
state_layer.display_background: false;
|
||||
title_layout.opacity: 38%;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
8
dashboard/ui/material/ui/components/list_view.slint
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ScrollView } from "./scroll_view.slint";
|
||||
|
||||
export component ListView inherits ScrollView {
|
||||
|
||||
}
|
||||
14
dashboard/ui/material/ui/components/material_text.slint
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialTypography, TextStyle } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component MaterialText inherits Text {
|
||||
in property <TextStyle> style: MaterialTypography.body_medium;
|
||||
|
||||
font_size: root.style.font_size;
|
||||
font_weight: root.style.font_weight;
|
||||
color: MaterialPalette.on_background;
|
||||
overflow: elide;
|
||||
}
|
||||
18
dashboard/ui/material/ui/components/material_window.slint
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export global MaterialWindowAdapter {
|
||||
in property <bool> disable_hover;
|
||||
}
|
||||
|
||||
export component MaterialWindow inherits Window {
|
||||
in property <bool> disable_hover;
|
||||
|
||||
background: MaterialPalette.background;
|
||||
|
||||
changed disable_hover => {
|
||||
MaterialWindowAdapter.disable_hover = root.disable_hover;
|
||||
}
|
||||
}
|
||||
136
dashboard/ui/material/ui/components/menu.slint
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { Elevation } from "elevation.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { MenuItem } from "../items/menu_item.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
|
||||
component MenuItemTemplate {
|
||||
in property <image> icon;
|
||||
in property <string> text;
|
||||
in property <string> trailing_text;
|
||||
in property <bool> enabled <=> state_area.enabled;
|
||||
in property <bool> selected;
|
||||
|
||||
callback clicked();
|
||||
|
||||
property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
property <color> color: MaterialPalette.on_surface;
|
||||
property <color> color_variant: MaterialPalette.on_surface_variant;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_56, layout.min_height);
|
||||
|
||||
background_layer := Rectangle {
|
||||
state_area := StateLayerArea {
|
||||
color: root.color;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_12;
|
||||
padding_right: self.padding_left;
|
||||
padding_top: MaterialStyleMetrics.padding_8;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_12;
|
||||
|
||||
if root.has_icon : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
Icon {
|
||||
source: root.icon;
|
||||
colorize: root.color;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
horizontal_stretch: 1;
|
||||
text: root.text;
|
||||
style: MaterialTypography.body_large;
|
||||
color: root.color;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
|
||||
if root.trailing_text != "" : MaterialText {
|
||||
text: root.trailing_text;
|
||||
style: MaterialTypography.body_large;
|
||||
color: root.color_variant;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
state_area.opacity: MaterialPalette.disable_opacity;
|
||||
}
|
||||
selected when root.selected : {
|
||||
background_layer.background: MaterialPalette.surface_container_highest;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component MenuInner {
|
||||
in property <[MenuItem]> items;
|
||||
in_out property <int> current_index: -1;
|
||||
|
||||
callback activated(index: int);
|
||||
|
||||
// used to fix clipping of shadows
|
||||
out property <int> elevation: 2;
|
||||
|
||||
menu := Elevation {
|
||||
width: 100%;
|
||||
level: root.elevation;
|
||||
height: layout.min_height + root.elevation * 1px;
|
||||
border_radius: MaterialStyleMetrics.border_radius_4;
|
||||
background: MaterialPalette.surface_container;
|
||||
|
||||
layout := VerticalLayout {
|
||||
width: root.width;
|
||||
padding_top: MaterialStyleMetrics.padding_8;
|
||||
padding_bottom: self.padding_top;
|
||||
|
||||
for item[index] in root.items : MenuItemTemplate {
|
||||
icon: item.icon;
|
||||
text: item.text;
|
||||
trailing_text: item.trailing_text;
|
||||
enabled: item.enabled;
|
||||
selected: index == root.current_index;
|
||||
|
||||
clicked => {
|
||||
root.activated(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component PopupMenu inherits PopupWindow {
|
||||
in property <[MenuItem]> items;
|
||||
|
||||
callback activated(index: int);
|
||||
|
||||
width: MaterialStyleMetrics.size_200;
|
||||
close_policy: close_on_click_outside;
|
||||
|
||||
VerticalLayout {
|
||||
padding: inner.elevation * 1px;
|
||||
|
||||
inner := MenuInner {
|
||||
items: root.items;
|
||||
|
||||
activated(index) => {
|
||||
root.activated(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
dashboard/ui/material/ui/components/modal.slint
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
|
||||
export component Modal {
|
||||
callback clicked <=> touch_area.clicked;
|
||||
|
||||
background_layer := Rectangle {
|
||||
clip: true;
|
||||
|
||||
touch_area := TouchArea {
|
||||
@children
|
||||
}
|
||||
|
||||
animate background { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
background_layer.background = MaterialPalette.background_modal;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
150
dashboard/ui/material/ui/components/navigation_bar.slint
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Badge } from "./badge.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { ExtendedTouchArea } from "./extended_touch_area.slint";
|
||||
import { StateLayer, Ripple } from "./state_layer.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { NavigationItem } from "../items/navigation_item.slint";
|
||||
import { BaseNavigationItemTemplate, BaseNavigation } from "./base_navigation.slint";
|
||||
|
||||
export component NavigationItemTemplate inherits BaseNavigationItemTemplate {
|
||||
property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
property <bool> has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0;
|
||||
property <color> color: MaterialPalette.on_surface;
|
||||
|
||||
in property <length> navitem_padding_top: MaterialStyleMetrics.padding_12;
|
||||
in property <length> navitem_padding_bottom: MaterialStyleMetrics.padding_16;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
touch_area := ExtendedTouchArea {
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
padding_top: root.navitem_padding_top;
|
||||
padding_bottom: root.navitem_padding_bottom;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
background_layer := Rectangle {
|
||||
min_width: state_layout.min_width;
|
||||
min_height: max(MaterialStyleMetrics.size_32, state_layout.min_height);
|
||||
border_radius: self.height / 2;
|
||||
|
||||
state_layer := StateLayer {
|
||||
background: root.color;
|
||||
border_radius: parent.border_radius;
|
||||
enabled: true;
|
||||
has_focus: touch_area.has_focus;
|
||||
has_hover: touch_area.has_hover;
|
||||
pressed: touch_area.pressed || touch_area.enter_pressed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
if touch_area.pressed && !root.selected : Ripple {
|
||||
width: root.width;
|
||||
height: root.height;
|
||||
pressed_x: touch_area.pressed_x;
|
||||
pressed_y: touch_area.pressed_y;
|
||||
color: root.color;
|
||||
}
|
||||
|
||||
state_layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_20;
|
||||
padding_right: self.padding_left;
|
||||
alignment: center;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
if !root.has_icon && !root.has_selected_icon : Rectangle {
|
||||
width: 12px;
|
||||
height: self.width;
|
||||
border_radius: self.width / 2;
|
||||
background: root.color;
|
||||
}
|
||||
|
||||
if root.has_icon || root.has_selected_icon : Icon {
|
||||
source: root.icon;
|
||||
colorize: root.color;
|
||||
|
||||
states [
|
||||
selected when root.selected && root.has_selected_icon : {
|
||||
source: root.selected_icon;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.show_badge || root.badge != "" : Badge {
|
||||
x: parent.width / 2;
|
||||
y: 0;
|
||||
text: root.badge;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label := MaterialText {
|
||||
text: root.text;
|
||||
style: MaterialTypography.label_medium;
|
||||
color: root.color;
|
||||
horizontal_alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
background_layer.background: MaterialPalette.secondary_container;
|
||||
state_layer.background: transparent;
|
||||
label.style: MaterialTypography.label_medium_prominent;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component NavigationBar inherits BaseNavigation {
|
||||
min_height: max(MaterialStyleMetrics.size_80, layout.min_height);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_8;
|
||||
padding_right: self.padding_left;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
|
||||
for item[index] in root.items : NavigationItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
selected: index == root.current_index;
|
||||
show_badge: item.show_badge;
|
||||
badge: item.badge;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
dashboard/ui/material/ui/components/navigation_drawer.slint
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Drawer, ModalDrawer, DrawerHeader } from "./drawer.slint";
|
||||
import { NavigationItem, NavigationGroup } from "../items/navigation_item.slint";
|
||||
import { HorizontalDivider } from "./divider.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { BaseNavigationItemTemplate } from "./base_navigation.slint";
|
||||
|
||||
component NavigationItemTemplate inherits BaseNavigationItemTemplate {
|
||||
property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
property <bool> has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0;
|
||||
property <color> color: MaterialPalette.on_surface_variant;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_56, layout.min_height);
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: self.height / 2;
|
||||
|
||||
state_layer := StateLayerArea {
|
||||
border_radius: parent.border_radius;
|
||||
color: root.selected ? transparent : root.color;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_16;
|
||||
padding_right: MaterialStyleMetrics.padding_24;
|
||||
padding_top: MaterialStyleMetrics.padding_16;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_12;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
if !root.has_icon && !root.has_selected_icon : Rectangle {
|
||||
width: MaterialStyleMetrics.icon_size_24;
|
||||
height: self.width;
|
||||
|
||||
Rectangle {
|
||||
width: 12px;
|
||||
height: self.width;
|
||||
border_radius: self.width / 2;
|
||||
background: root.color;
|
||||
}
|
||||
}
|
||||
|
||||
if root.has_icon || root.has_selected_icon : Icon {
|
||||
source: root.icon;
|
||||
colorize: root.color;
|
||||
|
||||
states [
|
||||
selected when root.selected && root.has_selected_icon : {
|
||||
source: root.selected_icon;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
horizontal_stretch: 1;
|
||||
text: root.text;
|
||||
style: MaterialTypography.label_large;
|
||||
color: root.color;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
|
||||
if root.badge != "" : MaterialText {
|
||||
text: root.badge;
|
||||
style: MaterialTypography.label_large;
|
||||
color: root.color;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
pointer_event(event) => {
|
||||
root.pointer_event(event, {
|
||||
x: self.mouse_x,
|
||||
y: self.mouse_y,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
animate background { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
background_layer.background: MaterialPalette.secondary_container;
|
||||
root.color: MaterialPalette.on_secondary_container;
|
||||
}
|
||||
]
|
||||
|
||||
animate color { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
component NavigationGroupTemplate {
|
||||
in property <string> title;
|
||||
in property <[NavigationItem]> items;
|
||||
in property <int> current_index: -1;
|
||||
in property <bool> has_divider;
|
||||
|
||||
callback select(index: int);
|
||||
callback item_pointer_event(index: int, event: PointerEvent, position: Point);
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
if root.title != 0 : DrawerHeader {
|
||||
title: root.title;
|
||||
}
|
||||
|
||||
for item[index] in root.items : NavigationItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
badge: item.badge;
|
||||
selected: index == root.current_index;
|
||||
index: index;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
pointer_event(event, position) => {
|
||||
root.item_pointer_event(index, event, {
|
||||
x: self.x + position.x,
|
||||
y: self.y + position.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if root.has_divider : HorizontalDivider {}
|
||||
}
|
||||
}
|
||||
|
||||
export component NavigationDrawer inherits Drawer {
|
||||
callback selected(group-index: int, item_index: int);
|
||||
callback item_pointer_event(group_index: int, item_index: int, event: PointerEvent, position: Point);
|
||||
in property <[NavigationGroup]> groups;
|
||||
in_out property <int> current_group;
|
||||
in_out property <int> current_index;
|
||||
|
||||
accessible-role: tab-list;
|
||||
// accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index;
|
||||
// accessible-label: root.title;
|
||||
accessible-item-count: root.groups.length;
|
||||
|
||||
for group[group_index] in root.groups : NavigationGroupTemplate {
|
||||
title: group.title;
|
||||
items: group.items;
|
||||
current_index: group_index == root.current_group ? root.current_index : -1;
|
||||
has_divider: root.groups.length > 1 && group_index < root.groups.length - 1;
|
||||
|
||||
select(index) => {
|
||||
root.select(group_index, index);
|
||||
}
|
||||
item_pointer_event(index, event, position) => {
|
||||
root.item_pointer_event(group_index, index, event, { x: self.x + position.x, y: self.y + position.y });
|
||||
}
|
||||
}
|
||||
|
||||
function select(group_index: int, item_index: int) {
|
||||
if group_index < 0 || group_index >= root.groups.length || item_index < 0 || item_index >= root.groups[group_index].items.length {
|
||||
return;
|
||||
}
|
||||
|
||||
root.current_group = group_index;
|
||||
root.current_index = item_index;
|
||||
root.selected(group_index, item_index);
|
||||
}
|
||||
}
|
||||
|
||||
export component ModalNavigationDrawer inherits ModalDrawer {
|
||||
in property <[NavigationGroup]> groups;
|
||||
in_out property <int> current_group;
|
||||
in_out property <int> current_index;
|
||||
|
||||
accessible-role: tab-list;
|
||||
// accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index;
|
||||
// accessible-label: root.title;
|
||||
accessible-item-count: root.groups.length;
|
||||
|
||||
for group[group_index] in root.groups : NavigationGroupTemplate {
|
||||
title: group.title;
|
||||
items: group.items;
|
||||
current_index: group_index == root.current_group ? root.current_index : -1;
|
||||
has_divider: root.groups.length > 1 && group_index < root.groups.length - 1;
|
||||
|
||||
select(index) => {
|
||||
root.select(group_index, index);
|
||||
}
|
||||
}
|
||||
|
||||
function select(group_index: int, item_index: int) {
|
||||
if group_index < 0 || group_index >= root.groups.length || item_index < 0 || item_index >= root.groups[group_index].items.length {
|
||||
return;
|
||||
}
|
||||
|
||||
root.current_group = group_index;
|
||||
root.current_index = item_index;
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
84
dashboard/ui/material/ui/components/navigation_rail.slint
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { NavigationItem } from "../items/navigation_item.slint";
|
||||
import { FloatingActionButton, FABStyle } from "./floating_action_button.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { IconButton } from "icon_button.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { NavigationItemTemplate } from "navigation_bar.slint";
|
||||
import { BaseNavigation } from "./base_navigation.slint";
|
||||
|
||||
export component NavigationRail inherits BaseNavigation {
|
||||
in property <bool> has_menu;
|
||||
in property <image> fab_icon;
|
||||
in property <LayoutAlignment> alignment;
|
||||
|
||||
callback menu_clicked();
|
||||
callback fab_clicked();
|
||||
|
||||
min_width: max(MaterialStyleMetrics.size_80, layout.min_width);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
layout := VerticalLayout {
|
||||
padding_top: MaterialStyleMetrics.padding_44;
|
||||
padding_bottom: MaterialStyleMetrics.padding_56;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
if root.has_menu : IconButton {
|
||||
icon: Icons.menu;
|
||||
|
||||
clicked => {
|
||||
root.menu_clicked();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if root.fab_icon.width > 0 && root.fab_icon.height > 0 : HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
FloatingActionButton {
|
||||
icon: root.fab_icon;
|
||||
style: FABStyle.standard;
|
||||
|
||||
clicked => {
|
||||
root.fab_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper to place fab at the top if no items are present
|
||||
if root.items.length == 0 : Rectangle {
|
||||
horizontal_stretch: 1;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
vertical_stretch: 1;
|
||||
alignment: center;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: root.alignment;
|
||||
|
||||
for item[index] in root.items : NavigationItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
selected: index == root.current_index;
|
||||
show_badge: item.show_badge;
|
||||
badge: item.badge;
|
||||
navitem_padding_top: 0;
|
||||
navitem_padding_bottom: MaterialStyleMetrics.padding_4;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
dashboard/ui/material/ui/components/outline_button.slint
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component OutlineButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: border.border_radius;
|
||||
color: MaterialPalette.primary;
|
||||
}
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: root.height / 2;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.color: MaterialPalette.on_surface;
|
||||
base.transparent_background: true;
|
||||
border.border_color: MaterialPalette.on_surface;
|
||||
border.opacity: MaterialPalette.state_layer_opacity_hover;
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component OutlineIconButton {
|
||||
in property <image> icon;
|
||||
in property <image> checked_icon: root.icon;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked();
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: true;
|
||||
accessible-label: root.tooltip;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: base.border_radius;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
icon: root.checked ? root.checked_icon : root.icon;
|
||||
border_radius: self.height / 2;
|
||||
color: !root.enabled ? MaterialPalette.on_surface_variant : MaterialPalette.on_surface_variant;
|
||||
button_horizontal_padding: 0;
|
||||
button_vertical_padding: 0;
|
||||
width: self.height;
|
||||
icon_size: MaterialStyleMetrics.icon_size_24;
|
||||
display_background: false;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.color: MaterialPalette.on_surface;
|
||||
base.transparent_background: true;
|
||||
background_layer.border_color: root.checked ? transparent : MaterialPalette.on_surface;
|
||||
background_layer.background: root.checked ? MaterialPalette.on_surface : transparent;
|
||||
background_layer.opacity: MaterialPalette.state_layer_opacity_hover;
|
||||
}
|
||||
checked when root.enabled && root.checked : {
|
||||
base.color: MaterialPalette.inverse_on_surface;
|
||||
background_layer.background: MaterialPalette.inverse_surface;
|
||||
background_layer.border_width: 0;
|
||||
}
|
||||
checked_disabled when root.enabled && root.checked : {
|
||||
base.color: MaterialPalette.inverse_on_surface;
|
||||
background_layer.background: MaterialPalette.inverse_on_surface;
|
||||
background_layer.border_width: 0;
|
||||
}
|
||||
]
|
||||
}
|
||||
107
dashboard/ui/material/ui/components/progress_indicator.slint
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component LinearProgressIndicator {
|
||||
in property <float> progress;
|
||||
in property <bool> indeterminate;
|
||||
|
||||
height: MaterialStyleMetrics.size_4;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
clip: true;
|
||||
background: MaterialPalette.primary_container;
|
||||
border_radius: MaterialStyleMetrics.border_radius_2;
|
||||
|
||||
track := Rectangle {
|
||||
x: 0;
|
||||
y: 0;
|
||||
width: parent.width * root.progress;
|
||||
height: 100%;
|
||||
border_radius: parent.border_radius;
|
||||
background: MaterialPalette.primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component CircularProgressIndicator {
|
||||
in property <float> progress;
|
||||
in property <bool> indeterminate;
|
||||
|
||||
property <length> bar_height: MaterialStyleMetrics.size_4;
|
||||
|
||||
width: self.height;
|
||||
min_height: MaterialStyleMetrics.size_40;
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: root.width;
|
||||
height: root.height;
|
||||
border_width: root.bar_height;
|
||||
border_color: root.progress == 1 ? MaterialPalette.primary : MaterialPalette.primary_container;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
}
|
||||
|
||||
track := Path {
|
||||
property <float> radius: min(self.viewbox_width, self.viewbox_height) / 2;
|
||||
property <float> start_x: self.viewbox_width / 2;
|
||||
property <float> start_y: self.viewbox_height / 2;
|
||||
property <float> inner_radius: self.radius - (root.bar_height * (self.viewbox_height / self.height));
|
||||
property <float> start: !root.indeterminate ? 0 : 1 * mod(animation-tick(), 1.5s) / 1.5s;
|
||||
property <float> progress: !root.indeterminate ? root.progress : 0.5;
|
||||
|
||||
x: background_layer.x;
|
||||
y: background_layer.y;
|
||||
viewbox_width: 100;
|
||||
viewbox_height: 100;
|
||||
width: background_layer.width;
|
||||
height: background_layer.height;
|
||||
fill: MaterialPalette.primary;
|
||||
visible: self.progress > 0 && self.progress < 1;
|
||||
|
||||
MoveTo {
|
||||
x: !root.indeterminate ? track.start_x : track.start_x - track.radius * sin(-track.start * 360deg);
|
||||
y: !root.indeterminate ? 0 : track.start_y - track.radius * cos(-track.start * 360deg);
|
||||
}
|
||||
|
||||
ArcTo {
|
||||
radius_x: 1;
|
||||
radius_y: 1;
|
||||
x: !root.indeterminate ? track.start_x : track.start_x - track.inner-radius * sin(-track.start * 360deg);
|
||||
y: !root.indeterminate ? root.bar_height * (track.viewbox_height / track.height) : track.start_y - track.inner_radius * cos(-track.start * 360deg);
|
||||
}
|
||||
|
||||
ArcTo {
|
||||
radius_x: track.inner_radius;
|
||||
radius_y: track.inner_radius;
|
||||
x: start_x - track.inner_radius * sin(-(track.start + track.progress) * 360deg);
|
||||
y: start_y - track.inner_radius * cos(-(track.start + track.progress) * 360deg);
|
||||
sweep: track.progress > 0;
|
||||
large-arc: track.progress > 0.5;
|
||||
}
|
||||
|
||||
ArcTo {
|
||||
radius_x: 1;
|
||||
radius_y: 1;
|
||||
x: start_x - radius * sin(-(track.start + track.progress) * 360deg);
|
||||
y: start_y - radius * cos(-(track.start + track.progress) * 360deg);
|
||||
}
|
||||
|
||||
ArcTo {
|
||||
radius_x: radius;
|
||||
radius_y: radius;
|
||||
x: start_x - radius * sin(-track.start * 360deg);
|
||||
y: start_y - radius * cos(-track.start * 360deg);
|
||||
sweep: track.progress < 0;
|
||||
large-arc: track.progress > 0.5;
|
||||
}
|
||||
|
||||
LineTo {
|
||||
x: start_x - radius * sin(-track.start * 360deg);
|
||||
y: start_y - radius * cos(-track.start * 360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
99
dashboard/ui/material/ui/components/radio_button.slint
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { ListTile } from "./list.slint";
|
||||
import { ListView } from "./list_view.slint";
|
||||
|
||||
export component RadioButton {
|
||||
in property <bool> checked;
|
||||
in property <bool> enabled;
|
||||
|
||||
callback clicked();
|
||||
|
||||
min_width: MaterialStyleMetrics.size_40;
|
||||
min_height: self.min_width;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-checkable: true;
|
||||
accessible-checked <=> root.checked;
|
||||
accessible-role: checkbox;
|
||||
accessible-action-default => { state_area.clicked(); }
|
||||
forward_focus: state_area;
|
||||
|
||||
state_area := StateLayerArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
color: MaterialPalette.on_surface;
|
||||
|
||||
border := Rectangle {
|
||||
width: MaterialStyleMetrics.size_20;
|
||||
height: self.width;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
border_color: MaterialPalette.on_surface_variant;
|
||||
border_width: MaterialStyleMetrics.size_2;
|
||||
|
||||
indicator := Rectangle {
|
||||
width: parent.width / 2;
|
||||
height: self.width;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
background: MaterialPalette.primary;
|
||||
opacity: !root.checked ? 0 : 1;
|
||||
|
||||
animate opacity {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
animate border_color {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
border.border_color: MaterialPalette.on_surface;
|
||||
border.opacity: MaterialPalette.disable_opacity;
|
||||
indicator.background: MaterialPalette.on_surface;
|
||||
indicator.opacity: root.checked ? MaterialPalette.disable_opacity : 0;
|
||||
}
|
||||
highlighted when !root.checked && (state_area.pressed || state_area.has_focus || state_area.has_hover) : {
|
||||
border.border_color: MaterialPalette.on_surface;
|
||||
}
|
||||
checked when root.checked : {
|
||||
border.border_color: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component RadioButtonTile inherits ListTile {
|
||||
in_out property <bool> checked <=> radio_button.checked;
|
||||
|
||||
callback radio_button_clicked();
|
||||
|
||||
Rectangle {
|
||||
horizontal_stretch: 0;
|
||||
|
||||
radio_button := RadioButton {
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
root.radio_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
radio_button.clicked();
|
||||
}
|
||||
}
|
||||
166
dashboard/ui/material/ui/components/scroll_view.slint
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component ScrollBar {
|
||||
in property <bool> enabled;
|
||||
out property <bool> has_hover: touch_area.has_hover;
|
||||
in_out property <bool> horizontal;
|
||||
in_out property <length> maximum;
|
||||
in_out property <length> page_size;
|
||||
in_out property <length> value;
|
||||
in property <ScrollBarPolicy> policy: ScrollBarPolicy.as_needed;
|
||||
|
||||
property <length> track_size: root.horizontal ? root.width - 2 * root.offset : root.height - 2 * offset;
|
||||
property <length> step_size: 10px;
|
||||
property <length> offset: 2px;
|
||||
property <float> state_opacity: 40%;
|
||||
|
||||
visible: (self.policy == ScrollBarPolicy.always_on) || (self.policy == ScrollBarPolicy.as_needed && self.maximum > 0);
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
|
||||
thumb := Rectangle {
|
||||
width: !root.horizontal ? parent.width : root.maximum <= 0phx ? 0phx : max(min(32px, root.width), root.track-size * root.page-size / (root.maximum + root.page-size));
|
||||
height: root.horizontal ? parent.height : root.maximum <= 0phx ? 0phx : max(min(32px, root.height), root.track-size * (root.page-size / (root.maximum + root.page-size)));
|
||||
x: !root.horizontal ? (parent.width - self.width) / 2 : root.offset + (root.track-size - thumb.width) * (-root.value / root.maximum);
|
||||
y: root.horizontal ? (parent.height - self.height) / 2 : root.offset + (root.track-size - thumb.height) * (-root.value / root.maximum);
|
||||
border-radius: (root.horizontal ? self.height : self.width) / 2;
|
||||
background: MaterialPalette.on_background;
|
||||
opacity: hide_timer.running || touch_area.has_hover || touch_area.pressed ? root.state_opacity : 0;
|
||||
|
||||
animate opacity { duration: 150ms; }
|
||||
}
|
||||
}
|
||||
|
||||
touch_area := TouchArea {
|
||||
property <length> pressed_value;
|
||||
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
|
||||
pointer-event(event) => {
|
||||
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
|
||||
self.pressed-value = -root.value;
|
||||
}
|
||||
}
|
||||
|
||||
moved => {
|
||||
if (self.enabled && self.pressed) {
|
||||
root.value = -max(0px, min(root.maximum, self.pressed-value + (
|
||||
root.horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (root.maximum / (root.track-size - thumb.width))
|
||||
: (touch-area.mouse-y - touch-area.pressed-y) * (root.maximum / (root.track-size - thumb.height))
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
scroll-event(event) => {
|
||||
if (root.horizontal && event.delta-x != 0) {
|
||||
root.value = max(-root.maximum, min(0px, root.value + event.delta-x));
|
||||
return accept;
|
||||
} else if (!root.horizontal && event.delta-y != 0) {
|
||||
root.value = max(-root.maximum, min(0px, root.value + event.delta-y));
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
|
||||
changed has_hover => {
|
||||
if !self.has_hover && !hide_timer.running {
|
||||
hide_timer.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
changed pressed => {
|
||||
if self.pressed && hide_timer.running {
|
||||
hide_timer.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed value => {
|
||||
if !self.has_hover && !hide_timer.running {
|
||||
hide_timer.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
hide_timer := Timer {
|
||||
interval: 1.5s;
|
||||
running: false;
|
||||
|
||||
triggered => {
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
pressed when touch_area.pressed : {
|
||||
root.state_opacity: 80%;
|
||||
}
|
||||
hover when root.has_hover : {
|
||||
root.state_opacity: 70%;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component ScrollView {
|
||||
in property <bool> enabled: true;
|
||||
out property <length> visible_width <=> flickable.width;
|
||||
out property <length> visible_height <=> flickable.height;
|
||||
in_out property <length> viewport_width <=> flickable.viewport_width;
|
||||
in_out property <length> viewport_height <=> flickable.viewport_height;
|
||||
in_out property <length> viewport_x <=> flickable.viewport_x;
|
||||
in_out property <length> viewport_y <=> flickable.viewport_y;
|
||||
in property <ScrollBarPolicy> vertical_scrollbar_policy <=> vertical_bar.policy;
|
||||
in property <ScrollBarPolicy> horizontal_scrollbar_policy <=> horizontal_bar.policy;
|
||||
// FIXME: remove. This property is currently set by the ListView and is used by the native style to draw the scrollbar differently when it has focus
|
||||
in_out property <bool> has_focus;
|
||||
|
||||
callback scrolled <=> flickable.flicked;
|
||||
|
||||
property <length> scroll_bar_size: 8px;
|
||||
property <length> scroll_bar_padding: 1px;
|
||||
|
||||
min_height: 50px;
|
||||
min_width: 50px;
|
||||
horizontal_stretch: 1;
|
||||
vertical_stretch: 1;
|
||||
preferred_height: 100%;
|
||||
preferred_width: 100%;
|
||||
|
||||
flickable := Flickable {
|
||||
x: 0;
|
||||
y: 0;
|
||||
viewport_y <=> vertical_bar.value;
|
||||
viewport_x <=> horizontal_bar.value;
|
||||
width: root.width - (root.vertical_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size - 2 * root.scroll_bar_padding : 0);
|
||||
height: root.height - (root.horizontal_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size : 0);
|
||||
|
||||
@children
|
||||
}
|
||||
|
||||
vertical_bar := ScrollBar {
|
||||
enabled: root.enabled;
|
||||
x: flickable.width + root.scroll_bar_padding;
|
||||
y: 0;
|
||||
width: root.visible ? root.scroll_bar_size : 0;
|
||||
height: root.vertical_scrollbar_policy != ScrollBarPolicy.always_off ? parent.height - horizontal_bar.height : parent.height;
|
||||
horizontal: false;
|
||||
maximum: flickable.viewport_height - flickable.height;
|
||||
page-size: flickable.height;
|
||||
}
|
||||
|
||||
horizontal_bar := ScrollBar {
|
||||
enabled: root.enabled;
|
||||
width: vertical_bar.visible ? parent.width - vertical_bar.width : parent.width;
|
||||
height: root.horizontal_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size : 0;
|
||||
x: 0;
|
||||
y: flickable.height + root.scroll_bar_padding;
|
||||
horizontal: true;
|
||||
maximum: flickable.viewport_width - flickable.width;
|
||||
page_size: flickable.width;
|
||||
}
|
||||
}
|
||||
317
dashboard/ui/material/ui/components/search_bar.slint
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ListItem } from "../items/list_item.slint";
|
||||
import { Avatar, ListTile } from "./list.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { HorizontalDivider } from "./divider.slint";
|
||||
import { ListView } from "./list_view.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
|
||||
component SearchTile inherits ListTile {
|
||||
in property <image> action_button_icon;
|
||||
|
||||
callback action_button_clicked();
|
||||
|
||||
if root.action_button_icon.width > 0 && root.action_button_icon.height > 0 : IconButton {
|
||||
icon: root.action_button_icon;
|
||||
|
||||
clicked => {
|
||||
root.action_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SearchIcon {
|
||||
in property <image> icon;
|
||||
in property <color> color;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
padding_left: MaterialStyleMetrics.padding_8;
|
||||
padding_right: self.padding_left;
|
||||
|
||||
Icon {
|
||||
source: root.icon;
|
||||
colorize: root.color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SearchTextInput {
|
||||
in_out property <string> text <=> text_input.text;
|
||||
in property <string> placeholder_text;
|
||||
property <length> computed_x;
|
||||
|
||||
callback accepted(text: string);
|
||||
callback edited(text: string);
|
||||
callback key_pressed(event: KeyEvent) -> EventResult;
|
||||
callback key_released(event: KeyEvent) -> EventResult;
|
||||
|
||||
forward_focus: text_input;
|
||||
horizontal_stretch: 1;
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
|
||||
text_input := TextInput {
|
||||
x: min(0px, max(parent.width - self.width - self.text_cursor_width, root.computed_x));
|
||||
width: max(parent.width - self.text-cursor-width, self.preferred-width);
|
||||
height: 100%;
|
||||
font_size: MaterialTypography.body_large.font_size;
|
||||
font_weight: MaterialTypography.body_large.font_weight;
|
||||
vertical_alignment: center;
|
||||
single_line: true;
|
||||
color: MaterialPalette.on_surface;
|
||||
selection_background_color: MaterialPalette.secondary_container;
|
||||
selection_foreground_color: self.color;
|
||||
// Disable TextInput's built-in accessibility support as the component takes care of that.
|
||||
accessible-role: none;
|
||||
|
||||
cursor_position_changed(cursor_position) => {
|
||||
if cursor_position.x + root.computed_x < 0 {
|
||||
root.computed_x = - cursor_position.x;
|
||||
} else if cursor-position.x + root.computed_x > parent.width - self.text-cursor-width {
|
||||
root.computed_x = parent.width - cursor_position.x - self.text-cursor-width;
|
||||
}
|
||||
}
|
||||
|
||||
accepted => {
|
||||
root.accepted(self.text);
|
||||
}
|
||||
|
||||
edited => {
|
||||
root.edited(self.text);
|
||||
}
|
||||
|
||||
key_pressed(event) => {
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
root.key_released(event)
|
||||
}
|
||||
|
||||
init => {
|
||||
if root.text.character_count > 0 {
|
||||
self.set_selection_offsets(root.text.character_count, root.text.character_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
if root.text == "" && root.placeholder_text != "" : MaterialText {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text: root.placeholder_text;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
vertical_alignment: center;
|
||||
style: MaterialTypography.body_large;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component SearchBar {
|
||||
in property <image> leading_icon: Icons.menu;
|
||||
in property <image> trailing_icon;
|
||||
in property <image> avatar_icon;
|
||||
in property <color> avatar_background: #00000000;
|
||||
in property <string> placeholder_text;
|
||||
in property <string> empty_text;
|
||||
in_out property <string> text;
|
||||
in_out property <int> current_index;
|
||||
in property <[ListItem]> items;
|
||||
|
||||
callback accepted(text: string);
|
||||
callback edited(text: string);
|
||||
callback action_button_clicked(index: int);
|
||||
callback key_pressed(event: KeyEvent) -> EventResult;
|
||||
callback key_released(event: KeyEvent) -> EventResult;
|
||||
|
||||
property <color> color: MaterialPalette.on_surface_variant;
|
||||
property <length> item_height: MaterialStyleMetrics.size_72;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_56, layout.min_height);
|
||||
forward_focus: state_layer;
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container_high;
|
||||
border_radius: MaterialStyleMetrics.border_radius_28;
|
||||
|
||||
state_layer := StateLayerArea {
|
||||
border_radius: parent.border_radius;
|
||||
color: root.color;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_4;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
SearchIcon {
|
||||
icon: root.leading_icon;
|
||||
color: root.color;
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
text: root.text;
|
||||
font_size: MaterialTypography.body_large.font_size;
|
||||
font_weight: MaterialTypography.body_large.font_weight;
|
||||
vertical_alignment: center;
|
||||
color: MaterialPalette.on_surface;
|
||||
|
||||
states [
|
||||
placeholder when root.text == "" : {
|
||||
text: root.placeholder_text;
|
||||
color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
SearchIcon {
|
||||
icon: root.trailing_icon;
|
||||
color: root.color;
|
||||
}
|
||||
|
||||
if (root.avatar_icon.width > 0 && root.avatar_icon.height > 0) || root.avatar_background != #00000000 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
Avatar {
|
||||
image: root.avatar_icon;
|
||||
background: root.avatar_background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed has_focus => {
|
||||
if self.has_focus {
|
||||
popup.show();
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
popup.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popup := PopupWindow {
|
||||
x: 0;
|
||||
y: 0;
|
||||
width: root.width;
|
||||
height: root.height + clamp(root.item_height * root.items.length, 3 * root.item_height, 6 * root.item_height);
|
||||
close_policy: close_on_click_outside;
|
||||
forward-focus: popup_input;
|
||||
|
||||
background_layer := Rectangle {
|
||||
x: 0;
|
||||
y: 0;
|
||||
width: 100%;
|
||||
height: root.height;
|
||||
background: MaterialPalette.surface_container_high;
|
||||
border_radius: MaterialStyleMetrics.border_radius_28;
|
||||
clip: true;
|
||||
|
||||
VerticalLayout {
|
||||
HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_4;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
IconButton {
|
||||
icon: Icons.arrow_back;
|
||||
|
||||
clicked => {
|
||||
popup.close();
|
||||
}
|
||||
}
|
||||
|
||||
popup_input := SearchTextInput {
|
||||
placeholder_text: root.placeholder_text;
|
||||
text: root.text;
|
||||
|
||||
accepted => {
|
||||
root.accepted(self.text);
|
||||
}
|
||||
|
||||
edited => {
|
||||
root.text = self.text;
|
||||
root.edited(self.text);
|
||||
}
|
||||
|
||||
key_pressed(event) => {
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
root.key_released(event)
|
||||
}
|
||||
|
||||
changed text => {
|
||||
root.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: Icons.close;
|
||||
|
||||
clicked => {
|
||||
root.text = "";
|
||||
popup.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider {}
|
||||
|
||||
if root.items.length == 0 : MaterialText {
|
||||
text: root.empty_text;
|
||||
color: root.color;
|
||||
horizontal_alignment: center;
|
||||
style: MaterialTypography.label_small;
|
||||
}
|
||||
|
||||
if root.items.length == 0 : Rectangle {}
|
||||
|
||||
if root.items.length > 0 : ListView {
|
||||
horizontal_stretch: 1;
|
||||
|
||||
for item[index] in root.items : SearchTile {
|
||||
text: item.text;
|
||||
supporting_text: item.supporting_text;
|
||||
avatar_icon: item.avatar_icon;
|
||||
avatar_text: item.avatar_text;
|
||||
avatar_background: item.avatar_background;
|
||||
avatar_foreground: item.avatar_foreground;
|
||||
action_button_icon: item.action_button_icon;
|
||||
|
||||
clicked => {
|
||||
root.text = self.text;
|
||||
popup.close();
|
||||
}
|
||||
|
||||
action_button_clicked => {
|
||||
root.action_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animate height {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
background_layer.height = popup.height;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
dashboard/ui/material/ui/components/segmented_button.slint
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { BaseButton } from "base_button.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
|
||||
export struct SegmentedItem {
|
||||
icon: image,
|
||||
text: string
|
||||
}
|
||||
|
||||
component SegmentedItemTemplate {
|
||||
in property <image> icon;
|
||||
in property <string> text;
|
||||
in property <int> index;
|
||||
in property <bool> selected;
|
||||
in property <bool> last;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: list-item;
|
||||
accessible-label: root.text;
|
||||
accessible-item-selectable: true;
|
||||
accessible-item-selected: root.selected;
|
||||
accessible-item-index: root.index;
|
||||
|
||||
if root.selected : Rectangle {
|
||||
background: MaterialPalette.secondary_container;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
min_layout_width: 0;
|
||||
min_layout_height: 0;
|
||||
color: MaterialPalette.on_surface;
|
||||
button_vertical_padding: MaterialStyleMetrics.padding_10;
|
||||
button_horizontal_padding: MaterialStyleMetrics.padding_24;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
icon: root.icon;
|
||||
text: root.text;
|
||||
|
||||
|
||||
animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; }
|
||||
}
|
||||
|
||||
if !root.last : Rectangle {
|
||||
x: parent.width - self.width;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background: MaterialPalette.outline;
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
base.icon: Icons.check;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component SegmentedButton {
|
||||
in property <[SegmentedItem]> items;
|
||||
in_out property <int> current_index;
|
||||
|
||||
callback index_changed(index: int);
|
||||
|
||||
min_width: max(2 * MaterialStyleMetrics.size_40, layout.min_width);
|
||||
min_height: max(MaterialStyleMetrics.size_40, layout.min_height);
|
||||
|
||||
accessible-role: list;
|
||||
accessible-item-count: root.items.length;
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
border_width: 1px;
|
||||
border_radius: self.height / 2;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
for item[index] in root.items : SegmentedItemTemplate {
|
||||
icon: item.icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
last: index == root.items.length - 1;
|
||||
selected: index == root.current_index;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function select(index: int) {
|
||||
if index < 0 || index >= root.items.length {
|
||||
return;
|
||||
}
|
||||
|
||||
root.current_index = index;
|
||||
root.index_changed(index);
|
||||
}
|
||||
}
|
||||
136
dashboard/ui/material/ui/components/slider.slint
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { StateLayer } from "./state_layer.slint";
|
||||
|
||||
export component Slider {
|
||||
in property <bool> enabled;
|
||||
in_out property <float> value;
|
||||
in property <float> minimum;
|
||||
in property <int> stop_count: 0;
|
||||
in property <float> maximum: 100;
|
||||
|
||||
callback released(value: float);
|
||||
callback value_changed(value: float);
|
||||
|
||||
property <int> steps: root.stop_count > 0 ? (root.maximum - root.minimum) / root.stop_count : 1;
|
||||
|
||||
min_height: 20px;
|
||||
accessible-role: slider;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-value: root.value;
|
||||
accessible-value-minimum: root.minimum;
|
||||
accessible-value-maximum: root.maximum;
|
||||
accessible-value-step: min(steps, (root.maximum - root.minimum) / 100);
|
||||
forward-focus: focus_scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
touch_area := TouchArea {
|
||||
track := Rectangle {
|
||||
x: 0;
|
||||
height: MaterialStyleMetrics.size_16;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
border_radius: self.height / 2;
|
||||
|
||||
Rectangle {
|
||||
x: 0;
|
||||
width: state_layer.x - MaterialStyleMetrics.size_6;
|
||||
border-top-left-radius: track.border_radius;
|
||||
border-bottom-left-radius: track.border_radius;
|
||||
border-top-right-radius: MaterialStyleMetrics.size_1;
|
||||
border-bottom-right-radius: MaterialStyleMetrics.size_1;
|
||||
background: thumb.background;
|
||||
}
|
||||
|
||||
if root.stop_count > 0: Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
for i in (root.maximum - root.minimum) / root.steps: Rectangle {
|
||||
x: root.value_to_length(track.width, (i + 1) * root.steps) - self.width / 2;
|
||||
height: MaterialStyleMetrics.size_4;
|
||||
width: self.height;
|
||||
border_radius: self.height / 2;
|
||||
background: MaterialPalette.outline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state_layer := Rectangle {
|
||||
x: root.value_to_length(track.width, root.value) - (self.width) / 2;
|
||||
width: 0;
|
||||
height: track.height + (MaterialStyleMetrics.size_14 * 2);
|
||||
StateLayer {
|
||||
height: track.height + (MaterialStyleMetrics.size_16 * 2);
|
||||
width: thumb.width + (MaterialStyleMetrics.size_2 * 2);
|
||||
border_radius: self.width / 2;
|
||||
pressed: touch_area.pressed;
|
||||
has_hover: touch_area.has_hover;
|
||||
has_focus: focus_scope.has_focus;
|
||||
enabled: root.enabled;
|
||||
background: thumb.background;
|
||||
display_background: thumb_area.pressed || thumb_area.has_hover || focus_scope.has_focus;
|
||||
|
||||
thumb_area := TouchArea {
|
||||
height: track.height + (MaterialStyleMetrics.size_14 * 2);
|
||||
width: self.height;
|
||||
thumb := Rectangle {
|
||||
width: MaterialStyleMetrics.size_4;
|
||||
border_radius: self.width / 2;
|
||||
background: MaterialPalette.primary;
|
||||
}
|
||||
|
||||
moved => {
|
||||
root.set_value(root.length_to_value(state_layer.x + self.mouse_x, track.width));
|
||||
}
|
||||
|
||||
pointer_event(event) => {
|
||||
if event.kind == PointerEventKind.up {
|
||||
root.released(root.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pointer_event(event) => {
|
||||
if event.kind == PointerEventKind.down && event.button == PointerEventButton.left {
|
||||
root.set_value(root.length_to_value(self.mouse_x, track.width));
|
||||
}
|
||||
if event.kind == PointerEventKind.up {
|
||||
root.released(root.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
key_pressed(event) => {
|
||||
if event.text == Key.LeftArrow {
|
||||
root.set_value(root.value - root.steps);
|
||||
return accept;
|
||||
}
|
||||
if event.text == Key.RightArrow {
|
||||
root.set_value(root.value + root.steps);
|
||||
return accept;
|
||||
}
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
changed value => {
|
||||
root.value_changed(root.value);
|
||||
}
|
||||
|
||||
pure function value_to_length(width: length, value: float) -> length {
|
||||
clamp(width * (value - root.minimum) / (root.maximum - root.minimum), 0, width)
|
||||
}
|
||||
|
||||
pure function length_to_value(x: length, width: length) -> float {
|
||||
x * (root.maximum - root.minimum) / width
|
||||
}
|
||||
|
||||
function set_value(value: float) {
|
||||
root.value = clamp(round(value / root.steps) * root.steps, root.minimum, root.maximum)
|
||||
}
|
||||
}
|
||||
88
dashboard/ui/material/ui/components/snack_bar.slint
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { Elevation } from "elevation.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { TextButton } from "./text_button.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
|
||||
export component SnackBar inherits PopupWindow {
|
||||
in property <string> text;
|
||||
in property <string> action_text;
|
||||
in property <bool> has_close_button;
|
||||
|
||||
close_policy: no_auto_close;
|
||||
|
||||
callback action_clicked();
|
||||
|
||||
snack_bar := Elevation {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: parent.height - self.height;
|
||||
width: MaterialStyleMetrics.size_344;
|
||||
level: 3;
|
||||
|
||||
background_layer := Rectangle {
|
||||
background: MaterialPalette.inverse_surface;
|
||||
border_radius: MaterialStyleMetrics.border_radius_4;
|
||||
|
||||
HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_16;
|
||||
padding_right: self.padding_left;
|
||||
padding_top: MaterialStyleMetrics.padding_10;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
MaterialText {
|
||||
text: root.text;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: left;
|
||||
wrap: word_wrap;
|
||||
style: MaterialTypography.body_medium;
|
||||
color: MaterialPalette.inverse_on_surface;
|
||||
}
|
||||
|
||||
if root.action_text != "" : TextButton {
|
||||
text: root.action_text;
|
||||
inverse: true;
|
||||
|
||||
clicked => {
|
||||
root.raise_action();
|
||||
}
|
||||
}
|
||||
|
||||
if root.has_close_button : IconButton {
|
||||
icon: Icons.close;
|
||||
inverse: true;
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animate y, height {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
snack_bar.y = root.height - snack_bar.height - MaterialStyleMetrics.padding_30;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
function raise_action() {
|
||||
root.action_clicked();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
171
dashboard/ui/material/ui/components/state_layer.slint
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { ToolTip } from "./tooltip.slint";
|
||||
import { MaterialWindowAdapter } from "./material_window.slint";
|
||||
|
||||
export component Ripple {
|
||||
in property <length> border_radius;
|
||||
in property <length> pressed_x;
|
||||
in property <length> pressed_y;
|
||||
in property <color> color;
|
||||
in property <bool> clip_ripple: true;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: root.border_radius;
|
||||
clip: root.clip_ripple;
|
||||
|
||||
ripple := Rectangle {
|
||||
x: root.pressed_x - self.width / 2;
|
||||
y: root.pressed_y - self.height / 2;
|
||||
height: self.width;
|
||||
border_radius: self.width / 2;
|
||||
opacity: MaterialPalette.state_layer_opacity_press;
|
||||
background: root.color;
|
||||
|
||||
init => {
|
||||
self.width = root.width * 2 * 1.4142;
|
||||
}
|
||||
|
||||
animate width { duration: MaterialAnimations.ripple_duration; easing: MaterialAnimations.ripple_easing; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component StateLayer {
|
||||
in property <color> background;
|
||||
in property <length> border_radius;
|
||||
in property <bool> display_background: true;
|
||||
in property <bool> enabled;
|
||||
in property <bool> pressed;
|
||||
in property <bool> has_focus;
|
||||
in property <bool> has_hover;
|
||||
|
||||
property <float> state_layer_opacity;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: root.state_layer_opacity;
|
||||
background: root.background;
|
||||
border_radius: root.border_radius;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
clip: true;
|
||||
border_radius: root.border_radius;
|
||||
|
||||
@children
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled && root.display_background : {
|
||||
root.state_layer_opacity: MaterialPalette.state_layer_opacity_focus;
|
||||
}
|
||||
focus when root.enabled && root.has_focus : {
|
||||
root.state_layer_opacity: MaterialPalette.state_layer_opacity_focus;
|
||||
}
|
||||
pressed when root.enabled && root.pressed : {
|
||||
root.state_layer_opacity: MaterialPalette.state_layer_opacity_press;
|
||||
}
|
||||
hover when root.enabled && root.has_hover && !MaterialWindowAdapter.disable_hover : {
|
||||
root.state_layer_opacity: MaterialPalette.state_layer_opacity_hover;
|
||||
}
|
||||
]
|
||||
|
||||
animate state_layer_opacity { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
export component FocusTouchArea inherits TouchArea {
|
||||
out property <bool> has_focus <=> focus_scope.has_focus;
|
||||
out property <bool> enter_pressed;
|
||||
in property <string> tooltip;
|
||||
in property <length> tooltip_offset;
|
||||
|
||||
callback key_pressed(KeyEvent) -> EventResult;
|
||||
|
||||
forward_focus: focus_scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
x: 0;
|
||||
width: 0px;
|
||||
enabled: root.enabled;
|
||||
|
||||
key_pressed(event) => {
|
||||
if !root.enabled {
|
||||
return reject;
|
||||
}
|
||||
|
||||
if (event.text == " " || event.text == "\n") && !root.enter_pressed {
|
||||
root.enter_pressed = true;
|
||||
root.clicked();
|
||||
return accept;
|
||||
}
|
||||
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
if !root.enabled {
|
||||
return reject;
|
||||
}
|
||||
|
||||
if (event.text == " " || event.text == "\n") && root.enter_pressed {
|
||||
root.enter_pressed = false;
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
|
||||
|
||||
if root.tooltip != "" && root.has_hover && !MaterialWindowAdapter.disable_hover && !root.pressed : ToolTip {
|
||||
y: root.tooltip_offset;
|
||||
text: root.tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
export component StateLayerArea inherits FocusTouchArea {
|
||||
in property <color> color;
|
||||
in property <length> border_radius;
|
||||
in property <bool> transparent_background;
|
||||
in property <bool> display_background: true;
|
||||
in property <bool> clip_ripple: true;
|
||||
|
||||
property <bool> popup_open;
|
||||
property <bool> has_h: root.mouse-x > 0 && root.mouse-x < root.width && root.mouse-y > 0 && root.mouse-y < root.height;
|
||||
|
||||
if !root.enabled || (root.enabled && (root.has_focus || root.pressed || root.enter_pressed || root.has_hover)) : StateLayer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: root.transparent_background ? transparent : root.color;
|
||||
border_radius: root.border_radius;
|
||||
enabled: root.enabled;
|
||||
pressed: root.pressed || root.enter_pressed;
|
||||
has_focus: root.has_focus;
|
||||
has_hover: root.has_hover;
|
||||
display_background: root.display_background;
|
||||
}
|
||||
|
||||
// ripple
|
||||
if root.enabled && (root.pressed || root.enter_pressed) : Ripple {
|
||||
pressed_x: root.pressed_x;
|
||||
pressed_y: root.pressed_y;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: root.border_radius;
|
||||
color: root.color;
|
||||
clip_ripple: root.clip_ripple;
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
151
dashboard/ui/material/ui/components/switch.slint
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { FocusTouchArea, StateLayer, Ripple } from "./state_layer.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
export component Switch {
|
||||
in_out property <bool> checked;
|
||||
in property <bool> enabled: true;
|
||||
in property <string> tooltip <=> touch_area.tooltip;
|
||||
in property <image> on_icon;
|
||||
in property <image> off_icon;
|
||||
|
||||
property <bool> has_icon: (root.checked && root.on_icon.width > 0 && root.on_icon.height > 0) || (root.off_icon.width > 0 && root.off_icon.height > 0);
|
||||
property <color> foreground: root.checked ? MaterialPalette.on_primary_container : MaterialPalette.surface_container_highest;
|
||||
|
||||
callback checked_state_changed(checked: bool);
|
||||
|
||||
min_width: MaterialStyleMetrics.size_52;
|
||||
min_height: MaterialStyleMetrics.size_32;
|
||||
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-checkable: true;
|
||||
accessible-checked <=> root.checked;
|
||||
accessible-role: checkbox;
|
||||
accessible-action-default => { touch_area.clicked(); }
|
||||
forward_focus: touch_area;
|
||||
|
||||
touch_area := FocusTouchArea {
|
||||
enabled: root.enabled;
|
||||
|
||||
background_layer := Rectangle {
|
||||
background: root.checked ? MaterialPalette.primary : MaterialPalette.surface_container_highest;
|
||||
border_radius: self.height / 2;
|
||||
border_width: root.checked ? 0 : 2px;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
state_layer := StateLayer {
|
||||
x: indicator.x + (indicator.width - self.width) / 2;
|
||||
width: self.height;
|
||||
height: root.height + MaterialStyleMetrics.size_8;
|
||||
border_radius: self.height / 2;
|
||||
has_hover: touch_area.has_hover;
|
||||
has_focus: touch_area.has_focus;
|
||||
pressed: touch_area.pressed;
|
||||
enabled: root.enabled;
|
||||
|
||||
// ripple
|
||||
if root.enabled && (touch_area.pressed || touch_area.enter_pressed) : Ripple {
|
||||
pressed_x: touch_area.pressed_x;
|
||||
pressed_y: touch_area.pressed_y;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: state_layer.border_radius;
|
||||
color: state_layer.background;
|
||||
clip_ripple: true;
|
||||
}
|
||||
}
|
||||
|
||||
indicator := Rectangle {
|
||||
property <length> indicator-padding: root.checked || root.has_icon ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_8;
|
||||
|
||||
property <length> default_height: background_layer.height - 2 * self.indicator-padding;
|
||||
|
||||
x: self.indicator-padding - (self.height - self.default_height) / 2;
|
||||
width: self.height;
|
||||
height: self.default_height;
|
||||
border_radius: self.height / 2;
|
||||
background: root.checked ? MaterialPalette.primary_container : MaterialPalette.outline;
|
||||
|
||||
if root.has_icon : Icon {
|
||||
source: root.checked ? root.on_icon : root.off_icon;
|
||||
colorize: root.foreground;
|
||||
|
||||
animate colorize {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
animate height {
|
||||
duration: MaterialAnimations.emphasized_accelerate_duration;
|
||||
easing: MaterialAnimations.emphasized_easing;
|
||||
}
|
||||
|
||||
animate background {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.checked : {
|
||||
x: background_layer.width - self.indicator-padding - self.width;
|
||||
|
||||
in {
|
||||
animate x {
|
||||
duration: MaterialAnimations.emphasized_accelerate_duration;
|
||||
easing: MaterialAnimations.emphasized_easing;
|
||||
}
|
||||
}
|
||||
|
||||
out {
|
||||
animate x {
|
||||
duration: MaterialAnimations.emphasized_accelerate_duration;
|
||||
easing: MaterialAnimations.emphasized_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
animate background, border_color {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
changed checked => {
|
||||
root.checked_state_changed(root.checked);
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
state_layer.display_background: false;
|
||||
indicator.background: root.checked ? MaterialPalette.surface : MaterialPalette.outline.with_alpha(MaterialPalette.disable_opacity);
|
||||
background_layer.background: (root.checked ? MaterialPalette.on_surface : MaterialPalette.surface_variant).with_alpha(MaterialPalette.state_layer_opacity_disabled);
|
||||
background_layer.border_color: MaterialPalette.on_surface.with_alpha(MaterialPalette.state_layer_opacity_disabled);
|
||||
foreground: root.checked ? MaterialPalette.on_surface.with_alpha(MaterialPalette.disable_opacity) : MaterialPalette.surface_container_highest;
|
||||
}
|
||||
pressed when touch_area.pressed: {
|
||||
state_layer.background: indicator.background;
|
||||
indicator.height: background_layer.height - 2px;
|
||||
indicator.background: root.checked ? MaterialPalette.primary_container : MaterialPalette.on_surface_variant;
|
||||
}
|
||||
hover when touch_area.has_hover: {
|
||||
state_layer.background: indicator.background;
|
||||
}
|
||||
]
|
||||
}
|
||||
210
dashboard/ui/material/ui/components/tab_bar.slint
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { NavigationItem } from "../items/navigation_item.slint";
|
||||
import { BaseNavigationItemTemplate, BaseNavigation } from "./base_navigation.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
component TabItemTemplate inherits BaseNavigationItemTemplate {
|
||||
out property <length> label_width: label.width;
|
||||
|
||||
property <bool> has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0;
|
||||
|
||||
StateLayerArea {
|
||||
color: MaterialPalette.primary;
|
||||
|
||||
VerticalLayout {
|
||||
padding_top: MaterialStyleMetrics.padding_10;
|
||||
padding_bottom: MaterialStyleMetrics.padding_8;
|
||||
spacing: MaterialStyleMetrics.spacing_2;
|
||||
|
||||
if root.icon.width > 0 && root.icon.height > 0 : HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
Icon {
|
||||
source: root.icon;
|
||||
colorize: label.color;
|
||||
|
||||
states [
|
||||
selected when root.selected && root.has_selected_icon : {
|
||||
source: root.selected_icon;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
label := MaterialText {
|
||||
text: root.text;
|
||||
style: MaterialTypography.title_small;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
label.color: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
component SecondaryTabItemTemplate inherits BaseNavigationItemTemplate {
|
||||
property <bool> has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0;
|
||||
|
||||
StateLayerArea {
|
||||
color: label.color;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
|
||||
if root.icon.width > 0 && root.icon.height > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
Icon {
|
||||
source: root.icon;
|
||||
colorize: label.color;
|
||||
|
||||
states [
|
||||
selected when root.selected && root.has_selected_icon : {
|
||||
source: root.selected_icon;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
label := MaterialText {
|
||||
text: root.text;
|
||||
style: MaterialTypography.title_small;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
label.color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component TabBar inherits BaseNavigation {
|
||||
property <length> indicator_width;
|
||||
property <length> item_width: root.width / root.items.length;
|
||||
property <length> current_x: root.item_width * self.current_index;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_49, layout.min_height);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
for item[index] in root.items : tab_item := TabItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
selected: index == root.current_index;
|
||||
show_badge: item.show_badge;
|
||||
badge: item.badge;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
root.set_indicator_width(index, self.label_width);
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
root.set_indicator_width(index, tab_item.label_width);
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indicator := Rectangle {
|
||||
x: root.current_x + (root.item_width - self.width) / 2;
|
||||
y: parent.height - self.height;
|
||||
width: root.indicator_width;
|
||||
height: MaterialStyleMetrics.size_3;
|
||||
border_top_left_radius: self.height / 2;
|
||||
border_top_right_radius: self.border_top_left_radius;
|
||||
background: MaterialPalette.primary;
|
||||
|
||||
animate x, width {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function set_indicator_width(index: int, width: length) {
|
||||
if index != root.current_index {
|
||||
return;
|
||||
}
|
||||
|
||||
root.indicator_width = width;
|
||||
}
|
||||
}
|
||||
|
||||
export component SecondaryTabBar inherits BaseNavigation {
|
||||
property <length> item_width: root.width / root.items.length;
|
||||
property <length> current_x: root.item_width * self.current_index;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_49, layout.min_height);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
for item[index] in root.items : SecondaryTabItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
selected: index == root.current_index;
|
||||
show_badge: item.show_badge;
|
||||
badge: item.badge;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indicator := Rectangle {
|
||||
x: root.current_x;
|
||||
y: parent.height - self.height;
|
||||
width: root.item_width;
|
||||
height: MaterialStyleMetrics.size_2;
|
||||
background: MaterialPalette.primary;
|
||||
|
||||
animate x {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
dashboard/ui/material/ui/components/text_button.slint
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component TextButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> inverse;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: root.height / 2;
|
||||
color: root.inverse ? MaterialPalette.inverse_primary : MaterialPalette.primary;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.color: MaterialPalette.on_surface;
|
||||
base.transparent_background: true;
|
||||
}
|
||||
]
|
||||
}
|
||||
209
dashboard/ui/material/ui/components/text_field.slint
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
export component TextField {
|
||||
in property <string> label;
|
||||
in property <string> placeholder_text;
|
||||
in property <string> supporting_text;
|
||||
in property <bool> has_error;
|
||||
in property <image> leading_icon;
|
||||
in property <image> trailing_icon;
|
||||
in property <bool> enabled: true;
|
||||
in property <bool> read_only <=> input.read_only;
|
||||
out property <bool> has_focus: input.has_focus;
|
||||
in_out property <string> text <=> input.text;
|
||||
|
||||
callback leading_icon_clicked();
|
||||
callback trailing_icon_clicked();
|
||||
callback accepted(text: string);
|
||||
callback edited(text: string);
|
||||
callback key_pressed(event: KeyEvent) -> EventResult;
|
||||
callback key_released(event: KeyEvent) -> EventResult;
|
||||
|
||||
property <bool> has_leading: root.leading_icon.width > 0 && root.leading_icon.height > 0;
|
||||
property <bool> has_trailing: root.trailing_icon.width > 0 && root.trailing_icon.height > 0;
|
||||
property <length> computed_x;
|
||||
property <color> highlight: root.enabled && root.has_error ? MaterialPalette.error : root.has_focus ? MaterialPalette.primary : MaterialPalette.on_surface_variant;
|
||||
|
||||
forward_focus: input;
|
||||
accessible-role: text-input;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-value <=> text;
|
||||
accessible-placeholder-text: root.text == "" ? placeholder_text : "";
|
||||
accessible-action-set-value(v) => { text = v; edited(v); }
|
||||
|
||||
layout := VerticalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_top_left_radius: MaterialStyleMetrics.border_radius_4;
|
||||
border_top_right_radius: MaterialStyleMetrics.border_radius_4;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
min_height: max(MaterialStyleMetrics.size_56, inner_layout.min_height);
|
||||
|
||||
inner_layout := HorizontalLayout {
|
||||
padding_left: root.has_leading ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_16;
|
||||
padding_right: root.has_trailing ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_16;
|
||||
padding_top: MaterialStyleMetrics.padding_4;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_2;
|
||||
|
||||
if root.has_leading : IconButton {
|
||||
icon: root.leading_icon;
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
root.leading_icon_clicked();
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
padding_top: MaterialStyleMetrics.padding_4;
|
||||
padding_bottom: self.padding_top;
|
||||
|
||||
Rectangle {
|
||||
horizontal_stretch: 1;
|
||||
clip: true;
|
||||
min_height: input.min_height;
|
||||
|
||||
input := TextInput {
|
||||
x: min(0px, max(parent.width - self.width - self.text_cursor_width, root.computed_x));
|
||||
width: max(parent.width - self.text-cursor-width, self.preferred-width);
|
||||
font_size: MaterialTypography.body_large.font_size;
|
||||
font_weight: MaterialTypography.body_large.font_weight;
|
||||
text_cursor_width: MaterialStyleMetrics.size_3;
|
||||
vertical_alignment: bottom;
|
||||
single_line: true;
|
||||
color: MaterialPalette.on_surface;
|
||||
selection_background_color: MaterialPalette.primary.with_alpha(0.5);
|
||||
selection_foreground_color: self.color;
|
||||
enabled: root.enabled;
|
||||
// Disable TextInput's built-in accessibility support as the component takes care of that.
|
||||
accessible-role: none;
|
||||
|
||||
cursor_position_changed(cursor_position) => {
|
||||
if cursor_position.x + root.computed_x < 0 {
|
||||
root.computed_x = - cursor_position.x;
|
||||
} else if cursor-position.x + root.computed_x > parent.width - self.text-cursor-width {
|
||||
root.computed_x = parent.width - cursor_position.x - self.text-cursor-width;
|
||||
}
|
||||
}
|
||||
|
||||
accepted => {
|
||||
root.accepted(self.text);
|
||||
}
|
||||
|
||||
edited => {
|
||||
root.edited(self.text);
|
||||
}
|
||||
|
||||
key_pressed(event) => {
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
root.key_released(event)
|
||||
}
|
||||
}
|
||||
|
||||
text_label := MaterialText {
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: 100%;
|
||||
text: root.label;
|
||||
color: root.highlight;
|
||||
style: MaterialTypography.body_large;
|
||||
|
||||
states [
|
||||
on_top when root.text != "" || input.has_focus : {
|
||||
y: 0;
|
||||
style: MaterialTypography.body_small;
|
||||
}
|
||||
]
|
||||
|
||||
animate font_size, y {
|
||||
duration: MaterialAnimations.standard_fast_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.trailing_icon.width > 0 && root.trailing_icon.height > 0 : IconButton {
|
||||
icon: root.trailing_icon;
|
||||
enabled: root.enabled;
|
||||
has_error: root.has_error;
|
||||
|
||||
clicked => {
|
||||
root.trailing_icon_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
active_indicator := Rectangle {
|
||||
y: parent.height - self.height;
|
||||
height: MaterialStyleMetrics.size_1;
|
||||
background: root.highlight;
|
||||
|
||||
animate height {
|
||||
duration: MaterialAnimations.standard_fast_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.supporting_text != "" : HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_16;
|
||||
padding_right: self.padding_left;
|
||||
|
||||
supporting_text := MaterialText {
|
||||
text: root.supporting_text;
|
||||
style: MaterialTypography.body_small;
|
||||
color: root.highlight;
|
||||
opacity: root.enabled ? 1 : MaterialPalette.disable_opacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function set_selection_offsets(start: int, end: int) {
|
||||
input.set_selection_offsets(start, end);
|
||||
}
|
||||
|
||||
public function select_all() {
|
||||
input.select_all();
|
||||
}
|
||||
|
||||
public function clear_selection() {
|
||||
input.clear_selection();
|
||||
}
|
||||
|
||||
public function cut() {
|
||||
input.cut();
|
||||
}
|
||||
|
||||
public function copy() {
|
||||
input.copy();
|
||||
}
|
||||
|
||||
public function paste() {
|
||||
input.paste();
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
background_layer.background: MaterialPalette.surface_container_highest.with_alpha(MaterialPalette.disable_opacity);
|
||||
text_label.opacity: MaterialPalette.disable_opacity;
|
||||
input.opacity: MaterialPalette.disable_opacity;
|
||||
}
|
||||
focused when input.has_focus : {
|
||||
active_indicator.height: MaterialStyleMetrics.size_3;
|
||||
}
|
||||
]
|
||||
}
|
||||
674
dashboard/ui/material/ui/components/time_picker.slint
Normal file
@@ -0,0 +1,674 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseDialog } from "./dialog.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { StateLayerArea } from "state_layer.slint";
|
||||
|
||||
component TimeSelector inherits Rectangle {
|
||||
in property <bool> selected;
|
||||
in property <int> value;
|
||||
|
||||
callback clicked <=> touch_area.clicked;
|
||||
|
||||
width: max(MaterialStyleMetrics.size_48, text_label.min_width);
|
||||
height: max(MaterialStyleMetrics.size_48, text_label.min_height);
|
||||
border_radius: max(root.width, root.height) / 2;
|
||||
vertical_stretch: 0;
|
||||
horizontal_stretch: 0;
|
||||
|
||||
touch_area := TouchArea {
|
||||
text_label := MaterialText {
|
||||
text: root.value;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
style: MaterialTypography.body_large;
|
||||
color: MaterialPalette.on_surface;
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected: {
|
||||
text_label.color: MaterialPalette.on_primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
component Clock {
|
||||
in property <[int]> model;
|
||||
in property <bool> two_columns;
|
||||
in property <int> total;
|
||||
in_out property <int> current_index;
|
||||
in property <int> current_value;
|
||||
|
||||
callback current_index_changed(index: int);
|
||||
|
||||
property <length> radius: max(root.width, root.height) / 2;
|
||||
property <length> picker_ditameter: MaterialStyleMetrics.size_48;
|
||||
property <length> center: root.radius - root.picker_ditameter / 2;
|
||||
property <length> outer_padding: 2px;
|
||||
property <length> inner_padding: 32px;
|
||||
property <length> radius_outer: root.center - root.outer_padding;
|
||||
property <length> radius_inner: root.center - root.inner_padding;
|
||||
property <int> half_total: root.total / 2;
|
||||
property <angle> rotation: 0.25turn;
|
||||
property <length> current_x: get_index_x(root.current_value);
|
||||
property <length> current_y: get_index_y(root.current_value);
|
||||
|
||||
min_width: MaterialStyleMetrics.size_256;
|
||||
min_height: self.min_width;
|
||||
vertical_stretch: 0;
|
||||
horizontal_stretch: 0;
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
|
||||
if root.current_index >= 0 || root.current_index < root.model.length: Path {
|
||||
stroke: MaterialPalette.primary;
|
||||
stroke_width: 2px;
|
||||
viewbox_width: self.width / 1px;
|
||||
viewbox_height: self.height / 1px;
|
||||
|
||||
MoveTo {
|
||||
x: root.width / 2px;
|
||||
y: root.height / 2px;
|
||||
}
|
||||
|
||||
LineTo {
|
||||
x: (root.current_x + root.picker_ditameter / 2) / 1px;
|
||||
y: (root.current_y + root.picker_ditameter / 2) / 1px;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: MaterialStyleMetrics.size_8;
|
||||
height: self.width;
|
||||
background: MaterialPalette.primary;
|
||||
border_radius: self.width / 2;
|
||||
}
|
||||
|
||||
if root.current_index < root.model.length: Rectangle {
|
||||
x: root.current_x;
|
||||
y: root.current_y;
|
||||
width: root.picker_ditameter;
|
||||
height: root.picker_ditameter;
|
||||
border_radius: root.picker_ditameter / 2;
|
||||
background: MaterialPalette.primary;
|
||||
|
||||
if root.current_index < 0: Rectangle {
|
||||
width: MaterialStyleMetrics.size_4;
|
||||
height: self.width;
|
||||
border_radius: self.width / 2;
|
||||
background: MaterialPalette.on_primary;
|
||||
}
|
||||
}
|
||||
|
||||
for val[index] in root.model: TimeSelector {
|
||||
x: get_index_x(val);
|
||||
y: get_index_y(val);
|
||||
width: root.picker_ditameter;
|
||||
height: root.picker_ditameter;
|
||||
value: val;
|
||||
selected: index == root.current_index;
|
||||
accessible_role: button;
|
||||
accessible_label: @tr("{} Hours or minutes of {}", val, root.total);
|
||||
accessible_action_default => {
|
||||
self.clicked();
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.set_current_index(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pure function value_to_angle(value: int) -> angle {
|
||||
if root.two_columns {
|
||||
if value >= root.half_total {
|
||||
return clamp((value - root.half_total) / root.half_total * 1turn, 0, 0.999999turn) - root.rotation;
|
||||
}
|
||||
return clamp(value / root.half_total * 1turn, 0, 0.99999turn) - root.rotation;
|
||||
}
|
||||
|
||||
clamp(value / root.total * 1turn, 0, 0.99999turn) - root.rotation
|
||||
}
|
||||
|
||||
pure function get_index_x(value: int) -> length {
|
||||
if root.two_columns && value >= root.half_total {
|
||||
return root.center + (root.radius_inner / 1px * cos(root.value_to_angle(value))) * 1px;
|
||||
}
|
||||
|
||||
root.center + (root.radius_outer / 1px * cos(root.value_to_angle(value))) * 1px
|
||||
}
|
||||
|
||||
pure function get_index_y(value: int) -> length {
|
||||
// this is only for 24 mode
|
||||
if root.total == 24 && value == 0 {
|
||||
return root.center + (root.radius_inner / 1px * sin(root.value_to_angle(value))) * 1px;
|
||||
}
|
||||
if root.total == 24 && value == 12 {
|
||||
return root.center + (root.radius_outer / 1px * sin(root.value_to_angle(value))) * 1px;
|
||||
}
|
||||
if root.two_columns && value >= root.half_total {
|
||||
return root.center + (root.radius_inner / 1px * sin(root.value_to_angle(value))) * 1px;
|
||||
}
|
||||
|
||||
root.center + (root.radius_outer / 1px * sin(root.value_to_angle(value))) * 1px
|
||||
}
|
||||
|
||||
function set_current_index(index: int) {
|
||||
root.current_index_changed(index);
|
||||
}
|
||||
}
|
||||
|
||||
component TimePickerInput {
|
||||
in property <bool> read_only <=> text_input.read_only;
|
||||
in property <bool> checked;
|
||||
in_out property <string> text <=> text_input.text;
|
||||
|
||||
callback clicked;
|
||||
callback edited(value: int);
|
||||
|
||||
width: MaterialStyleMetrics.size_90;
|
||||
min_height: max(MaterialStyleMetrics.size_80, layout.min_height);
|
||||
vertical_stretch: 0;
|
||||
horizontal_stretch: 0;
|
||||
|
||||
forward_focus: text_input;
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: MaterialStyleMetrics.border_radius_8;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
clip: true;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
|
||||
text_input := TextInput {
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
color: MaterialPalette.on_surface;
|
||||
font_size: MaterialTypography.display_large.font_size;
|
||||
font_weight: MaterialTypography.display_large.font_weight;
|
||||
input_type: number;
|
||||
visible: !root.read_only;
|
||||
|
||||
edited => {
|
||||
root.edited(self.text.to_float());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.read_only : StateLayerArea {
|
||||
border_radius: background_layer.border_radius;
|
||||
color: MaterialPalette.on_surface;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
|
||||
MaterialText {
|
||||
text: root.text.to_float() >= 10 ? root.text : "0" + root.text;
|
||||
style: MaterialTypography.display_large;
|
||||
color: MaterialPalette.on_surface;
|
||||
horizontal_alignment: center;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
has_focus when text_input.has_focus && !root.read_only: {
|
||||
background_layer.background: MaterialPalette.primary_container;
|
||||
background_layer.border_width: 2px;
|
||||
background_layer.border_color: MaterialPalette.primary;
|
||||
text_input.color: MaterialPalette.on_primary_container;
|
||||
}
|
||||
checked when root.checked: {
|
||||
background_layer.background: MaterialPalette.primary_container;
|
||||
text_input.color: MaterialPalette.on_primary_container;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
component PeriodSelectorItem {
|
||||
in property <string> text <=> label.text;
|
||||
in property <bool> checked;
|
||||
|
||||
callback clicked <=> state_layer.clicked;
|
||||
|
||||
background_layer := Rectangle {
|
||||
state_layer := StateLayerArea {
|
||||
color: label.color;
|
||||
|
||||
label := MaterialText {
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
horizontal_alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.checked: {
|
||||
background_layer.background: MaterialPalette.tertiary_container;
|
||||
label.color: MaterialPalette.on_tertiary_container;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
component PeriodSelector {
|
||||
in property <bool> am_selected;
|
||||
in property <bool> horizontal;
|
||||
|
||||
callback update_period(am_selected: bool);
|
||||
|
||||
min_width: max(MaterialStyleMetrics.size_52, layout.min_width);
|
||||
min_height: max(root.horizontal ? MaterialStyleMetrics.size_38 : MaterialStyleMetrics.size_80, layout.min_height);
|
||||
accessible_label: "AM or PM";
|
||||
accessible_role: checkbox;
|
||||
accessible_checked: root.am_selected;
|
||||
|
||||
Rectangle {
|
||||
border_radius: border.border_radius;
|
||||
clip: true;
|
||||
|
||||
layout := VerticalLayout {
|
||||
if root.horizontal : HorizontalLayout {
|
||||
PeriodSelectorItem {
|
||||
text: "AM";
|
||||
checked: root.am_selected;
|
||||
|
||||
clicked => {
|
||||
if root.am_selected {
|
||||
return;
|
||||
}
|
||||
root.update_period(true);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1px;
|
||||
background: border.border_color;
|
||||
vertical_stretch: 0;
|
||||
}
|
||||
|
||||
PeriodSelectorItem {
|
||||
text: "PM";
|
||||
checked: !root.am_selected;
|
||||
|
||||
clicked => {
|
||||
if !root.am_selected {
|
||||
return;
|
||||
}
|
||||
root.update_period(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !root.horizontal : VerticalLayout {
|
||||
PeriodSelectorItem {
|
||||
text: "AM";
|
||||
checked: root.am_selected;
|
||||
|
||||
clicked => {
|
||||
if root.am_selected {
|
||||
return;
|
||||
}
|
||||
root.update_period(true);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: border.border_color;
|
||||
vertical_stretch: 0;
|
||||
}
|
||||
|
||||
PeriodSelectorItem {
|
||||
text: "PM";
|
||||
checked: !root.am_selected;
|
||||
|
||||
clicked => {
|
||||
if !root.am_selected {
|
||||
return;
|
||||
}
|
||||
root.update_period(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: MaterialStyleMetrics.border_radius_8;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
}
|
||||
}
|
||||
|
||||
export struct Time {
|
||||
hour: int,
|
||||
minute: int,
|
||||
second: int
|
||||
}
|
||||
|
||||
export component TimePickerPopup inherits PopupWindow {
|
||||
in property <bool> use_24_hour_format: true;
|
||||
in property <string> title: @tr("Select time");
|
||||
in property <string> cancel_text: @tr("Cancel");
|
||||
in property <string> ok_text: @tr("Ok");
|
||||
in property <Time> time: { hour: 12 };
|
||||
in property <string> hour_label: @tr("Hour");
|
||||
in property <string> minute_label: @tr("Minute");
|
||||
|
||||
property <bool> keyboard_mode;
|
||||
property <bool> minutes_selected;
|
||||
property <bool> am_selected: true;
|
||||
property <[int]> twelfth_hour_model: [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
property <[int]> use-24_hour_format_model: [
|
||||
12,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
0,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23
|
||||
];
|
||||
property <[int]> minute_model: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
|
||||
property <[int]> current_model: root.get_current_model();
|
||||
property <int> current_index: root.minutes_selected ? root.index_of_minute(root.current_time.minute) : root.index_of_hour(root.current_time.hour);
|
||||
property <Time> current_time: root.time;
|
||||
property <int> time_picker_hour: hour_time_picker.text.to_float();
|
||||
property <int> time_picker_minute: minute_time_picker.text.to_float();
|
||||
property <bool> horizontal: root.width >= MaterialStyleMetrics.size_572;
|
||||
|
||||
callback ok();
|
||||
|
||||
close_policy: no_auto_close;
|
||||
forward_focus: base;
|
||||
|
||||
base := BaseDialog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
title: root.title;
|
||||
action_button_icons: [
|
||||
root.keyboard_mode ? Icons.schedule : Icons.keyboard
|
||||
];
|
||||
actions: [
|
||||
root.cancel_text,
|
||||
root.ok_text
|
||||
];
|
||||
|
||||
layout := HorizontalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_52;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
hour_time_picker := TimePickerInput {
|
||||
read_only: !root.keyboard_mode;
|
||||
checked: self.read_only && !root.minutes_selected;
|
||||
text: root.current_time.hour;
|
||||
|
||||
accessible_role: AccessibleRole.text_input;
|
||||
accessible_value: self.text;
|
||||
accessible_label: "hour";
|
||||
|
||||
clicked => {
|
||||
root.minutes_selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
separator := MaterialText {
|
||||
min_width: MaterialStyleMetrics.size_24;
|
||||
text: ":";
|
||||
color: MaterialPalette.on_surface;
|
||||
style: MaterialTypography.display_large;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
}
|
||||
|
||||
minute_time_picker := TimePickerInput {
|
||||
read_only: !root.keyboard_mode;
|
||||
checked: self.read_only && root.minutes_selected;
|
||||
text: root.current_time.minute;
|
||||
|
||||
accessible_role: AccessibleRole.text_input;
|
||||
accessible_value: self.text;
|
||||
accessible_label: "minute";
|
||||
|
||||
clicked => {
|
||||
root.minutes_selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// spacer
|
||||
if !root.use_24_hour_format && (!root.horizontal || root.keyboard_mode) : Rectangle {
|
||||
width: MaterialStyleMetrics.spacing_12;
|
||||
}
|
||||
|
||||
if !root.use_24_hour_format && (!root.horizontal || root.keyboard_mode) : PeriodSelector {
|
||||
am_selected: root.am_selected;
|
||||
|
||||
update-period(am) => {
|
||||
root.am-selected = am;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// labels
|
||||
if root.keyboard_mode : HorizontalLayout {
|
||||
alignment: start;
|
||||
|
||||
MaterialText {
|
||||
width: hour_time_picker.width;
|
||||
text: root.hour_label;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: MaterialStyleMetrics.size_24;
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
text: root.minute_label;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
}
|
||||
}
|
||||
|
||||
if !root.use_24_hour_format && !root.keyboard_mode && root.horizontal : PeriodSelector {
|
||||
am_selected: root.am_selected;
|
||||
horizontal: true;
|
||||
|
||||
update-period(am) => {
|
||||
root.am-selected = am;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.horizontal && !root.keyboard_mode : Clock {
|
||||
model: root.current_model;
|
||||
two_columns: !root.minutes_selected && root.use-24_hour_format;
|
||||
current_index: root.current_index;
|
||||
total: root.minutes_selected ? 60 : root.use-24_hour_format ? 24 : 12;
|
||||
current_value: root.minutes_selected ? root.current_time.minute : root.current_time.hour;
|
||||
|
||||
current_index_changed(index) => {
|
||||
root.update_time_by_item(index);
|
||||
|
||||
if !root.minutes_selected {
|
||||
root.minutes_selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spacer
|
||||
if !root.horizontal && !root.keyboard_mode : Rectangle {
|
||||
height: MaterialStyleMetrics.spacing_12;
|
||||
}
|
||||
|
||||
if !root.horizontal && !root.keyboard_mode : Clock {
|
||||
model: root.current_model;
|
||||
two_columns: !root.minutes_selected && root.use-24_hour_format;
|
||||
current_index: root.current_index;
|
||||
total: root.minutes_selected ? 60 : root.use-24_hour_format ? 24 : 12;
|
||||
current_value: root.minutes_selected ? root.current_time.minute : root.current_time.hour;
|
||||
height: self.width;
|
||||
|
||||
current_index_changed(index) => {
|
||||
root.update_time_by_item(index);
|
||||
|
||||
if !root.minutes_selected {
|
||||
root.minutes_selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action_button_clicked(index) => {
|
||||
if index == 0 {
|
||||
root.keyboard_mode = !root.keyboard_mode;
|
||||
}
|
||||
}
|
||||
|
||||
action_clicked(index) => {
|
||||
// cancel
|
||||
if index == 0 {
|
||||
root.close();
|
||||
} else if index == 1 {
|
||||
root.ok();
|
||||
}
|
||||
}
|
||||
|
||||
close => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
pure public function ok_enabled() -> bool {
|
||||
if !root.keyboard_mode {
|
||||
return root.current_time.hour <= 23 && root.current_time.minute <= 59;
|
||||
}
|
||||
|
||||
if root.use-24_hour_format {
|
||||
return root.time_picker_hour <= 23 && root.time_picker_minute <= 59;
|
||||
}
|
||||
|
||||
root.time_picker_hour <= 12 && root.time_picker_minute <= 59
|
||||
}
|
||||
|
||||
public function get_current_time() -> Time {
|
||||
if root.keyboard_mode {
|
||||
if !root.use-24_hour_format && !root.am_selected {
|
||||
if root.current_time.hour == 12 {
|
||||
return { hour: 0, minute: root.current_time.minute };
|
||||
}
|
||||
|
||||
return { hour: root.current_time.hour + 12, minute: root.current_time.minute };
|
||||
}
|
||||
|
||||
return root.current_time;
|
||||
}
|
||||
|
||||
return { hour: root.time_picker_hour, minute: root.time_picker_minute };
|
||||
}
|
||||
|
||||
changed keyboard_mode => {
|
||||
if root.keyboard_mode {
|
||||
return;
|
||||
}
|
||||
|
||||
root.update_time(min(root.time_picker_hour, root.use-24_hour_format ? 23 : 12), min(root.time_picker_minute, 59));
|
||||
}
|
||||
|
||||
changed time => {
|
||||
root.current_time = root.time;
|
||||
}
|
||||
|
||||
function update_time_by_item(index: int) {
|
||||
root.update_time_by_value(root.current_model[index]);
|
||||
}
|
||||
|
||||
function update_time_by_value(value: int) {
|
||||
if root.minutes_selected {
|
||||
root.update_time(root.current_time.hour, value);
|
||||
return;
|
||||
}
|
||||
|
||||
root.update_time(value, root.current_time.minute);
|
||||
}
|
||||
|
||||
function update_time(hour: int, minute: int) {
|
||||
root.current_time = { hour: hour, minute: minute };
|
||||
hour_time_picker.text = root.current_time.hour;
|
||||
minute_time_picker.text = minute;
|
||||
}
|
||||
|
||||
pure function index_of_minute(minute: int) -> int {
|
||||
if mod(minute, 5) != 0 {
|
||||
return -1;
|
||||
}
|
||||
|
||||
minute / 5
|
||||
}
|
||||
|
||||
pure function index_of_hour(hour: int) -> int {
|
||||
if hour == 12 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if hour == 0 {
|
||||
return 12;
|
||||
}
|
||||
|
||||
if !root.use-24_hour_format && hour > 12 {
|
||||
return hour - 12;
|
||||
}
|
||||
|
||||
hour
|
||||
}
|
||||
|
||||
pure function get_current_model() -> [int] {
|
||||
if root.minutes_selected {
|
||||
return root.minute_model;
|
||||
}
|
||||
|
||||
if root.use-24_hour_format {
|
||||
return root.use-24_hour_format_model;
|
||||
}
|
||||
|
||||
root.twelfth_hour_model
|
||||
}
|
||||
}
|
||||
44
dashboard/ui/material/ui/components/tonal_button.slint
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component TonalButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
elevation := Elevation {
|
||||
border_radius: root.height / 2;
|
||||
background: MaterialPalette.secondary_container;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: elevation.border_radius;
|
||||
color: MaterialPalette.on_secondary_container;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
elevation.background: transparent;
|
||||
elevation.level: 0;
|
||||
base.color: MaterialPalette.on_surface;
|
||||
}
|
||||
hover when base.has_hover && !base.pressed && !base.enter_pressed : {
|
||||
elevation.level: 2;
|
||||
}
|
||||
]
|
||||
}
|
||||
63
dashboard/ui/material/ui/components/tonal_icon_button.slint
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component TonalIconButton {
|
||||
in property <image> icon;
|
||||
in property <image> checked_icon: root.icon;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked();
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: true;
|
||||
accessible-label: root.tooltip;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: base.border_radius;
|
||||
background: root.checkable ? MaterialPalette.surface_container_highest : MaterialPalette.secondary_container;
|
||||
visible: root.enabled;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
icon: root.checked ? root.checked_icon : root.icon;
|
||||
border_radius: self.height / 2;
|
||||
color: !root.enabled ? MaterialPalette.on_surface_variant : MaterialPalette.on_secondary_container;
|
||||
button_horizontal_padding: 0;
|
||||
button_vertical_padding: 0;
|
||||
width: self.height;
|
||||
icon_size: MaterialStyleMetrics.icon_size_24;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.enabled && root.checked : {
|
||||
background_layer.background: MaterialPalette.secondary_container;
|
||||
}
|
||||
]
|
||||
}
|
||||
33
dashboard/ui/material/ui/components/tooltip.slint
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
|
||||
export component ToolTip {
|
||||
in property <string> text;
|
||||
|
||||
z: 10000;
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: 4px;
|
||||
background: MaterialPalette.inverse_surface;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
max_width: 200px;
|
||||
padding_top: 4px;
|
||||
padding_bottom: 4px;
|
||||
padding_left: 8px;
|
||||
padding_right: 8px;
|
||||
|
||||
MaterialText {
|
||||
text: root.text;
|
||||
vertical_alignment: center;
|
||||
color: MaterialPalette.inverse_on_surface;
|
||||
style: MaterialTypography.body_small;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
dashboard/ui/material/ui/components/vertical.slint
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component Vertical inherits VerticalLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
}
|
||||
1
dashboard/ui/material/ui/icons/10k.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 10.5h1.5v3H10zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM7.5 15H6v-4.5H4.5V9h3v6zm5.5-1c0 .55-.45 1-1 1H9.5c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1H12c.55 0 1 .45 1 1v4zm6.5 1h-1.75L16 12.75V15h-1.5V9H16v2.25L17.75 9h1.75l-2.25 3 2.25 3z"/></svg>
|
||||
|
After Width: | Height: | Size: 371 B |
1
dashboard/ui/material/ui/icons/10mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5 7H15v3h-1.5zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 5.5v6H8.5V7H7V5.5h3zm6.5 5c0 .55-.45 1-1 1H13c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h2.5c.55 0 1 .45 1 1v4zm-1 3.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 457 B |
1
dashboard/ui/material/ui/icons/11mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM11 5.5v6H9.5V7H8V5.5h3zm5 0v6h-1.5V7H13V5.5h3zm-.5 8.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 380 B |
1
dashboard/ui/material/ui/icons/123.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7 15H5.5v-4.5H4V9h3v6zm6.5-1.5h-3v-1h2c.55 0 1-.45 1-1V10c0-.55-.45-1-1-1H9v1.5h3v1h-2c-.55 0-1 .45-1 1V15h4.5v-1.5zm6 .5v-4c0-.55-.45-1-1-1H15v1.5h3v1h-2v1h2v1h-3V15h3.5c.55 0 1-.45 1-1z"/></svg>
|
||||
|
After Width: | Height: | Size: 289 B |
1
dashboard/ui/material/ui/icons/12mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 5.5v6H8.5V7H7V5.5h3zM15.5 9h-2v1h3v1.5H12V9c0-.55.45-1 1-1h2V7h-3V5.5h3.5c.55 0 1 .45 1 1V8c0 .55-.45 1-1 1zm0 5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 441 B |
1
dashboard/ui/material/ui/icons/13mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 5.5v6H8.5V7H7V5.5h3zm6.5 5c0 .55-.45 1-1 1H12V10h3V9h-2V8h2V7h-3V5.5h3.5c.55 0 1 .45 1 1v4zm-1 3.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 427 B |
1
dashboard/ui/material/ui/icons/14mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 5.5v6H8.5V7H7V5.5h3zm7.5 4.5h-1v1.5H15V10h-3V5.5h1.5v3H15v-3h1.5v3h1V10zm-2 4H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 406 B |
1
dashboard/ui/material/ui/icons/15mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 5.5v6H8.5V7H7V5.5h3zM16.5 7h-3v1h2c.55 0 1 .45 1 1v1.5c0 .55-.45 1-1 1H12V10h3V9h-3V5.5h4.5V7zm-1 7H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 428 B |
1
dashboard/ui/material/ui/icons/16mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13.5 9H15v1.5h-1.5zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 5.5v6H8.5V7H7V5.5h3zm3 6c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h3.5V7h-3v1h2c.55 0 1 .45 1 1v1.5c0 .55-.45 1-1 1H13zm2.5 2.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 469 B |
1
dashboard/ui/material/ui/icons/17mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 5.5v6H8.5V7H7V5.5h3zm5 6h-1.75L14.62 7H12V5.5h3.5c.67 0 1.15.65.96 1.29L15 11.5zm.5 2.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 416 B |
1
dashboard/ui/material/ui/icons/18_up_rating.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13 12.5h1.5V14H13zm0-2.5h1.5v1.5H13z"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9 12H8.5v-4.5H7V9h3v6zm6-1c0 .55-.45 1-1 1h-2.5c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1H15c.55 0 1 .45 1 1v4z"/></svg>
|
||||
|
After Width: | Height: | Size: 332 B |
1
dashboard/ui/material/ui/icons/18mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 5.5v6H8.5V7H7V5.5h3zm6.5 5c0 .55-.45 1-1 1H13c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h2.5c.55 0 1 .45 1 1v4zm-3 0H15V9h-1.5v1.5zm0-2.5H15V6.5h-1.5V8zm2 6H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 477 B |
1
dashboard/ui/material/ui/icons/19mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 7h3V9h-2c-.55 0-1-.45-1-1V6.5c0-.55.45-1 1-1h2.5c.55 0 1 .45 1 1v4c0 .55-.45 1-1 1H12V10zm1.5-2H15V6.5h-1.5V8zM7 5.5h3v6H8.5V7H7V5.5zm5 13h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm6.5-2.5c0 .55-.45 1-1 1h-2v1.5H14v-6h3.5c.55 0 1 .45 1 1V16zm-3-2H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 466 B |
1
dashboard/ui/material/ui/icons/1k.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-8.5 12H9v-4.5H7.5V9h3v6zm7 0h-1.75L14 12.75V15h-1.5V9H14v2.25L15.75 9h1.75l-2.25 3 2.25 3z"/></svg>
|
||||
|
After Width: | Height: | Size: 269 B |
1
dashboard/ui/material/ui/icons/1k_plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 15H7.5v-4.5H6V9h3v6zm4.75 0L12 12.75V15h-1.5V9H12v2.25L13.75 9h1.75l-2.25 3 2.25 3h-1.75zm5.75-2.5H18V14h-1v-1.5h-1.5v-1H17V10h1v1.5h1.5v1z"/></svg>
|
||||
|
After Width: | Height: | Size: 319 B |
1
dashboard/ui/material/ui/icons/1x_mobiledata.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M4 7h4v10H6V9H4V7zm11.83 4.72L18.66 7h-2.33l-1.66 2.77L13 7h-2.33l2.83 4.72L10.33 17h2.33l2-3.34 2 3.34H19l-3.17-5.28z"/></svg>
|
||||
|
After Width: | Height: | Size: 219 B |
1
dashboard/ui/material/ui/icons/20mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M14.5 7H16v3h-1.5zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zm2-8c0 .55-.45 1-1 1H14c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1h2.5c.55 0 1 .45 1 1v4zM10 9H8v1h3v1.5H6.5V9c0-.55.45-1 1-1h2V7h-3V5.5H10c.55 0 1 .45 1 1V8c0 .55-.45 1-1 1zm5.5 5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 515 B |
1
dashboard/ui/material/ui/icons/21mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM11 9H9v1h3v1.5H7.5V9c0-.55.45-1 1-1h2V7h-3V5.5H11c.55 0 1 .45 1 1V8c0 .55-.45 1-1 1zm3-3.5h3v6h-1.5V7H14V5.5zm1.5 8.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 443 B |
1
dashboard/ui/material/ui/icons/22mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 9H8v1h3v1.5H6.5V9c0-.55.45-1 1-1h2V7h-3V5.5H10c.55 0 1 .45 1 1V8c0 .55-.45 1-1 1zm6.5 0h-2v1h3v1.5H13V9c0-.55.45-1 1-1h2V7h-3V5.5h3.5c.55 0 1 .45 1 1V8c0 .55-.45 1-1 1zm-1 5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 502 B |
1
dashboard/ui/material/ui/icons/23mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 9H8v1h3v1.5H6.5V9c0-.55.45-1 1-1h2V7h-3V5.5H10c.55 0 1 .45 1 1V8c0 .55-.45 1-1 1zm7.5 1.5c0 .55-.45 1-1 1H13V10h3V9h-2V8h2V7h-3V5.5h3.5c.55 0 1 .45 1 1v4zm-2 3.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 490 B |
1
dashboard/ui/material/ui/icons/24mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM10 9H8v1h3v1.5H6.5V9c0-.55.45-1 1-1h2V7h-3V5.5H10c.55 0 1 .45 1 1V8c0 .55-.45 1-1 1zm8.5 1h-1v1.5H16V10h-3V5.5h1.5v3H16v-3h1.5v3h1V10zm-3 4H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 465 B |
1
dashboard/ui/material/ui/icons/2k.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9 9.5H8v1h3V15H6.5v-2.5c0-.55.45-1 1-1h2v-1h-3V9H10c.55 0 1 .45 1 1v1.5c0 .55-.45 1-1 1zm8 2.5h-1.75l-1.75-2.25V15H13V9h1.5v2.25L16.25 9H18l-2.25 3L18 15z"/></svg>
|
||||
|
After Width: | Height: | Size: 333 B |
1
dashboard/ui/material/ui/icons/2k_plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9.5 8.5c0 .55-.45 1-1 1h-2v1h3V15H5v-2.5c0-.55.45-1 1-1h2v-1H5V9h3.5c.55 0 1 .45 1 1v1.5zm4.75 3.5-1.75-2.25V15H11V9h1.5v2.25L14.25 9H16l-2.25 3L16 15h-1.75zM20 12.5h-1.5V14h-1v-1.5H16v-1h1.5V10h1v1.5H20v1z"/></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
1
dashboard/ui/material/ui/icons/2mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zm-2-9.5h-2v1h3v1.5H10V9c0-.55.45-1 1-1h2V7h-3V5.5h3.5c.55 0 1 .45 1 1V8c0 .55-.45 1-1 1zm2 5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 417 B |
1
dashboard/ui/material/ui/icons/30fps.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M2 5v3h6v2.5H3v3h5V16H2v3h6c1.66 0 3-1.34 3-3v-1.9A2.1 2.1 0 0 0 8.9 12 2.1 2.1 0 0 0 11 9.9V8c0-1.66-1.34-3-3-3H2zm17 3v8h-4V8h4m0-3h-4c-1.66 0-3 1.34-3 3v8c0 1.66 1.34 3 3 3h4c1.66 0 3-1.34 3-3V8c0-1.66-1.34-3-3-3z"/></svg>
|
||||
|
After Width: | Height: | Size: 317 B |
1
dashboard/ui/material/ui/icons/30fps_select.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M4 4v2h5v2H5v2h4v2H4v2h5c1.1 0 2-.9 2-2v-1.5c0-.83-.17-1.5-1-1.5.83 0 1-.67 1-1.5V6c0-1.1-.9-2-2-2H4zm14 0c1.1 0 2 .9 2 2v6c0 1.1-.9 2-2 2h-3c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2h3zm0 2h-3v6h3V6zM5 22H3v-5h2v5zm4 0H7v-5h2v5zm4 0h-2v-5h2v5zm8 0h-6v-5h6v5z"/></svg>
|
||||
|
After Width: | Height: | Size: 350 B |
1
dashboard/ui/material/ui/icons/360.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 7C6.48 7 2 9.24 2 12c0 2.24 2.94 4.13 7 4.77V20l4-4-4-4v2.73c-3.15-.56-5-1.9-5-2.73 0-1.06 3.04-3 8-3s8 1.94 8 3c0 .73-1.46 1.89-4 2.53v2.05c3.53-.77 6-2.53 6-4.58 0-2.76-4.48-5-10-5z"/></svg>
|
||||
|
After Width: | Height: | Size: 288 B |
1
dashboard/ui/material/ui/icons/3d_rotation.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.52 21.48A10.487 10.487 0 0 1 1.55 13H.05C.56 19.16 5.71 24 12 24l.66-.03-3.81-3.81-1.33 1.32zm.89-6.52c-.19 0-.37-.03-.52-.08a1.07 1.07 0 0 1-.4-.24c-.11-.1-.2-.22-.26-.37-.06-.14-.09-.3-.09-.47h-1.3c0 .36.07.68.21.95.14.27.33.5.56.69.24.18.51.32.82.41.3.1.62.15.96.15.37 0 .72-.05 1.03-.15.32-.1.6-.25.83-.44s.42-.43.55-.72c.13-.29.2-.61.2-.97 0-.19-.02-.38-.07-.56a1.67 1.67 0 0 0-.23-.51c-.1-.16-.24-.3-.4-.43-.17-.13-.37-.23-.61-.31a2.098 2.098 0 0 0 .89-.75c.1-.15.17-.3.22-.46.05-.16.07-.32.07-.48 0-.36-.06-.68-.18-.96a1.78 1.78 0 0 0-.51-.69c-.2-.19-.47-.33-.77-.43C9.1 8.05 8.76 8 8.39 8c-.36 0-.69.05-1 .16-.3.11-.57.26-.79.45-.21.19-.38.41-.51.67-.12.26-.18.54-.18.85h1.3c0-.17.03-.32.09-.45s.14-.25.25-.34c.11-.09.23-.17.38-.22.15-.05.3-.08.48-.08.4 0 .7.1.89.31.19.2.29.49.29.86 0 .18-.03.34-.08.49a.87.87 0 0 1-.25.37c-.11.1-.25.18-.41.24-.16.06-.36.09-.58.09H7.5v1.03h.77c.22 0 .42.02.6.07s.33.13.45.23c.12.11.22.24.29.4.07.16.1.35.1.57 0 .41-.12.72-.35.93-.23.23-.55.33-.95.33zm8.55-5.92c-.32-.33-.7-.59-1.14-.77-.43-.18-.92-.27-1.46-.27H12v8h2.3c.55 0 1.06-.09 1.51-.27.45-.18.84-.43 1.16-.76.32-.33.57-.73.74-1.19.17-.47.26-.99.26-1.57v-.4c0-.58-.09-1.1-.26-1.57-.18-.47-.43-.87-.75-1.2zm-.39 3.16c0 .42-.05.79-.14 1.13-.1.33-.24.62-.43.85-.19.23-.43.41-.71.53-.29.12-.62.18-.99.18h-.91V9.12h.97c.72 0 1.27.23 1.64.69.38.46.57 1.12.57 1.99v.4zM12 0l-.66.03 3.81 3.81 1.33-1.33c3.27 1.55 5.61 4.72 5.96 8.48h1.5C23.44 4.84 18.29 0 12 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
dashboard/ui/material/ui/icons/3g_mobiledata.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3 7v2h5v2H4v2h4v2H3v2h5c1.1 0 2-.9 2-2v-1.5c0-.83-.67-1.5-1.5-1.5.83 0 1.5-.67 1.5-1.5V9c0-1.1-.9-2-2-2H3zm18 4v4c0 1.1-.9 2-2 2h-5c-1.1 0-2-.9-2-2V9c0-1.1.9-2 2-2h5c1.1 0 2 .9 2 2h-7v6h5v-2h-2.5v-2H21z"/></svg>
|
||||
|
After Width: | Height: | Size: 304 B |
1
dashboard/ui/material/ui/icons/3k.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-8 11c0 .55-.45 1-1 1H6.5v-1.5h3v-1h-2v-1h2v-1h-3V9H10c.55 0 1 .45 1 1v4zm7 1h-1.75l-1.75-2.25V15H13V9h1.5v2.25L16.25 9H18l-2.25 3L18 15z"/></svg>
|
||||
|
After Width: | Height: | Size: 315 B |
1
dashboard/ui/material/ui/icons/3k_plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9.5 14c0 .55-.45 1-1 1H5v-1.5h3v-1H6v-1h2v-1H5V9h3.5c.55 0 1 .45 1 1v4zm6.5 1h-1.75l-1.75-2.25V15H11V9h1.5v2.25L14.25 9H16l-2.25 3L16 15zm4-2.5h-1.5V14h-1v-1.5H16v-1h1.5V10h1v1.5H20v1z"/></svg>
|
||||
|
After Width: | Height: | Size: 362 B |
1
dashboard/ui/material/ui/icons/3mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zm-1-8c0 .55-.45 1-1 1H10V10h3V9h-2V8h2V7h-3V5.5h3.5c.55 0 1 .45 1 1v4zm1 3.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 401 B |
1
dashboard/ui/material/ui/icons/3p.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 2H4.01c-1.1 0-2 .9-2 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8 4c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm4 8H8v-.57c0-.81.48-1.53 1.22-1.85a6.95 6.95 0 0 1 5.56 0A2.01 2.01 0 0 1 16 13.43V14z"/></svg>
|
||||
|
After Width: | Height: | Size: 308 B |
1
dashboard/ui/material/ui/icons/4g_mobiledata.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9 7H7v5H5V7H3v7h4v3h2v-3h2v-2H9V7zm8 4v2h2v2h-5V9h7c0-1.1-.9-2-2-2h-5c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h5c1.1 0 2-.9 2-2v-4h-4z"/></svg>
|
||||
|
After Width: | Height: | Size: 227 B |
1
dashboard/ui/material/ui/icons/4g_plus_mobiledata.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M13 11v2h2v2h-4V9h6c0-1.1-.9-2-2-2h-4c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h4c1.1 0 2-.9 2-2v-4h-4zm11 0h-2V9h-2v2h-2v2h2v2h2v-2h2v-2zM7 7H5v5H3V7H1v7h4v3h2v-3h1v-2H7V7z"/></svg>
|
||||
|
After Width: | Height: | Size: 264 B |
1
dashboard/ui/material/ui/icons/4k.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 10.5h-1V15H9.5v-1.5h-3V9H8v3h1.5V9H11v3h1v1.5zm6 1.5h-1.75l-1.75-2.25V15H13V9h1.5v2.25L16.25 9H18l-2.25 3L18 15z"/></svg>
|
||||
|
After Width: | Height: | Size: 293 B |
1
dashboard/ui/material/ui/icons/4k_plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-8.5 10.5h-1V15H8v-1.5H5V9h1.5v3H8V9h1.5v3h1v1.5zM16 15h-1.75l-1.75-2.25V15H11V9h1.5v2.25L14.25 9H16l-2.25 3L16 15zm4-2.5h-1.5V14h-1v-1.5H16v-1h1.5V10h1v1.5H20v1z"/></svg>
|
||||
|
After Width: | Height: | Size: 340 B |
1
dashboard/ui/material/ui/icons/4mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3-8.5h-1v1.5h-1.5V10h-3V5.5H11v3h1.5v-3H14v3h1V10zm.5 8.5H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zm0-4.5H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 383 B |
1
dashboard/ui/material/ui/icons/5g.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M17 13h2v2h-5V9h7c0-1.1-.9-2-2-2h-5c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h5c1.1 0 2-.9 2-2v-4h-4v2zM3 13h5v2H3v2h5c1.1 0 2-.9 2-2v-2c0-1.1-.9-2-2-2H5V9h5V7H3v6z"/></svg>
|
||||
|
After Width: | Height: | Size: 255 B |
1
dashboard/ui/material/ui/icons/5k.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-8 7.5H8v1h2c.55 0 1 .45 1 1V14c0 .55-.45 1-1 1H6.5v-1.5h3v-1h-3V9H11v1.5zm7 4.5h-1.75l-1.75-2.25V15H13V9h1.5v2.25L16.25 9H18l-2.25 3L18 15z"/></svg>
|
||||
|
After Width: | Height: | Size: 318 B |
1
dashboard/ui/material/ui/icons/5k_plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9.5 7.5h-3v1h2c.55 0 1 .45 1 1V14c0 .55-.45 1-1 1H5v-1.5h3v-1H5V9h4.5v1.5zM16 15h-1.75l-1.75-2.25V15H11V9h1.5v2.25L14.25 9H16l-2.25 3L16 15zm4-2.5h-1.5V14h-1v-1.5H16v-1h1.5V10h1v1.5H20v1z"/></svg>
|
||||
|
After Width: | Height: | Size: 366 B |
1
dashboard/ui/material/ui/icons/5mp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 15.5h-1.5V14h-1v3H8v-3H7v4.5H5.5v-5c0-.55.45-1 1-1H11c.55 0 1 .45 1 1v5zm3.5 0H14v-6h3.5c.55 0 1 .45 1 1V16c0 .55-.45 1-1 1h-2v1.5zM14.5 7h-3v1h2c.55 0 1 .45 1 1v1.5c0 .55-.45 1-1 1H10V10h3V9h-3V5.5h4.5V7zm1 7H17v1.5h-1.5z"/></svg>
|
||||
|
After Width: | Height: | Size: 403 B |
1
dashboard/ui/material/ui/icons/60fps.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 8v8h-4V8h4m0-3h-4c-1.66 0-3 1.34-3 3v8c0 1.66 1.34 3 3 3h4c1.66 0 3-1.34 3-3V8c0-1.66-1.34-3-3-3zm-9 3V5H5C3.34 5 2 6.34 2 8v8c0 1.66 1.34 3 3 3h3c1.66 0 3-1.34 3-3v-3c0-1.66-1.34-3-3-3H5V8h5zm-2 5v3H5v-3h3z"/></svg>
|
||||
|
After Width: | Height: | Size: 312 B |
1
dashboard/ui/material/ui/icons/60fps_select.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M18 6v6h-3V6h3zm0-2h-3c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h3c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-7 2V4H6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h3c1.1 0 2-.9 2-2v-2c0-1.1-.9-2-2-2H6V6h5zm-2 4v2H6v-2h3zM5 22H3v-5h2v5zm4 0H7v-5h2v5zm4 0h-2v-5h2v5zm8 0h-6v-5h6v5z"/></svg>
|
||||
|
After Width: | Height: | Size: 347 B |