1786205236
@@ -1,3 +1,5 @@
|
|||||||
|
import { BottomAppBar } from "./material/material.slint";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Palette,
|
Palette,
|
||||||
StyleMetrics,
|
StyleMetrics,
|
||||||
@@ -11,6 +13,8 @@ import {
|
|||||||
import { Climate, WeatherData } from "./climate.slint";
|
import { Climate, WeatherData } from "./climate.slint";
|
||||||
import { Calendar } from "calendar.slint";
|
import { Calendar } from "calendar.slint";
|
||||||
import { Energy } from "energy.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 {
|
export struct LightData {
|
||||||
id: string,
|
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 {
|
export component AppWindow inherits Window {
|
||||||
width: 1024px;
|
width: 1024px;
|
||||||
height: 600px;
|
height: 600px;
|
||||||
@@ -134,34 +168,23 @@ export component AppWindow inherits Window {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
preferred-width: root.width;
|
preferred-width: root.width;
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
if root.page == 0: Climate {
|
if tab-bar.current-index == 0: Climate {
|
||||||
is-night: is-night;
|
is-night: is-night;
|
||||||
weather: weather;
|
weather: weather;
|
||||||
}
|
}
|
||||||
if root.page == 1: Calendar { }
|
if tab-bar.current-index == 1: Calendar { }
|
||||||
if root.page == 2: Energy { }
|
if tab-bar.current-index == 2: Energy { }
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
height: 64px;
|
height: 64px;
|
||||||
HorizontalBox {
|
|
||||||
padding-top: 0;
|
|
||||||
Button {
|
|
||||||
text: "Climate";
|
|
||||||
clicked => { root.page = 0; }
|
|
||||||
primary: root.page == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
tab-bar := TabBar {
|
||||||
text: "Calendar";
|
width: parent.width;
|
||||||
clicked => { root.page = 1; }
|
items: [
|
||||||
primary: root.page == 1;
|
{ icon: Icons.heat_pump, text: "Climate" },
|
||||||
}
|
{ icon: Icons.edit_calendar, text: "Calendar" },
|
||||||
|
{ icon: Icons.energy_savings_leaf, text: "Energy" }
|
||||||
Button {
|
];
|
||||||
text: "Energy";
|
|
||||||
clicked => { root.page = 2; }
|
|
||||||
primary: root.page == 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,10 +192,8 @@ export component AppWindow inherits Window {
|
|||||||
|
|
||||||
VerticalBox {
|
VerticalBox {
|
||||||
preferred-width: 0;
|
preferred-width: 0;
|
||||||
for light[index] in root.lights: Button {
|
for light[index] in root.lights: LightTile {
|
||||||
text: light.name;
|
light: light;
|
||||||
primary: light.on;
|
|
||||||
enabled: light.available;
|
|
||||||
clicked => { toggle-light(light.id) }
|
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 |