Initial commit
12
.cargo/config.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[target.x86_64-pc-windows-msvc]
|
||||
# Increase default stack size to avoid running out of stack
|
||||
# space in debug builds. The size matches Linux's default.
|
||||
rustflags = [
|
||||
"-C", "link-arg=/STACK:8000000"
|
||||
]
|
||||
[target.aarch64-pc-windows-msvc]
|
||||
# Increase default stack size to avoid running out of stack
|
||||
# space in debug builds. The size matches Linux's default.
|
||||
rustflags = [
|
||||
"-C", "link-arg=/STACK:8000000"
|
||||
]
|
||||
21
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Generated by cargo mutants
|
||||
# Contains mutation testing data
|
||||
**/mutants.out*/
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
5
.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"Slint.slint"
|
||||
]
|
||||
}
|
||||
5
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"slint.libraryPaths": {
|
||||
"material": "./material-1.0/material.slint"
|
||||
}
|
||||
}
|
||||
6069
Cargo.lock
generated
Normal file
30
Cargo.toml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "parts_rs"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib", "cdylib"]
|
||||
path = "src/lib.rs"
|
||||
name = "parts_rs_lib"
|
||||
|
||||
[[bin]]
|
||||
path = "src/main.rs"
|
||||
name = "parts_rs"
|
||||
|
||||
[dependencies]
|
||||
slint = { version = "1.13", features = ["backend-android-activity-06"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
console_error_panic_hook = "0.1.5"
|
||||
|
||||
[build-dependencies]
|
||||
slint-build = { version = "1.13" }
|
||||
|
||||
[package.metadata.android]
|
||||
package = "org.zervo.partsrs"
|
||||
apk_name = "parts_rs"
|
||||
|
||||
[package.metadata.android.application]
|
||||
label = "PartsRS"
|
||||
9
LICENSE.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
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.
|
||||
7
README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# PartsRS
|
||||
|
||||
PartsRS is a simple desktop parts management / inventory tracking system intended for use by hobbyists and smaller companies.
|
||||
|
||||
It is designed for tracking inventory of electronic components but can probably be used for other stuff as well.
|
||||
|
||||
NOTE: Early WIP, does not work yet.
|
||||
13
build.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() {
|
||||
let config = slint_build::CompilerConfiguration::new().with_library_paths(
|
||||
std::collections::HashMap::from([(
|
||||
"material".to_string(),
|
||||
std::path::Path::new(&std::env::var_os("CARGO_MANIFEST_DIR").unwrap())
|
||||
.join("material-1.0/material.slint"),
|
||||
)]),
|
||||
);
|
||||
slint_build::compile_with_config("ui/main.slint", config).unwrap();
|
||||
}
|
||||
33
index.html
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>PartsRS</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100dvw !important;
|
||||
height: 100dvh !important;
|
||||
background: black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- canvas required by the Slint runtime -->
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="module">
|
||||
// import the generated file.
|
||||
import init from "./pkg/parts_rs_lib.js";
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
11
material-1.0/LICENSE.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 -->
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 SixtyFPS GmbH
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
30
material-1.0/README.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
|
||||
# Material Design 3 component set for Slint
|
||||
|
||||
[](examples/gallery)
|
||||
|
||||
Welcome to the official Material Design 3 component set for [Slint](https://slint.dev). These UI components adhere to [Material Design 3 guidelines](https://m3.material.io/).
|
||||
The components are intended to use for development of user interfaces with Slint for Android apps, touch friendly interfaces for embedded devices and even for desktop application development.
|
||||
Contributions and feedback from the community are welcome.
|
||||
|
||||
|
||||
## Demos
|
||||
|
||||
[WebAssembly build in the web browser](https://material.slint.dev/wasm/)
|
||||
|
||||
Download and install the [APK for android](https://material.slint.dev/apk/slint_material.apk)
|
||||
|
||||
## Documentation
|
||||
|
||||
View the documentation online at https://material.slint.dev/getting-started/
|
||||
|
||||
### Get Started
|
||||
|
||||
Clone one of our Material Components for Slint templates and follow the instruction from their README:
|
||||
|
||||
- Rust: https://github.com/slint-ui/material-rust-template
|
||||
- C++: https://github.com/slint-ui/material-cpp-template
|
||||
- Node.js/Deno: https://github.com/slint-ui/material-nodejs-template
|
||||
- Python: https://github.com/slint-ui/material-python-template
|
||||
|
||||
66
material-1.0/material.slint
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// components
|
||||
export { AppBar, SmallAppBar, MediumAppBar, LargeAppBar } from "./ui/components/app_bar.slint";
|
||||
export { Badge } from "./ui/components/badge.slint";
|
||||
export { BottomAppBar } from "./ui/components/bottom_app_bar.slint";
|
||||
export { CheckState, CheckBox, CheckBoxTile } from "./ui/components/check_box.slint";
|
||||
export { ActionChip, FilterChip, InputChip } from "./ui/components/chip.slint";
|
||||
export { DatePickerPopup, DatePickerAdapter } from "./ui/components/date_picker.slint";
|
||||
export { Dialog, FullscreenDialog } from "./ui/components/dialog.slint";
|
||||
export { Drawer, ModalDrawer } from "./ui/components/drawer.slint";
|
||||
export { DropDownMenu } from "./ui/components/drop_down_menu.slint";
|
||||
export { VerticalDivider, HorizontalDivider } from "./ui/components/divider.slint";
|
||||
export { ModalBottomSheet } from "./ui/components/bottom_sheet.slint";
|
||||
export { ElevatedCard, FilledCard, OutlinedCard } from "./ui/components/card.slint";
|
||||
export { ElevatedButton } from "./ui/components/elevated_button.slint";
|
||||
export { Elevation } from "./ui/components/elevation.slint";
|
||||
export { ExtendedTouchArea } from "./ui/components/extended_touch_area.slint";
|
||||
export { FilledButton } from "./ui/components/filled_button.slint";
|
||||
export { FilledIconButton } from "./ui/components/filled_icon_button.slint";
|
||||
export { FloatingActionButton, FABStyle } from "./ui/components/floating_action_button.slint";
|
||||
export { Grid } from "./ui/components/grid.slint";
|
||||
export { Horizontal } from "./ui/components/horizontal.slint";
|
||||
export { Icon } from "./ui/components/icon.slint";
|
||||
export { IconButton } from "./ui/components/icon_button.slint";
|
||||
export { OutlineButton } from "./ui/components/outline_button.slint";
|
||||
export { OutlineIconButton } from "./ui/components/outline_icon_button.slint";
|
||||
export { Avatar, ListTile } from "./ui/components/list.slint";
|
||||
export { ListView } from "./ui/components/list_view.slint";
|
||||
export { MaterialText } from "./ui/components/material_text.slint";
|
||||
export { MaterialWindow, MaterialWindowAdapter } from "./ui/components/material_window.slint";
|
||||
export { PopupMenu } from "./ui/components/menu.slint";
|
||||
export { NavigationBar } from "./ui/components/navigation_bar.slint";
|
||||
export { NavigationDrawer, ModalNavigationDrawer } from "./ui/components/navigation_drawer.slint";
|
||||
export { NavigationRail } from "./ui/components/navigation_rail.slint";
|
||||
export { CircularProgressIndicator, LinearProgressIndicator } from "./ui/components/progress_indicator.slint";
|
||||
export { RadioButton, RadioButtonTile } from "./ui/components/radio_button.slint";
|
||||
export { SearchBar } from "./ui/components/search_bar.slint";
|
||||
export { ScrollView } from "./ui/components/scroll_view.slint";
|
||||
export { Slider } from "./ui/components/slider.slint";
|
||||
export { SnackBar } from "./ui/components/snack_bar.slint";
|
||||
export { StateLayerArea, StateLayer, Ripple } from "./ui/components/state_layer.slint";
|
||||
export { SegmentedButton } from "./ui/components/segmented_button.slint";
|
||||
export { Switch } from "./ui/components/switch.slint";
|
||||
export { TabBar, SecondaryTabBar } from "./ui/components/tab_bar.slint";
|
||||
export { TextButton } from "./ui/components/text_button.slint";
|
||||
export { TextField } from "./ui/components/text_field.slint";
|
||||
export { TimePickerPopup, Time } from "./ui/components/time_picker.slint";
|
||||
export { TonalButton } from "./ui/components/tonal_button.slint";
|
||||
export { TonalIconButton } from "./ui/components/tonal_icon_button.slint";
|
||||
export { ToolTip } from "./ui/components/tooltip.slint";
|
||||
export { Vertical } from "./ui/components/vertical.slint";
|
||||
export { Modal } from "./ui/components/modal.slint";
|
||||
|
||||
// items
|
||||
export { ListItem } from "./ui/items/list_item.slint";
|
||||
export { NavigationItem, NavigationGroup } from "./ui/items/navigation_item.slint";
|
||||
export { MenuItem } from "./ui/items/menu_item.slint";
|
||||
|
||||
// styles
|
||||
export { MaterialAnimations } from "./ui/styling/material_animations.slint";
|
||||
export { MaterialScheme, MaterialSchemes } from "./ui/styling/material_schemes.slint";
|
||||
export { MaterialStyleMetrics } from "./ui/styling/material_style_metrics.slint";
|
||||
export { MaterialPalette } from "./ui/styling/material_palette.slint";
|
||||
export { MaterialTypography } from "./ui/styling/material_typography.slint";
|
||||
256
material-1.0/ui/components/app_bar.slint
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialTypography, TextStyle } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { IconButtonItem } from "./bottom_app_bar.slint";
|
||||
|
||||
component BaseAppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <bool> show_background;
|
||||
in property <[IconButtonItem]> trailing_buttons;
|
||||
in property <TextHorizontalAlignment> horizontal_alignment;
|
||||
in property <length> spacing;
|
||||
in property <length> vertical_spacing;
|
||||
in property <bool> two_rows;
|
||||
in property <TextStyle> text_style;
|
||||
in property <length> text_padding;
|
||||
in property <length> appbar_padding_right: 0;
|
||||
in property <length> appbar_padding_top: 0;
|
||||
in property <length> appbar_padding_bottom: 0;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked(index: int);
|
||||
|
||||
min_width: layout.min_width;
|
||||
min_height: layout.min_height;
|
||||
|
||||
Rectangle {
|
||||
background: root.show_background ? MaterialPalette.surface_container : MaterialPalette.surface;
|
||||
|
||||
layout := VerticalLayout {
|
||||
padding_top: root.appbar_padding_top;
|
||||
padding_bottom: root.appbar_padding_bottom;
|
||||
min_width: top_layout.min_width;
|
||||
spacing: root.vertical_spacing;
|
||||
|
||||
top_layout := HorizontalLayout {
|
||||
padding_left: root.spacing;
|
||||
padding_right: root.appbar_padding_right;
|
||||
spacing: MaterialStyleMetrics.spacing_6;
|
||||
|
||||
if root.leading_button.icon.width > 0 && root.leading_button.icon.height > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
IconButton {
|
||||
icon: root.leading_button.icon;
|
||||
enabled: root.leading_button.enabled;
|
||||
tooltip: root.leading_button.tooltip;
|
||||
|
||||
clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !root.two_rows : MaterialText {
|
||||
horizontal_stretch: 1;
|
||||
text: root.title;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: root.horizontal_alignment;
|
||||
color: MaterialPalette.on_surface;
|
||||
overflow: elide;
|
||||
style: root.text_style;
|
||||
}
|
||||
|
||||
// spacer
|
||||
if root.two_rows : Rectangle {
|
||||
horizontal_stretch: 1;
|
||||
}
|
||||
|
||||
if root.trailing_buttons.length > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: top_layout.spacing;
|
||||
|
||||
for item[index] in root.trailing_buttons : IconButton {
|
||||
icon: item.icon;
|
||||
enabled: item.enabled;
|
||||
tooltip: item.tooltip;
|
||||
|
||||
clicked => {
|
||||
root.trailing_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.two_rows && root.title != "" : HorizontalLayout {
|
||||
padding_left: root.text_padding;
|
||||
padding_right: self.padding_left;
|
||||
|
||||
MaterialText {
|
||||
text: root.title;
|
||||
horizontal_alignment: root.horizontal_alignment;
|
||||
color: MaterialPalette.on_surface;
|
||||
overflow: elide;
|
||||
style: root.text_style;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component AppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <IconButtonItem> trailing_button;
|
||||
in property <bool> show_background;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked();
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_64, base.min_height);
|
||||
|
||||
HorizontalLayout {
|
||||
base := BaseAppBar {
|
||||
title: root.title;
|
||||
horizontal_alignment: center;
|
||||
|
||||
leading_button: root.leading_button;
|
||||
trailing_buttons: [root.trailing_button];
|
||||
show_background: root.show_background;
|
||||
|
||||
appbar_padding_right: self.padding_left;
|
||||
appbar_padding_top: MaterialStyleMetrics.padding_8;
|
||||
appbar_padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_6;
|
||||
text_style: MaterialTypography.title_large;
|
||||
|
||||
leading_button_clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
|
||||
trailing_button_clicked(index) => {
|
||||
root.trailing_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component SmallAppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <[IconButtonItem]> trailing_buttons;
|
||||
in property <bool> show_background;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked(index: int);
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_64, base.min_height);
|
||||
|
||||
HorizontalLayout {
|
||||
base := BaseAppBar {
|
||||
title: root.title;
|
||||
horizontal_alignment: left;
|
||||
leading_button: root.leading_button;
|
||||
trailing_buttons: root.trailing_buttons;
|
||||
show_background: root.show_background;
|
||||
appbar_padding_right: self.padding_left;
|
||||
appbar_padding_top: MaterialStyleMetrics.padding_8;
|
||||
appbar_padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
text_style: MaterialTypography.title_large;
|
||||
|
||||
leading_button_clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
|
||||
trailing_button_clicked(index) => {
|
||||
root.trailing_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component MediumAppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <[IconButtonItem]> trailing_buttons;
|
||||
in property <bool> show_background;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked(index: int);
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_64, base.min_height);
|
||||
|
||||
HorizontalLayout {
|
||||
base := BaseAppBar {
|
||||
title: root.title;
|
||||
horizontal_alignment: left;
|
||||
leading_button: root.leading_button;
|
||||
trailing_buttons: root.trailing_buttons;
|
||||
show_background: root.show_background;
|
||||
appbar_padding_right: self.padding_left;
|
||||
appbar_padding_top: MaterialStyleMetrics.padding_8;
|
||||
appbar_padding_bottom: MaterialStyleMetrics.padding_24;
|
||||
vertical_spacing: MaterialStyleMetrics.spacing_4;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
two_rows: true;
|
||||
text_style: MaterialTypography.headline_small;
|
||||
text_padding: MaterialStyleMetrics.padding_16;
|
||||
|
||||
leading_button_clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
|
||||
trailing_button_clicked(index) => {
|
||||
root.trailing_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component LargeAppBar {
|
||||
in property <string> title;
|
||||
in property <IconButtonItem> leading_button;
|
||||
in property <[IconButtonItem]> trailing_buttons;
|
||||
in property <bool> show_background;
|
||||
|
||||
callback leading_button_clicked();
|
||||
callback trailing_button_clicked(index: int);
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_64, base.min_height);
|
||||
|
||||
HorizontalLayout {
|
||||
base := BaseAppBar {
|
||||
title: root.title;
|
||||
horizontal_alignment: left;
|
||||
leading_button: root.leading_button;
|
||||
trailing_buttons: root.trailing_buttons;
|
||||
show_background: root.show_background;
|
||||
appbar_padding_right: self.padding_left;
|
||||
appbar_padding_top: MaterialStyleMetrics.padding_8;
|
||||
appbar_padding_bottom: MaterialStyleMetrics.padding_28;
|
||||
vertical_spacing: MaterialStyleMetrics.spacing_40;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
two_rows: true;
|
||||
text_style: MaterialTypography.headline_medium;
|
||||
text_padding: MaterialStyleMetrics.padding_16;
|
||||
|
||||
leading_button_clicked => {
|
||||
root.leading_button_clicked();
|
||||
}
|
||||
|
||||
trailing_button_clicked(index) => {
|
||||
root.trailing_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
material-1.0/ui/components/badge.slint
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialText } from "material_text.slint";
|
||||
|
||||
export component Badge {
|
||||
in property <string> text;
|
||||
|
||||
width: max(MaterialStyleMetrics.size_16, label.width + 2 * MaterialStyleMetrics.padding_4);
|
||||
height: max(MaterialStyleMetrics.size_16, label.height);
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
border_radius: self.height / 2;
|
||||
background: MaterialPalette.error;
|
||||
}
|
||||
|
||||
label := MaterialText {
|
||||
text: root.text;
|
||||
color: MaterialPalette.on_error;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
style: MaterialTypography.label_small;
|
||||
}
|
||||
|
||||
states [
|
||||
small when root.text == "" : {
|
||||
background_layer.width: MaterialStyleMetrics.size_6;
|
||||
background_layer.height: MaterialStyleMetrics.size_6;
|
||||
}
|
||||
]
|
||||
}
|
||||
72
material-1.0/ui/components/base_button.slint
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Avatar } from "./list.slint";
|
||||
|
||||
export component BaseButton inherits StateLayerArea {
|
||||
in property <image> icon;
|
||||
in property <color> icon_color: self.color;
|
||||
in property <string> text;
|
||||
in property <length> button_horizontal_padding: MaterialStyleMetrics.padding_24;
|
||||
in property <length> button_vertical_padding: MaterialStyleMetrics.padding_10;
|
||||
in property <length> button_padding_left: self.button_horizontal_padding;
|
||||
in property <length> button_padding_right: self.button_horizontal_padding;
|
||||
in property <length> button_padding_top: self.button_vertical_padding;
|
||||
in property <length> button_padding_bottom: self.button_vertical_padding;
|
||||
in property <length> spacing: MaterialStyleMetrics.spacing_8;
|
||||
in property <length> min_layout_width: MaterialStyleMetrics.size_40;
|
||||
in property <length> min_layout_height: MaterialStyleMetrics.size_40;
|
||||
in property <length> icon_size: MaterialStyleMetrics.icon_size_18;
|
||||
in property <image> avatar_icon;
|
||||
in property <length> avatar_size;
|
||||
in property <color> avatar_background: #00000000;
|
||||
out property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
out property <bool> has_avatar: root.avatar_icon.width > 0 && root.avatar_icon.height > 0;
|
||||
|
||||
min_width: max(root.min_layout_width, layout.min_width);
|
||||
min_height: max(root.min_layout_height, layout.min_height);
|
||||
tooltip_offset: root.height / 2 + root.icon_size / 2 + MaterialStyleMetrics.padding_14;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: root.button_padding_left;
|
||||
padding_right: root.button_padding_right;
|
||||
padding_top: root.button_padding_top;
|
||||
padding_bottom: root.button_padding_bottom;
|
||||
spacing: root.spacing;
|
||||
alignment: center;
|
||||
|
||||
if root.has_icon || root.has_avatar : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
if root.has_avatar && root.avatar_size > 0 : Avatar {
|
||||
width: root.avatar_size;
|
||||
height: self.width;
|
||||
image: root.avatar_icon;
|
||||
background: root.avatar_background;
|
||||
}
|
||||
|
||||
if root.has_icon : Icon {
|
||||
source: root.icon;
|
||||
colorize: root.icon_color;
|
||||
opacity: root.enabled ? 100% : 38%;
|
||||
width: root.icon_size;
|
||||
}
|
||||
}
|
||||
|
||||
if root.text != "" : MaterialText {
|
||||
text: root.text;
|
||||
style: MaterialTypography.label_large;
|
||||
color: root.color;
|
||||
opacity: root.enabled ? 100% : 38%;
|
||||
overflow: clip;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
51
material-1.0/ui/components/base_navigation.slint
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { NavigationItem } from "../items/navigation_item.slint";
|
||||
|
||||
export component BaseNavigationItemTemplate {
|
||||
in property <image> icon;
|
||||
in property <image> selected_icon;
|
||||
in property <string> text;
|
||||
in property <int> index;
|
||||
in property <bool> selected;
|
||||
in property <bool> show_badge;
|
||||
in property <string> badge;
|
||||
|
||||
callback clicked;
|
||||
callback pointer_event(event: PointerEvent, position: Point);
|
||||
|
||||
accessible-role: tab;
|
||||
accessible-label: root.text;
|
||||
accessible-item-index: root.index;
|
||||
accessible-item-selectable: true;
|
||||
accessible-item-selected: root.selected;
|
||||
accessible-action-default => { self.clicked(); }
|
||||
|
||||
@children
|
||||
|
||||
}
|
||||
|
||||
export component BaseNavigation {
|
||||
in property <[NavigationItem]> items;
|
||||
in_out property <int> current_index;
|
||||
|
||||
callback index_changed(index: int);
|
||||
|
||||
accessible-role: tab-list;
|
||||
// accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index;
|
||||
// accessible-label: root.title;
|
||||
accessible-item-count: root.items.length;
|
||||
|
||||
@children
|
||||
|
||||
protected function select(index: int) {
|
||||
if index < 0 || index >= root.items.length {
|
||||
return;
|
||||
}
|
||||
|
||||
root.current_index = index;
|
||||
root.index_changed(index);
|
||||
}
|
||||
|
||||
}
|
||||
64
material-1.0/ui/components/bottom_app_bar.slint
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { FloatingActionButton, FABStyle } from "./floating_action_button.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export struct IconButtonItem {
|
||||
icon: image,
|
||||
tooltip: string,
|
||||
enabled: bool
|
||||
}
|
||||
|
||||
export component BottomAppBar {
|
||||
in property <[IconButtonItem]> icon_buttons;
|
||||
in property <image> fab_icon;
|
||||
|
||||
callback fab_clicked();
|
||||
callback icon_button_clicked(index: int);
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_80, layout.min_height);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_4;
|
||||
padding_right: MaterialStyleMetrics.padding_16;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
HorizontalLayout {
|
||||
for button[index] in root.icon_buttons : IconButton {
|
||||
icon: button.icon;
|
||||
enabled: button.enabled;
|
||||
tooltip: button.tooltip;
|
||||
|
||||
clicked => {
|
||||
root.icon_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spacer
|
||||
Rectangle {}
|
||||
|
||||
if root.fab_icon.width > 0 && root.fab_icon.height > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
FloatingActionButton {
|
||||
icon: root.fab_icon;
|
||||
style: FABStyle.standard;
|
||||
|
||||
clicked => {
|
||||
root.fab_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
material-1.0/ui/components/bottom_sheet.slint
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Horizontal } from "./horizontal.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Modal } from "./modal.slint";
|
||||
|
||||
component BottomSheet {
|
||||
callback drag_moved(offset_y: length);
|
||||
callback pressed();
|
||||
callback released();
|
||||
|
||||
elevation := Elevation {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
level: 3;
|
||||
border_radius: MaterialStyleMetrics.border_radius_16;
|
||||
}
|
||||
|
||||
TouchArea {
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container_low;
|
||||
border_top_left_radius: MaterialStyleMetrics.border_radius_16;
|
||||
border_top_right_radius: self.border_top_left_radius;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
drag_handle := TouchArea {
|
||||
height: MaterialStyleMetrics.size_36;
|
||||
|
||||
Rectangle {
|
||||
width: MaterialStyleMetrics.size_32;
|
||||
height: MaterialStyleMetrics.size_4;
|
||||
background: MaterialPalette.outline;
|
||||
border_radius: self.height / 2;
|
||||
}
|
||||
|
||||
moved => {
|
||||
root.drag_moved(self.pressed_y - self.mouse_y);
|
||||
}
|
||||
|
||||
changed pressed => {
|
||||
if self.pressed {
|
||||
root.pressed();
|
||||
return;
|
||||
}
|
||||
|
||||
root.released();
|
||||
}
|
||||
}
|
||||
|
||||
Horizontal {
|
||||
@children
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ModalBottomSheet inherits PopupWindow {
|
||||
close_policy: no_auto_close;
|
||||
|
||||
property <length> drag_margin: MaterialStyleMetrics.padding_56;
|
||||
|
||||
modal := Modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
sheet := BottomSheet {
|
||||
property <duration> y_duration: MaterialAnimations.standard_accelerate_duration;
|
||||
property <length> y_drag_start;
|
||||
property <length> height_drag_start;
|
||||
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: parent.height;
|
||||
width: min(MaterialStyleMetrics.size_640, parent.width - 2 * MaterialStyleMetrics.padding_56);
|
||||
|
||||
@children
|
||||
|
||||
drag_moved(offset_y) => {
|
||||
self.y_duration = 0;
|
||||
self.y = max(self.y - offset_y, self.y_drag_start);
|
||||
self.height += offset_y;
|
||||
}
|
||||
|
||||
pressed => {
|
||||
self.y_drag_start = self.y;
|
||||
self.height_drag_start = self.height;
|
||||
}
|
||||
|
||||
released => {
|
||||
if self.y >= root.height - root.drag_margin {
|
||||
root.close();
|
||||
return;
|
||||
}
|
||||
|
||||
self.y_duration = MaterialAnimations.standard_accelerate_duration;
|
||||
self.height = self.height_drag_start;
|
||||
self.y = self.y_drag_start;
|
||||
}
|
||||
|
||||
animate y, height {
|
||||
duration: self.y_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
sheet.y = modal.height - sheet.height;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
112
material-1.0/ui/components/card.slint
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
|
||||
component BaseCard {
|
||||
in property <bool> clickable;
|
||||
in property <bool> has_elevation;
|
||||
in property <color> background;
|
||||
in property <color> border_color;
|
||||
in property <length> border_width;
|
||||
|
||||
callback clicked();
|
||||
|
||||
forward_focus: state_layer;
|
||||
|
||||
if root.has_elevation : Elevation {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: background_layer.border_radius;
|
||||
level: 1;
|
||||
}
|
||||
|
||||
background_layer := Rectangle {
|
||||
background: root.background;
|
||||
border_radius: MaterialStyleMetrics.border_radius_12;
|
||||
border_width: root.border_width;
|
||||
border_color: root.border_color;
|
||||
}
|
||||
|
||||
state_layer := StateLayerArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: background_layer.border_radius;
|
||||
visible: root.clickable;
|
||||
enabled: root.visible;
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
border_radius: MaterialStyleMetrics.border_radius_12;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
export component ElevatedCard {
|
||||
in property <bool> clickable <=> base.clickable;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
forward_focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.clickable;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseCard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
has_elevation: true;
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
export component FilledCard {
|
||||
in property <bool> clickable <=> base.clickable;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
forward_focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.clickable;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseCard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
export component OutlinedCard {
|
||||
in property <bool> clickable <=> base.clickable;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
forward_focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.clickable;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseCard {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: MaterialPalette.surface_container_low;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
130
material-1.0/ui/components/check_box.slint
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { ListTile } from "./list.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
export enum CheckState {
|
||||
unchecked,
|
||||
partially_checked,
|
||||
checked,
|
||||
}
|
||||
|
||||
export component CheckBox {
|
||||
|
||||
in_out property <CheckState> check_state;
|
||||
in_out property <bool> tristate;
|
||||
in property <bool> has_error;
|
||||
in property <bool> enabled <=> state_area.enabled;
|
||||
|
||||
property <bool> checked: root.check_state == CheckState.checked || root.check_state == CheckState.partially_checked;
|
||||
|
||||
callback checked_state_changed(check_state: CheckState);
|
||||
|
||||
min_width: MaterialStyleMetrics.size_48;
|
||||
min_height: self.min_width;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-checkable: true;
|
||||
accessible-checked <=> root.checked;
|
||||
accessible-role: checkbox;
|
||||
accessible-action-default => { state_area.clicked(); }
|
||||
forward_focus: state_area;
|
||||
|
||||
state_area := StateLayerArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
color: MaterialPalette.on_surface;
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: MaterialStyleMetrics.size_18;
|
||||
height: self.width;
|
||||
border_radius: MaterialStyleMetrics.border_radius_2;
|
||||
border_width: root.checked ? 0 : 2px;
|
||||
border_color: root.has_error ? MaterialPalette.error : MaterialPalette.on_surface_variant;
|
||||
|
||||
if root.checked : Icon {
|
||||
colorize: MaterialPalette.on_primary;
|
||||
source: Icons.check;
|
||||
|
||||
states [
|
||||
partial_checked when root.check_state == CheckState.partially-checked : {
|
||||
source: Icons.remove;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
animate background, border_width {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
changed check_state => {
|
||||
root.checked_state_changed(root.check_state);
|
||||
}
|
||||
|
||||
public function toggle() {
|
||||
if !root.tristate {
|
||||
root.check_state = root.check_state != CheckState.checked ? CheckState.checked : CheckState.unchecked;
|
||||
return;
|
||||
}
|
||||
|
||||
root.check_state = root.check_state == CheckState.checked ? CheckState.partially_checked :
|
||||
root.check_state == CheckState.partially_checked ? CheckState.unchecked : CheckState.checked;
|
||||
}
|
||||
|
||||
public function set_check_state(check_state: CheckState) {
|
||||
if check_state == CheckState.partially-checked {
|
||||
root.tristate = true;
|
||||
}
|
||||
root.check_state = check_state;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
state_area.display_background: false;
|
||||
background_layer.border_color: root.checked ? transparent : MaterialPalette.on_surface.with_alpha(38%);
|
||||
background_layer.background: root.checked ? MaterialPalette.on_surface.with_alpha(38%) : transparent;
|
||||
}
|
||||
checked when root.checked : {
|
||||
background_layer.border_width: 0;
|
||||
background_layer.background: root.has_error ? MaterialPalette.error : MaterialPalette.primary;
|
||||
state_area.color: background_layer.background;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component CheckBoxTile inherits ListTile {
|
||||
in_out property <CheckState> check_state <=> check_box.check_state;
|
||||
in_out property <bool> tristate <=> check_box.tristate;
|
||||
in property <bool> has_error <=> check_box.has_error;
|
||||
|
||||
callback checked_state_changed <=> check_box.checked_state_changed;
|
||||
|
||||
Rectangle {
|
||||
horizontal-stretch: 0;
|
||||
|
||||
check_box := CheckBox {
|
||||
enabled: root.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
check_box.toggle();
|
||||
}
|
||||
|
||||
public function set_check_state(check-state: CheckState) {
|
||||
check_box.set_check_state(check_state);
|
||||
}
|
||||
}
|
||||
205
material-1.0/ui/components/chip.slint
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { IconButton } from "icon_button.slint";
|
||||
|
||||
export component ActionChip {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <image> avatar_icon <=> base.avatar-icon;
|
||||
in property <color> avatar_background <=> base.avatar_background;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
forward-focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: base.border_radius;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
base := BaseButton {
|
||||
min_layout_height: MaterialStyleMetrics.size_32;
|
||||
border_radius: MaterialStyleMetrics.border_radius_8;
|
||||
color: MaterialPalette.on_surface;
|
||||
icon_color: MaterialPalette.primary;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
button_padding_left: self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_16;
|
||||
button_padding_right:MaterialStyleMetrics.padding_16;
|
||||
button_vertical_padding: MaterialStyleMetrics.padding_6;
|
||||
avatar_size: MaterialStyleMetrics.size_18;
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.display_background: false;
|
||||
base.icon_color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component FilterChip {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in_out property <bool> checked;
|
||||
|
||||
forward-focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-checkable: true;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: base.border_radius;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
|
||||
base := BaseButton {
|
||||
min_layout_height: MaterialStyleMetrics.size_32;
|
||||
border_radius: MaterialStyleMetrics.border_radius_8;
|
||||
color: MaterialPalette.on_surface;
|
||||
icon_color: MaterialPalette.primary;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
button_padding_left: self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_16;
|
||||
button_padding_right:MaterialStyleMetrics.padding_16;
|
||||
button_vertical_padding: MaterialStyleMetrics.padding_6;
|
||||
avatar_size: MaterialStyleMetrics.size_18;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.checked : {
|
||||
base.icon: Icons.check;
|
||||
base.icon_color: MaterialPalette.on_secondary_container;
|
||||
base.color: MaterialPalette.on_secondary_container;
|
||||
border.border_width: 0;
|
||||
border.background: MaterialPalette.secondary_container;
|
||||
}
|
||||
]
|
||||
|
||||
animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; }
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.display_background: false;
|
||||
base.icon_color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component InputChip {
|
||||
in property <image> leading_icon <=> base.icon;
|
||||
in property <image> trailing_icon;
|
||||
in property <image> avatar <=> base.avatar-icon;
|
||||
in property <color> avatar_background <=> base.avatar_background;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
|
||||
property <bool> has_avatar: root.avatar.width > 0 && root.avatar.height > 0;
|
||||
|
||||
callback clicked();
|
||||
callback trailing_icon_clicked();
|
||||
|
||||
forward-focus: base;
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: base.border_radius;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
clip: true;
|
||||
|
||||
base := BaseButton {
|
||||
min_layout_height: MaterialStyleMetrics.size_32;
|
||||
border_radius: root.has_avatar ? self.height / 2 : MaterialStyleMetrics.border_radius_8;
|
||||
color: MaterialPalette.on_surface;
|
||||
icon_color: MaterialPalette.primary;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
button_padding_left: root.has_avatar ? MaterialStyleMetrics.padding_4 : self.has_icon ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_12;
|
||||
button_padding_right: root.trailing_icon.width > 0 && root.trailing_icon.height > 0 ? MaterialStyleMetrics.padding_8 : MaterialStyleMetrics.padding_12;
|
||||
button_vertical_padding: MaterialStyleMetrics.padding_6;
|
||||
avatar_size: MaterialStyleMetrics.size_18;
|
||||
|
||||
if root.trailing_icon.width > 0 && root.trailing_icon.height > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
IconButton {
|
||||
icon: root.trailing_icon;
|
||||
inline: true;
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
root.trailing_icon_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
root.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.checked : {
|
||||
base.icon_color: MaterialPalette.on_secondary_container;
|
||||
base.color: MaterialPalette.on_secondary_container;
|
||||
border.border_width: 0;
|
||||
border.background: MaterialPalette.secondary_container;
|
||||
}
|
||||
]
|
||||
|
||||
animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; }
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.display_background: false;
|
||||
base.icon_color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
568
material-1.0/ui/components/date_picker.slint
Normal file
|
|
@ -0,0 +1,568 @@
|
|||
// 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;
|
||||
rotation-angle: root.checked ? 180deg : 0;
|
||||
rotation-origin-x: self.width / 2;
|
||||
rotation-origin-y: self.height / 2;
|
||||
|
||||
animate rotation-angle { duration: 250ms; }
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
root.opacity: 0.38;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
export component DatePickerPopup inherits PopupWindow {
|
||||
in property <string> title;
|
||||
in property <Date> date : { day: today[0], month: today[1], year: today[2] };
|
||||
|
||||
callback canceled();
|
||||
callback accepted(date: Date);
|
||||
|
||||
// this is used for the navigation between months
|
||||
property <string> input_title: @tr("Enter date");
|
||||
property <string> input_placeholder_text: "mm/dd/yyyy";
|
||||
property <string> input_format: "%m/%d/%Y";
|
||||
property <string> cancel_text: @tr("Cancel");
|
||||
property <string> ok_text: @tr("Ok");
|
||||
property <Date> display_date: root.date;
|
||||
property <Date> current_date: root.date;
|
||||
property <length> delegate_size: 40px;
|
||||
property <length> year_delegate_width: 72px;
|
||||
property <int> calendar_column_count: 7;
|
||||
property <int> calendar_row_count: 6;
|
||||
property <length> calendar_min_width: root.delegate_size * root.calendar_column_count + (root.calendar_column_count - 1) * 1px;
|
||||
property <length> calendar_min_height: root.delegate_size *(root.calendar_row_count + 1) + (root.calendar_row_count - 1) * 1px;
|
||||
property <int> year_selection_column_count: 3;
|
||||
property <int> year_selection_row_count: 5;
|
||||
property <bool> year_selection;
|
||||
property <bool> selection_mode: true;
|
||||
property <string> current_input;
|
||||
|
||||
property <int> calendar_month_count: DatePickerAdapter.month_day_count(root.display_date.month, root.display_date.year);
|
||||
property <[string]> calendar_header_model: [
|
||||
@tr("One-letter abbrev for Sunday" => "S"),
|
||||
@tr("One-letter abbrev for Monday" => "M"),
|
||||
@tr("One-letter abbrev for Tuesday" => "T"),
|
||||
@tr("One-letter abbrev for Wednesday" => "W"),
|
||||
@tr("One-letter abbrev for Thursday" => "T"),
|
||||
@tr("One-letter abbrev for Friday" => "F"),
|
||||
@tr("One-letter abbrev for Saturday" => "S"),
|
||||
];
|
||||
property <[int]> today: DatePickerAdapter.date_now();
|
||||
property <int> start_column: DatePickerAdapter.month_offset(root.display_date.month, root.display_date.year);
|
||||
property <string> current_month: DatePickerAdapter.format_date("%B %Y", root.display_date.day, root.display_date.month, root.display_date.year);
|
||||
property <[int]> year_model: [2024, 2025, 2026, 2027, 2028, 2029, 2031, 2032, 2033, 2034, 2035, 2036, 2037, 2038, 2039, 2040, 2041, 2042, 2043];
|
||||
property <int> selected_year: root.display_date.year;
|
||||
property <int> today_year: root.today[2];
|
||||
property <string> current_day: DatePickerAdapter.format_date("%a, %b %d", root.current_date.day, root.current_date.month, root.current_date.year);
|
||||
property <[int]> input_formatted: DatePickerAdapter.parse_date(root.current_input, root.input_format);
|
||||
|
||||
close_policy: no_auto_close;
|
||||
forward_focus: base;
|
||||
|
||||
base := BaseDialog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
title: root.title;
|
||||
actions: [
|
||||
root.cancel_text,
|
||||
root.ok_text
|
||||
];
|
||||
|
||||
content_layer := VerticalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_12;
|
||||
|
||||
header := HorizontalLayout {
|
||||
MaterialText {
|
||||
text: root.selection_mode ? root.current_day : root.input_title;
|
||||
horizontal_alignment: left;
|
||||
vertical_alignment: center;
|
||||
style: MaterialTypography.headline_large;
|
||||
}
|
||||
|
||||
if root.selection_mode : IconButton {
|
||||
icon: Icons.edit;
|
||||
accessible_label: "Toggle selection mode";
|
||||
|
||||
clicked => {
|
||||
root.toggle_selection_mode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: MaterialPalette.outline;
|
||||
}
|
||||
|
||||
if root.selection_mode : HorizontalLayout {
|
||||
// FIXME: spacing
|
||||
|
||||
VerticalLayout {
|
||||
horizontal_stretch: 0;
|
||||
alignment: center;
|
||||
|
||||
SelectionButton {
|
||||
text: root.current_month;
|
||||
icon: Icons.arrow_drop_down;
|
||||
checked <=> root.year_selection;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {}
|
||||
|
||||
IconButton {
|
||||
icon: Icons.chevron_backward;
|
||||
accessible_label: "Previous month";
|
||||
|
||||
clicked => {
|
||||
root.show_previous();
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: Icons.chevron_forward;
|
||||
accessible_label: "Next month";
|
||||
|
||||
clicked => {
|
||||
root.show_next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.selection_mode : VerticalLayout {
|
||||
if !root.year_selection : Calendar {
|
||||
min_width: root.calendar_min_width;
|
||||
min_height: root.calendar_min_height;
|
||||
column_count: root.calendar_column_count;
|
||||
row_count: root.calendar_row_count;
|
||||
delegate_size: root.delegate_size;
|
||||
header_model: root.calendar_header_model;
|
||||
month_count: root.calendar_month_count;
|
||||
today: { day: root.today[0], month: root.today[1], year: root.today[2] };
|
||||
selected_date <=> root.current_date;
|
||||
start_column: root.start_column;
|
||||
display_month: root.display_date.month;
|
||||
display_year: root.display_date.year;
|
||||
|
||||
select_date(date) => {
|
||||
root.select_date(date);
|
||||
}
|
||||
}
|
||||
|
||||
if root.year_selection : YearSelection {
|
||||
min_width: root.calendar_min_width;
|
||||
min_height: root.calendar_min_height;
|
||||
column_count: root.year_selection_column_count;
|
||||
visible_row_count: root.year_selection_row_count;
|
||||
delegate_width: root.year_delegate_width;
|
||||
delegate_height: root.delegate_size;
|
||||
model: root.year_model;
|
||||
selected_year: root.selected_year;
|
||||
today_year: root.today_year;
|
||||
|
||||
select_year(year) => {
|
||||
root.select_year(year);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
visible: root.year_selection;
|
||||
background: MaterialPalette.outline;
|
||||
}
|
||||
|
||||
if !root.selection_mode : TextField {
|
||||
text <=> root.current_input;
|
||||
placeholder_text: root.input_placeholder_text;
|
||||
trailing_icon: Icons.calendar_today;
|
||||
|
||||
trailing_icon_clicked => {
|
||||
root.toggle_selection_mode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action_clicked(index) => {
|
||||
// cancel
|
||||
if index == 0 {
|
||||
root.close();
|
||||
root.canceled();
|
||||
} else if index == 1 {
|
||||
root.accepted(root.date);
|
||||
}
|
||||
}
|
||||
|
||||
close => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
changed date => {
|
||||
root.display_date = root.date;
|
||||
root.current_date = root.date;
|
||||
}
|
||||
|
||||
changed selection_mode => {
|
||||
// check switch from input mode if input is valid
|
||||
if root.selection_mode && root.current_input_valid() {
|
||||
root.current_date = root.input_as_date();
|
||||
root.display_date = root.current_date;
|
||||
}
|
||||
}
|
||||
|
||||
pure public function ok_enabled() -> bool {
|
||||
root.selection_mode || root.current_input_valid()
|
||||
}
|
||||
|
||||
public function get_current_date() -> Date {
|
||||
if root.selection_mode {
|
||||
return root.current_date;
|
||||
}
|
||||
|
||||
root.input_as_date()
|
||||
}
|
||||
|
||||
pure function current_input_valid() -> bool {
|
||||
DatePickerAdapter.valid_date(root.current_input, root.input_format)
|
||||
}
|
||||
|
||||
pure function input_as_date() -> Date {
|
||||
{ day: root.input_formatted[0], month: root.input_formatted[1], year: root.input_formatted[2] }
|
||||
}
|
||||
|
||||
function select_date(date: Date) {
|
||||
root.current_date = date;
|
||||
}
|
||||
|
||||
function select_year(year: int) {
|
||||
root.current_date = { day: 1, month: 1, year: year };
|
||||
root.display_date = root.current_date;
|
||||
root.year_selection = false;
|
||||
}
|
||||
|
||||
function show_next() {
|
||||
if root.display_date.month >= 12 {
|
||||
root.display_date = { day: 1, month: 1, year: root.display_date.year + 1 };
|
||||
return;
|
||||
}
|
||||
|
||||
root.display_date = { day: 1, month: root.display_date.month + 1, year: root.display_date.year };
|
||||
}
|
||||
|
||||
function show_previous() {
|
||||
if root.display_date.month <= 1 {
|
||||
root.display_date = { day: 1, month: 12, year: root.display_date.year - 1 };
|
||||
return;
|
||||
}
|
||||
|
||||
root.display_date = { day: 1, month: root.display_date.month - 1, year: root.display_date.year };
|
||||
}
|
||||
|
||||
function toggle_selection_mode() {
|
||||
root.selection_mode = !root.selection_mode;
|
||||
}
|
||||
}
|
||||
233
material-1.0/ui/components/dialog.slint
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { TextButton } from "./text_button.slint";
|
||||
import { FilledButton } from "./filled_button.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Modal } from "./modal.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
|
||||
export component FullscreenDialog inherits PopupWindow {
|
||||
in property <string> title;
|
||||
in property <[string]> actions;
|
||||
|
||||
callback action_clicked(index: int);
|
||||
|
||||
close_policy: no_auto_close;
|
||||
forward_focus: focus_scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
x: 0;
|
||||
width: 0;
|
||||
|
||||
key_pressed(event) => {
|
||||
if event.text == Key.Escape {
|
||||
root.close();
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
VerticalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_16;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_24;
|
||||
spacing: MaterialStyleMetrics.spacing_16;
|
||||
vertical_stretch: 0;
|
||||
|
||||
IconButton {
|
||||
icon: Icons.close;
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
text: root.title;
|
||||
style: MaterialTypography.headline_small;
|
||||
color: MaterialPalette.on_surface;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
|
||||
for action[index] in root.actions : TextButton {
|
||||
text: action;
|
||||
|
||||
clicked => {
|
||||
root.action_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
background_layer.opacity = 1;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component BaseDialog {
|
||||
in property <string> title;
|
||||
in property <image> icon;
|
||||
in property <[image]> action_button_icons;
|
||||
in property <string> default_action_text;
|
||||
in property <[string]> actions;
|
||||
|
||||
property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
|
||||
callback default_action_clicked();
|
||||
callback action_button_clicked(index: int);
|
||||
callback action_clicked(index: int);
|
||||
callback close();
|
||||
|
||||
forward_focus: focus_scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
x: 0;
|
||||
width: 0;
|
||||
|
||||
key_pressed(event) => {
|
||||
if event.text == Key.Return && root.default_action_text != "" {
|
||||
root.default_action_clicked();
|
||||
return accept;
|
||||
}
|
||||
|
||||
if event.text == Key.Escape {
|
||||
root.close();
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
modal := Modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background_layer := Rectangle {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: layout.min_width;
|
||||
height: layout.min_height;
|
||||
opacity: 0;
|
||||
border_radius: MaterialStyleMetrics.border_radius_28;
|
||||
background: MaterialPalette.surface_container_high;
|
||||
|
||||
TouchArea {
|
||||
layout := VerticalLayout {
|
||||
padding: MaterialStyleMetrics.padding_24;
|
||||
spacing: MaterialStyleMetrics.spacing_16;
|
||||
|
||||
if root.has_icon : Icon {
|
||||
x: (parent.width - self.width) / 2;
|
||||
colorize: MaterialPalette.on_surface;
|
||||
source: root.icon;
|
||||
}
|
||||
|
||||
if root.title != "" : MaterialText {
|
||||
horizontal_alignment: root.has_icon ? center : left;
|
||||
text: root.title;
|
||||
style: MaterialTypography.headline_small;
|
||||
color: MaterialPalette.on_surface;
|
||||
}
|
||||
|
||||
@children
|
||||
|
||||
HorizontalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
|
||||
for action_button[index] in root.action_button_icons : IconButton {
|
||||
icon: action_button;
|
||||
|
||||
clicked => {
|
||||
root.action_button_clicked(index);
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Rectangle {}
|
||||
|
||||
for action[index] in root.actions : TextButton {
|
||||
text: action;
|
||||
|
||||
clicked => {
|
||||
root.action_clicked(index);
|
||||
}
|
||||
}
|
||||
|
||||
if root.default_action_text != "" : FilledButton {
|
||||
text: root.default_action_text;
|
||||
|
||||
clicked => {
|
||||
root.default_action_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animate opacity { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
background_layer.opacity = 1;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component Dialog inherits PopupWindow {
|
||||
in property <string> title <=> base.title;
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> default_action_text <=> base.default_action_text;
|
||||
in property <[string]> actions <=> base.actions;
|
||||
|
||||
callback default_action_clicked <=> base.default_action_clicked;
|
||||
callback action_clicked <=> base.action_clicked;
|
||||
|
||||
close_policy: no_auto_close;
|
||||
forward_focus: base;
|
||||
|
||||
base := BaseDialog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@children
|
||||
|
||||
close => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
material-1.0/ui/components/divider.slint
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
component BaseDivider {
|
||||
Rectangle {
|
||||
background: MaterialPalette.outline_variant;
|
||||
}
|
||||
}
|
||||
|
||||
export component VerticalDivider {
|
||||
width: 1px;
|
||||
|
||||
BaseDivider {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
export component HorizontalDivider {
|
||||
height: 1px;
|
||||
|
||||
BaseDivider {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
89
material-1.0/ui/components/drawer.slint
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Modal } from "./modal.slint";
|
||||
|
||||
export component DrawerHeader {
|
||||
in property <string> title;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_56, title_label.min_width);
|
||||
|
||||
title_label := MaterialText {
|
||||
x: MaterialStyleMetrics.padding_16;
|
||||
width: root.width - MaterialStyleMetrics.padding_16;
|
||||
text: root.title;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
vertical_alignment: center;
|
||||
style: MaterialTypography.title_small;
|
||||
}
|
||||
}
|
||||
|
||||
export component Drawer {
|
||||
in property <string> title;
|
||||
|
||||
min_width: max(MaterialStyleMetrics.size_360, layout.min_width);
|
||||
|
||||
TouchArea {
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container_low;
|
||||
border_radius: MaterialStyleMetrics.border_radius_16;
|
||||
|
||||
layout := VerticalLayout {
|
||||
alignment: start;
|
||||
padding: MaterialStyleMetrics.padding_12;
|
||||
|
||||
if root.title != "" : DrawerHeader {
|
||||
width: 100%;
|
||||
title: root.title;
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ModalDrawer inherits PopupWindow {
|
||||
in property <bool> position_right;
|
||||
in property <string> title;
|
||||
|
||||
close_policy: no_auto_close;
|
||||
|
||||
modal := Modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
drawer := Drawer {
|
||||
x: root.position_right ? parent.width : -self.width;
|
||||
y: 0;
|
||||
height: 100%;
|
||||
title: root.title;
|
||||
|
||||
@children
|
||||
|
||||
animate x {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
drawer.x = root.position_right ? root.width - drawer.width : 0;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
material-1.0/ui/components/drop_down_menu.slint
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { TextField } from "./text_field.slint";
|
||||
import { MenuItem } from "../items/menu_item.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MenuInner } from "./menu.slint";
|
||||
import { StateLayer } from "./state_layer.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { ScrollView } from "./scroll_view.slint";
|
||||
|
||||
export component DropDownMenu {
|
||||
in property <string> label <=> text_field.label;
|
||||
in property <bool> enabled <=> text_field.enabled;
|
||||
in property <image> leading_icon <=> text_field.leading_icon;
|
||||
in property <[MenuItem]> items;
|
||||
in_out property <int> current_index: -1;
|
||||
|
||||
callback selected(index: int);
|
||||
|
||||
property <length> item_height: MaterialStyleMetrics.size_56;
|
||||
property <int> max_displayed_items: 6;
|
||||
property <string> text <=> text_field.text;
|
||||
|
||||
min_width: text_field.min_width;
|
||||
min_height: text_field.min_height;
|
||||
forward_focus: text_field; accessible-role: combobox;
|
||||
accessible_enabled: root.enabled;
|
||||
accessible_expandable: true;
|
||||
accessible_value <=> text_field.text;
|
||||
accessible_action_expand => { menu.show(); }
|
||||
|
||||
text_field := TextField {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
trailing_icon: Icons.arrow_drop_down;
|
||||
read_only: true;
|
||||
}
|
||||
|
||||
state_layer := StateLayer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: MaterialPalette.on_surface;
|
||||
has_hover: touch_area.has_hover;
|
||||
enabled: root.enabled;
|
||||
}
|
||||
|
||||
touch_area := TouchArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
menu.show();
|
||||
}
|
||||
}
|
||||
|
||||
menu := PopupWindow {
|
||||
x: 0;
|
||||
width: root.width;
|
||||
close_policy: close_on_click_outside;
|
||||
forward_focus: inner_text_field;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
inner_text_field := TextField {
|
||||
width: root.width;
|
||||
leading_icon: root.leading_icon;
|
||||
trailing_icon: Icons.arrow_drop_up;
|
||||
text: root.text;
|
||||
label: root.label;
|
||||
read_only: true;
|
||||
|
||||
leading_icon_clicked => {
|
||||
menu.close();
|
||||
}
|
||||
|
||||
trailing_icon_clicked => {
|
||||
menu.close();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: min(max(root.item_height, items.length * root.item_height), root.max_displayed_items * root.item_height);
|
||||
background: MaterialPalette.surface_container;
|
||||
|
||||
ScrollView {
|
||||
VerticalLayout {
|
||||
menu_inner := MenuInner {
|
||||
current_index: root.current_index;
|
||||
items: root.items;
|
||||
|
||||
activated(index) => {
|
||||
root.update_selection(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function update_selection(index: int) {
|
||||
root.update_text(index);
|
||||
root.current_index = index;
|
||||
menu.close();
|
||||
root.selected(index);
|
||||
}
|
||||
|
||||
function update_text(index: int) {
|
||||
if index < 0 || root.current_index >= root.items.length {
|
||||
text_field.text = "";
|
||||
return;
|
||||
}
|
||||
|
||||
text_field.text = root.items[index].text;
|
||||
}
|
||||
|
||||
changed current_index => {
|
||||
root.update_text(root.current_index);
|
||||
}
|
||||
}
|
||||
45
material-1.0/ui/components/elevated_button.slint
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component ElevatedButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
elevation := Elevation {
|
||||
border_radius: root.height / 2;
|
||||
background: MaterialPalette.surface_container_low;
|
||||
level: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: elevation.border_radius;
|
||||
color: MaterialPalette.primary;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
elevation.background: transparent;
|
||||
elevation.level: 0;
|
||||
base.color: MaterialPalette.on_surface;
|
||||
}
|
||||
hover when base.has_hover && !base.pressed && !base.enter_pressed : {
|
||||
elevation.level: 3;
|
||||
}
|
||||
]
|
||||
}
|
||||
108
material-1.0/ui/components/elevation.slint
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Palette } from "std-widgets.slint";
|
||||
import { MaterialWindow } from "material_window.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component Elevation {
|
||||
in property <brush> background;
|
||||
in property <length> border_radius;
|
||||
in property <int> level;
|
||||
|
||||
property <bool> dark: Palette.color_scheme == ColorScheme.dark;
|
||||
|
||||
outer_shadow := Rectangle {
|
||||
border_radius: root.border_radius;
|
||||
|
||||
inner_shadow := Rectangle {
|
||||
border_radius: root.border_radius;
|
||||
background: root.background;
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
level_1_light when root.level == 1 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 1px;
|
||||
outer_shadow.drop_shadow_blur: 2px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 2px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
}
|
||||
level_2_light when root.level == 2 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 1px;
|
||||
outer_shadow.drop_shadow_blur: 2px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
inner_shadow.drop_shadow_offset_y: 2px;
|
||||
inner_shadow.drop_shadow_blur: 6px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
}
|
||||
level_3_light when root.level == 3 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 4px;
|
||||
outer_shadow.drop_shadow_blur: 8px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 3px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_4_light when root.level == 4 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 6px;
|
||||
outer_shadow.drop_shadow_blur: 10px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 2px;
|
||||
inner_shadow.drop_shadow_blur: 3px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_5_light when root.level == 5 && !root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 8px;
|
||||
outer_shadow.drop_shadow_blur: 12px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 4px;
|
||||
inner_shadow.drop_shadow_blur: 4px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_1_dark when root.level == 1 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 1px;
|
||||
outer_shadow.drop_shadow_blur: 3px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 2px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_2_dark when root.level == 2 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 2px;
|
||||
outer_shadow.drop_shadow_blur: 6px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 2px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_3_dark when root.level == 3 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 4px;
|
||||
outer_shadow.drop_shadow_blur: 8px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 1px;
|
||||
inner_shadow.drop_shadow_blur: 3px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_4_dark when root.level == 4 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 6px;
|
||||
outer_shadow.drop_shadow_blur: 10px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 2px;
|
||||
inner_shadow.drop_shadow_blur: 3px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
level_5_dark when root.level == 5 && root.dark: {
|
||||
outer_shadow.drop_shadow_offset_y: 8px;
|
||||
outer_shadow.drop_shadow_blur: 12px;
|
||||
outer_shadow.drop_shadow_color: MaterialPalette.shadow_15;
|
||||
inner_shadow.drop_shadow_offset_y: 4px;
|
||||
inner_shadow.drop_shadow_blur: 4px;
|
||||
inner_shadow.drop_shadow_color: MaterialPalette.shadow_30;
|
||||
}
|
||||
]
|
||||
}
|
||||
57
material-1.0/ui/components/extended_touch_area.slint
Normal 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;
|
||||
}
|
||||
}
|
||||
43
material-1.0/ui/components/filled_button.slint
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component FilledButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
elevation := Elevation {
|
||||
border_radius: root.height / 2;
|
||||
background: MaterialPalette.primary;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: elevation.border_radius;
|
||||
color: MaterialPalette.on_primary;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
elevation.background: transparent;
|
||||
base.color: MaterialPalette.on_surface;
|
||||
}
|
||||
hover when base.has_hover && !base.pressed && !base.enter_pressed : {
|
||||
elevation.level: 1;
|
||||
}
|
||||
]
|
||||
}
|
||||
64
material-1.0/ui/components/filled_icon_button.slint
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component FilledIconButton {
|
||||
in property <image> icon;
|
||||
in property <image> checked_icon: root.icon;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked();
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: true;
|
||||
accessible-label: root.tooltip;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: base.border_radius;
|
||||
background: root.checkable ? MaterialPalette.surface_container_highest : MaterialPalette.primary;
|
||||
visible: root.enabled;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
icon: root.checked ? root.checked_icon : root.icon;
|
||||
border_radius: self.height / 2;
|
||||
color: !root.enabled ? MaterialPalette.on_surface_variant : root.checkable ? MaterialPalette.primary : MaterialPalette.on_primary;
|
||||
button_horizontal_padding: 0;
|
||||
button_vertical_padding: 0;
|
||||
width: self.height;
|
||||
icon_size: MaterialStyleMetrics.icon_size_24;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.enabled && root.checked : {
|
||||
base.color: MaterialPalette.on_primary;
|
||||
background_layer.background: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
55
material-1.0/ui/components/floating_action_button.slint
Normal 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;
|
||||
}
|
||||
]
|
||||
}
|
||||
10
material-1.0/ui/components/grid.slint
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component Grid inherits GridLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
spacing_vertical: MaterialStyleMetrics.spacing_8;
|
||||
spacing_horizontal: MaterialStyleMetrics.spacing_8;
|
||||
}
|
||||
9
material-1.0/ui/components/horizontal.slint
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component Horizontal inherits HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
}
|
||||
11
material-1.0/ui/components/icon.slint
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component Icon inherits Image {
|
||||
width: MaterialStyleMetrics.icon_size_18;
|
||||
height: self.width;
|
||||
colorize: MaterialPalette.on_background;
|
||||
}
|
||||
62
material-1.0/ui/components/icon_button.slint
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component IconButton {
|
||||
in property <image> icon;
|
||||
in property <image> checked_icon: root.icon;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <bool> inverse;
|
||||
in property <bool> inline: false;
|
||||
in property <bool> has_error;
|
||||
|
||||
callback clicked();
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: true;
|
||||
accessible-label: root.tooltip;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseButton {
|
||||
icon: root.checked ? root.checked_icon : root.icon;
|
||||
border_radius: self.height / 2;
|
||||
color: root.has_error ? MaterialPalette.error : root.inverse ? MaterialPalette.inverse_on_surface : MaterialPalette.on_surface_variant;
|
||||
button_horizontal_padding: 0;
|
||||
button_vertical_padding: 0;
|
||||
width: self.height;
|
||||
display_background: false;
|
||||
icon_size: root.inline ? MaterialStyleMetrics.icon_size_18 : MaterialStyleMetrics.icon_size_24;
|
||||
min_layout_width: root.inline ? MaterialStyleMetrics.icon_size_18 : MaterialStyleMetrics.size_40;
|
||||
min_layout_height: self.min_layout_width;
|
||||
clip_ripple: !root.inline;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.enabled && root.checked : {
|
||||
base.color: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
107
material-1.0/ui/components/list.slint
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
export component Avatar {
|
||||
in property <image> image;
|
||||
in property <color> background: MaterialPalette.primary;
|
||||
|
||||
width: MaterialStyleMetrics.size_32;
|
||||
height: self.width;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: self.width / 2;
|
||||
background: root.background;
|
||||
clip: true;
|
||||
|
||||
Image {
|
||||
source: root.image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component ListTile {
|
||||
in property <string> text;
|
||||
in property <string> supporting_text;
|
||||
in property <image> avatar_icon;
|
||||
in property <string> avatar_text;
|
||||
in property <color> avatar_background;
|
||||
in property <color> avatar_foreground;
|
||||
in property <bool> enabled <=> state_layer.enabled;
|
||||
|
||||
property <color> color: MaterialPalette.on_surface;
|
||||
|
||||
callback clicked <=> state_layer.clicked;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_72, layout.min_height);
|
||||
|
||||
state_layer := StateLayerArea {
|
||||
color: root.color;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_16;
|
||||
padding_right: MaterialStyleMetrics.padding_24;
|
||||
padding_top: MaterialStyleMetrics.padding_8;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_16;
|
||||
|
||||
if root.avatar_background != #00000000 || root.avatar_text != "" || (root.avatar_icon.width > 0 && root.avatar_icon.height > 0) : Rectangle {
|
||||
width: self.height;
|
||||
border_radius: self.height / 2;
|
||||
background: root.avatar_background;
|
||||
clip: true;
|
||||
|
||||
if root.avatar_text != "" : MaterialText {
|
||||
text: root.avatar_text;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
color: root.avatar_foreground;
|
||||
style: MaterialTypography.title_medium;
|
||||
}
|
||||
|
||||
if root.avatar_icon.width > 0 && root.avatar_icon.height > 0 : Icon {
|
||||
width: parent.width;
|
||||
source: root.avatar_icon;
|
||||
colorize: root.avatar_foreground;
|
||||
}
|
||||
}
|
||||
|
||||
title_layout := VerticalLayout {
|
||||
alignment: center;
|
||||
horizontal_stretch: 1;
|
||||
|
||||
MaterialText {
|
||||
text: root.text;
|
||||
color: root.color;
|
||||
overflow: elide;
|
||||
style: MaterialTypography.body_large;
|
||||
}
|
||||
|
||||
if root.supporting_text != "" : MaterialText {
|
||||
text: root.supporting_text;
|
||||
color: root.color;
|
||||
overflow: elide;
|
||||
style: MaterialTypography.body_medium;
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
state_layer.display_background: false;
|
||||
title_layout.opacity: 38%;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
8
material-1.0/ui/components/list_view.slint
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ScrollView } from "./scroll_view.slint";
|
||||
|
||||
export component ListView inherits ScrollView {
|
||||
|
||||
}
|
||||
14
material-1.0/ui/components/material_text.slint
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialTypography, TextStyle } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component MaterialText inherits Text {
|
||||
in property <TextStyle> style: MaterialTypography.body_medium;
|
||||
|
||||
font_size: root.style.font_size;
|
||||
font_weight: root.style.font_weight;
|
||||
color: MaterialPalette.on_background;
|
||||
overflow: elide;
|
||||
}
|
||||
18
material-1.0/ui/components/material_window.slint
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export global MaterialWindowAdapter {
|
||||
in property <bool> disable_hover;
|
||||
}
|
||||
|
||||
export component MaterialWindow inherits Window {
|
||||
in property <bool> disable_hover;
|
||||
|
||||
background: MaterialPalette.background;
|
||||
|
||||
changed disable_hover => {
|
||||
MaterialWindowAdapter.disable_hover = root.disable_hover;
|
||||
}
|
||||
}
|
||||
136
material-1.0/ui/components/menu.slint
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { Elevation } from "elevation.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { MenuItem } from "../items/menu_item.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
|
||||
component MenuItemTemplate {
|
||||
in property <image> icon;
|
||||
in property <string> text;
|
||||
in property <string> trailing_text;
|
||||
in property <bool> enabled <=> state_area.enabled;
|
||||
in property <bool> selected;
|
||||
|
||||
callback clicked();
|
||||
|
||||
property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
property <color> color: MaterialPalette.on_surface;
|
||||
property <color> color_variant: MaterialPalette.on_surface_variant;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_56, layout.min_height);
|
||||
|
||||
background_layer := Rectangle {
|
||||
state_area := StateLayerArea {
|
||||
color: root.color;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_12;
|
||||
padding_right: self.padding_left;
|
||||
padding_top: MaterialStyleMetrics.padding_8;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_12;
|
||||
|
||||
if root.has_icon : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
Icon {
|
||||
source: root.icon;
|
||||
colorize: root.color;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
horizontal_stretch: 1;
|
||||
text: root.text;
|
||||
style: MaterialTypography.body_large;
|
||||
color: root.color;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
|
||||
if root.trailing_text != "" : MaterialText {
|
||||
text: root.trailing_text;
|
||||
style: MaterialTypography.body_large;
|
||||
color: root.color_variant;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
state_area.opacity: MaterialPalette.disable_opacity;
|
||||
}
|
||||
selected when root.selected : {
|
||||
background_layer.background: MaterialPalette.surface_container_highest;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component MenuInner {
|
||||
in property <[MenuItem]> items;
|
||||
in_out property <int> current_index: -1;
|
||||
|
||||
callback activated(index: int);
|
||||
|
||||
// used to fix clipping of shadows
|
||||
out property <int> elevation: 2;
|
||||
|
||||
menu := Elevation {
|
||||
width: 100%;
|
||||
level: root.elevation;
|
||||
height: layout.min_height + root.elevation * 1px;
|
||||
border_radius: MaterialStyleMetrics.border_radius_4;
|
||||
background: MaterialPalette.surface_container;
|
||||
|
||||
layout := VerticalLayout {
|
||||
width: root.width;
|
||||
padding_top: MaterialStyleMetrics.padding_8;
|
||||
padding_bottom: self.padding_top;
|
||||
|
||||
for item[index] in root.items : MenuItemTemplate {
|
||||
icon: item.icon;
|
||||
text: item.text;
|
||||
trailing_text: item.trailing_text;
|
||||
enabled: item.enabled;
|
||||
selected: index == root.current_index;
|
||||
|
||||
clicked => {
|
||||
root.activated(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component PopupMenu inherits PopupWindow {
|
||||
in property <[MenuItem]> items;
|
||||
|
||||
callback activated(index: int);
|
||||
|
||||
width: MaterialStyleMetrics.size_200;
|
||||
close_policy: close_on_click_outside;
|
||||
|
||||
VerticalLayout {
|
||||
padding: inner.elevation * 1px;
|
||||
|
||||
inner := MenuInner {
|
||||
items: root.items;
|
||||
|
||||
activated(index) => {
|
||||
root.activated(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
material-1.0/ui/components/modal.slint
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
|
||||
export component Modal {
|
||||
callback clicked <=> touch_area.clicked;
|
||||
|
||||
background_layer := Rectangle {
|
||||
clip: true;
|
||||
|
||||
touch_area := TouchArea {
|
||||
@children
|
||||
}
|
||||
|
||||
animate background { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
background_layer.background = MaterialPalette.background_modal;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
150
material-1.0/ui/components/navigation_bar.slint
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Badge } from "./badge.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { ExtendedTouchArea } from "./extended_touch_area.slint";
|
||||
import { StateLayer, Ripple } from "./state_layer.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { NavigationItem } from "../items/navigation_item.slint";
|
||||
import { BaseNavigationItemTemplate, BaseNavigation } from "./base_navigation.slint";
|
||||
|
||||
export component NavigationItemTemplate inherits BaseNavigationItemTemplate {
|
||||
property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
property <bool> has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0;
|
||||
property <color> color: MaterialPalette.on_surface;
|
||||
|
||||
in property <length> navitem_padding_top: MaterialStyleMetrics.padding_12;
|
||||
in property <length> navitem_padding_bottom: MaterialStyleMetrics.padding_16;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
touch_area := ExtendedTouchArea {
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
padding_top: root.navitem_padding_top;
|
||||
padding_bottom: root.navitem_padding_bottom;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
background_layer := Rectangle {
|
||||
min_width: state_layout.min_width;
|
||||
min_height: max(MaterialStyleMetrics.size_32, state_layout.min_height);
|
||||
border_radius: self.height / 2;
|
||||
|
||||
state_layer := StateLayer {
|
||||
background: root.color;
|
||||
border_radius: parent.border_radius;
|
||||
enabled: true;
|
||||
has_focus: touch_area.has_focus;
|
||||
has_hover: touch_area.has_hover;
|
||||
pressed: touch_area.pressed || touch_area.enter_pressed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
if touch_area.pressed && !root.selected : Ripple {
|
||||
width: root.width;
|
||||
height: root.height;
|
||||
pressed_x: touch_area.pressed_x;
|
||||
pressed_y: touch_area.pressed_y;
|
||||
color: root.color;
|
||||
}
|
||||
|
||||
state_layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_20;
|
||||
padding_right: self.padding_left;
|
||||
alignment: center;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
if !root.has_icon && !root.has_selected_icon : Rectangle {
|
||||
width: 12px;
|
||||
height: self.width;
|
||||
border_radius: self.width / 2;
|
||||
background: root.color;
|
||||
}
|
||||
|
||||
if root.has_icon || root.has_selected_icon : Icon {
|
||||
source: root.icon;
|
||||
colorize: root.color;
|
||||
|
||||
states [
|
||||
selected when root.selected && root.has_selected_icon : {
|
||||
source: root.selected_icon;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.show_badge || root.badge != "" : Badge {
|
||||
x: parent.width / 2;
|
||||
y: 0;
|
||||
text: root.badge;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label := MaterialText {
|
||||
text: root.text;
|
||||
style: MaterialTypography.label_medium;
|
||||
color: root.color;
|
||||
horizontal_alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
background_layer.background: MaterialPalette.secondary_container;
|
||||
state_layer.background: transparent;
|
||||
label.style: MaterialTypography.label_medium_prominent;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component NavigationBar inherits BaseNavigation {
|
||||
min_height: max(MaterialStyleMetrics.size_80, layout.min_height);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_8;
|
||||
padding_right: self.padding_left;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
|
||||
for item[index] in root.items : NavigationItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
selected: index == root.current_index;
|
||||
show_badge: item.show_badge;
|
||||
badge: item.badge;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
material-1.0/ui/components/navigation_drawer.slint
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Drawer, ModalDrawer, DrawerHeader } from "./drawer.slint";
|
||||
import { NavigationItem, NavigationGroup } from "../items/navigation_item.slint";
|
||||
import { HorizontalDivider } from "./divider.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { BaseNavigationItemTemplate } from "./base_navigation.slint";
|
||||
|
||||
component NavigationItemTemplate inherits BaseNavigationItemTemplate {
|
||||
property <bool> has_icon: root.icon.width > 0 && root.icon.height > 0;
|
||||
property <bool> has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0;
|
||||
property <color> color: MaterialPalette.on_surface_variant;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_56, layout.min_height);
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: self.height / 2;
|
||||
|
||||
state_layer := StateLayerArea {
|
||||
border_radius: parent.border_radius;
|
||||
color: root.selected ? transparent : root.color;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_16;
|
||||
padding_right: MaterialStyleMetrics.padding_24;
|
||||
padding_top: MaterialStyleMetrics.padding_16;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_12;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
if !root.has_icon && !root.has_selected_icon : Rectangle {
|
||||
width: MaterialStyleMetrics.icon_size_24;
|
||||
height: self.width;
|
||||
|
||||
Rectangle {
|
||||
width: 12px;
|
||||
height: self.width;
|
||||
border_radius: self.width / 2;
|
||||
background: root.color;
|
||||
}
|
||||
}
|
||||
|
||||
if root.has_icon || root.has_selected_icon : Icon {
|
||||
source: root.icon;
|
||||
colorize: root.color;
|
||||
|
||||
states [
|
||||
selected when root.selected && root.has_selected_icon : {
|
||||
source: root.selected_icon;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
horizontal_stretch: 1;
|
||||
text: root.text;
|
||||
style: MaterialTypography.label_large;
|
||||
color: root.color;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
|
||||
if root.badge != "" : MaterialText {
|
||||
text: root.badge;
|
||||
style: MaterialTypography.label_large;
|
||||
color: root.color;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
pointer_event(event) => {
|
||||
root.pointer_event(event, {
|
||||
x: self.mouse_x,
|
||||
y: self.mouse_y,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
animate background { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
background_layer.background: MaterialPalette.secondary_container;
|
||||
root.color: MaterialPalette.on_secondary_container;
|
||||
}
|
||||
]
|
||||
|
||||
animate color { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
component NavigationGroupTemplate {
|
||||
in property <string> title;
|
||||
in property <[NavigationItem]> items;
|
||||
in property <int> current_index: -1;
|
||||
in property <bool> has_divider;
|
||||
|
||||
callback select(index: int);
|
||||
callback item_pointer_event(index: int, event: PointerEvent, position: Point);
|
||||
|
||||
VerticalLayout {
|
||||
alignment: start;
|
||||
|
||||
if root.title != 0 : DrawerHeader {
|
||||
title: root.title;
|
||||
}
|
||||
|
||||
for item[index] in root.items : NavigationItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
badge: item.badge;
|
||||
selected: index == root.current_index;
|
||||
index: index;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
pointer_event(event, position) => {
|
||||
root.item_pointer_event(index, event, {
|
||||
x: self.x + position.x,
|
||||
y: self.y + position.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if root.has_divider : HorizontalDivider {}
|
||||
}
|
||||
}
|
||||
|
||||
export component NavigationDrawer inherits Drawer {
|
||||
callback selected(group-index: int, item_index: int);
|
||||
callback item_pointer_event(group_index: int, item_index: int, event: PointerEvent, position: Point);
|
||||
in property <[NavigationGroup]> groups;
|
||||
in_out property <int> current_group;
|
||||
in_out property <int> current_index;
|
||||
|
||||
accessible-role: tab-list;
|
||||
// accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index;
|
||||
// accessible-label: root.title;
|
||||
accessible-item-count: root.groups.length;
|
||||
|
||||
for group[group_index] in root.groups : NavigationGroupTemplate {
|
||||
title: group.title;
|
||||
items: group.items;
|
||||
current_index: group_index == root.current_group ? root.current_index : -1;
|
||||
has_divider: root.groups.length > 1 && group_index < root.groups.length - 1;
|
||||
|
||||
select(index) => {
|
||||
root.select(group_index, index);
|
||||
}
|
||||
item_pointer_event(index, event, position) => {
|
||||
root.item_pointer_event(group_index, index, event, { x: self.x + position.x, y: self.y + position.y });
|
||||
}
|
||||
}
|
||||
|
||||
function select(group_index: int, item_index: int) {
|
||||
if group_index < 0 || group_index >= root.groups.length || item_index < 0 || item_index >= root.groups[group_index].items.length {
|
||||
return;
|
||||
}
|
||||
|
||||
root.current_group = group_index;
|
||||
root.current_index = item_index;
|
||||
root.selected(group_index, item_index);
|
||||
}
|
||||
}
|
||||
|
||||
export component ModalNavigationDrawer inherits ModalDrawer {
|
||||
in property <[NavigationGroup]> groups;
|
||||
in_out property <int> current_group;
|
||||
in_out property <int> current_index;
|
||||
|
||||
accessible-role: tab-list;
|
||||
// accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-index;
|
||||
// accessible-label: root.title;
|
||||
accessible-item-count: root.groups.length;
|
||||
|
||||
for group[group_index] in root.groups : NavigationGroupTemplate {
|
||||
title: group.title;
|
||||
items: group.items;
|
||||
current_index: group_index == root.current_group ? root.current_index : -1;
|
||||
has_divider: root.groups.length > 1 && group_index < root.groups.length - 1;
|
||||
|
||||
select(index) => {
|
||||
root.select(group_index, index);
|
||||
}
|
||||
}
|
||||
|
||||
function select(group_index: int, item_index: int) {
|
||||
if group_index < 0 || group_index >= root.groups.length || item_index < 0 || item_index >= root.groups[group_index].items.length {
|
||||
return;
|
||||
}
|
||||
|
||||
root.current_group = group_index;
|
||||
root.current_index = item_index;
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
85
material-1.0/ui/components/navigation_rail.slint
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// 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;
|
||||
border-radius: 5px;
|
||||
|
||||
layout := VerticalLayout {
|
||||
padding_top: MaterialStyleMetrics.padding_44;
|
||||
padding_bottom: MaterialStyleMetrics.padding_56;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
if root.has_menu : IconButton {
|
||||
icon: Icons.menu;
|
||||
|
||||
clicked => {
|
||||
root.menu_clicked();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if root.fab_icon.width > 0 && root.fab_icon.height > 0 : HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
FloatingActionButton {
|
||||
icon: root.fab_icon;
|
||||
style: FABStyle.standard;
|
||||
|
||||
clicked => {
|
||||
root.fab_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// helper to place fab at the top if no items are present
|
||||
if root.items.length == 0 : Rectangle {
|
||||
horizontal_stretch: 1;
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
vertical_stretch: 1;
|
||||
alignment: center;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: root.alignment;
|
||||
|
||||
for item[index] in root.items : NavigationItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
selected: index == root.current_index;
|
||||
show_badge: item.show_badge;
|
||||
badge: item.badge;
|
||||
navitem_padding_top: 0;
|
||||
navitem_padding_bottom: MaterialStyleMetrics.padding_4;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
material-1.0/ui/components/outline_button.slint
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component OutlineButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: border.border_radius;
|
||||
color: MaterialPalette.primary;
|
||||
}
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: root.height / 2;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.color: MaterialPalette.on_surface;
|
||||
base.transparent_background: true;
|
||||
border.border_color: MaterialPalette.on_surface;
|
||||
border.opacity: MaterialPalette.state_layer_opacity_hover;
|
||||
}
|
||||
]
|
||||
}
|
||||
78
material-1.0/ui/components/outline_icon_button.slint
Normal 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;
|
||||
}
|
||||
]
|
||||
}
|
||||
107
material-1.0/ui/components/progress_indicator.slint
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component LinearProgressIndicator {
|
||||
in property <float> progress;
|
||||
in property <bool> indeterminate;
|
||||
|
||||
height: MaterialStyleMetrics.size_4;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
clip: true;
|
||||
background: MaterialPalette.primary_container;
|
||||
border_radius: MaterialStyleMetrics.border_radius_2;
|
||||
|
||||
track := Rectangle {
|
||||
x: 0;
|
||||
y: 0;
|
||||
width: parent.width * root.progress;
|
||||
height: 100%;
|
||||
border_radius: parent.border_radius;
|
||||
background: MaterialPalette.primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component CircularProgressIndicator {
|
||||
in property <float> progress;
|
||||
in property <bool> indeterminate;
|
||||
|
||||
property <length> bar_height: MaterialStyleMetrics.size_4;
|
||||
|
||||
width: self.height;
|
||||
min_height: MaterialStyleMetrics.size_40;
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: root.width;
|
||||
height: root.height;
|
||||
border_width: root.bar_height;
|
||||
border_color: root.progress == 1 ? MaterialPalette.primary : MaterialPalette.primary_container;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
}
|
||||
|
||||
track := Path {
|
||||
property <float> radius: min(self.viewbox_width, self.viewbox_height) / 2;
|
||||
property <float> start_x: self.viewbox_width / 2;
|
||||
property <float> start_y: self.viewbox_height / 2;
|
||||
property <float> inner_radius: self.radius - (root.bar_height * (self.viewbox_height / self.height));
|
||||
property <float> start: !root.indeterminate ? 0 : 1 * mod(animation-tick(), 1.5s) / 1.5s;
|
||||
property <float> progress: !root.indeterminate ? root.progress : 0.5;
|
||||
|
||||
x: background_layer.x;
|
||||
y: background_layer.y;
|
||||
viewbox_width: 100;
|
||||
viewbox_height: 100;
|
||||
width: background_layer.width;
|
||||
height: background_layer.height;
|
||||
fill: MaterialPalette.primary;
|
||||
visible: self.progress > 0 && self.progress < 1;
|
||||
|
||||
MoveTo {
|
||||
x: !root.indeterminate ? track.start_x : track.start_x - track.radius * sin(-track.start * 360deg);
|
||||
y: !root.indeterminate ? 0 : track.start_y - track.radius * cos(-track.start * 360deg);
|
||||
}
|
||||
|
||||
ArcTo {
|
||||
radius_x: 1;
|
||||
radius_y: 1;
|
||||
x: !root.indeterminate ? track.start_x : track.start_x - track.inner-radius * sin(-track.start * 360deg);
|
||||
y: !root.indeterminate ? root.bar_height * (track.viewbox_height / track.height) : track.start_y - track.inner_radius * cos(-track.start * 360deg);
|
||||
}
|
||||
|
||||
ArcTo {
|
||||
radius_x: track.inner_radius;
|
||||
radius_y: track.inner_radius;
|
||||
x: start_x - track.inner_radius * sin(-(track.start + track.progress) * 360deg);
|
||||
y: start_y - track.inner_radius * cos(-(track.start + track.progress) * 360deg);
|
||||
sweep: track.progress > 0;
|
||||
large-arc: track.progress > 0.5;
|
||||
}
|
||||
|
||||
ArcTo {
|
||||
radius_x: 1;
|
||||
radius_y: 1;
|
||||
x: start_x - radius * sin(-(track.start + track.progress) * 360deg);
|
||||
y: start_y - radius * cos(-(track.start + track.progress) * 360deg);
|
||||
}
|
||||
|
||||
ArcTo {
|
||||
radius_x: radius;
|
||||
radius_y: radius;
|
||||
x: start_x - radius * sin(-track.start * 360deg);
|
||||
y: start_y - radius * cos(-track.start * 360deg);
|
||||
sweep: track.progress < 0;
|
||||
large-arc: track.progress > 0.5;
|
||||
}
|
||||
|
||||
LineTo {
|
||||
x: start_x - radius * sin(-track.start * 360deg);
|
||||
y: start_y - radius * cos(-track.start * 360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
99
material-1.0/ui/components/radio_button.slint
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { ListTile } from "./list.slint";
|
||||
import { ListView } from "./list_view.slint";
|
||||
|
||||
export component RadioButton {
|
||||
in property <bool> checked;
|
||||
in property <bool> enabled;
|
||||
|
||||
callback clicked();
|
||||
|
||||
min_width: MaterialStyleMetrics.size_40;
|
||||
min_height: self.min_width;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-checkable: true;
|
||||
accessible-checked <=> root.checked;
|
||||
accessible-role: checkbox;
|
||||
accessible-action-default => { state_area.clicked(); }
|
||||
forward_focus: state_area;
|
||||
|
||||
state_area := StateLayerArea {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
color: MaterialPalette.on_surface;
|
||||
|
||||
border := Rectangle {
|
||||
width: MaterialStyleMetrics.size_20;
|
||||
height: self.width;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
border_color: MaterialPalette.on_surface_variant;
|
||||
border_width: MaterialStyleMetrics.size_2;
|
||||
|
||||
indicator := Rectangle {
|
||||
width: parent.width / 2;
|
||||
height: self.width;
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
background: MaterialPalette.primary;
|
||||
opacity: !root.checked ? 0 : 1;
|
||||
|
||||
animate opacity {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
animate border_color {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
border.border_color: MaterialPalette.on_surface;
|
||||
border.opacity: MaterialPalette.disable_opacity;
|
||||
indicator.background: MaterialPalette.on_surface;
|
||||
indicator.opacity: root.checked ? MaterialPalette.disable_opacity : 0;
|
||||
}
|
||||
highlighted when !root.checked && (state_area.pressed || state_area.has_focus || state_area.has_hover) : {
|
||||
border.border_color: MaterialPalette.on_surface;
|
||||
}
|
||||
checked when root.checked : {
|
||||
border.border_color: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component RadioButtonTile inherits ListTile {
|
||||
in_out property <bool> checked <=> radio_button.checked;
|
||||
|
||||
callback radio_button_clicked();
|
||||
|
||||
Rectangle {
|
||||
horizontal_stretch: 0;
|
||||
|
||||
radio_button := RadioButton {
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
root.radio_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
radio_button.clicked();
|
||||
}
|
||||
}
|
||||
166
material-1.0/ui/components/scroll_view.slint
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component ScrollBar {
|
||||
in property <bool> enabled;
|
||||
out property <bool> has_hover: touch_area.has_hover;
|
||||
in_out property <bool> horizontal;
|
||||
in_out property <length> maximum;
|
||||
in_out property <length> page_size;
|
||||
in_out property <length> value;
|
||||
in property <ScrollBarPolicy> policy: ScrollBarPolicy.as_needed;
|
||||
|
||||
property <length> track_size: root.horizontal ? root.width - 2 * root.offset : root.height - 2 * offset;
|
||||
property <length> step_size: 10px;
|
||||
property <length> offset: 2px;
|
||||
property <float> state_opacity: 40%;
|
||||
|
||||
visible: (self.policy == ScrollBarPolicy.always_on) || (self.policy == ScrollBarPolicy.as_needed && self.maximum > 0);
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
|
||||
thumb := Rectangle {
|
||||
width: !root.horizontal ? parent.width : root.maximum <= 0phx ? 0phx : max(min(32px, root.width), root.track-size * root.page-size / (root.maximum + root.page-size));
|
||||
height: root.horizontal ? parent.height : root.maximum <= 0phx ? 0phx : max(min(32px, root.height), root.track-size * (root.page-size / (root.maximum + root.page-size)));
|
||||
x: !root.horizontal ? (parent.width - self.width) / 2 : root.offset + (root.track-size - thumb.width) * (-root.value / root.maximum);
|
||||
y: root.horizontal ? (parent.height - self.height) / 2 : root.offset + (root.track-size - thumb.height) * (-root.value / root.maximum);
|
||||
border-radius: (root.horizontal ? self.height : self.width) / 2;
|
||||
background: MaterialPalette.on_background;
|
||||
opacity: hide_timer.running || touch_area.has_hover || touch_area.pressed ? root.state_opacity : 0;
|
||||
|
||||
animate opacity { duration: 150ms; }
|
||||
}
|
||||
}
|
||||
|
||||
touch_area := TouchArea {
|
||||
property <length> pressed_value;
|
||||
|
||||
width: parent.width;
|
||||
height: parent.height;
|
||||
|
||||
pointer-event(event) => {
|
||||
if (event.button == PointerEventButton.left && event.kind == PointerEventKind.down) {
|
||||
self.pressed-value = -root.value;
|
||||
}
|
||||
}
|
||||
|
||||
moved => {
|
||||
if (self.enabled && self.pressed) {
|
||||
root.value = -max(0px, min(root.maximum, self.pressed-value + (
|
||||
root.horizontal ? (touch-area.mouse-x - touch-area.pressed-x) * (root.maximum / (root.track-size - thumb.width))
|
||||
: (touch-area.mouse-y - touch-area.pressed-y) * (root.maximum / (root.track-size - thumb.height))
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
scroll-event(event) => {
|
||||
if (root.horizontal && event.delta-x != 0) {
|
||||
root.value = max(-root.maximum, min(0px, root.value + event.delta-x));
|
||||
return accept;
|
||||
} else if (!root.horizontal && event.delta-y != 0) {
|
||||
root.value = max(-root.maximum, min(0px, root.value + event.delta-y));
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
|
||||
changed has_hover => {
|
||||
if !self.has_hover && !hide_timer.running {
|
||||
hide_timer.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
changed pressed => {
|
||||
if self.pressed && hide_timer.running {
|
||||
hide_timer.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed value => {
|
||||
if !self.has_hover && !hide_timer.running {
|
||||
hide_timer.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
hide_timer := Timer {
|
||||
interval: 1.5s;
|
||||
running: false;
|
||||
|
||||
triggered => {
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
pressed when touch_area.pressed : {
|
||||
root.state_opacity: 80%;
|
||||
}
|
||||
hover when root.has_hover : {
|
||||
root.state_opacity: 70%;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component ScrollView {
|
||||
in property <bool> enabled: true;
|
||||
out property <length> visible_width <=> flickable.width;
|
||||
out property <length> visible_height <=> flickable.height;
|
||||
in_out property <length> viewport_width <=> flickable.viewport_width;
|
||||
in_out property <length> viewport_height <=> flickable.viewport_height;
|
||||
in_out property <length> viewport_x <=> flickable.viewport_x;
|
||||
in_out property <length> viewport_y <=> flickable.viewport_y;
|
||||
in property <ScrollBarPolicy> vertical_scrollbar_policy <=> vertical_bar.policy;
|
||||
in property <ScrollBarPolicy> horizontal_scrollbar_policy <=> horizontal_bar.policy;
|
||||
// FIXME: remove. This property is currently set by the ListView and is used by the native style to draw the scrollbar differently when it has focus
|
||||
in_out property <bool> has_focus;
|
||||
|
||||
callback scrolled <=> flickable.flicked;
|
||||
|
||||
property <length> scroll_bar_size: 8px;
|
||||
property <length> scroll_bar_padding: 1px;
|
||||
|
||||
min_height: 50px;
|
||||
min_width: 50px;
|
||||
horizontal_stretch: 1;
|
||||
vertical_stretch: 1;
|
||||
preferred_height: 100%;
|
||||
preferred_width: 100%;
|
||||
|
||||
flickable := Flickable {
|
||||
x: 0;
|
||||
y: 0;
|
||||
viewport_y <=> vertical_bar.value;
|
||||
viewport_x <=> horizontal_bar.value;
|
||||
width: root.width - (root.vertical_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size - 2 * root.scroll_bar_padding : 0);
|
||||
height: root.height - (root.horizontal_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size : 0);
|
||||
|
||||
@children
|
||||
}
|
||||
|
||||
vertical_bar := ScrollBar {
|
||||
enabled: root.enabled;
|
||||
x: flickable.width + root.scroll_bar_padding;
|
||||
y: 0;
|
||||
width: root.visible ? root.scroll_bar_size : 0;
|
||||
height: root.vertical_scrollbar_policy != ScrollBarPolicy.always_off ? parent.height - horizontal_bar.height : parent.height;
|
||||
horizontal: false;
|
||||
maximum: flickable.viewport_height - flickable.height;
|
||||
page-size: flickable.height;
|
||||
}
|
||||
|
||||
horizontal_bar := ScrollBar {
|
||||
enabled: root.enabled;
|
||||
width: vertical_bar.visible ? parent.width - vertical_bar.width : parent.width;
|
||||
height: root.horizontal_scrollbar_policy != ScrollBarPolicy.always_off ? root.scroll_bar_size : 0;
|
||||
x: 0;
|
||||
y: flickable.height + root.scroll_bar_padding;
|
||||
horizontal: true;
|
||||
maximum: flickable.viewport_width - flickable.width;
|
||||
page_size: flickable.width;
|
||||
}
|
||||
}
|
||||
317
material-1.0/ui/components/search_bar.slint
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { ListItem } from "../items/list_item.slint";
|
||||
import { Avatar, ListTile } from "./list.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { HorizontalDivider } from "./divider.slint";
|
||||
import { ListView } from "./list_view.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
|
||||
component SearchTile inherits ListTile {
|
||||
in property <image> action_button_icon;
|
||||
|
||||
callback action_button_clicked();
|
||||
|
||||
if root.action_button_icon.width > 0 && root.action_button_icon.height > 0 : IconButton {
|
||||
icon: root.action_button_icon;
|
||||
|
||||
clicked => {
|
||||
root.action_button_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SearchIcon {
|
||||
in property <image> icon;
|
||||
in property <color> color;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
padding_left: MaterialStyleMetrics.padding_8;
|
||||
padding_right: self.padding_left;
|
||||
|
||||
Icon {
|
||||
source: root.icon;
|
||||
colorize: root.color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component SearchTextInput {
|
||||
in_out property <string> text <=> text_input.text;
|
||||
in property <string> placeholder_text;
|
||||
property <length> computed_x;
|
||||
|
||||
callback accepted(text: string);
|
||||
callback edited(text: string);
|
||||
callback key_pressed(event: KeyEvent) -> EventResult;
|
||||
callback key_released(event: KeyEvent) -> EventResult;
|
||||
|
||||
forward_focus: text_input;
|
||||
horizontal_stretch: 1;
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
|
||||
text_input := TextInput {
|
||||
x: min(0px, max(parent.width - self.width - self.text_cursor_width, root.computed_x));
|
||||
width: max(parent.width - self.text-cursor-width, self.preferred-width);
|
||||
height: 100%;
|
||||
font_size: MaterialTypography.body_large.font_size;
|
||||
font_weight: MaterialTypography.body_large.font_weight;
|
||||
vertical_alignment: center;
|
||||
single_line: true;
|
||||
color: MaterialPalette.on_surface;
|
||||
selection_background_color: MaterialPalette.secondary_container;
|
||||
selection_foreground_color: self.color;
|
||||
// Disable TextInput's built-in accessibility support as the component takes care of that.
|
||||
accessible-role: none;
|
||||
|
||||
cursor_position_changed(cursor_position) => {
|
||||
if cursor_position.x + root.computed_x < 0 {
|
||||
root.computed_x = - cursor_position.x;
|
||||
} else if cursor-position.x + root.computed_x > parent.width - self.text-cursor-width {
|
||||
root.computed_x = parent.width - cursor_position.x - self.text-cursor-width;
|
||||
}
|
||||
}
|
||||
|
||||
accepted => {
|
||||
root.accepted(self.text);
|
||||
}
|
||||
|
||||
edited => {
|
||||
root.edited(self.text);
|
||||
}
|
||||
|
||||
key_pressed(event) => {
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
root.key_released(event)
|
||||
}
|
||||
|
||||
init => {
|
||||
if root.text.character_count > 0 {
|
||||
self.set_selection_offsets(root.text.character_count, root.text.character_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
if root.text == "" && root.placeholder_text != "" : MaterialText {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text: root.placeholder_text;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
vertical_alignment: center;
|
||||
style: MaterialTypography.body_large;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component SearchBar {
|
||||
in property <image> leading_icon: Icons.menu;
|
||||
in property <image> trailing_icon;
|
||||
in property <image> avatar_icon;
|
||||
in property <color> avatar_background: #00000000;
|
||||
in property <string> placeholder_text;
|
||||
in property <string> empty_text;
|
||||
in_out property <string> text;
|
||||
in_out property <int> current_index;
|
||||
in property <[ListItem]> items;
|
||||
|
||||
callback accepted(text: string);
|
||||
callback edited(text: string);
|
||||
callback action_button_clicked(index: int);
|
||||
callback key_pressed(event: KeyEvent) -> EventResult;
|
||||
callback key_released(event: KeyEvent) -> EventResult;
|
||||
|
||||
property <color> color: MaterialPalette.on_surface_variant;
|
||||
property <length> item_height: MaterialStyleMetrics.size_72;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_56, layout.min_height);
|
||||
forward_focus: state_layer;
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface_container_high;
|
||||
border_radius: MaterialStyleMetrics.border_radius_28;
|
||||
|
||||
state_layer := StateLayerArea {
|
||||
border_radius: parent.border_radius;
|
||||
color: root.color;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_4;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
SearchIcon {
|
||||
icon: root.leading_icon;
|
||||
color: root.color;
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
text: root.text;
|
||||
font_size: MaterialTypography.body_large.font_size;
|
||||
font_weight: MaterialTypography.body_large.font_weight;
|
||||
vertical_alignment: center;
|
||||
color: MaterialPalette.on_surface;
|
||||
|
||||
states [
|
||||
placeholder when root.text == "" : {
|
||||
text: root.placeholder_text;
|
||||
color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
SearchIcon {
|
||||
icon: root.trailing_icon;
|
||||
color: root.color;
|
||||
}
|
||||
|
||||
if (root.avatar_icon.width > 0 && root.avatar_icon.height > 0) || root.avatar_background != #00000000 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
Avatar {
|
||||
image: root.avatar_icon;
|
||||
background: root.avatar_background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changed has_focus => {
|
||||
if self.has_focus {
|
||||
popup.show();
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
popup.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popup := PopupWindow {
|
||||
x: 0;
|
||||
y: 0;
|
||||
width: root.width;
|
||||
height: root.height + clamp(root.item_height * root.items.length, 3 * root.item_height, 6 * root.item_height);
|
||||
close_policy: close_on_click_outside;
|
||||
forward-focus: popup_input;
|
||||
|
||||
background_layer := Rectangle {
|
||||
x: 0;
|
||||
y: 0;
|
||||
width: 100%;
|
||||
height: root.height;
|
||||
background: MaterialPalette.surface_container_high;
|
||||
border_radius: MaterialStyleMetrics.border_radius_28;
|
||||
clip: true;
|
||||
|
||||
VerticalLayout {
|
||||
HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_4;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
IconButton {
|
||||
icon: Icons.arrow_back;
|
||||
|
||||
clicked => {
|
||||
popup.close();
|
||||
}
|
||||
}
|
||||
|
||||
popup_input := SearchTextInput {
|
||||
placeholder_text: root.placeholder_text;
|
||||
text: root.text;
|
||||
|
||||
accepted => {
|
||||
root.accepted(self.text);
|
||||
}
|
||||
|
||||
edited => {
|
||||
root.text = self.text;
|
||||
root.edited(self.text);
|
||||
}
|
||||
|
||||
key_pressed(event) => {
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
root.key_released(event)
|
||||
}
|
||||
|
||||
changed text => {
|
||||
root.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
icon: Icons.close;
|
||||
|
||||
clicked => {
|
||||
root.text = "";
|
||||
popup.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider {}
|
||||
|
||||
if root.items.length == 0 : MaterialText {
|
||||
text: root.empty_text;
|
||||
color: root.color;
|
||||
horizontal_alignment: center;
|
||||
style: MaterialTypography.label_small;
|
||||
}
|
||||
|
||||
if root.items.length == 0 : Rectangle {}
|
||||
|
||||
if root.items.length > 0 : ListView {
|
||||
horizontal_stretch: 1;
|
||||
|
||||
for item[index] in root.items : SearchTile {
|
||||
text: item.text;
|
||||
supporting_text: item.supporting_text;
|
||||
avatar_icon: item.avatar_icon;
|
||||
avatar_text: item.avatar_text;
|
||||
avatar_background: item.avatar_background;
|
||||
avatar_foreground: item.avatar_foreground;
|
||||
action_button_icon: item.action_button_icon;
|
||||
|
||||
clicked => {
|
||||
root.text = self.text;
|
||||
popup.close();
|
||||
}
|
||||
|
||||
action_button_clicked => {
|
||||
root.action_button_clicked(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animate height {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
background_layer.height = popup.height;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
material-1.0/ui/components/segmented_button.slint
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { BaseButton } from "base_button.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
|
||||
export struct SegmentedItem {
|
||||
icon: image,
|
||||
text: string
|
||||
}
|
||||
|
||||
component SegmentedItemTemplate {
|
||||
in property <image> icon;
|
||||
in property <string> text;
|
||||
in property <int> index;
|
||||
in property <bool> selected;
|
||||
in property <bool> last;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: list-item;
|
||||
accessible-label: root.text;
|
||||
accessible-item-selectable: true;
|
||||
accessible-item-selected: root.selected;
|
||||
accessible-item-index: root.index;
|
||||
|
||||
if root.selected : Rectangle {
|
||||
background: MaterialPalette.secondary_container;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
min_layout_width: 0;
|
||||
min_layout_height: 0;
|
||||
color: MaterialPalette.on_surface;
|
||||
button_vertical_padding: MaterialStyleMetrics.padding_10;
|
||||
button_horizontal_padding: MaterialStyleMetrics.padding_24;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
icon: root.icon;
|
||||
text: root.text;
|
||||
|
||||
|
||||
animate width { duration: MaterialAnimations.standard_accelerate_duration; easing: MaterialAnimations.standard_easing; }
|
||||
}
|
||||
|
||||
if !root.last : Rectangle {
|
||||
x: parent.width - self.width;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background: MaterialPalette.outline;
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
base.icon: Icons.check;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component SegmentedButton {
|
||||
in property <[SegmentedItem]> items;
|
||||
in_out property <int> current_index;
|
||||
|
||||
callback index_changed(index: int);
|
||||
|
||||
min_width: max(2 * MaterialStyleMetrics.size_40, layout.min_width);
|
||||
min_height: max(MaterialStyleMetrics.size_40, layout.min_height);
|
||||
|
||||
accessible-role: list;
|
||||
accessible-item-count: root.items.length;
|
||||
|
||||
Rectangle {
|
||||
clip: true;
|
||||
border_width: 1px;
|
||||
border_radius: self.height / 2;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
for item[index] in root.items : SegmentedItemTemplate {
|
||||
icon: item.icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
last: index == root.items.length - 1;
|
||||
selected: index == root.current_index;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function select(index: int) {
|
||||
if index < 0 || index >= root.items.length {
|
||||
return;
|
||||
}
|
||||
|
||||
root.current_index = index;
|
||||
root.index_changed(index);
|
||||
}
|
||||
}
|
||||
136
material-1.0/ui/components/slider.slint
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { StateLayer } from "./state_layer.slint";
|
||||
|
||||
export component Slider {
|
||||
in property <bool> enabled;
|
||||
in_out property <float> value;
|
||||
in property <float> minimum;
|
||||
in property <int> stop_count: 0;
|
||||
in property <float> maximum: 100;
|
||||
|
||||
callback released(value: float);
|
||||
callback value_changed(value: float);
|
||||
|
||||
property <int> steps: root.stop_count > 0 ? (root.maximum - root.minimum) / root.stop_count : 1;
|
||||
|
||||
min_height: 20px;
|
||||
accessible-role: slider;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-value: root.value;
|
||||
accessible-value-minimum: root.minimum;
|
||||
accessible-value-maximum: root.maximum;
|
||||
accessible-value-step: min(steps, (root.maximum - root.minimum) / 100);
|
||||
forward-focus: focus_scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
touch_area := TouchArea {
|
||||
track := Rectangle {
|
||||
x: 0;
|
||||
height: MaterialStyleMetrics.size_16;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
border_radius: self.height / 2;
|
||||
|
||||
Rectangle {
|
||||
x: 0;
|
||||
width: state_layer.x - MaterialStyleMetrics.size_6;
|
||||
border-top-left-radius: track.border_radius;
|
||||
border-bottom-left-radius: track.border_radius;
|
||||
border-top-right-radius: MaterialStyleMetrics.size_1;
|
||||
border-bottom-right-radius: MaterialStyleMetrics.size_1;
|
||||
background: thumb.background;
|
||||
}
|
||||
|
||||
if root.stop_count > 0: Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
for i in (root.maximum - root.minimum) / root.steps: Rectangle {
|
||||
x: root.value_to_length(track.width, (i + 1) * root.steps) - self.width / 2;
|
||||
height: MaterialStyleMetrics.size_4;
|
||||
width: self.height;
|
||||
border_radius: self.height / 2;
|
||||
background: MaterialPalette.outline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state_layer := Rectangle {
|
||||
x: root.value_to_length(track.width, root.value) - (self.width) / 2;
|
||||
width: 0;
|
||||
height: track.height + (MaterialStyleMetrics.size_14 * 2);
|
||||
StateLayer {
|
||||
height: track.height + (MaterialStyleMetrics.size_16 * 2);
|
||||
width: thumb.width + (MaterialStyleMetrics.size_2 * 2);
|
||||
border_radius: self.width / 2;
|
||||
pressed: touch_area.pressed;
|
||||
has_hover: touch_area.has_hover;
|
||||
has_focus: focus_scope.has_focus;
|
||||
enabled: root.enabled;
|
||||
background: thumb.background;
|
||||
display_background: thumb_area.pressed || thumb_area.has_hover || focus_scope.has_focus;
|
||||
|
||||
thumb_area := TouchArea {
|
||||
height: track.height + (MaterialStyleMetrics.size_14 * 2);
|
||||
width: self.height;
|
||||
thumb := Rectangle {
|
||||
width: MaterialStyleMetrics.size_4;
|
||||
border_radius: self.width / 2;
|
||||
background: MaterialPalette.primary;
|
||||
}
|
||||
|
||||
moved => {
|
||||
root.set_value(root.length_to_value(state_layer.x + self.mouse_x, track.width));
|
||||
}
|
||||
|
||||
pointer_event(event) => {
|
||||
if event.kind == PointerEventKind.up {
|
||||
root.released(root.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pointer_event(event) => {
|
||||
if event.kind == PointerEventKind.down && event.button == PointerEventButton.left {
|
||||
root.set_value(root.length_to_value(self.mouse_x, track.width));
|
||||
}
|
||||
if event.kind == PointerEventKind.up {
|
||||
root.released(root.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
key_pressed(event) => {
|
||||
if event.text == Key.LeftArrow {
|
||||
root.set_value(root.value - root.steps);
|
||||
return accept;
|
||||
}
|
||||
if event.text == Key.RightArrow {
|
||||
root.set_value(root.value + root.steps);
|
||||
return accept;
|
||||
}
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
changed value => {
|
||||
root.value_changed(root.value);
|
||||
}
|
||||
|
||||
pure function value_to_length(width: length, value: float) -> length {
|
||||
clamp(width * (value - root.minimum) / (root.maximum - root.minimum), 0, width)
|
||||
}
|
||||
|
||||
pure function length_to_value(x: length, width: length) -> float {
|
||||
x * (root.maximum - root.minimum) / width
|
||||
}
|
||||
|
||||
function set_value(value: float) {
|
||||
root.value = clamp(round(value / root.steps) * root.steps, root.minimum, root.maximum)
|
||||
}
|
||||
}
|
||||
88
material-1.0/ui/components/snack_bar.slint
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { Elevation } from "elevation.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { TextButton } from "./text_button.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
|
||||
export component SnackBar inherits PopupWindow {
|
||||
in property <string> text;
|
||||
in property <string> action_text;
|
||||
in property <bool> has_close_button;
|
||||
|
||||
close_policy: no_auto_close;
|
||||
|
||||
callback action_clicked();
|
||||
|
||||
snack_bar := Elevation {
|
||||
x: (parent.width - self.width) / 2;
|
||||
y: parent.height - self.height;
|
||||
width: MaterialStyleMetrics.size_344;
|
||||
level: 3;
|
||||
|
||||
background_layer := Rectangle {
|
||||
background: MaterialPalette.inverse_surface;
|
||||
border_radius: MaterialStyleMetrics.border_radius_4;
|
||||
|
||||
HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_16;
|
||||
padding_right: self.padding_left;
|
||||
padding_top: MaterialStyleMetrics.padding_10;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
MaterialText {
|
||||
text: root.text;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: left;
|
||||
wrap: word_wrap;
|
||||
style: MaterialTypography.body_medium;
|
||||
color: MaterialPalette.inverse_on_surface;
|
||||
}
|
||||
|
||||
if root.action_text != "" : TextButton {
|
||||
text: root.action_text;
|
||||
inverse: true;
|
||||
|
||||
clicked => {
|
||||
root.raise_action();
|
||||
}
|
||||
}
|
||||
|
||||
if root.has_close_button : IconButton {
|
||||
icon: Icons.close;
|
||||
inverse: true;
|
||||
|
||||
clicked => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animate y, height {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
snack_bar.y = root.height - snack_bar.height - MaterialStyleMetrics.padding_30;
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
function raise_action() {
|
||||
root.action_clicked();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
171
material-1.0/ui/components/state_layer.slint
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { ToolTip } from "./tooltip.slint";
|
||||
import { MaterialWindowAdapter } from "./material_window.slint";
|
||||
|
||||
export component Ripple {
|
||||
in property <length> border_radius;
|
||||
in property <length> pressed_x;
|
||||
in property <length> pressed_y;
|
||||
in property <color> color;
|
||||
in property <bool> clip_ripple: true;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: root.border_radius;
|
||||
clip: root.clip_ripple;
|
||||
|
||||
ripple := Rectangle {
|
||||
x: root.pressed_x - self.width / 2;
|
||||
y: root.pressed_y - self.height / 2;
|
||||
height: self.width;
|
||||
border_radius: self.width / 2;
|
||||
opacity: MaterialPalette.state_layer_opacity_press;
|
||||
background: root.color;
|
||||
|
||||
init => {
|
||||
self.width = root.width * 2 * 1.4142;
|
||||
}
|
||||
|
||||
animate width { duration: MaterialAnimations.ripple_duration; easing: MaterialAnimations.ripple_easing; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export component StateLayer {
|
||||
in property <color> background;
|
||||
in property <length> border_radius;
|
||||
in property <bool> display_background: true;
|
||||
in property <bool> enabled;
|
||||
in property <bool> pressed;
|
||||
in property <bool> has_focus;
|
||||
in property <bool> has_hover;
|
||||
|
||||
property <float> state_layer_opacity;
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: root.state_layer_opacity;
|
||||
background: root.background;
|
||||
border_radius: root.border_radius;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
clip: true;
|
||||
border_radius: root.border_radius;
|
||||
|
||||
@children
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled && root.display_background : {
|
||||
root.state_layer_opacity: MaterialPalette.state_layer_opacity_focus;
|
||||
}
|
||||
focus when root.enabled && root.has_focus : {
|
||||
root.state_layer_opacity: MaterialPalette.state_layer_opacity_focus;
|
||||
}
|
||||
pressed when root.enabled && root.pressed : {
|
||||
root.state_layer_opacity: MaterialPalette.state_layer_opacity_press;
|
||||
}
|
||||
hover when root.enabled && root.has_hover && !MaterialWindowAdapter.disable_hover : {
|
||||
root.state_layer_opacity: MaterialPalette.state_layer_opacity_hover;
|
||||
}
|
||||
]
|
||||
|
||||
animate state_layer_opacity { duration: MaterialAnimations.opacity_duration; easing: MaterialAnimations.opacity_easing; }
|
||||
}
|
||||
|
||||
export component FocusTouchArea inherits TouchArea {
|
||||
out property <bool> has_focus <=> focus_scope.has_focus;
|
||||
out property <bool> enter_pressed;
|
||||
in property <string> tooltip;
|
||||
in property <length> tooltip_offset;
|
||||
|
||||
callback key_pressed(KeyEvent) -> EventResult;
|
||||
|
||||
forward_focus: focus_scope;
|
||||
|
||||
focus_scope := FocusScope {
|
||||
x: 0;
|
||||
width: 0px;
|
||||
enabled: root.enabled;
|
||||
|
||||
key_pressed(event) => {
|
||||
if !root.enabled {
|
||||
return reject;
|
||||
}
|
||||
|
||||
if (event.text == " " || event.text == "\n") && !root.enter_pressed {
|
||||
root.enter_pressed = true;
|
||||
root.clicked();
|
||||
return accept;
|
||||
}
|
||||
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
if !root.enabled {
|
||||
return reject;
|
||||
}
|
||||
|
||||
if (event.text == " " || event.text == "\n") && root.enter_pressed {
|
||||
root.enter_pressed = false;
|
||||
return accept;
|
||||
}
|
||||
|
||||
reject
|
||||
}
|
||||
}
|
||||
|
||||
@children
|
||||
|
||||
|
||||
if root.tooltip != "" && root.has_hover && !MaterialWindowAdapter.disable_hover && !root.pressed : ToolTip {
|
||||
y: root.tooltip_offset;
|
||||
text: root.tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
export component StateLayerArea inherits FocusTouchArea {
|
||||
in property <color> color;
|
||||
in property <length> border_radius;
|
||||
in property <bool> transparent_background;
|
||||
in property <bool> display_background: true;
|
||||
in property <bool> clip_ripple: true;
|
||||
|
||||
property <bool> popup_open;
|
||||
property <bool> has_h: root.mouse-x > 0 && root.mouse-x < root.width && root.mouse-y > 0 && root.mouse-y < root.height;
|
||||
|
||||
if !root.enabled || (root.enabled && (root.has_focus || root.pressed || root.enter_pressed || root.has_hover)) : StateLayer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: root.transparent_background ? transparent : root.color;
|
||||
border_radius: root.border_radius;
|
||||
enabled: root.enabled;
|
||||
pressed: root.pressed || root.enter_pressed;
|
||||
has_focus: root.has_focus;
|
||||
has_hover: root.has_hover;
|
||||
display_background: root.display_background;
|
||||
}
|
||||
|
||||
// ripple
|
||||
if root.enabled && (root.pressed || root.enter_pressed) : Ripple {
|
||||
pressed_x: root.pressed_x;
|
||||
pressed_y: root.pressed_y;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: root.border_radius;
|
||||
color: root.color;
|
||||
clip_ripple: root.clip_ripple;
|
||||
}
|
||||
|
||||
@children
|
||||
}
|
||||
151
material-1.0/ui/components/switch.slint
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { FocusTouchArea, StateLayer, Ripple } from "./state_layer.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
export component Switch {
|
||||
in_out property <bool> checked;
|
||||
in property <bool> enabled: true;
|
||||
in property <string> tooltip <=> touch_area.tooltip;
|
||||
in property <image> on_icon;
|
||||
in property <image> off_icon;
|
||||
|
||||
property <bool> has_icon: (root.checked && root.on_icon.width > 0 && root.on_icon.height > 0) || (root.off_icon.width > 0 && root.off_icon.height > 0);
|
||||
property <color> foreground: root.checked ? MaterialPalette.on_primary_container : MaterialPalette.surface_container_highest;
|
||||
|
||||
callback checked_state_changed(checked: bool);
|
||||
|
||||
min_width: MaterialStyleMetrics.size_52;
|
||||
min_height: MaterialStyleMetrics.size_32;
|
||||
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-checkable: true;
|
||||
accessible-checked <=> root.checked;
|
||||
accessible-role: checkbox;
|
||||
accessible-action-default => { touch_area.clicked(); }
|
||||
forward_focus: touch_area;
|
||||
|
||||
touch_area := FocusTouchArea {
|
||||
enabled: root.enabled;
|
||||
|
||||
background_layer := Rectangle {
|
||||
background: root.checked ? MaterialPalette.primary : MaterialPalette.surface_container_highest;
|
||||
border_radius: self.height / 2;
|
||||
border_width: root.checked ? 0 : 2px;
|
||||
border_color: MaterialPalette.outline;
|
||||
|
||||
state_layer := StateLayer {
|
||||
x: indicator.x + (indicator.width - self.width) / 2;
|
||||
width: self.height;
|
||||
height: root.height + MaterialStyleMetrics.size_8;
|
||||
border_radius: self.height / 2;
|
||||
has_hover: touch_area.has_hover;
|
||||
has_focus: touch_area.has_focus;
|
||||
pressed: touch_area.pressed;
|
||||
enabled: root.enabled;
|
||||
|
||||
// ripple
|
||||
if root.enabled && (touch_area.pressed || touch_area.enter_pressed) : Ripple {
|
||||
pressed_x: touch_area.pressed_x;
|
||||
pressed_y: touch_area.pressed_y;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: state_layer.border_radius;
|
||||
color: state_layer.background;
|
||||
clip_ripple: true;
|
||||
}
|
||||
}
|
||||
|
||||
indicator := Rectangle {
|
||||
property <length> indicator-padding: root.checked || root.has_icon ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_8;
|
||||
|
||||
property <length> default_height: background_layer.height - 2 * self.indicator-padding;
|
||||
|
||||
x: self.indicator-padding - (self.height - self.default_height) / 2;
|
||||
width: self.height;
|
||||
height: self.default_height;
|
||||
border_radius: self.height / 2;
|
||||
background: root.checked ? MaterialPalette.primary_container : MaterialPalette.outline;
|
||||
|
||||
if root.has_icon : Icon {
|
||||
source: root.checked ? root.on_icon : root.off_icon;
|
||||
colorize: root.foreground;
|
||||
|
||||
animate colorize {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
animate height {
|
||||
duration: MaterialAnimations.emphasized_accelerate_duration;
|
||||
easing: MaterialAnimations.emphasized_easing;
|
||||
}
|
||||
|
||||
animate background {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.checked : {
|
||||
x: background_layer.width - self.indicator-padding - self.width;
|
||||
|
||||
in {
|
||||
animate x {
|
||||
duration: MaterialAnimations.emphasized_accelerate_duration;
|
||||
easing: MaterialAnimations.emphasized_easing;
|
||||
}
|
||||
}
|
||||
|
||||
out {
|
||||
animate x {
|
||||
duration: MaterialAnimations.emphasized_accelerate_duration;
|
||||
easing: MaterialAnimations.emphasized_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
animate background, border_color {
|
||||
duration: MaterialAnimations.opacity_duration;
|
||||
easing: MaterialAnimations.opacity_easing;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
changed checked => {
|
||||
root.checked_state_changed(root.checked);
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
state_layer.display_background: false;
|
||||
indicator.background: root.checked ? MaterialPalette.surface : MaterialPalette.outline.with_alpha(MaterialPalette.disable_opacity);
|
||||
background_layer.background: (root.checked ? MaterialPalette.on_surface : MaterialPalette.surface_variant).with_alpha(MaterialPalette.state_layer_opacity_disabled);
|
||||
background_layer.border_color: MaterialPalette.on_surface.with_alpha(MaterialPalette.state_layer_opacity_disabled);
|
||||
foreground: root.checked ? MaterialPalette.on_surface.with_alpha(MaterialPalette.disable_opacity) : MaterialPalette.surface_container_highest;
|
||||
}
|
||||
pressed when touch_area.pressed: {
|
||||
state_layer.background: indicator.background;
|
||||
indicator.height: background_layer.height - 2px;
|
||||
indicator.background: root.checked ? MaterialPalette.primary_container : MaterialPalette.on_surface_variant;
|
||||
}
|
||||
hover when touch_area.has_hover: {
|
||||
state_layer.background: indicator.background;
|
||||
}
|
||||
]
|
||||
}
|
||||
210
material-1.0/ui/components/tab_bar.slint
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { NavigationItem } from "../items/navigation_item.slint";
|
||||
import { BaseNavigationItemTemplate, BaseNavigation } from "./base_navigation.slint";
|
||||
import { StateLayerArea } from "./state_layer.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
component TabItemTemplate inherits BaseNavigationItemTemplate {
|
||||
out property <length> label_width: label.width;
|
||||
|
||||
property <bool> has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0;
|
||||
|
||||
StateLayerArea {
|
||||
color: MaterialPalette.primary;
|
||||
|
||||
VerticalLayout {
|
||||
padding_top: MaterialStyleMetrics.padding_10;
|
||||
padding_bottom: MaterialStyleMetrics.padding_8;
|
||||
spacing: MaterialStyleMetrics.spacing_2;
|
||||
|
||||
if root.icon.width > 0 && root.icon.height > 0 : HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
Icon {
|
||||
source: root.icon;
|
||||
colorize: label.color;
|
||||
|
||||
states [
|
||||
selected when root.selected && root.has_selected_icon : {
|
||||
source: root.selected_icon;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
label := MaterialText {
|
||||
text: root.text;
|
||||
style: MaterialTypography.title_small;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
label.color: MaterialPalette.primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
component SecondaryTabItemTemplate inherits BaseNavigationItemTemplate {
|
||||
property <bool> has_selected_icon: root.selected_icon.width > 0 || root.selected_icon.height > 0;
|
||||
|
||||
StateLayerArea {
|
||||
color: label.color;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
|
||||
if root.icon.width > 0 && root.icon.height > 0 : VerticalLayout {
|
||||
alignment: center;
|
||||
|
||||
Icon {
|
||||
source: root.icon;
|
||||
colorize: label.color;
|
||||
|
||||
states [
|
||||
selected when root.selected && root.has_selected_icon : {
|
||||
source: root.selected_icon;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
label := MaterialText {
|
||||
text: root.text;
|
||||
style: MaterialTypography.title_small;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected : {
|
||||
label.color: MaterialPalette.on_surface;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export component TabBar inherits BaseNavigation {
|
||||
property <length> indicator_width;
|
||||
property <length> item_width: root.width / root.items.length;
|
||||
property <length> current_x: root.item_width * self.current_index;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_49, layout.min_height);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
for item[index] in root.items : tab_item := TabItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
selected: index == root.current_index;
|
||||
show_badge: item.show_badge;
|
||||
badge: item.badge;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
root.set_indicator_width(index, self.label_width);
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 50ms;
|
||||
|
||||
triggered => {
|
||||
root.set_indicator_width(index, tab_item.label_width);
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indicator := Rectangle {
|
||||
x: root.current_x + (root.item_width - self.width) / 2;
|
||||
y: parent.height - self.height;
|
||||
width: root.indicator_width;
|
||||
height: MaterialStyleMetrics.size_3;
|
||||
border_top_left_radius: self.height / 2;
|
||||
border_top_right_radius: self.border_top_left_radius;
|
||||
background: MaterialPalette.primary;
|
||||
|
||||
animate x, width {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function set_indicator_width(index: int, width: length) {
|
||||
if index != root.current_index {
|
||||
return;
|
||||
}
|
||||
|
||||
root.indicator_width = width;
|
||||
}
|
||||
}
|
||||
|
||||
export component SecondaryTabBar inherits BaseNavigation {
|
||||
property <length> item_width: root.width / root.items.length;
|
||||
property <length> current_x: root.item_width * self.current_index;
|
||||
|
||||
min_height: max(MaterialStyleMetrics.size_49, layout.min_height);
|
||||
|
||||
Rectangle {
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
for item[index] in root.items : SecondaryTabItemTemplate {
|
||||
icon: item.icon;
|
||||
selected_icon: item.selected_icon;
|
||||
text: item.text;
|
||||
index: index;
|
||||
selected: index == root.current_index;
|
||||
show_badge: item.show_badge;
|
||||
badge: item.badge;
|
||||
|
||||
clicked => {
|
||||
root.select(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indicator := Rectangle {
|
||||
x: root.current_x;
|
||||
y: parent.height - self.height;
|
||||
width: root.item_width;
|
||||
height: MaterialStyleMetrics.size_2;
|
||||
background: MaterialPalette.primary;
|
||||
|
||||
animate x {
|
||||
duration: MaterialAnimations.standard_accelerate_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
material-1.0/ui/components/text_button.slint
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component TextButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> inverse;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: root.height / 2;
|
||||
color: root.inverse ? MaterialPalette.inverse_primary : MaterialPalette.primary;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
base.color: MaterialPalette.on_surface;
|
||||
base.transparent_background: true;
|
||||
}
|
||||
]
|
||||
}
|
||||
209
material-1.0/ui/components/text_field.slint
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialAnimations } from "../styling/material_animations.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { IconButton } from "./icon_button.slint";
|
||||
import { Icon } from "./icon.slint";
|
||||
|
||||
export component TextField {
|
||||
in property <string> label;
|
||||
in property <string> placeholder_text;
|
||||
in property <string> supporting_text;
|
||||
in property <bool> has_error;
|
||||
in property <image> leading_icon;
|
||||
in property <image> trailing_icon;
|
||||
in property <bool> enabled: true;
|
||||
in property <bool> read_only <=> input.read_only;
|
||||
out property <bool> has_focus: input.has_focus;
|
||||
in_out property <string> text <=> input.text;
|
||||
|
||||
callback leading_icon_clicked();
|
||||
callback trailing_icon_clicked();
|
||||
callback accepted(text: string);
|
||||
callback edited(text: string);
|
||||
callback key_pressed(event: KeyEvent) -> EventResult;
|
||||
callback key_released(event: KeyEvent) -> EventResult;
|
||||
|
||||
property <bool> has_leading: root.leading_icon.width > 0 && root.leading_icon.height > 0;
|
||||
property <bool> has_trailing: root.trailing_icon.width > 0 && root.trailing_icon.height > 0;
|
||||
property <length> computed_x;
|
||||
property <color> highlight: root.enabled && root.has_error ? MaterialPalette.error : root.has_focus ? MaterialPalette.primary : MaterialPalette.on_surface_variant;
|
||||
|
||||
forward_focus: input;
|
||||
accessible-role: text-input;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-value <=> text;
|
||||
accessible-placeholder-text: root.text == "" ? placeholder_text : "";
|
||||
accessible-action-set-value(v) => { text = v; edited(v); }
|
||||
|
||||
layout := VerticalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_4;
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_top_left_radius: MaterialStyleMetrics.border_radius_4;
|
||||
border_top_right_radius: MaterialStyleMetrics.border_radius_4;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
min_height: max(MaterialStyleMetrics.size_56, inner_layout.min_height);
|
||||
|
||||
inner_layout := HorizontalLayout {
|
||||
padding_left: root.has_leading ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_16;
|
||||
padding_right: root.has_trailing ? MaterialStyleMetrics.padding_4 : MaterialStyleMetrics.padding_16;
|
||||
padding_top: MaterialStyleMetrics.padding_4;
|
||||
padding_bottom: self.padding_top;
|
||||
spacing: MaterialStyleMetrics.spacing_2;
|
||||
|
||||
if root.has_leading : IconButton {
|
||||
icon: root.leading_icon;
|
||||
enabled: root.enabled;
|
||||
|
||||
clicked => {
|
||||
root.leading_icon_clicked();
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalLayout {
|
||||
padding_top: MaterialStyleMetrics.padding_4;
|
||||
padding_bottom: self.padding_top;
|
||||
|
||||
Rectangle {
|
||||
horizontal_stretch: 1;
|
||||
clip: true;
|
||||
min_height: input.min_height;
|
||||
|
||||
input := TextInput {
|
||||
x: min(0px, max(parent.width - self.width - self.text_cursor_width, root.computed_x));
|
||||
width: max(parent.width - self.text-cursor-width, self.preferred-width);
|
||||
font_size: MaterialTypography.body_large.font_size;
|
||||
font_weight: MaterialTypography.body_large.font_weight;
|
||||
text_cursor_width: MaterialStyleMetrics.size_3;
|
||||
vertical_alignment: bottom;
|
||||
single_line: true;
|
||||
color: MaterialPalette.on_surface;
|
||||
selection_background_color: MaterialPalette.primary.with_alpha(0.5);
|
||||
selection_foreground_color: self.color;
|
||||
enabled: root.enabled;
|
||||
// Disable TextInput's built-in accessibility support as the component takes care of that.
|
||||
accessible-role: none;
|
||||
|
||||
cursor_position_changed(cursor_position) => {
|
||||
if cursor_position.x + root.computed_x < 0 {
|
||||
root.computed_x = - cursor_position.x;
|
||||
} else if cursor-position.x + root.computed_x > parent.width - self.text-cursor-width {
|
||||
root.computed_x = parent.width - cursor_position.x - self.text-cursor-width;
|
||||
}
|
||||
}
|
||||
|
||||
accepted => {
|
||||
root.accepted(self.text);
|
||||
}
|
||||
|
||||
edited => {
|
||||
root.edited(self.text);
|
||||
}
|
||||
|
||||
key_pressed(event) => {
|
||||
root.key_pressed(event)
|
||||
}
|
||||
|
||||
key_released(event) => {
|
||||
root.key_released(event)
|
||||
}
|
||||
}
|
||||
|
||||
text_label := MaterialText {
|
||||
y: (parent.height - self.height) / 2;
|
||||
width: 100%;
|
||||
text: root.label;
|
||||
color: root.highlight;
|
||||
style: MaterialTypography.body_large;
|
||||
|
||||
states [
|
||||
on_top when root.text != "" || input.has_focus : {
|
||||
y: 0;
|
||||
style: MaterialTypography.body_small;
|
||||
}
|
||||
]
|
||||
|
||||
animate font_size, y {
|
||||
duration: MaterialAnimations.standard_fast_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.trailing_icon.width > 0 && root.trailing_icon.height > 0 : IconButton {
|
||||
icon: root.trailing_icon;
|
||||
enabled: root.enabled;
|
||||
has_error: root.has_error;
|
||||
|
||||
clicked => {
|
||||
root.trailing_icon_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
active_indicator := Rectangle {
|
||||
y: parent.height - self.height;
|
||||
height: MaterialStyleMetrics.size_1;
|
||||
background: root.highlight;
|
||||
|
||||
animate height {
|
||||
duration: MaterialAnimations.standard_fast_duration;
|
||||
easing: MaterialAnimations.standard_easing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.supporting_text != "" : HorizontalLayout {
|
||||
padding_left: MaterialStyleMetrics.padding_16;
|
||||
padding_right: self.padding_left;
|
||||
|
||||
supporting_text := MaterialText {
|
||||
text: root.supporting_text;
|
||||
style: MaterialTypography.body_small;
|
||||
color: root.highlight;
|
||||
opacity: root.enabled ? 1 : MaterialPalette.disable_opacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function set_selection_offsets(start: int, end: int) {
|
||||
input.set_selection_offsets(start, end);
|
||||
}
|
||||
|
||||
public function select_all() {
|
||||
input.select_all();
|
||||
}
|
||||
|
||||
public function clear_selection() {
|
||||
input.clear_selection();
|
||||
}
|
||||
|
||||
public function cut() {
|
||||
input.cut();
|
||||
}
|
||||
|
||||
public function copy() {
|
||||
input.copy();
|
||||
}
|
||||
|
||||
public function paste() {
|
||||
input.paste();
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
background_layer.background: MaterialPalette.surface_container_highest.with_alpha(MaterialPalette.disable_opacity);
|
||||
text_label.opacity: MaterialPalette.disable_opacity;
|
||||
input.opacity: MaterialPalette.disable_opacity;
|
||||
}
|
||||
focused when input.has_focus : {
|
||||
active_indicator.height: MaterialStyleMetrics.size_3;
|
||||
}
|
||||
]
|
||||
}
|
||||
674
material-1.0/ui/components/time_picker.slint
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { BaseDialog } from "./dialog.slint";
|
||||
import { Icons } from "../icons/icons.slint";
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { StateLayerArea } from "state_layer.slint";
|
||||
|
||||
component TimeSelector inherits Rectangle {
|
||||
in property <bool> selected;
|
||||
in property <int> value;
|
||||
|
||||
callback clicked <=> touch_area.clicked;
|
||||
|
||||
width: max(MaterialStyleMetrics.size_48, text_label.min_width);
|
||||
height: max(MaterialStyleMetrics.size_48, text_label.min_height);
|
||||
border_radius: max(root.width, root.height) / 2;
|
||||
vertical_stretch: 0;
|
||||
horizontal_stretch: 0;
|
||||
|
||||
touch_area := TouchArea {
|
||||
text_label := MaterialText {
|
||||
text: root.value;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
style: MaterialTypography.body_large;
|
||||
color: MaterialPalette.on_surface;
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
selected when root.selected: {
|
||||
text_label.color: MaterialPalette.on_primary;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
component Clock {
|
||||
in property <[int]> model;
|
||||
in property <bool> two_columns;
|
||||
in property <int> total;
|
||||
in_out property <int> current_index;
|
||||
in property <int> current_value;
|
||||
|
||||
callback current_index_changed(index: int);
|
||||
|
||||
property <length> radius: max(root.width, root.height) / 2;
|
||||
property <length> picker_ditameter: MaterialStyleMetrics.size_48;
|
||||
property <length> center: root.radius - root.picker_ditameter / 2;
|
||||
property <length> outer_padding: 2px;
|
||||
property <length> inner_padding: 32px;
|
||||
property <length> radius_outer: root.center - root.outer_padding;
|
||||
property <length> radius_inner: root.center - root.inner_padding;
|
||||
property <int> half_total: root.total / 2;
|
||||
property <angle> rotation: 0.25turn;
|
||||
property <length> current_x: get_index_x(root.current_value);
|
||||
property <length> current_y: get_index_y(root.current_value);
|
||||
|
||||
min_width: MaterialStyleMetrics.size_256;
|
||||
min_height: self.min_width;
|
||||
vertical_stretch: 0;
|
||||
horizontal_stretch: 0;
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: max(self.width, self.height) / 2;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
|
||||
if root.current_index >= 0 || root.current_index < root.model.length: Path {
|
||||
stroke: MaterialPalette.primary;
|
||||
stroke_width: 2px;
|
||||
viewbox_width: self.width / 1px;
|
||||
viewbox_height: self.height / 1px;
|
||||
|
||||
MoveTo {
|
||||
x: root.width / 2px;
|
||||
y: root.height / 2px;
|
||||
}
|
||||
|
||||
LineTo {
|
||||
x: (root.current_x + root.picker_ditameter / 2) / 1px;
|
||||
y: (root.current_y + root.picker_ditameter / 2) / 1px;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: MaterialStyleMetrics.size_8;
|
||||
height: self.width;
|
||||
background: MaterialPalette.primary;
|
||||
border_radius: self.width / 2;
|
||||
}
|
||||
|
||||
if root.current_index < root.model.length: Rectangle {
|
||||
x: root.current_x;
|
||||
y: root.current_y;
|
||||
width: root.picker_ditameter;
|
||||
height: root.picker_ditameter;
|
||||
border_radius: root.picker_ditameter / 2;
|
||||
background: MaterialPalette.primary;
|
||||
|
||||
if root.current_index < 0: Rectangle {
|
||||
width: MaterialStyleMetrics.size_4;
|
||||
height: self.width;
|
||||
border_radius: self.width / 2;
|
||||
background: MaterialPalette.on_primary;
|
||||
}
|
||||
}
|
||||
|
||||
for val[index] in root.model: TimeSelector {
|
||||
x: get_index_x(val);
|
||||
y: get_index_y(val);
|
||||
width: root.picker_ditameter;
|
||||
height: root.picker_ditameter;
|
||||
value: val;
|
||||
selected: index == root.current_index;
|
||||
accessible_role: button;
|
||||
accessible_label: @tr("{} Hours or minutes of {}", val, root.total);
|
||||
accessible_action_default => {
|
||||
self.clicked();
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.set_current_index(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pure function value_to_angle(value: int) -> angle {
|
||||
if root.two_columns {
|
||||
if value >= root.half_total {
|
||||
return clamp((value - root.half_total) / root.half_total * 1turn, 0, 0.999999turn) - root.rotation;
|
||||
}
|
||||
return clamp(value / root.half_total * 1turn, 0, 0.99999turn) - root.rotation;
|
||||
}
|
||||
|
||||
clamp(value / root.total * 1turn, 0, 0.99999turn) - root.rotation
|
||||
}
|
||||
|
||||
pure function get_index_x(value: int) -> length {
|
||||
if root.two_columns && value >= root.half_total {
|
||||
return root.center + (root.radius_inner / 1px * cos(root.value_to_angle(value))) * 1px;
|
||||
}
|
||||
|
||||
root.center + (root.radius_outer / 1px * cos(root.value_to_angle(value))) * 1px
|
||||
}
|
||||
|
||||
pure function get_index_y(value: int) -> length {
|
||||
// this is only for 24 mode
|
||||
if root.total == 24 && value == 0 {
|
||||
return root.center + (root.radius_inner / 1px * sin(root.value_to_angle(value))) * 1px;
|
||||
}
|
||||
if root.total == 24 && value == 12 {
|
||||
return root.center + (root.radius_outer / 1px * sin(root.value_to_angle(value))) * 1px;
|
||||
}
|
||||
if root.two_columns && value >= root.half_total {
|
||||
return root.center + (root.radius_inner / 1px * sin(root.value_to_angle(value))) * 1px;
|
||||
}
|
||||
|
||||
root.center + (root.radius_outer / 1px * sin(root.value_to_angle(value))) * 1px
|
||||
}
|
||||
|
||||
function set_current_index(index: int) {
|
||||
root.current_index_changed(index);
|
||||
}
|
||||
}
|
||||
|
||||
component TimePickerInput {
|
||||
in property <bool> read_only <=> text_input.read_only;
|
||||
in property <bool> checked;
|
||||
in_out property <string> text <=> text_input.text;
|
||||
|
||||
callback clicked;
|
||||
callback edited(value: int);
|
||||
|
||||
width: MaterialStyleMetrics.size_90;
|
||||
min_height: max(MaterialStyleMetrics.size_80, layout.min_height);
|
||||
vertical_stretch: 0;
|
||||
horizontal_stretch: 0;
|
||||
|
||||
forward_focus: text_input;
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: MaterialStyleMetrics.border_radius_8;
|
||||
background: MaterialPalette.surface_container_highest;
|
||||
clip: true;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
|
||||
text_input := TextInput {
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
color: MaterialPalette.on_surface;
|
||||
font_size: MaterialTypography.display_large.font_size;
|
||||
font_weight: MaterialTypography.display_large.font_weight;
|
||||
input_type: number;
|
||||
visible: !root.read_only;
|
||||
|
||||
edited => {
|
||||
root.edited(self.text.to_float());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.read_only : StateLayerArea {
|
||||
border_radius: background_layer.border_radius;
|
||||
color: MaterialPalette.on_surface;
|
||||
|
||||
HorizontalLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
|
||||
MaterialText {
|
||||
text: root.text.to_float() >= 10 ? root.text : "0" + root.text;
|
||||
style: MaterialTypography.display_large;
|
||||
color: MaterialPalette.on_surface;
|
||||
horizontal_alignment: center;
|
||||
vertical_alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
clicked => {
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
has_focus when text_input.has_focus && !root.read_only: {
|
||||
background_layer.background: MaterialPalette.primary_container;
|
||||
background_layer.border_width: 2px;
|
||||
background_layer.border_color: MaterialPalette.primary;
|
||||
text_input.color: MaterialPalette.on_primary_container;
|
||||
}
|
||||
checked when root.checked: {
|
||||
background_layer.background: MaterialPalette.primary_container;
|
||||
text_input.color: MaterialPalette.on_primary_container;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
component PeriodSelectorItem {
|
||||
in property <string> text <=> label.text;
|
||||
in property <bool> checked;
|
||||
|
||||
callback clicked <=> state_layer.clicked;
|
||||
|
||||
background_layer := Rectangle {
|
||||
state_layer := StateLayerArea {
|
||||
color: label.color;
|
||||
|
||||
label := MaterialText {
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
horizontal_alignment: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.checked: {
|
||||
background_layer.background: MaterialPalette.tertiary_container;
|
||||
label.color: MaterialPalette.on_tertiary_container;
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
component PeriodSelector {
|
||||
in property <bool> am_selected;
|
||||
in property <bool> horizontal;
|
||||
|
||||
callback update_period(am_selected: bool);
|
||||
|
||||
min_width: max(MaterialStyleMetrics.size_52, layout.min_width);
|
||||
min_height: max(root.horizontal ? MaterialStyleMetrics.size_38 : MaterialStyleMetrics.size_80, layout.min_height);
|
||||
accessible_label: "AM or PM";
|
||||
accessible_role: checkbox;
|
||||
accessible_checked: root.am_selected;
|
||||
|
||||
Rectangle {
|
||||
border_radius: border.border_radius;
|
||||
clip: true;
|
||||
|
||||
layout := VerticalLayout {
|
||||
if root.horizontal : HorizontalLayout {
|
||||
PeriodSelectorItem {
|
||||
text: "AM";
|
||||
checked: root.am_selected;
|
||||
|
||||
clicked => {
|
||||
if root.am_selected {
|
||||
return;
|
||||
}
|
||||
root.update_period(true);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1px;
|
||||
background: border.border_color;
|
||||
vertical_stretch: 0;
|
||||
}
|
||||
|
||||
PeriodSelectorItem {
|
||||
text: "PM";
|
||||
checked: !root.am_selected;
|
||||
|
||||
clicked => {
|
||||
if !root.am_selected {
|
||||
return;
|
||||
}
|
||||
root.update_period(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !root.horizontal : VerticalLayout {
|
||||
PeriodSelectorItem {
|
||||
text: "AM";
|
||||
checked: root.am_selected;
|
||||
|
||||
clicked => {
|
||||
if root.am_selected {
|
||||
return;
|
||||
}
|
||||
root.update_period(true);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1px;
|
||||
background: border.border_color;
|
||||
vertical_stretch: 0;
|
||||
}
|
||||
|
||||
PeriodSelectorItem {
|
||||
text: "PM";
|
||||
checked: !root.am_selected;
|
||||
|
||||
clicked => {
|
||||
if !root.am_selected {
|
||||
return;
|
||||
}
|
||||
root.update_period(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
border := Rectangle {
|
||||
border_radius: MaterialStyleMetrics.border_radius_8;
|
||||
border_width: 1px;
|
||||
border_color: MaterialPalette.outline;
|
||||
}
|
||||
}
|
||||
|
||||
export struct Time {
|
||||
hour: int,
|
||||
minute: int,
|
||||
second: int
|
||||
}
|
||||
|
||||
export component TimePickerPopup inherits PopupWindow {
|
||||
in property <bool> use_24_hour_format: true;
|
||||
in property <string> title: @tr("Select time");
|
||||
in property <string> cancel_text: @tr("Cancel");
|
||||
in property <string> ok_text: @tr("Ok");
|
||||
in property <Time> time: { hour: 12 };
|
||||
in property <string> hour_label: @tr("Hour");
|
||||
in property <string> minute_label: @tr("Minute");
|
||||
|
||||
property <bool> keyboard_mode;
|
||||
property <bool> minutes_selected;
|
||||
property <bool> am_selected: true;
|
||||
property <[int]> twelfth_hour_model: [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
property <[int]> use-24_hour_format_model: [
|
||||
12,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
0,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23
|
||||
];
|
||||
property <[int]> minute_model: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
|
||||
property <[int]> current_model: root.get_current_model();
|
||||
property <int> current_index: root.minutes_selected ? root.index_of_minute(root.current_time.minute) : root.index_of_hour(root.current_time.hour);
|
||||
property <Time> current_time: root.time;
|
||||
property <int> time_picker_hour: hour_time_picker.text.to_float();
|
||||
property <int> time_picker_minute: minute_time_picker.text.to_float();
|
||||
property <bool> horizontal: root.width >= MaterialStyleMetrics.size_572;
|
||||
|
||||
callback ok();
|
||||
|
||||
close_policy: no_auto_close;
|
||||
forward_focus: base;
|
||||
|
||||
base := BaseDialog {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
title: root.title;
|
||||
action_button_icons: [
|
||||
root.keyboard_mode ? Icons.schedule : Icons.keyboard
|
||||
];
|
||||
actions: [
|
||||
root.cancel_text,
|
||||
root.ok_text
|
||||
];
|
||||
|
||||
layout := HorizontalLayout {
|
||||
spacing: MaterialStyleMetrics.spacing_52;
|
||||
|
||||
VerticalLayout {
|
||||
alignment: center;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
|
||||
HorizontalLayout {
|
||||
alignment: center;
|
||||
|
||||
hour_time_picker := TimePickerInput {
|
||||
read_only: !root.keyboard_mode;
|
||||
checked: self.read_only && !root.minutes_selected;
|
||||
text: root.current_time.hour;
|
||||
|
||||
accessible_role: AccessibleRole.text_input;
|
||||
accessible_value: self.text;
|
||||
accessible_label: "hour";
|
||||
|
||||
clicked => {
|
||||
root.minutes_selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
separator := MaterialText {
|
||||
min_width: MaterialStyleMetrics.size_24;
|
||||
text: ":";
|
||||
color: MaterialPalette.on_surface;
|
||||
style: MaterialTypography.display_large;
|
||||
vertical_alignment: center;
|
||||
horizontal_alignment: center;
|
||||
}
|
||||
|
||||
minute_time_picker := TimePickerInput {
|
||||
read_only: !root.keyboard_mode;
|
||||
checked: self.read_only && root.minutes_selected;
|
||||
text: root.current_time.minute;
|
||||
|
||||
accessible_role: AccessibleRole.text_input;
|
||||
accessible_value: self.text;
|
||||
accessible_label: "minute";
|
||||
|
||||
clicked => {
|
||||
root.minutes_selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// spacer
|
||||
if !root.use_24_hour_format && (!root.horizontal || root.keyboard_mode) : Rectangle {
|
||||
width: MaterialStyleMetrics.spacing_12;
|
||||
}
|
||||
|
||||
if !root.use_24_hour_format && (!root.horizontal || root.keyboard_mode) : PeriodSelector {
|
||||
am_selected: root.am_selected;
|
||||
|
||||
update-period(am) => {
|
||||
root.am-selected = am;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// labels
|
||||
if root.keyboard_mode : HorizontalLayout {
|
||||
alignment: start;
|
||||
|
||||
MaterialText {
|
||||
width: hour_time_picker.width;
|
||||
text: root.hour_label;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: MaterialStyleMetrics.size_24;
|
||||
}
|
||||
|
||||
MaterialText {
|
||||
text: root.minute_label;
|
||||
color: MaterialPalette.on_surface_variant;
|
||||
}
|
||||
}
|
||||
|
||||
if !root.use_24_hour_format && !root.keyboard_mode && root.horizontal : PeriodSelector {
|
||||
am_selected: root.am_selected;
|
||||
horizontal: true;
|
||||
|
||||
update-period(am) => {
|
||||
root.am-selected = am;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if root.horizontal && !root.keyboard_mode : Clock {
|
||||
model: root.current_model;
|
||||
two_columns: !root.minutes_selected && root.use-24_hour_format;
|
||||
current_index: root.current_index;
|
||||
total: root.minutes_selected ? 60 : root.use-24_hour_format ? 24 : 12;
|
||||
current_value: root.minutes_selected ? root.current_time.minute : root.current_time.hour;
|
||||
|
||||
current_index_changed(index) => {
|
||||
root.update_time_by_item(index);
|
||||
|
||||
if !root.minutes_selected {
|
||||
root.minutes_selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// spacer
|
||||
if !root.horizontal && !root.keyboard_mode : Rectangle {
|
||||
height: MaterialStyleMetrics.spacing_12;
|
||||
}
|
||||
|
||||
if !root.horizontal && !root.keyboard_mode : Clock {
|
||||
model: root.current_model;
|
||||
two_columns: !root.minutes_selected && root.use-24_hour_format;
|
||||
current_index: root.current_index;
|
||||
total: root.minutes_selected ? 60 : root.use-24_hour_format ? 24 : 12;
|
||||
current_value: root.minutes_selected ? root.current_time.minute : root.current_time.hour;
|
||||
height: self.width;
|
||||
|
||||
current_index_changed(index) => {
|
||||
root.update_time_by_item(index);
|
||||
|
||||
if !root.minutes_selected {
|
||||
root.minutes_selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
action_button_clicked(index) => {
|
||||
if index == 0 {
|
||||
root.keyboard_mode = !root.keyboard_mode;
|
||||
}
|
||||
}
|
||||
|
||||
action_clicked(index) => {
|
||||
// cancel
|
||||
if index == 0 {
|
||||
root.close();
|
||||
} else if index == 1 {
|
||||
root.ok();
|
||||
}
|
||||
}
|
||||
|
||||
close => {
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
pure public function ok_enabled() -> bool {
|
||||
if !root.keyboard_mode {
|
||||
return root.current_time.hour <= 23 && root.current_time.minute <= 59;
|
||||
}
|
||||
|
||||
if root.use-24_hour_format {
|
||||
return root.time_picker_hour <= 23 && root.time_picker_minute <= 59;
|
||||
}
|
||||
|
||||
root.time_picker_hour <= 12 && root.time_picker_minute <= 59
|
||||
}
|
||||
|
||||
public function get_current_time() -> Time {
|
||||
if root.keyboard_mode {
|
||||
if !root.use-24_hour_format && !root.am_selected {
|
||||
if root.current_time.hour == 12 {
|
||||
return { hour: 0, minute: root.current_time.minute };
|
||||
}
|
||||
|
||||
return { hour: root.current_time.hour + 12, minute: root.current_time.minute };
|
||||
}
|
||||
|
||||
return root.current_time;
|
||||
}
|
||||
|
||||
return { hour: root.time_picker_hour, minute: root.time_picker_minute };
|
||||
}
|
||||
|
||||
changed keyboard_mode => {
|
||||
if root.keyboard_mode {
|
||||
return;
|
||||
}
|
||||
|
||||
root.update_time(min(root.time_picker_hour, root.use-24_hour_format ? 23 : 12), min(root.time_picker_minute, 59));
|
||||
}
|
||||
|
||||
changed time => {
|
||||
root.current_time = root.time;
|
||||
}
|
||||
|
||||
function update_time_by_item(index: int) {
|
||||
root.update_time_by_value(root.current_model[index]);
|
||||
}
|
||||
|
||||
function update_time_by_value(value: int) {
|
||||
if root.minutes_selected {
|
||||
root.update_time(root.current_time.hour, value);
|
||||
return;
|
||||
}
|
||||
|
||||
root.update_time(value, root.current_time.minute);
|
||||
}
|
||||
|
||||
function update_time(hour: int, minute: int) {
|
||||
root.current_time = { hour: hour, minute: minute };
|
||||
hour_time_picker.text = root.current_time.hour;
|
||||
minute_time_picker.text = minute;
|
||||
}
|
||||
|
||||
pure function index_of_minute(minute: int) -> int {
|
||||
if mod(minute, 5) != 0 {
|
||||
return -1;
|
||||
}
|
||||
|
||||
minute / 5
|
||||
}
|
||||
|
||||
pure function index_of_hour(hour: int) -> int {
|
||||
if hour == 12 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if hour == 0 {
|
||||
return 12;
|
||||
}
|
||||
|
||||
if !root.use-24_hour_format && hour > 12 {
|
||||
return hour - 12;
|
||||
}
|
||||
|
||||
hour
|
||||
}
|
||||
|
||||
pure function get_current_model() -> [int] {
|
||||
if root.minutes_selected {
|
||||
return root.minute_model;
|
||||
}
|
||||
|
||||
if root.use-24_hour_format {
|
||||
return root.use-24_hour_format_model;
|
||||
}
|
||||
|
||||
root.twelfth_hour_model
|
||||
}
|
||||
}
|
||||
44
material-1.0/ui/components/tonal_button.slint
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
|
||||
export component TonalButton {
|
||||
in property <image> icon <=> base.icon;
|
||||
in property <string> text <=> base.text;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
|
||||
callback clicked <=> base.clicked;
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: root.enabled;
|
||||
accessible-label: root.text == "" ? root.tooltip : root.text;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
elevation := Elevation {
|
||||
border_radius: root.height / 2;
|
||||
background: MaterialPalette.secondary_container;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
border_radius: elevation.border_radius;
|
||||
color: MaterialPalette.on_secondary_container;
|
||||
}
|
||||
|
||||
states [
|
||||
disabled when !root.enabled : {
|
||||
elevation.background: transparent;
|
||||
elevation.level: 0;
|
||||
base.color: MaterialPalette.on_surface;
|
||||
}
|
||||
hover when base.has_hover && !base.pressed && !base.enter_pressed : {
|
||||
elevation.level: 2;
|
||||
}
|
||||
]
|
||||
}
|
||||
63
material-1.0/ui/components/tonal_icon_button.slint
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Elevation } from "./elevation.slint";
|
||||
import { BaseButton } from "./base_button.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component TonalIconButton {
|
||||
in property <image> icon;
|
||||
in property <image> checked_icon: root.icon;
|
||||
in property <bool> checkable;
|
||||
in_out property <bool> checked;
|
||||
in property <string> tooltip <=> base.tooltip;
|
||||
in property <bool> enabled <=> base.enabled;
|
||||
|
||||
callback clicked();
|
||||
|
||||
accessible-role: button;
|
||||
accessible-enabled: true;
|
||||
accessible-label: root.tooltip;
|
||||
accessible-checkable: root.checkable;
|
||||
accessible-checked: root.checked;
|
||||
accessible-action-default => { base.clicked(); }
|
||||
|
||||
background_layer := Rectangle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border_radius: base.border_radius;
|
||||
background: root.checkable ? MaterialPalette.surface_container_highest : MaterialPalette.secondary_container;
|
||||
visible: root.enabled;
|
||||
}
|
||||
|
||||
base := BaseButton {
|
||||
icon: root.checked ? root.checked_icon : root.icon;
|
||||
border_radius: self.height / 2;
|
||||
color: !root.enabled ? MaterialPalette.on_surface_variant : MaterialPalette.on_secondary_container;
|
||||
button_horizontal_padding: 0;
|
||||
button_vertical_padding: 0;
|
||||
width: self.height;
|
||||
icon_size: MaterialStyleMetrics.icon_size_24;
|
||||
|
||||
clicked => {
|
||||
root.toggle();
|
||||
root.clicked();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if !root.checkable {
|
||||
return;
|
||||
}
|
||||
|
||||
root.checked = !root.checked;
|
||||
}
|
||||
|
||||
states [
|
||||
checked when root.enabled && root.checked : {
|
||||
background_layer.background: MaterialPalette.secondary_container;
|
||||
}
|
||||
]
|
||||
}
|
||||
33
material-1.0/ui/components/tooltip.slint
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialText } from "./material_text.slint";
|
||||
import { MaterialPalette } from "../styling/material_palette.slint";
|
||||
import { MaterialTypography } from "../styling/material_typography.slint";
|
||||
|
||||
export component ToolTip {
|
||||
in property <string> text;
|
||||
|
||||
z: 10000;
|
||||
|
||||
background_layer := Rectangle {
|
||||
border_radius: 4px;
|
||||
background: MaterialPalette.inverse_surface;
|
||||
|
||||
layout := HorizontalLayout {
|
||||
max_width: 200px;
|
||||
padding_top: 4px;
|
||||
padding_bottom: 4px;
|
||||
padding_left: 8px;
|
||||
padding_right: 8px;
|
||||
|
||||
MaterialText {
|
||||
text: root.text;
|
||||
vertical_alignment: center;
|
||||
color: MaterialPalette.inverse_on_surface;
|
||||
style: MaterialTypography.body_small;
|
||||
wrap: word-wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
material-1.0/ui/components/vertical.slint
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MaterialStyleMetrics } from "../styling/material_style_metrics.slint";
|
||||
|
||||
export component Vertical inherits VerticalLayout {
|
||||
padding: MaterialStyleMetrics.padding_8;
|
||||
spacing: MaterialStyleMetrics.spacing_8;
|
||||
}
|
||||
1
material-1.0/ui/icons/account_box.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>account-box</title><path d="M6,17C6,15 10,13.9 12,13.9C14,13.9 18,15 18,17V18H6M15,9A3,3 0 0,1 12,12A3,3 0 0,1 9,9A3,3 0 0,1 12,6A3,3 0 0,1 15,9M3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5C3.89,3 3,3.9 3,5Z" /></svg>
|
||||
|
After Width: | Height: | Size: 299 B |
1
material-1.0/ui/icons/arrow_back.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 162 B |
2
material-1.0/ui/icons/arrow_drop_down.svg
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7 10l5 5l5-5H7z"/></svg>
|
||||
|
After Width: | Height: | Size: 119 B |
1
material-1.0/ui/icons/arrow_drop_up.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7 14l5-5l5 5H7z"/></svg>
|
||||
|
After Width: | Height: | Size: 118 B |
1
material-1.0/ui/icons/arrow_right.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M10 17l5-5l-5-5v10z"/></svg>
|
||||
|
After Width: | Height: | Size: 121 B |
1
material-1.0/ui/icons/calendar_today.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Z"/></svg>
|
||||
|
After Width: | Height: | Size: 325 B |
1
material-1.0/ui/icons/check.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
|
After Width: | Height: | Size: 151 B |
1
material-1.0/ui/icons/chevron_backward.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M560-240 320-480l240-240 56 56-184 184 184 184-56 56Z"/></svg>
|
||||
|
After Width: | Height: | Size: 178 B |
1
material-1.0/ui/icons/chevron_forward.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M504-480 320-664l56-56 240 240-240 240-56-56 184-184Z"/></svg>
|
||||
|
After Width: | Height: | Size: 178 B |
1
material-1.0/ui/icons/close.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||||
|
After Width: | Height: | Size: 203 B |
1
material-1.0/ui/icons/cog.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>cog</title><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></svg>
|
||||
|
After Width: | Height: | Size: 1,004 B |
1
material-1.0/ui/icons/edit.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 18l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z"/></svg>
|
||||
|
After Width: | Height: | Size: 329 B |
1
material-1.0/ui/icons/home.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>home</title><path d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" /></svg>
|
||||
|
After Width: | Height: | Size: 141 B |
27
material-1.0/ui/icons/icons.slint
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// Copyright © Zervó Zadachin <contact@zervo.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export global Icons {
|
||||
out property <image> calendar_today: @image-url("calendar_today.svg");
|
||||
out property <image> check: @image-url("check.svg");
|
||||
out property <image> close: @image-url("close.svg");
|
||||
out property <image> chevron_backward: @image-url("chevron_backward.svg");
|
||||
out property <image> chevron_forward: @image-url("chevron_forward.svg");
|
||||
out property <image> edit: @image-url("edit.svg");
|
||||
out property <image> menu: @image-url("menu.svg");
|
||||
out property <image> arrow_back: @image-url("arrow_back.svg");
|
||||
out property <image> arrow_right: @image-url("arrow_right.svg");
|
||||
out property <image> arrow_drop_down: @image-url("arrow_drop_down.svg");
|
||||
out property <image> arrow_drop_up: @image-url("arrow_drop_up.svg");
|
||||
out property <image> remove: @image-url("remove.svg");
|
||||
out property <image> schedule: @image-url("schedule.svg");
|
||||
out property <image> keyboard: @image-url("keyboard.svg");
|
||||
// extended:
|
||||
out property <image> home: @image-url("home.svg");
|
||||
out property <image> list_box: @image-url("list_box.svg");
|
||||
out property <image> shape: @image-url("shape.svg");
|
||||
out property <image> account_box: @image-url("account_box.svg");
|
||||
out property <image> cog: @image-url("cog.svg");
|
||||
out property <image> package_variant: @image-url("package_variant.svg");
|
||||
}
|
||||
1
material-1.0/ui/icons/keyboard.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#e3e3e3"><path d="M0 0h24v24H0V0zm0 0h24v24H0V0z" fill="none"/><path d="M20 7v10H4V7h16m0-2H4c-1.1 0-1.99.9-1.99 2L2 17c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm-9 3h2v2h-2zm0 3h2v2h-2zM8 8h2v2H8zm0 3h2v2H8zm-3 0h2v2H5zm0-3h2v2H5zm3 6h8v2H8zm6-3h2v2h-2zm0-3h2v2h-2zm3 3h2v2h-2zm0-3h2v2h-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 401 B |
1
material-1.0/ui/icons/list_box.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>list-box</title><path d="M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M7 7H9V9H7V7M7 11H9V13H7V11M7 15H9V17H7V15M17 17H11V15H17V17M17 13H11V11H17V13M17 9H11V7H17V9Z" /></svg>
|
||||
|
After Width: | Height: | Size: 287 B |
1
material-1.0/ui/icons/menu.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>
|
||||
|
After Width: | Height: | Size: 146 B |
1
material-1.0/ui/icons/package_variant.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>package-variant</title><path d="M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z" /></svg>
|
||||
|
After Width: | Height: | Size: 737 B |
1
material-1.0/ui/icons/remove.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#e3e3e3"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 13H5v-2h14v2z"/></svg>
|
||||
|
After Width: | Height: | Size: 177 B |
1
material-1.0/ui/icons/schedule.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#e3e3e3"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg>
|
||||
|
After Width: | Height: | Size: 342 B |
1
material-1.0/ui/icons/shape.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>shape</title><path d="M11,13.5V21.5H3V13.5H11M12,2L17.5,11H6.5L12,2M17.5,13C20,13 22,15 22,17.5C22,20 20,22 17.5,22C15,22 13,20 13,17.5C13,15 15,13 17.5,13Z" /></svg>
|
||||
|
After Width: | Height: | Size: 233 B |
12
material-1.0/ui/items/list_item.slint
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export struct ListItem {
|
||||
text: string,
|
||||
supporting_text: string,
|
||||
avatar_icon: image,
|
||||
avatar_text: string,
|
||||
avatar_background: color,
|
||||
avatar_foreground: color,
|
||||
action_button_icon: image
|
||||
}
|
||||
9
material-1.0/ui/items/menu_item.slint
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export struct MenuItem {
|
||||
icon: image,
|
||||
text: string,
|
||||
trailing_text: string,
|
||||
enabled: bool
|
||||
}
|
||||
15
material-1.0/ui/items/navigation_item.slint
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export struct NavigationItem {
|
||||
icon: image,
|
||||
selected_icon: image,
|
||||
text: string,
|
||||
show_badge: bool,
|
||||
badge: string
|
||||
}
|
||||
|
||||
export struct NavigationGroup {
|
||||
title: string,
|
||||
items: [NavigationItem]
|
||||
}
|
||||
17
material-1.0/ui/styling/material_animations.slint
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export global MaterialAnimations {
|
||||
out property <easing> emphasized_easing: cubic-bezier(0.05, 0.7, 0.1, 1.0);
|
||||
out property <duration> emphasized_duration: 500ms;
|
||||
out property <duration> emphasized_decelerate_duration: 400ms;
|
||||
out property <duration> emphasized_accelerate_duration: 200ms;
|
||||
out property <easing> standard_easing: cubic-bezier(0, 0, 0, 1);
|
||||
out property <duration> standard_decelerate_duration: 250ms;
|
||||
out property <duration> standard_accelerate_duration: 200ms;
|
||||
out property <duration> standard_fast_duration: 150ms;
|
||||
out property <easing> ripple_easing: ease_out;
|
||||
out property <duration> ripple_duration: 2s;
|
||||
out property <easing> opacity_easing: ease;
|
||||
out property <duration> opacity_duration: 250ms;
|
||||
}
|
||||
173
material-1.0/ui/styling/material_palette.slint
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Palette } from "std-widgets.slint";
|
||||
import { MaterialSchemes, MaterialScheme } from "material_schemes.slint";
|
||||
|
||||
export global MaterialPalette {
|
||||
out property <color> primary: root.scheme.primary;
|
||||
out property <color> surface_tint: root.scheme.surfaceTint;
|
||||
out property <color> on_primary: root.scheme.onPrimary;
|
||||
out property <color> primary_container: root.scheme.primaryContainer;
|
||||
out property <color> on_primary_container: root.scheme.onPrimaryContainer;
|
||||
out property <color> secondary: root.scheme.secondary;
|
||||
out property <color> on_secondary: root.scheme.onSecondary;
|
||||
out property <color> secondary_container: root.scheme.secondaryContainer;
|
||||
out property <color> on_secondary_container: root.scheme.onSecondaryContainer;
|
||||
out property <color> tertiary: root.scheme.tertiary;
|
||||
out property <color> on_tertiary: root.scheme.onTertiary;
|
||||
out property <color> tertiary_container: root.scheme.tertiaryContainer;
|
||||
out property <color> on_tertiary_container: root.scheme.onTertiaryContainer;
|
||||
out property <color> error: root.scheme.error;
|
||||
out property <color> on_error: root.scheme.onError;
|
||||
out property <color> error_container: root.scheme.errorContainer;
|
||||
out property <color> on_error_container: root.scheme.onErrorContainer;
|
||||
out property <color> background: root.scheme.background;
|
||||
out property <color> on_background: root.scheme.onBackground;
|
||||
out property <color> surface: root.scheme.surface;
|
||||
out property <color> on_surface: root.scheme.onSurface;
|
||||
out property <color> surface_variant: root.scheme.surfaceVariant;
|
||||
out property <color> on_surface_variant: root.scheme.onSurfaceVariant;
|
||||
out property <color> outline: root.scheme.outline;
|
||||
out property <color> outline_variant: root.scheme.outlineVariant;
|
||||
out property <color> shadow: root.scheme.shadow;
|
||||
out property <color> scrim: root.scheme.scrim;
|
||||
out property <color> inverse_surface: root.scheme.inverseSurface;
|
||||
out property <color> inverse_on_surface: root.scheme.inverseOnSurface;
|
||||
out property <color> inverse_primary: root.scheme.inversePrimary;
|
||||
out property <color> primary_fixed: root.scheme.primaryFixed;
|
||||
out property <color> on_primary_fixed: root.scheme.onPrimaryFixed;
|
||||
out property <color> primary_fixed_dim: root.scheme.primaryFixedDim;
|
||||
out property <color> on_primary_fixed_variant: root.scheme.onPrimaryFixedVariant;
|
||||
out property <color> secondary_fixed: root.scheme.secondaryFixed;
|
||||
out property <color> on_secondary_fixed: root.scheme.onSecondaryFixed;
|
||||
out property <color> secondary_fixed_dim: root.scheme.secondaryFixedDim;
|
||||
out property <color> on_secondary_fixed_variant: root.scheme.onSecondaryFixedVariant;
|
||||
out property <color> tertiary_fixed: root.scheme.tertiaryFixed;
|
||||
out property <color> on_tertiary_fixed: root.scheme.onTertiaryFixed;
|
||||
out property <color> tertiary_fixed_dim: root.scheme.tertiaryFixedDim;
|
||||
out property <color> on_tertiary_fixed_variant: root.scheme.onTertiaryFixedVariant;
|
||||
out property <color> surface_dim: root.scheme.surfaceDim;
|
||||
out property <color> surface_bright: root.scheme.surfaceBright;
|
||||
out property <color> surface_container_lowest: root.scheme.surfaceContainerLowest;
|
||||
out property <color> surface_container_low: root.scheme.surfaceContainerLow;
|
||||
out property <color> surface_container: root.scheme.surfaceContainer;
|
||||
out property <color> surface_container_high: root.scheme.surfaceContainerHigh;
|
||||
out property <color> surface_container_highest: root.scheme.surfaceContainerHighest;
|
||||
out property <color> shadow_15: #000000.with_alpha(15%);
|
||||
out property <color> shadow_30: #000000.with_alpha(30%);
|
||||
out property <float> state_layer_opacity_hover: 8%;
|
||||
out property <float> state_layer_opacity_focus: 10%;
|
||||
out property <float> state_layer_opacity_press: 10%;
|
||||
out property <float> state_layer_opacity_disabled: 12%;
|
||||
out property <float> state_layer_opacity_drag: 16%;
|
||||
out property <float> disable_opacity: 38%;
|
||||
out property <color> background_modal: #000000.with_alpha(50%);
|
||||
|
||||
in property <MaterialSchemes> schemes: {
|
||||
light: {
|
||||
primary: rgb(68, 94, 145),
|
||||
surfaceTint: rgb(68, 94, 145),
|
||||
onPrimary: rgb(255, 255, 255),
|
||||
primaryContainer: rgb(216, 226, 255),
|
||||
onPrimaryContainer: rgb(0, 26, 66),
|
||||
secondary: rgb(87, 94, 113),
|
||||
onSecondary: rgb(255, 255, 255),
|
||||
secondaryContainer: rgb(219, 226, 249),
|
||||
onSecondaryContainer: rgb(20, 27, 44),
|
||||
tertiary: rgb(113, 85, 115),
|
||||
onTertiary: rgb(255, 255, 255),
|
||||
tertiaryContainer: rgb(252, 215, 251),
|
||||
onTertiaryContainer: rgb(41, 19, 45),
|
||||
error: rgb(186, 26, 26),
|
||||
onError: rgb(255, 255, 255),
|
||||
errorContainer: rgb(255, 218, 214),
|
||||
onErrorContainer: rgb(65, 0, 2),
|
||||
background: rgb(249, 249, 255),
|
||||
onBackground: rgb(26, 27, 32),
|
||||
surface: rgb(249, 249, 255),
|
||||
onSurface: rgb(26, 27, 32),
|
||||
surfaceVariant: rgb(225, 226, 236),
|
||||
onSurfaceVariant: rgb(68, 71, 79),
|
||||
outline: rgb(117, 119, 127),
|
||||
outlineVariant: rgb(196, 198, 208),
|
||||
shadow: rgb(0, 0, 0),
|
||||
scrim: rgb(0, 0, 0),
|
||||
inverseSurface: rgb(47, 48, 54),
|
||||
inverseOnSurface: rgb(240, 240, 247),
|
||||
inversePrimary: rgb(173, 198, 255),
|
||||
primaryFixed: rgb(216, 226, 255),
|
||||
onPrimaryFixed: rgb(0, 26, 66),
|
||||
primaryFixedDim: rgb(173, 198, 255),
|
||||
onPrimaryFixedVariant: rgb(43, 70, 120),
|
||||
secondaryFixed: rgb(219, 226, 249),
|
||||
onSecondaryFixed: rgb(20, 27, 44),
|
||||
secondaryFixedDim: rgb(191, 198, 220),
|
||||
onSecondaryFixedVariant: rgb(63, 71, 89),
|
||||
tertiaryFixed: rgb(252, 215, 251),
|
||||
onTertiaryFixed: rgb(41, 19, 45),
|
||||
tertiaryFixedDim: rgb(222, 188, 223),
|
||||
onTertiaryFixedVariant: rgb(88, 62, 91),
|
||||
surfaceDim: rgb(217, 217, 224),
|
||||
surfaceBright: rgb(246, 246, 255),
|
||||
surfaceContainerLowest: rgb(255, 255, 255),
|
||||
surfaceContainerLow: rgb(243, 243, 250),
|
||||
surfaceContainer: rgb(238, 237, 244),
|
||||
surfaceContainerHigh: rgb(232, 231, 239),
|
||||
surfaceContainerHighest: rgb(226, 226, 233),
|
||||
},
|
||||
dark: {
|
||||
primary: rgb(173, 198, 255),
|
||||
surfaceTint: rgb(173, 198, 255),
|
||||
onPrimary: rgb(17, 47, 96),
|
||||
primaryContainer: rgb(43, 70, 120),
|
||||
onPrimaryContainer: rgb(216, 226, 255),
|
||||
secondary: rgb(191, 198, 220),
|
||||
onSecondary: rgb(41, 48, 65),
|
||||
secondaryContainer: rgb(63, 71, 89),
|
||||
onSecondaryContainer: rgb(219, 226, 249),
|
||||
tertiary: rgb(222, 188, 223),
|
||||
onTertiary: rgb(64, 40, 67),
|
||||
tertiaryContainer: rgb(88, 62, 91),
|
||||
onTertiaryContainer: rgb(252, 215, 251),
|
||||
error: rgb(255, 180, 171),
|
||||
onError: rgb(105, 0, 5),
|
||||
errorContainer: rgb(147, 0, 10),
|
||||
onErrorContainer: rgb(255, 218, 214),
|
||||
background: rgb(17, 19, 24),
|
||||
onBackground: rgb(226, 226, 233),
|
||||
surface: rgb(17, 19, 24),
|
||||
onSurface: rgb(226, 226, 233),
|
||||
surfaceVariant: rgb(68, 71, 79),
|
||||
onSurfaceVariant: rgb(196, 198, 208),
|
||||
outline: rgb(142, 144, 153),
|
||||
outlineVariant: rgb(68, 71, 79),
|
||||
shadow: rgb(0, 0, 0),
|
||||
scrim: rgb(0, 0, 0),
|
||||
inverseSurface: rgb(226, 226, 233),
|
||||
inverseOnSurface: rgb(47, 48, 54),
|
||||
inversePrimary: rgb(68, 94, 145),
|
||||
primaryFixed: rgb(216, 226, 255),
|
||||
onPrimaryFixed: rgb(0, 26, 66),
|
||||
primaryFixedDim: rgb(173, 198, 255),
|
||||
onPrimaryFixedVariant: rgb(43, 70, 120),
|
||||
secondaryFixed: rgb(219, 226, 249),
|
||||
onSecondaryFixed: rgb(20, 27, 44),
|
||||
secondaryFixedDim: rgb(191, 198, 220),
|
||||
onSecondaryFixedVariant: rgb(63, 71, 89),
|
||||
tertiaryFixed: rgb(252, 215, 251),
|
||||
onTertiaryFixed: rgb(41, 19, 45),
|
||||
tertiaryFixedDim: rgb(222, 188, 223),
|
||||
onTertiaryFixedVariant: rgb(88, 62, 91),
|
||||
surfaceDim: rgb(17, 19, 24),
|
||||
surfaceBright: rgb(55, 57, 62),
|
||||
surfaceContainerLowest: rgb(12, 14, 19),
|
||||
surfaceContainerLow: rgb(26, 27, 32),
|
||||
surfaceContainer: rgb(30, 31, 37),
|
||||
surfaceContainerHigh: rgb(40, 42, 47),
|
||||
surfaceContainerHighest: rgb(51, 53, 58),
|
||||
},
|
||||
};
|
||||
|
||||
property <MaterialScheme> scheme: Palette.color_scheme == ColorScheme.dark ? root.schemes.dark : root.schemes.light;
|
||||
}
|
||||
59
material-1.0/ui/styling/material_schemes.slint
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export struct MaterialScheme {
|
||||
primary: color,
|
||||
surfaceTint: color,
|
||||
onPrimary: color,
|
||||
primaryContainer: color,
|
||||
onPrimaryContainer: color,
|
||||
secondary: color,
|
||||
onSecondary: color,
|
||||
secondaryContainer: color,
|
||||
onSecondaryContainer: color,
|
||||
tertiary: color,
|
||||
onTertiary: color,
|
||||
tertiaryContainer: color,
|
||||
onTertiaryContainer: color,
|
||||
error: color,
|
||||
onError: color,
|
||||
errorContainer: color,
|
||||
onErrorContainer: color,
|
||||
background: color,
|
||||
onBackground: color,
|
||||
surface: color,
|
||||
onSurface: color,
|
||||
surfaceVariant: color,
|
||||
onSurfaceVariant: color,
|
||||
outline: color,
|
||||
outlineVariant: color,
|
||||
shadow: color,
|
||||
scrim: color,
|
||||
inverseSurface: color,
|
||||
inverseOnSurface: color,
|
||||
inversePrimary: color,
|
||||
primaryFixed: color,
|
||||
onPrimaryFixed: color,
|
||||
primaryFixedDim: color,
|
||||
onPrimaryFixedVariant: color,
|
||||
secondaryFixed: color,
|
||||
onSecondaryFixed: color,
|
||||
secondaryFixedDim: color,
|
||||
onSecondaryFixedVariant: color,
|
||||
tertiaryFixed: color,
|
||||
onTertiaryFixed: color,
|
||||
tertiaryFixedDim: color,
|
||||
onTertiaryFixedVariant: color,
|
||||
surfaceDim: color,
|
||||
surfaceBright: color,
|
||||
surfaceContainerLowest: color,
|
||||
surfaceContainerLow: color,
|
||||
surfaceContainer: color,
|
||||
surfaceContainerHigh: color,
|
||||
surfaceContainerHighest: color,
|
||||
}
|
||||
|
||||
export struct MaterialSchemes {
|
||||
light: MaterialScheme,
|
||||
dark: MaterialScheme
|
||||
}
|
||||
75
material-1.0/ui/styling/material_style_metrics.slint
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export global MaterialStyleMetrics {
|
||||
// sizes
|
||||
out property <length> size_1: 1px;
|
||||
out property <length> size_2: 2px;
|
||||
out property <length> size_3: 3px;
|
||||
out property <length> size_4: 4px;
|
||||
out property <length> size_6: 6px;
|
||||
out property <length> size_8: 8px;
|
||||
out property <length> size_14: 14px;
|
||||
out property <length> size_16: 16px;
|
||||
out property <length> size_18: 18px;
|
||||
out property <length> size_20: 20px;
|
||||
out property <length> size_24: 24px;
|
||||
out property <length> size_30: 30px;
|
||||
out property <length> size_32: 32px;
|
||||
out property <length> size_36: 36px;
|
||||
out property <length> size_38: 38px;
|
||||
out property <length> size_40: 40px;
|
||||
out property <length> size_48: 48px;
|
||||
out property <length> size_49: 49px;
|
||||
out property <length> size_52: 52px;
|
||||
out property <length> size_56: 56px;
|
||||
out property <length> size_64: 64px;
|
||||
out property <length> size_72: 72px;
|
||||
out property <length> size_80: 80px;
|
||||
out property <length> size_90: 96px;
|
||||
out property <length> size_200: 200px;
|
||||
out property <length> size_256: 256px;
|
||||
out property <length> size_344: 344px;
|
||||
out property <length> size_360: 360px;
|
||||
out property <length> size_572: 572px;
|
||||
out property <length> size_640: 640px;
|
||||
|
||||
// icon
|
||||
out property <length> icon_size_18: 18px;
|
||||
out property <length> icon_size_24: 24px;
|
||||
out property <length> icon_size_36: 36px;
|
||||
out property <length> icon_size_90: 27px;
|
||||
|
||||
// padding
|
||||
out property <length> padding_4: 4px;
|
||||
out property <length> padding_6: 6px;
|
||||
out property <length> padding_10: 10px;
|
||||
out property <length> padding_12: 12px;
|
||||
out property <length> padding_8: 8px;
|
||||
out property <length> padding_14: 14px;
|
||||
out property <length> padding_16: 16px;
|
||||
out property <length> padding_20: 20px;
|
||||
out property <length> padding_24: 24px;
|
||||
out property <length> padding_28: 28px;
|
||||
out property <length> padding_30: 30px;
|
||||
out property <length> padding_44: 44px;
|
||||
out property <length> padding_56: 56px;
|
||||
|
||||
// spacing
|
||||
out property <length> spacing_2: 2px;
|
||||
out property <length> spacing_6: 6px;
|
||||
out property <length> spacing_4: 4px;
|
||||
out property <length> spacing_8: 8px;
|
||||
out property <length> spacing_12: 12px;
|
||||
out property <length> spacing_16: 16px;
|
||||
out property <length> spacing_40: 40px;
|
||||
out property <length> spacing_52: 52px;
|
||||
|
||||
// border_radius
|
||||
out property <length> border_radius_2: 2px;
|
||||
out property <length> border_radius_4: 4px;
|
||||
out property <length> border_radius_8: 8px;
|
||||
out property <length> border_radius_12: 12px;
|
||||
out property <length> border_radius_16: 16px;
|
||||
out property <length> border_radius_28: 28px;
|
||||
}
|
||||
93
material-1.0/ui/styling/material_typography.slint
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export struct TextStyle {
|
||||
font_size: length,
|
||||
font_weight: int
|
||||
}
|
||||
|
||||
export global MaterialTypography {
|
||||
out property <int> regular: 300;
|
||||
out property <int> medium: 600;
|
||||
out property <int> semibold: 900;
|
||||
|
||||
out property <TextStyle> display_large: {
|
||||
font_size: 57px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> display_medium: {
|
||||
font_size: 45px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> display_small: {
|
||||
font_size: 36px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> headline_large: {
|
||||
font_size: 32px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> headline_medium: {
|
||||
font_size: 28px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> headline_small: {
|
||||
font_size: 24px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> title_large: {
|
||||
font_size: 22px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> title_medium: {
|
||||
font_size: 16px,
|
||||
font_weight: root.medium
|
||||
};
|
||||
|
||||
out property <TextStyle> title_small: {
|
||||
font_size: 14px,
|
||||
font_weight: root.medium
|
||||
};
|
||||
|
||||
out property <TextStyle> label_large: {
|
||||
font_size: 14px,
|
||||
font_weight: root.medium
|
||||
};
|
||||
|
||||
out property <TextStyle> label_medium: {
|
||||
font_size: 12px,
|
||||
font_weight: root.medium
|
||||
};
|
||||
|
||||
out property <TextStyle> label_medium_prominent: {
|
||||
font_size: 12px,
|
||||
font_weight: root.semibold
|
||||
};
|
||||
|
||||
out property <TextStyle> label_small: {
|
||||
font_size: 11px,
|
||||
font_weight: root.medium
|
||||
};
|
||||
|
||||
out property <TextStyle> body_large: {
|
||||
font_size: 16px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> body_medium: {
|
||||
font_size: 14px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
|
||||
out property <TextStyle> body_small: {
|
||||
font_size: 12px,
|
||||
font_weight: root.regular
|
||||
};
|
||||
}
|
||||
23
src/lib.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
fn ui() -> MainWindow {
|
||||
MainWindow::new().unwrap()
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
|
||||
pub fn main() {
|
||||
let ui = ui();
|
||||
ui.run().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[unsafe(no_mangle)]
|
||||
fn android_main(android_app: slint::android::AndroidApp) {
|
||||
slint::android::init(android_app).unwrap();
|
||||
let ui = ui();
|
||||
MaterialWindowAdapter::get(&ui).set_disable_hover(true);
|
||||
ui.run().unwrap();
|
||||
}
|
||||
6
src/main.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// In order to be compatible with both desktop, wasm, and android, the example is both a binary and a library.
|
||||
// Just forward to the library in main
|
||||
|
||||
fn main() {
|
||||
parts_rs_lib::main();
|
||||
}
|
||||
19
ui/home.slint
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { VerticalBox } from "std-widgets.slint";
|
||||
import { MaterialPalette } from "../material-1.0/ui/styling/material_palette.slint";
|
||||
export component ViewHome {
|
||||
Rectangle {
|
||||
border-radius: 10px;
|
||||
background: MaterialPalette.surface;
|
||||
|
||||
VerticalBox {
|
||||
alignment: center;
|
||||
padding: 16px;
|
||||
|
||||
Text {
|
||||
text: "Home View";
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
ui/main.slint
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { HorizontalBox, VerticalBox } from "std-widgets.slint";
|
||||
import { NavigationRail } from "@material";
|
||||
import { Icons } from "../material-1.0/ui/icons/icons.slint";
|
||||
import { MaterialWindow } from "../material-1.0/material.slint";
|
||||
import { ViewHome } from "home.slint";
|
||||
export { MaterialWindowAdapter } from "@material";
|
||||
|
||||
export component MainWindow inherits MaterialWindow {
|
||||
in-out property <int> current_view: 0;
|
||||
callback navigate(int);
|
||||
|
||||
preferred-width: 1000px;
|
||||
preferred-height: 700px;
|
||||
title: "PartsRS";
|
||||
|
||||
HorizontalBox {
|
||||
NavigationRail {
|
||||
width: 80px;
|
||||
|
||||
index_changed(index) => {
|
||||
root.current_view = index;
|
||||
root.navigate(index)
|
||||
}
|
||||
|
||||
items: [
|
||||
{
|
||||
icon: Icons.home,
|
||||
text: "Home"
|
||||
},
|
||||
{
|
||||
icon: Icons.list_box,
|
||||
text: "Parts"
|
||||
},
|
||||
{
|
||||
icon: Icons.shape,
|
||||
text: "Categories"
|
||||
},
|
||||
{
|
||||
icon: Icons.package_variant,
|
||||
text: "Locations"
|
||||
},
|
||||
{
|
||||
icon: Icons.cog,
|
||||
text: "Settings"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
VerticalBox {
|
||||
if root.current_view == 0: ViewHome {}
|
||||
if root.current_view == 1: Text { text: "parts"; }
|
||||
if root.current_view == 2: Text { text: "categories"; }
|
||||
if root.current_view == 3: Text { text: "locations"; }
|
||||
if root.current_view == 4: Text { text: "settings"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export global NavigationLogic {
|
||||
callback navigate(int);
|
||||
}
|
||||
4
ui/slint-logo.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.6632 55.828L48.7333 37.0309C48.7333 37.0309 50 36.3278 50 35.2182C50 33.7406 48.3981 33.2599 48.3981 33.2599L32.9557 27.355C32.4047 27.1462 31.6464 27.7312 32.3564 28.4728L37.4689 33.4165C37.4689 33.4165 38.889 34.765 38.889 35.6494C38.889 36.5338 38.017 37.322 38.017 37.322L19.4135 54.6909C18.7517 55.3089 19.6464 56.4294 20.6632 55.828Z" fill="white"/>
|
||||
<path d="M43.3368 8.17339L15.2667 26.9677C15.2667 26.9677 14 27.6708 14 28.7804C14 30.258 15.6019 30.7387 15.6019 30.7387L31.0443 36.6464C31.5953 36.8524 32.3565 36.2674 31.6436 35.5286L26.5311 30.5684C26.5311 30.5684 25.111 29.2226 25.111 28.3355C25.111 27.4483 25.983 26.6628 25.983 26.6628L44.5752 9.30769C45.2483 8.68973 44.3565 7.56916 43.3368 8.17339Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 846 B |