1786205236

This commit is contained in:
2026-08-08 18:07:16 +02:00
parent 9478b88349
commit c838596cda
2190 changed files with 10939 additions and 25 deletions

View File

@@ -1,3 +1,5 @@
import { BottomAppBar } from "./material/material.slint";
import {
Palette,
StyleMetrics,
@@ -11,6 +13,8 @@ import {
import { Climate, WeatherData } from "./climate.slint";
import { Calendar } from "calendar.slint";
import { Energy } from "energy.slint";
import { TabBar, Switch, MaterialStyleMetrics } from "material/material.slint";
import { Icons } from "material/ui/icons/icons.slint";
export struct LightData {
id: string,
@@ -84,6 +88,36 @@ component Thermometer inherits Rectangle {
}
}
component LightTile inherits Rectangle {
in property <LightData> light;
callback clicked();
TouchArea {
clicked => {
clicked();
}
}
HorizontalLayout {
spacing: MaterialStyleMetrics.padding_8;
Text {
text: light.name;
height: 32px;
vertical-alignment: center;
}
Rectangle { }
sw := Switch {
width: 48px;
height: 32px;
enabled: light.available;
checked: light.on;
checked_state_changed(checked) => { root.clicked() }
}
}
}
export component AppWindow inherits Window {
width: 1024px;
height: 600px;
@@ -134,34 +168,23 @@ export component AppWindow inherits Window {
Rectangle {
preferred-width: root.width;
VerticalLayout {
if root.page == 0: Climate {
if tab-bar.current-index == 0: Climate {
is-night: is-night;
weather: weather;
}
if root.page == 1: Calendar { }
if root.page == 2: Energy { }
if tab-bar.current-index == 1: Calendar { }
if tab-bar.current-index == 2: Energy { }
Rectangle {
height: 64px;
HorizontalBox {
padding-top: 0;
Button {
text: "Climate";
clicked => { root.page = 0; }
primary: root.page == 0;
}
Button {
text: "Calendar";
clicked => { root.page = 1; }
primary: root.page == 1;
}
Button {
text: "Energy";
clicked => { root.page = 2; }
primary: root.page == 2;
}
tab-bar := TabBar {
width: parent.width;
items: [
{ icon: Icons.heat_pump, text: "Climate" },
{ icon: Icons.edit_calendar, text: "Calendar" },
{ icon: Icons.energy_savings_leaf, text: "Energy" }
];
}
}
}
@@ -169,10 +192,8 @@ export component AppWindow inherits Window {
VerticalBox {
preferred-width: 0;
for light[index] in root.lights: Button {
text: light.name;
primary: light.on;
enabled: light.available;
for light[index] in root.lights: LightTile {
light: light;
clicked => { toggle-light(light.id) }
}
}

View 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.

View File

@@ -0,0 +1,30 @@
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
# Material Design 3 component set for Slint
[![Gallery image](https://material.slint.dev/tablet-material.webp)](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

View 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";

View 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);
}
}
}
}

View 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;
}
]
}

View 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
}
}

View 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);
}
}

View 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();
}
}
}
}
}
}

View 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;
}
}
}

View 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
}
}

View 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);
}
}

View 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;
}
]
}

View 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;
}
}

View 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();
}
}
}

View 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%;
}
}

View 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;
}
}
}

View 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);
}
}

View 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;
}
]
}

View 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;
}
]
}

View File

@@ -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;
}
}

View 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;
}
]
}

View 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;
}
]
}

View File

@@ -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;
}
]
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}
]
}

View 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%;
}
]
}

View 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 {
}

View 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;
}

View 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;
}
}

View 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);
}
}
}
}

View 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;
}
}
}

View 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);
}
}
}
}
}

View 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();
}
}

View 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);
}
}
}
}
}
}
}

View 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;
}
]
}

View File

@@ -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;
}
]
}

View 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);
}
}
}

View 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();
}
}

View 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;
}
}

View 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;
}
}
}
}

View 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);
}
}

View 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)
}
}

View 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();
}
}

View 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
}

View 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;
}
]
}

View 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;
}
}
}
}

View 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;
}
]
}

View 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;
}
]
}

View 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
}
}

View 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;
}
]
}

View 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;
}
]
}

View 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;
}
}
}
}

View 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;
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

Some files were not shown because too many files have changed in this diff Show More