diff --git a/.github/workflows/wasm_demos.yaml b/.github/workflows/wasm_demos.yaml index eba72baa44a..7c887cb4423 100644 --- a/.github/workflows/wasm_demos.yaml +++ b/.github/workflows/wasm_demos.yaml @@ -46,7 +46,7 @@ jobs: - name: Remaining wasm demos if: ${{ inputs.build_artifacts }} run: | - for demo in demos/printerdemo/rust examples/todo/rust examples/todo-mvc/rust examples/carousel/rust examples/slide_puzzle examples/memory examples/imagefilter/rust examples/plotter examples/opengl_underlay demos/home-automation/rust; do + for demo in demos/printerdemo/rust demos/usecases/rust examples/todo/rust examples/todo-mvc/rust examples/carousel/rust examples/slide_puzzle examples/memory examples/imagefilter/rust examples/plotter examples/opengl_underlay demos/home-automation/rust; do pushd $demo sed -i "s/#wasm# //" Cargo.toml wasm-pack build --release --target web @@ -82,6 +82,7 @@ jobs: demos/energy-monitor/ demos/home-automation/rust demos/weather-demo/ + demos/usecases/rust !/**/.gitignore - name: Clean cache # Otherwise the cache is much too big run: | diff --git a/Cargo.toml b/Cargo.toml index 26dedb4dfd9..4a77e4c75c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ 'examples/mcu-board-support', 'examples/uefi-demo', 'demos/weather-demo', + 'demos/usecases/rust', 'helper_crates/const-field-offset', 'helper_crates/vtable', 'helper_crates/vtable/macro', diff --git a/REUSE.toml b/REUSE.toml index 93962b2e626..219063322af 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -190,6 +190,7 @@ path = [ "internal/compiler/widgets/cupertino/_**.svg", "internal/compiler/widgets/qt/_**.svg", "examples/todo-mvc/assets/**.svg", + "demos/usecases/ui/assets/**.svg" ] precedence = "aggregate" SPDX-FileCopyrightText = "Material Icons " diff --git a/demos/README.md b/demos/README.md index 39cc9f11ad6..d8f171f7ccf 100644 --- a/demos/README.md +++ b/demos/README.md @@ -11,6 +11,7 @@ These demos showcase different complex use-cases for building UIs with Slint. | [Printer UI ![Printer Demo image](https://github.com/user-attachments/assets/7e7400ad-283a-4404-b04a-8620ba4df452)](./printerdemo) | A fictional user interface for the touch screen of a printer.
[Project...](./printerdemo) | [Wasm Demo](https://slint.dev/snapshots/master/demos/printerdemo/) | | [Energy Meter![Energy meter demo image](https://github.com/user-attachments/assets/abfe03e3-ded6-4ddc-82b7-8303ee45515c "Energy meter demo image")](./energy-monitor/) | A fictional user interface of a device that monitors energy consumption in a building.
[Project...](./energy-monitor) | [Wasm Demo](https://slint.dev/snapshots/master/demos/energy-monitor/) | | [Weather![Weather demo image](./weather-demo/docs/img/desktop-preview.png "7 GUI's demo image")](./weather-demo/) | A simple, cross-platform (Desktop, Android, Wasm) weather application using real weather data from the [OpenWeather](https://openweathermap.org/) API.
[Project...](./weather-demo/) | [Wasm Demo](https://slint.dev/snapshots/master/demos/weather-demo/) | +| [Usecases ![Usecases Demo image](https://github.com/user-attachments/assets/72dd3e98-36b8-41b6-9d6e-6eb6053ace43)](./usecases) | Different example use cases in one app.
[Project...](./usecases) | [Wasm Demo](https://slint.dev/snapshots/master/demos/usecases/) | --- ### Running the Rust Demos diff --git a/demos/usecases/cpp/.gitignore b/demos/usecases/cpp/.gitignore new file mode 100644 index 00000000000..378eac25d31 --- /dev/null +++ b/demos/usecases/cpp/.gitignore @@ -0,0 +1 @@ +build diff --git a/demos/usecases/cpp/CMakeLists.txt b/demos/usecases/cpp/CMakeLists.txt new file mode 100644 index 00000000000..23842bb294c --- /dev/null +++ b/demos/usecases/cpp/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright © SixtyFPS GmbH +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.21) +project(slint_cpp_usecases LANGUAGES CXX) + +if (NOT TARGET Slint::Slint) + find_package(Slint REQUIRED) +endif() + +set(SLINT_STYLE "cosmic-light" CACHE STRING "Style for demo" FORCE) + +add_executable(usecases main.cpp) +target_link_libraries(usecases PRIVATE Slint::Slint) +slint_target_sources(usecases ../ui/app.slint) diff --git a/demos/usecases/cpp/main.cpp b/demos/usecases/cpp/main.cpp new file mode 100644 index 00000000000..99b90d8240c --- /dev/null +++ b/demos/usecases/cpp/main.cpp @@ -0,0 +1,85 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +#include "app.h" + +void init_virtual_keyboard(slint::ComponentHandle app) +{ + app->global().on_key_pressed([=](auto key) { + app->window().dispatch_key_press_event(key); + app->window().dispatch_key_release_event(key); + }); +} + +void run() +{ + auto app = App::create(); + + init_virtual_keyboard(app); + + auto mails = std::make_shared>(std::vector { + CardListViewItem { "Simon Hausmann", "1 hour ago", "Meeting tomorrow", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat." }, + CardListViewItem { "Tobias Hunger", "1 day ago", "Meeting tomorrow", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat." }, + CardListViewItem { "Olivier Goffart", "2 hour ago", "Meeting tomorrow", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat." }, + CardListViewItem { "Aurindam Jana", "5 hour ago", "Meeting tomorrow", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat." }, + CardListViewItem { "Simon Hausmann", "7 hour ago", "Meeting tomorrow", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat." }, + CardListViewItem { "Tobias Hunger", "1 day ago", "Meeting tomorrow", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat." }, + CardListViewItem { "Olivier Goffart", "8 hour ago", "Meeting tomorrow", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat." }, + CardListViewItem { "Aurindam Jana", "9 hour ago", "Meeting tomorrow", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut " + "enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat." }, + }); + + app->global().set_mails(mails); + + app->global().on_search_text_changed( + [mails, app = slint::ComponentWeakHandle(app)](const slint::SharedString &text) { + auto app_lock = app.lock(); + + std::string text_str(text.data()); + + (*app_lock)->global().set_mails( + std::make_shared>( + mails, [text_str](auto e) { + std::string title_str(e.title.data()); + return title_str.find(text_str) != std::string::npos; + })); + }); + + app->run(); +} + +int main() +{ + run(); +} diff --git a/demos/usecases/esp-idf/.gitignore b/demos/usecases/esp-idf/.gitignore new file mode 100644 index 00000000000..97b051f8b7f --- /dev/null +++ b/demos/usecases/esp-idf/.gitignore @@ -0,0 +1,6 @@ +build +managed_components +sdkconfig +sdkconfig.old +dependencies.lock +.cache diff --git a/demos/usecases/esp-idf/CMakeLists.txt b/demos/usecases/esp-idf/CMakeLists.txt new file mode 100644 index 00000000000..5064f79a372 --- /dev/null +++ b/demos/usecases/esp-idf/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright © SixtyFPS GmbH +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.14) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(SLINT_ESP_LOCAL_EXAMPLE ON) +set(SLINT_FEATURE_EXPERIMENTAL ON) +set(EXTRA_COMPONENT_DIRS ../../../api/cpp/esp-idf/) +set(SLINT_STYLE "cosmic-light" CACHE STRING "Style for demo" FORCE) + +add_compile_options(-fdiagnostics-color=always) + +project(slint_esp_usecases_mcu LANGUAGES CXX) diff --git a/demos/usecases/esp-idf/README.md b/demos/usecases/esp-idf/README.md new file mode 100644 index 00000000000..eb854884848 --- /dev/null +++ b/demos/usecases/esp-idf/README.md @@ -0,0 +1,8 @@ + + +# Building + +``` +cd demos/usecases/esp-idf +SLINT_SCALE_FACTOR=2 idf.py flash monitor +``` diff --git a/demos/usecases/esp-idf/main/CMakeLists.txt b/demos/usecases/esp-idf/main/CMakeLists.txt new file mode 100644 index 00000000000..961cd3efe4e --- /dev/null +++ b/demos/usecases/esp-idf/main/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright © SixtyFPS GmbH +# SPDX-License-Identifier: MIT + + +idf_component_register( + SRCS "main.cpp" + INCLUDE_DIRS "." + REQUIRES esp32_p4_function_ev_board_noglib slint +) + +slint_target_sources(${COMPONENT_LIB} ../../ui/app.slint) +target_link_options(${COMPONENT_LIB} PUBLIC -Wl,--allow-multiple-definition) diff --git a/demos/usecases/esp-idf/main/idf_component.yml b/demos/usecases/esp-idf/main/idf_component.yml new file mode 100644 index 00000000000..60a091c9c2a --- /dev/null +++ b/demos/usecases/esp-idf/main/idf_component.yml @@ -0,0 +1,7 @@ +# Copyright © SixtyFPS GmbH +# SPDX-License-Identifier: MIT + +## IDF Component Manager Manifest File +dependencies: + idf: ">=5.1" + espressif/esp32_p4_function_ev_board_noglib: "^1.0.0" diff --git a/demos/usecases/esp-idf/main/main.cpp b/demos/usecases/esp-idf/main/main.cpp new file mode 100644 index 00000000000..55fd26201fc --- /dev/null +++ b/demos/usecases/esp-idf/main/main.cpp @@ -0,0 +1,48 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +#include "../../cpp/main.cpp" + +#include "slint-esp.h" +#include +#include +#include "esp_log.h" + +#include + +#include +#include +#include +#include + +#undef BSP_LCD_H_RES +#define BSP_LCD_H_RES 800 +#undef BSP_LCD_V_RES +#define BSP_LCD_V_RES 1280 + +extern "C" void app_main(void) +{ + + /* Initialize I2C (for touch and audio) */ + bsp_i2c_init(); + + /* Initialize display */ + esp_lcd_panel_handle_t panel_handle = NULL; + bsp_lcd_handles_t handles {}; + + bsp_display_new_with_handles(nullptr, &handles); + + esp_lcd_touch_handle_t touch_handle = NULL; + const bsp_touch_config_t bsp_touch_cfg = {}; + bsp_touch_new(&bsp_touch_cfg, &touch_handle); + + panel_handle = handles.panel; + + /* Set display brightness to 100% */ + bsp_display_backlight_on(); + + slint_esp_init(slint::PhysicalSize({ BSP_LCD_H_RES, BSP_LCD_V_RES }), panel_handle, + touch_handle); + + run(); +} diff --git a/demos/usecases/esp-idf/rust-toolchain.toml b/demos/usecases/esp-idf/rust-toolchain.toml new file mode 100644 index 00000000000..7b45edf944d --- /dev/null +++ b/demos/usecases/esp-idf/rust-toolchain.toml @@ -0,0 +1,5 @@ +# Copyright © SixtyFPS GmbH +# SPDX-License-Identifier: MIT + +[toolchain] +channel = "esp" diff --git a/demos/usecases/esp-idf/sdkconfig.defaults b/demos/usecases/esp-idf/sdkconfig.defaults new file mode 100644 index 00000000000..07ca8f9a629 --- /dev/null +++ b/demos/usecases/esp-idf/sdkconfig.defaults @@ -0,0 +1,80 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_IDF_TARGET="esp32p4" + +# CONFIG_SPIRAM=y +# CONFIG_SPIRAM_MODE_HEX=y +# CONFIG_SPIRAM_SPEED_200M=y +# CONFIG_IDF_EXPERIMENTAL_FEATURES=y +# +# CONFIG_LV_CONF_SKIP=y +# +# #CLIB default +# CONFIG_LV_USE_CLIB_MALLOC=y +# CONFIG_LV_USE_CLIB_SPRINTF=y +# CONFIG_LV_USE_CLIB_STRING=y +# +# # Performance monitor +# CONFIG_LV_USE_OBSERVER=y +# CONFIG_LV_USE_SYSMON=y +# CONFIG_LV_USE_PERF_MONITOR=y +# +# +# # CONFIG_LV_BUILD_EXAMPLES is not set +# +CONFIG_MAIN_TASK_STACK_SIZE=20584 +# #CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +# +CONFIG_COMPILER_OPTIMIZATION_SIZE=y + + + +CONFIG_IDF_TARGET="esp32p4" +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +#CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_BSP_LCD_RGB_BUFFER_NUMS=2 +CONFIG_BSP_LCD_RGB_BOUNCE_BUFFER_MODE=y +CONFIG_BSP_DISPLAY_LVGL_AVOID_TEAR=y +CONFIG_BSP_DISPLAY_LVGL_DIRECT_MODE=y +CONFIG_LV_FONT_MONTSERRAT_12=y +CONFIG_LV_FONT_MONTSERRAT_16=y +CONFIG_LV_FONT_MONTSERRAT_24=y +CONFIG_LV_USE_DEMO_WIDGETS=y +CONFIG_LV_USE_DEMO_BENCHMARK=y +CONFIG_LV_USE_DEMO_STRESS=y +CONFIG_LV_USE_DEMO_MUSIC=y +CONFIG_LV_DEMO_MUSIC_AUTO_PLAY=y +CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y +CONFIG_LV_DISP_DEF_REFR_PERIOD=10 + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y + +## LVGL8 ## +CONFIG_LV_MEM_SIZE_KILOBYTES=48 +CONFIG_LV_USE_PERF_MONITOR=y + +## LVGL9 ## +CONFIG_LV_CONF_SKIP=y + +#CLIB default +CONFIG_LV_USE_CLIB_MALLOC=y +CONFIG_LV_USE_CLIB_SPRINTF=y +CONFIG_LV_USE_CLIB_STRING=y + +# Performance monitor +#CONFIG_LV_USE_OBSERVER=y +#CONFIG_LV_USE_SYSMON=y +#CONFIG_LV_USE_PERF_MONITOR=y diff --git a/demos/usecases/rust/Cargo.toml b/demos/usecases/rust/Cargo.toml new file mode 100644 index 00000000000..f852a0f0212 --- /dev/null +++ b/demos/usecases/rust/Cargo.toml @@ -0,0 +1,34 @@ +# Copyright © SixtyFPS GmbH +# SPDX-License-Identifier: MIT + +[package] +name = "usecases" +version = "1.10.0" +authors = ["Slint Developers "] +edition = "2021" +build = "build.rs" +publish = false +license = "MIT" + +[lib] +crate-type = ["lib", "cdylib"] +path = "src/lib.rs" +name = "usecases_lib" + +[[bin]] +path = "src/main.rs" +name = "usecases" + +[dependencies] +slint = { path = "../../../api/rs/slint", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { version = "0.2" } +console_error_panic_hook = "0.1.5" + +[build-dependencies] +slint-build = { path = "../../../api/rs/build" } + + diff --git a/demos/usecases/rust/build.rs b/demos/usecases/rust/build.rs new file mode 100644 index 00000000000..3f10c9e7640 --- /dev/null +++ b/demos/usecases/rust/build.rs @@ -0,0 +1,12 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +use slint_build::CompilerConfiguration; + +fn main() { + slint_build::compile_with_config( + "../ui/app.slint", + CompilerConfiguration::new().with_style("cosmic".into()), + ) + .unwrap(); +} diff --git a/demos/usecases/rust/index.html b/demos/usecases/rust/index.html new file mode 100644 index 00000000000..7fff70d50bb --- /dev/null +++ b/demos/usecases/rust/index.html @@ -0,0 +1,40 @@ + + + + + + + + + + + Slint use cases Demo (Web Assembly version) + + + + +

This is the Slint Use cases Demo compiled to WebAssembly.

+
+
Loading...
+
+ + + + + + diff --git a/demos/usecases/rust/src/lib.rs b/demos/usecases/rust/src/lib.rs new file mode 100644 index 00000000000..7ce5044c2cb --- /dev/null +++ b/demos/usecases/rust/src/lib.rs @@ -0,0 +1,119 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +slint::include_modules!(); + +#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] +pub fn main() { + // This provides better error messages in debug mode. + // It's disabled in release mode so it doesn't bloat up the file size. + #[cfg(all(debug_assertions, target_arch = "wasm32"))] + console_error_panic_hook::set_once(); + + let app = App::new().unwrap(); + + virtual_keyboard::init(&app); + data::init(&app); + + app.run().unwrap(); +} + +mod virtual_keyboard { + use super::*; + use slint::*; + + pub fn init(app: &App) { + let weak = app.as_weak(); + app.global::().on_key_pressed({ + move |key| { + weak.unwrap() + .window() + .dispatch_event(slint::platform::WindowEvent::KeyPressed { text: key.clone() }); + weak.unwrap() + .window() + .dispatch_event(slint::platform::WindowEvent::KeyReleased { text: key }); + } + }); + } +} + +mod data { + use std::rc::Rc; + + use super::*; + use slint::*; + + pub fn init(app: &App) { + let mail_box_adapter = MailBoxViewAdapter::get(app); + + let message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.".to_string(); + + let mails = VecModel::from_slice(&[ + CardListViewItem { + title: "Simon Hausmann".into(), + note: "1 hour ago".into(), + sub_title: "Meeting tomorrow".into(), + caption: message.clone().into(), + }, + CardListViewItem { + title: "Tobias Hunger".into(), + note: "1 day ago".into(), + sub_title: "Meeting tomorrow".into(), + caption: message.clone().into(), + }, + CardListViewItem { + title: "Olivier Goffart".into(), + note: "1 day".into(), + sub_title: "Meeting tomorrow".into(), + caption: message.clone().into(), + }, + CardListViewItem { + title: "Aurindam Jana".into(), + note: "2 hour ago".into(), + sub_title: "Meeting tomorrow".into(), + caption: message.clone().into(), + }, + CardListViewItem { + title: "Simon Hausmann".into(), + note: "5 hour ago".into(), + sub_title: "Meeting tomorrow".into(), + caption: message.clone().into(), + }, + CardListViewItem { + title: "Tobias Hunger".into(), + note: "7 hours ago".into(), + sub_title: "Meeting tomorrow".into(), + caption: message.clone().into(), + }, + CardListViewItem { + title: "Olivier Goffart".into(), + note: "8 hour ago".into(), + sub_title: "Meeting tomorrow".into(), + caption: message.clone().into(), + }, + CardListViewItem { + title: "Aurindam Jana".into(), + note: "9 hour ago".into(), + sub_title: "Meeting tomorrow".into(), + caption: message.into(), + }, + ]); + + mail_box_adapter.on_search_text_changed({ + let app_weak = app.as_weak(); + let mails = mails.clone(); + + move |text| { + let mails = mails + .clone() + .filter(move |e| e.title.to_lowercase().contains(text.to_lowercase().as_str())); + MailBoxViewAdapter::get(&app_weak.unwrap()).set_mails(Rc::new(mails).into()); + } + }); + + mail_box_adapter.set_mails(mails.into()); + } +} diff --git a/demos/usecases/rust/src/main.rs b/demos/usecases/rust/src/main.rs new file mode 100644 index 00000000000..60ff4cb8cc5 --- /dev/null +++ b/demos/usecases/rust/src/main.rs @@ -0,0 +1,9 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +// 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() { + usecases_lib::main(); +} diff --git a/demos/usecases/ui/app.slint b/demos/usecases/ui/app.slint new file mode 100644 index 00000000000..676fa6391d8 --- /dev/null +++ b/demos/usecases/ui/app.slint @@ -0,0 +1,31 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { MainView, MainViewAdapter, MailViewAdapter, MailBoxViewAdapter, DashboardViewAdapter } from "views.slint"; +export { MainViewAdapter, MailViewAdapter, MailBoxViewAdapter, DashboardViewAdapter } + +import { CardListViewItem } from "widgets.slint"; +export { CardListViewItem } + +import { VirtualKeyboardHandler, VirtualKeyboard } from "virtual_keyboard.slint"; + +export { VirtualKeyboardHandler } + +export component App inherits Window { + preferred-width: 800px; + preferred-height: 1280px; + title: "Slint usecases"; + + main-view := MainView { + width: 100%; + height: 100%; + } + + VirtualKeyboard { + y: TextInputInterface.text-input-focused ? parent.height - self.height : parent.height; + + close => { + main-view.focus(); + } + } +} diff --git a/demos/usecases/ui/assets.slint b/demos/usecases/ui/assets.slint new file mode 100644 index 00000000000..b591eb60356 --- /dev/null +++ b/demos/usecases/ui/assets.slint @@ -0,0 +1,21 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette } from "std-widgets.slint"; + +export global Icons { + out property archive: @image-url("assets/archive.svg"); + out property inbox: @image-url("assets/inbox.svg"); + out property cloud: @image-url("assets/cloud.svg"); + out property document: @image-url("assets/document.svg"); + out property forward: @image-url("assets/forward.svg"); + out property junk: @image-url("assets/junk.svg"); + out property message: @image-url("assets/message.svg"); + out property reply: @image-url("assets/reply.svg"); + out property search: @image-url("assets/search.svg"); + out property send: @image-url("assets/send.svg"); + out property trash: @image-url("assets/trash.svg"); + out property updates: @image-url("assets/updates.svg"); + out property useres: @image-url("assets/users.svg"); + out property slint-logo: Palette.color-scheme == ColorScheme.dark ? @image-url("../../../logo/slint-logo-simple-dark.png") : @image-url("../../../logo/slint-logo-simple-light.png"); +} diff --git a/demos/usecases/ui/assets/archive.svg b/demos/usecases/ui/assets/archive.svg new file mode 100644 index 00000000000..6a9c3344100 --- /dev/null +++ b/demos/usecases/ui/assets/archive.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/arrow-circle-o-left.svg b/demos/usecases/ui/assets/arrow-circle-o-left.svg new file mode 100644 index 00000000000..c5fca3ad996 --- /dev/null +++ b/demos/usecases/ui/assets/arrow-circle-o-left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/demos/usecases/ui/assets/arrow-left.svg b/demos/usecases/ui/assets/arrow-left.svg new file mode 100644 index 00000000000..2288ba1a853 --- /dev/null +++ b/demos/usecases/ui/assets/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/demos/usecases/ui/assets/arrow-right.svg b/demos/usecases/ui/assets/arrow-right.svg new file mode 100644 index 00000000000..f6524a8d73d --- /dev/null +++ b/demos/usecases/ui/assets/arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/demos/usecases/ui/assets/arrow-up.svg b/demos/usecases/ui/assets/arrow-up.svg new file mode 100644 index 00000000000..1b97c1325a7 --- /dev/null +++ b/demos/usecases/ui/assets/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/demos/usecases/ui/assets/chevron-left.svg b/demos/usecases/ui/assets/chevron-left.svg new file mode 100644 index 00000000000..3951e22d2c6 --- /dev/null +++ b/demos/usecases/ui/assets/chevron-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/demos/usecases/ui/assets/cloud.svg b/demos/usecases/ui/assets/cloud.svg new file mode 100644 index 00000000000..3a9d66d54e8 --- /dev/null +++ b/demos/usecases/ui/assets/cloud.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/document.svg b/demos/usecases/ui/assets/document.svg new file mode 100644 index 00000000000..d544b55e9dc --- /dev/null +++ b/demos/usecases/ui/assets/document.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/expand-more.svg b/demos/usecases/ui/assets/expand-more.svg new file mode 100644 index 00000000000..a8197744dbe --- /dev/null +++ b/demos/usecases/ui/assets/expand-more.svg @@ -0,0 +1,3 @@ + + + diff --git a/demos/usecases/ui/assets/forward.svg b/demos/usecases/ui/assets/forward.svg new file mode 100644 index 00000000000..600ae0dc3e3 --- /dev/null +++ b/demos/usecases/ui/assets/forward.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/globe.svg b/demos/usecases/ui/assets/globe.svg new file mode 100644 index 00000000000..abf1c09e0c3 --- /dev/null +++ b/demos/usecases/ui/assets/globe.svg @@ -0,0 +1,4 @@ + + + + diff --git a/demos/usecases/ui/assets/inbox.svg b/demos/usecases/ui/assets/inbox.svg new file mode 100644 index 00000000000..5a21177cc6d --- /dev/null +++ b/demos/usecases/ui/assets/inbox.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/junk.svg b/demos/usecases/ui/assets/junk.svg new file mode 100644 index 00000000000..ac8234200b9 --- /dev/null +++ b/demos/usecases/ui/assets/junk.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/message.svg b/demos/usecases/ui/assets/message.svg new file mode 100644 index 00000000000..493667cff9e --- /dev/null +++ b/demos/usecases/ui/assets/message.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/reply.svg b/demos/usecases/ui/assets/reply.svg new file mode 100644 index 00000000000..27f118082f0 --- /dev/null +++ b/demos/usecases/ui/assets/reply.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/search.svg b/demos/usecases/ui/assets/search.svg new file mode 100644 index 00000000000..fe201508ba5 --- /dev/null +++ b/demos/usecases/ui/assets/search.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/send.svg b/demos/usecases/ui/assets/send.svg new file mode 100644 index 00000000000..50317799800 --- /dev/null +++ b/demos/usecases/ui/assets/send.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/trash.svg b/demos/usecases/ui/assets/trash.svg new file mode 100644 index 00000000000..99e97c668eb --- /dev/null +++ b/demos/usecases/ui/assets/trash.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/updates.svg b/demos/usecases/ui/assets/updates.svg new file mode 100644 index 00000000000..5acaa78e395 --- /dev/null +++ b/demos/usecases/ui/assets/updates.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/demos/usecases/ui/assets/users.svg b/demos/usecases/ui/assets/users.svg new file mode 100644 index 00000000000..39176ec839d --- /dev/null +++ b/demos/usecases/ui/assets/users.svg @@ -0,0 +1,4 @@ + + + + diff --git a/demos/usecases/ui/icons.slint b/demos/usecases/ui/icons.slint new file mode 100644 index 00000000000..a57a1f40433 --- /dev/null +++ b/demos/usecases/ui/icons.slint @@ -0,0 +1,12 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +export global Icons { + out property arrow-up: @image-url("assets/arrow-up.svg"); + out property arrow-left: @image-url("assets/arrow-left.svg"); + out property arrow-right: @image-url("assets/arrow-right.svg"); + out property chevron-left: @image-url("assets/chevron-left.svg"); + out property arrow-circle-o-left: @image-url("assets/arrow-circle-o-left.svg"); + out property globe: @image-url("assets/globe.svg"); + out property expand-more: @image-url("assets/expand-more.svg"); +} diff --git a/demos/usecases/ui/views.slint b/demos/usecases/ui/views.slint new file mode 100644 index 00000000000..d64385e6fde --- /dev/null +++ b/demos/usecases/ui/views.slint @@ -0,0 +1,11 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { DashboardView, DashboardViewAdapter } from "views/dashboard_view.slint"; +export { DashboardView, DashboardViewAdapter } + +import { MailView, MailViewAdapter, MailBoxViewAdapter } from "views/mail_view.slint"; +export { MailView, MailViewAdapter, MailBoxViewAdapter } + +import { MainView, MainViewAdapter } from "views/main_view.slint"; +export { MainView, MainViewAdapter } \ No newline at end of file diff --git a/demos/usecases/ui/views/dashboard_view.slint b/demos/usecases/ui/views/dashboard_view.slint new file mode 100644 index 00000000000..cb3705ee56f --- /dev/null +++ b/demos/usecases/ui/views/dashboard_view.slint @@ -0,0 +1,146 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { ScrollView, GroupBox, Palette, VerticalBox } from "std-widgets.slint"; +import { TitleText, Tile, BarTileModel, BarTiles, BarChart, Value, ValueDisplay } from "../widgets.slint"; +import { Icons } from "../assets.slint"; + +export global WeatherViewAdapter { + in property current-temperature-icon: Icons.cloud; + in property current-temperature: "22°"; + in property current-day: "May 6th 2023"; + in property current-weather-description: "Very cloudy"; + in property <[BarTileModel]> week-model: [ + { + title: "Thu", + icon: Icons.cloud, + max: 21, + min: 18, + absolute-max: 21, + absolute-min: 15, + unit: "°" + }, + { + title: "Fri", + icon: Icons.cloud, + max: 20, + min: 17, + absolute-max: 21, + absolute-min: 15, + unit: "°" + }, + { + title: "Sat", + icon: Icons.cloud, + max: 18, + min: 15, + absolute-max: 21, + absolute-min: 15, + unit: "°" + } + ]; +} + +export global UsageViewAdapter { + in property title: "Usage"; + in property <[Value]> overview-model: [ + { + value: 16.41, + title: "Daily", + unit: "kWh", + }, + { + value: 15.23, + title: "Weekly", + unit: "kWh", + } + ]; + in property <[float]> model: [ + 10.0, + 9.0, + 11.0, + 12.0, + 8.0, + 14.0, + 9.0, + 16.0, + 18.0, + 12.0, + 11.0, + 14.0, + 12.0, + 16.0 + ]; + in property min: 0.0; + in property max: 24.0; +} + + + +export global DashboardViewAdapter { + +} + +export component DashboardView { + ScrollView { + width: 100%; + height: 100%; + + VerticalLayout { + padding: 4px; + spacing: 4px; + + TitleText { + horizontal-alignment: left; + text: "Dashboard"; + } + + GroupBox { + title: "Weather"; + + HorizontalLayout { + Tile { + value: WeatherViewAdapter.current-temperature; + text: WeatherViewAdapter.current-day; + sub-text: WeatherViewAdapter.current-weather-description; + icon: WeatherViewAdapter.current-temperature-icon; + } + + BarTiles { + model: WeatherViewAdapter.week-model; + active: true; + } + + // stretches the empty element + if WeatherViewAdapter.week-model.length == 0 : Rectangle {} + } + } + + GroupBox { + title: "Usage"; + + Rectangle { + BarChart { + preferred-width: 100%; + preferred-height: 100%; + model: UsageViewAdapter.model; + min: UsageViewAdapter.min; + max: UsageViewAdapter.max; + active: true; + } + + VerticalLayout { + alignment: start; + + ValueDisplay { + model: UsageViewAdapter.overview-model; + transparent-background: true; + alternative-colors: true; + active: true; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/demos/usecases/ui/views/header_view.slint b/demos/usecases/ui/views/header_view.slint new file mode 100644 index 00000000000..17efb525274 --- /dev/null +++ b/demos/usecases/ui/views/header_view.slint @@ -0,0 +1,29 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette, HorizontalBox, Switch } from "std-widgets.slint"; +import { Icons } from "../assets.slint"; + +export component HeaderView { + min-height: 48px; + + HorizontalBox { + Image { + max-height: 32px; + source: Icons.slint-logo; + horizontal-alignment: left; + } + + // spaceer + Rectangle {} + + Switch { + text: "Dark Mode"; + checked: Palette.color-scheme == ColorScheme.dark; + + toggled => { + Palette.color-scheme = self.checked ? ColorScheme.dark : ColorScheme.light; + } + } + } +} \ No newline at end of file diff --git a/demos/usecases/ui/views/mail_view.slint b/demos/usecases/ui/views/mail_view.slint new file mode 100644 index 00000000000..b793200bbcb --- /dev/null +++ b/demos/usecases/ui/views/mail_view.slint @@ -0,0 +1,286 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { GroupBox, ComboBox, VerticalBox, GroupBox, GridBox, Palette, TextEdit, Button, Switch, ScrollView } from "std-widgets.slint"; +import { NavigationListView, NavigationListViewItem, Container, ExtendedLineEdit, Icon, CardListView, CardListViewItem, IconButton, TitleText } from "../widgets.slint"; +import { Icons } from "../assets.slint"; + +export global MailViewAdapter { } + +export global MailSideBarViewAdapter { + out property <[string]> accounts: ["jon.doe@slint.dev", "jon.doe@my-mail.com", "jon.doe@gmail.com"]; + + out property <[NavigationListViewItem]> boxes: [ + { text: "Inbox", message: "128", icon: Icons.inbox }, + { text: "Drafts", message: "9", icon: Icons.document }, + { text: "Sent", icon: Icons.send }, + { + text: "Junk", + icon: Icons.junk, + message: "23", + }, + { text: "Trash", icon: Icons.trash }, + { text: "Archive", icon: Icons.archive } + ]; + + out property <[NavigationListViewItem]> custom-boxes: [ + { text: "Social", message: "3972", icon: Icons.useres }, + { text: "Updates", message: "342", icon: Icons.updates }, + { text: "Forums", message: "128", icon: Icons.message } + ]; + in-out property current-box; + in-out property current-custom-box: -1; + + public pure function current-title() -> string { + if current-box > -1 && current-box < boxes.length { + return boxes[current-box].text; + } + if current-custom-box > -1 && current-custom-box < custom-boxes.length { + return custom-boxes[current-custom-box].text; + } + "" + } +} + +export component MailSideBarView { + horizontal-stretch: 0; + min-width: 264px / 2; + + VerticalLayout { + spacing: 4px; + + ComboBox { + model: MailSideBarViewAdapter.accounts; + } + + Container { + NavigationListView { + model: MailSideBarViewAdapter.boxes; + current-item <=> MailSideBarViewAdapter.current-box; + min-height: 248px; + vertical-stretch: 0; + + selected(index) => { + MailSideBarViewAdapter.current-custom-box = -1; + } + } + + Rectangle { + background: Palette.border; + height: 1px; + } + + NavigationListView { + model: MailSideBarViewAdapter.custom-boxes; + current-item <=> MailSideBarViewAdapter.current-custom-box; + + selected(index) => { + MailSideBarViewAdapter.current-box = -1; + } + } + } + } +} + +export global MailBoxViewAdapter { + callback search-text-changed(/* search-text */ string); + + in property title: MailSideBarViewAdapter.current-title(); + in property <[CardListViewItem]> mails: [ + { + title: "Simon Hausmann", + note: "1 hour ago", + sub-title: "Meeting tomorrow", + caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + }, + { title: "Tobias Hunger", note: "1 day ago", sub-title: "Meeting tomorrow", caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." }, + { + title: "Olivier Goffart", + note: "2 hour ago", + sub-title: "Meeting tomorrow", + caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + }, + { + title: "Aurindam Jana", + note: "5 hour ago", + sub-title: "Meeting tomorrow", + caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + }, + { + title: "Simon Hausmann", + note: "7 hour ago", + sub-title: "Meeting tomorrow", + caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + }, + { title: "Tobias Hunger", note: "1 day ago", sub-title: "Meeting tomorrow", caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." }, + { + title: "Olivier Goffart", + note: "8 hour ago", + sub-title: "Meeting tomorrow", + caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + }, + { + title: "Aurindam Jana", + note: "9 hour ago", + sub-title: "Meeting tomorrow", + caption: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + }, + ]; +} + +export component MailBoxView { + horizontal-stretch: 1; + + VerticalLayout { + spacing: 4px; + + TitleText { + text: MailBoxViewAdapter.title; + min-height: 32px; + + } + + Container { + background: Palette.control-background; + + VerticalLayout { + spacing: 8px; + + ExtendedLineEdit { + vertical-stretch: 0; + placeholder-text: "Search by Sender"; + + Icon { + source: Icons.search; + } + + edited => { + MailBoxViewAdapter.search-text-changed(self.text); + } + } + + CardListView { + model: MailBoxViewAdapter.mails; + } + } + } + } +} + +export global MailMessageViewAdapter { + callback move-to-archive(); + callback move-to-junk(); + callback move-to-trash(); + callback reply(); + callback forward(); + callback send(); + + in property message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."; + in-out property mute-this-thread: false; +} + +export component MailMessageView { + horizontal-stretch: 1; + + Container { + background: Palette.control-background; + + HorizontalLayout { + spacing: 8px; + + IconButton { + icon: Icons.archive; + + clicked => { + MailMessageViewAdapter.move-to-archive(); + } + } + + IconButton { + icon: Icons.junk; + + clicked => { + MailMessageViewAdapter.move-to-junk(); + } + } + + IconButton { + icon: Icons.trash; + + clicked => { + MailMessageViewAdapter.move-to-trash(); + } + } + + Rectangle {} + + IconButton { + icon: Icons.reply; + + clicked => { + MailMessageViewAdapter.reply(); + } + } + + IconButton { + icon: Icons.forward; + + clicked => { + MailMessageViewAdapter.forward(); + } + } + } + + VerticalBox { + text-edit := TextEdit { + max-height: 94px; + wrap: word-wrap; + } + + ScrollView { + Text { + y: 0; + vertical-alignment: top; + width: parent.width; + font-size: 14px; + font-weight: 400; + color: Palette.foreground; + text: MailMessageViewAdapter.message; + wrap: word-wrap; + } + } + + HorizontalLayout { + Switch { + text: "Mute this thread"; + checked <=> MailMessageViewAdapter.mute-this-thread; + } + + Button { + text: "Send"; + primary: true; + enabled: text-edit.text != ""; + + clicked => { + MailMessageViewAdapter.send(); + } + } + } + } + } +} + +export component MailView { + HorizontalLayout { + spacing: 16px; + + MailSideBarView { } + + VerticalLayout { + spacing: 16px; + + MailBoxView { } + MailMessageView {} + } + } +} \ No newline at end of file diff --git a/demos/usecases/ui/views/main_view.slint b/demos/usecases/ui/views/main_view.slint new file mode 100644 index 00000000000..4044d93e842 --- /dev/null +++ b/demos/usecases/ui/views/main_view.slint @@ -0,0 +1,40 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { TabWidget, HorizontalBox } from "std-widgets.slint"; +import { MailView } from "mail_view.slint"; +import { HeaderView } from "header_view.slint"; +import { DashboardView } from "dashboard_view.slint"; + +export global MainViewAdapter { } + +export component MainView { + forward-focus: focus-scope; + focus-scope := FocusScope {} + + VerticalLayout { + HeaderView {} + + HorizontalBox { + TabWidget { + Tab { + title: "Mail"; + + HorizontalBox { + MailView { } + } + } + + Tab { + title: "Dashboard"; + + HorizontalLayout { + padding: 4px; + + DashboardView { } + } + } + } + } + } +} \ No newline at end of file diff --git a/demos/usecases/ui/virtual_keyboard.slint b/demos/usecases/ui/virtual_keyboard.slint new file mode 100644 index 00000000000..622f37d226a --- /dev/null +++ b/demos/usecases/ui/virtual_keyboard.slint @@ -0,0 +1,289 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Button, Palette } from "std-widgets.slint"; + +import { Icons } from "icons.slint"; + +component VirtualKeyboardButton { + in property key; + in property icon; + + callback key-pressed(/* key */ string); + + min-width: 32px; + min-height: 32px; + horizontal-stretch: 0; + + states [ + pressed when i-touch-area.pressed : { + i-state-area.opacity: 0.5; + } + ] + + i-container := Rectangle { + border-radius: 4px; + background: Palette.color-scheme == ColorScheme.dark ? #373737 : #ffffff; + + HorizontalLayout { + padding: 8px; + + if (root.key != "") : Text { + text: root.key; + color: Palette.color-scheme == ColorScheme.dark ? #ffffff : #000000; + font-size: 12px; + vertical-alignment: center; + horizontal-alignment: center; + } + + if (root.key == "") : Image { + y: (parent.height - self.height) / 2; + source: root.icon; + height: 18px; + colorize: Palette.color-scheme == ColorScheme.dark ? #ffffff : #000000; + } + } + } + + i-state-area := Rectangle { + border-radius: i-container.border-radius; + opacity: 0; + background: #000000; + + animate opacity { duration: 150ms; } + } + + i-touch-area := TouchArea { + pointer-event(event) => { + if(event.kind == PointerEventKind.down) { + root.key-pressed(key); + } + } + } +} + +export struct KeyModel { + key: string, + shift-key: string, +} + +export global VirtualKeyboardHandler { + in property <[[[KeyModel]]]> default-key-sets: [ + [ + [ + { key: "q", shift-key: "Q" }, + { key: "w", shift-key: "W" }, + { key: "e", shift-key: "E" }, + { key: "r", shift-key: "R" }, + { key: "t", shift-key: "T" }, + { key: "y", shift-key: "Y" }, + { key: "u", shift-key: "U" }, + { key: "i", shift-key: "I" }, + { key: "o", shift-key: "O" }, + { key: "p", shift-key: "P" } + ], + [ + { key: "a", shift-key: "A" }, + { key: "s", shift-key: "S" }, + { key: "d", shift-key: "D" }, + { key: "f", shift-key: "F" }, + { key: "g", shift-key: "G" }, + { key: "h", shift-key: "H" }, + { key: "j", shift-key: "J" }, + { key: "k", shift-key: "K" }, + { key: "l", shift-key: "L" } + ], + [ + { key: "z", shift-key: "Z" }, + { key: "x", shift-key: "X" }, + { key: "c", shift-key: "C" }, + { key: "v", shift-key: "V" }, + { key: "b", shift-key: "B" }, + { key: "n", shift-key: "N" }, + { key: "m", shift-key: "M" }, + { key: ",", shift-key: ";" }, + { key: ".", shift-key: ":" }, + { key: "?", shift-key: "?" } + ], + ], + [ + [ + { key: "1", shift-key: "[" }, + { key: "2", shift-key: "]" }, + { key: "3", shift-key: "{" }, + { key: "4", shift-key: "}" }, + { key: "5", shift-key: "#" }, + { key: "6", shift-key: "%" }, + { key: "7", shift-key: "^" }, + { key: "8", shift-key: "*" }, + { key: "9", shift-key: "+" }, + { key: "0", shift-key: "=" } + ], + [ + { key: "-", shift-key: "_" }, + { key: "/", shift-key: "\\" }, + { key: ":", shift-key: "|" }, + { key: ";", shift-key: "~" }, + { key: "(", shift-key: "<" }, + { key: ")", shift-key: ">" }, + { key: "€", shift-key: "$" }, + { key: "&", shift-key: "€" }, + { key: "@", shift-key: "°" }, + { key: "'", shift-key: "#" }, + ], + [ + { key: ".", shift-key: "." }, + { key: ",", shift-key: "," }, + { key: "?", shift-key: "?" }, + { key: "!", shift-key: "!" }, + { key: "'", shift-key: "'" }, + ], + ] + ]; + + out property current-key-set; + out property <[[KeyModel]]> keys: default-key-sets[self.current-key-set]; + in-out property open; + + callback key_pressed(/* key */ string); + + public function switch-keyboard() { + if (self.current-key-set < self.default-key-sets.length - 1) { + self.current-key-set += 1; + } else { + self.current-key-set -= 1; + } + + self.current-key-set = min(self.default-key-sets.length - 1, max(0, self.current-key-set)) + } +} + +export component VirtualKeyboard { + private property shift; + + callback close(); + + preferred-width: 100%; + + TouchArea {} + + Rectangle { + background: Palette.color-scheme == ColorScheme.dark ? #1c1c1c : #d4d4d4; + height: 100%; + } + + i-layout := VerticalLayout { + padding: 8px; + spacing: 4px; + + for row[index] in VirtualKeyboardHandler.keys : HorizontalLayout { + spacing: 4px; + + if (index == 0) : VirtualKeyboardButton { + key: "ESC"; + + key-pressed => { + VirtualKeyboardHandler.key-pressed(Key.Escape); + } + } + + if (index == 1) : VirtualKeyboardButton { + key: "Tab"; + + key-pressed => { + VirtualKeyboardHandler.key-pressed(Key.Tab); + } + } + + // shift + if (index == 2) : VirtualKeyboardButton { + icon: Icons.arrow-up; + + key-pressed => { + root.shift = !root.shift; + } + } + + for km in row : VirtualKeyboardButton { + key: root.shift ? km.shift-key : km.key; + + key-pressed(key) => { + VirtualKeyboardHandler.key-pressed(key); + root.shift = false; + } + } + + if (index == 0) : VirtualKeyboardButton { + icon: Icons.chevron-left; + + key-pressed => { + VirtualKeyboardHandler.key-pressed(Key.Backspace); + } + } + + if (index == 1) : VirtualKeyboardButton { + icon: Icons.arrow-circle-o-left; + + key-pressed => { + VirtualKeyboardHandler.key-pressed(Key.Return); + } + } + + // shift + if (index == 2) : VirtualKeyboardButton { + icon: Icons.arrow-up; + + key-pressed => { + root.shift = !root.shift; + } + } + } + + HorizontalLayout { + spacing: 4px; + + VirtualKeyboardButton { + icon: Icons.expand-more; + + key-pressed(key) => { + root.close(); + } + } + + VirtualKeyboardButton { + icon: Icons.globe; + + key-pressed(key) => { + VirtualKeyboardHandler.switch-keyboard(); + } + } + VirtualKeyboardButton { + horizontal-stretch: 1; + key: " "; + + key-pressed(key) => { + root.shift = false; + VirtualKeyboardHandler.key-pressed(key); + } + } + VirtualKeyboardButton { + icon: Icons.arrow-left; + + key-pressed(key) => { + VirtualKeyboardHandler.key-pressed(Key.LeftArrow); + } + } + VirtualKeyboardButton { + icon: Icons.arrow-right; + + key-pressed(key) => { + VirtualKeyboardHandler.key-pressed(Key.RightArrow); + } + } + } + + + } + + animate y { duration: 500ms; easing: cubic-bezier(0.05, 0.7, 0.1, 1.0); } +} diff --git a/demos/usecases/ui/widgets.slint b/demos/usecases/ui/widgets.slint new file mode 100644 index 00000000000..6816ac45d76 --- /dev/null +++ b/demos/usecases/ui/widgets.slint @@ -0,0 +1,35 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { CardListView, CardListViewItem } from "widgets/card_list_view.slint"; +export { CardListView, CardListViewItem } + +import { Container, StateContainer } from "widgets/container.slint"; +export { Container, StateContainer } + +import { ExtendedLineEdit } from "widgets/extended_line_edit.slint"; +export { ExtendedLineEdit } + +import { IconButton } from "widgets/icon_button.slint"; +export { IconButton } + +import { Icon } from "widgets/icon.slint"; +export { Icon } + +import { Segmented } from "widgets/segmented.slint"; +export { Segmented } + +import { NavigationListView, NavigationListViewItem } from "widgets/navigation_list_view.slint"; +export { NavigationListView, NavigationListViewItem } + +import { Tile, BarTileModel, BarTiles } from "widgets/tile.slint"; +export { Tile, BarTileModel, BarTiles } + +import { TitleText } from "widgets/title_text.slint"; +export { TitleText } + +import { BarChart } from "widgets/bar_chart.slint"; +export { BarChart } + +import { Value, ValueDisplay } from "widgets/value_display.slint"; +export { Value, ValueDisplay } \ No newline at end of file diff --git a/demos/usecases/ui/widgets/bar_chart.slint b/demos/usecases/ui/widgets/bar_chart.slint new file mode 100644 index 00000000000..b954c74a2db --- /dev/null +++ b/demos/usecases/ui/widgets/bar_chart.slint @@ -0,0 +1,73 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { CosmicPalette } from "styling.slint"; + +component Bar { + in property bar-height; + + horizontal-stretch: 1; + + Rectangle { + border-radius: 2px; + y: parent.height - self.height; + height: bar-height; + clip: true; + + Rectangle { + height: root.height; + y: parent.height - self.height; + background: CosmicPalette.bar-gradient; + } + } +} + +export component BarBackground inherits Rectangle { + border-radius: 2px; + // background: Theme.palette.bar-background-gradient; + opacity: 0.25; +} + +export component ChartPattern { + in property count; + + HorizontalLayout { + spacing: 1px; + for _ in count : BarBackground {} + } +} + +export component BarChart { + in property <[float]> model; + in property min; + in property max; + in property active; + + cache-rendering-hint: true; + + ChartPattern { + count: model.length / 2; + } + + layout := HorizontalLayout { + spacing: 1px; + + for value in model : Bar { + private property display-value; + + min-height: 120px; + preferred-height: 100%; + bar-height: parent.height * (display-value - root.min) / (root.max - root.min); + + states [ + active when active : { + display-value: value; + + in { + animate display-value { duration: 500ms; easing: ease-in-out; } + } + } + ] + } + } +} \ No newline at end of file diff --git a/demos/usecases/ui/widgets/card_list_view.slint b/demos/usecases/ui/widgets/card_list_view.slint new file mode 100644 index 00000000000..6680fa8921f --- /dev/null +++ b/demos/usecases/ui/widgets/card_list_view.slint @@ -0,0 +1,91 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { ListView, Palette, VerticalBox } from "std-widgets.slint"; +import { StateContainer } from "container.slint"; + +export component CardListItem { + callback clicked <=> state-container.clicked; + + in property title <=> title.text; + in property sub-title <=> sub-title.text; + in property note <=> note.text; + in property caption <=> caption.text; + in property selected <=> state-container.checked; + + min-height: max(41px, layout.min-height); + + layout := HorizontalLayout { + padding-bottom: 8px; + + state-container := StateContainer { + VerticalBox { + padding: 16px; + + HorizontalLayout { + spacing: 4px; + + title := Text { + horizontal-stretch: 1; + color: Palette.foreground; + font-size: 10px; + font-weight: 400; + overflow: elide; + } + + note := Text { + horizontal-stretch: 0; + color: Palette.foreground; + font-size: 10px; + font-weight: 400; + } + } + + sub-title := Text { + horizontal-stretch: 1; + color: Palette.foreground; + font-size: 14px; + font-weight: 600; + overflow: elide; + } + + caption := Text { + height: 40px; + wrap: word-wrap; + overflow: elide; + color: Palette.foreground; + font-size: 14px; + font-weight: 400; + } + } + } + } +} + +export struct CardListViewItem { + title: string, + sub-title: string, + note: string, + caption: string +} + +export component CardListView inherits ListView { + callback current-item-changed(/* current-item */ int); + + in property <[CardListViewItem]> model; + in-out property current-item; + + for item[index] in root.model : CardListItem { + height: self.min-height; + title: item.title; + sub-title: item.sub-title; + note: item.note; + caption: item.caption; + selected: index == root.current-item; + + clicked => { + root.current-item = index; + root.current-item-changed(index); + } + } +} diff --git a/demos/usecases/ui/widgets/container.slint b/demos/usecases/ui/widgets/container.slint new file mode 100644 index 00000000000..43f013f2d85 --- /dev/null +++ b/demos/usecases/ui/widgets/container.slint @@ -0,0 +1,43 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette, VerticalBox } from "std-widgets.slint"; +import { CosmicPalette } from "styling.slint"; + +export component StateContainer inherits Rectangle { + callback clicked <=> touch-area.clicked; + + in property checked; + + background: Palette.alternate-background; + border-radius: 8px; + + touch-area := TouchArea {} + + state-layer := Rectangle { + border-radius: root.border-radius; + + states [ + pressed when touch-area.pressed : { + state-layer.background: CosmicPalette.state-pressed; + } + hover when touch-area.has-hover : { + state-layer.background: CosmicPalette.state-hover; + } + checked when root.checked : { + state-layer.background: CosmicPalette.state-selected; + } + ] + } + + @children +} + +export component Container inherits Rectangle { + background: Palette.alternate-background; + border-radius: 8px; + + VerticalBox { + @children + } +} diff --git a/demos/usecases/ui/widgets/extended_line_edit.slint b/demos/usecases/ui/widgets/extended_line_edit.slint new file mode 100644 index 00000000000..3578668de13 --- /dev/null +++ b/demos/usecases/ui/widgets/extended_line_edit.slint @@ -0,0 +1,178 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette } from "std-widgets.slint"; +import { CosmicPalette } from "styling.slint"; + +// copied from common that is not public now +component LineEditBase inherits Rectangle { + in property placeholder-text; + in property font-size <=> text-input.font-size; + in_out property text <=> text-input.text; + in property placeholder-color; + in property enabled <=> text-input.enabled; + in property has-focus: text-input.has-focus; + in property input-type <=> text-input.input-type; + in property horizontal-alignment <=> text-input.horizontal-alignment; + in property read-only <=> text-input.read-only; + in property font-weight <=> text-input.font-weight; + in property text-color; + in property selection-background-color <=> text-input.selection-background-color; + in property selection-foreground-color <=> text-input.selection-foreground-color; + in property margin; + + callback accepted( /* text */ string); + callback edited(/* text */ string); + + public function set-selection-offsets(start: int, end: int) { + text-input.set-selection-offsets(start, end); + } + + public function select-all() { + text-input.select-all(); + } + + public function clear-selection() { + text-input.clear-selection(); + } + + public function cut() { + text-input.cut(); + } + + public function copy() { + text-input.copy(); + } + + public function paste() { + text-input.paste(); + } + + min-height: text-input.preferred-height; + min-width: max(50px, placeholder.min-width); + clip: true; + forward-focus: text-input; + + placeholder := Text { + width: 100%; + height: 100%; + vertical-alignment: center; + text: (root.text == "" && text-input.preedit-text == "") ? root.placeholder-text : ""; + font-size: text-input.font-size; + font-italic: text-input.font-italic; + font-weight: text-input.font-weight; + font-family: text-input.font-family; + color: root.placeholder-color; + horizontal-alignment: root.horizontal-alignment; + } + + text-input := TextInput { + property computed-x; + + x: min(0px, max(parent.width - self.width - self.text-cursor-width, self.computed-x)); + width: max(parent.width - self.text-cursor-width, self.preferred-width); + height: 100%; + vertical-alignment: center; + single-line: true; + color: root.text-color; + + cursor-position-changed(cpos) => { + if (cpos.x + self.computed_x < root.margin) { + self.computed_x = - cpos.x + root.margin; + } else if (cpos.x + self.computed_x > parent.width - root.margin - self.text-cursor-width) { + self.computed_x = parent.width - cpos.x - root.margin - self.text-cursor-width; + } + } + + accepted => { root.accepted(self.text); } + + edited => { root.edited(self.text); } + } +} + +export component ExtendedLineEdit { + in property enabled <=> base.enabled; + in property input-type <=> base.input-type; + in property horizontal-alignment <=> base.horizontal-alignment; + in property read-only <=> base.read-only; + in property font-size <=> base.font-size; + in property placeholder-text <=> base.placeholder-text; + out property has-focus <=> base.has-focus; + in-out property text <=> base.text; + + callback accepted <=> base.accepted; + callback edited <=> base.edited; + accessible-role: text-input; + accessible-value <=> text; + + public function set-selection-offsets(start: int, end: int) { + base.set-selection-offsets(start, end); + } + + public function select-all() { + base.select-all(); + } + + public function clear-selection() { + base.clear-selection(); + } + + public function cut() { + base.cut(); + } + + public function copy() { + base.copy(); + } + + public function paste() { + base.paste(); + } + + vertical-stretch: 0; + horizontal-stretch: 1; + min-width: max(160px, layout.min-width); + min-height: max(32px, layout.min-height); + forward-focus: base; + + states [ + disabled when !root.enabled : { + root.opacity: 0.5; + } + ] + + background := Rectangle { + border-radius: 8px; + background: Palette.control-background; + border-width: 1px; + border-color: CosmicPalette.control-divider; + + layout := HorizontalLayout { + padding-left: 16px; + padding-right: 16px; + spacing: 8px; + + HorizontalLayout { + @children + } + + base := LineEditBase { + font-size: 15 * 0.0769rem; + font-weight: 400; + selection-background-color: Palette.selection-background; + selection-foreground-color: Palette.accent-foreground; + text-color: Palette.foreground; + placeholder-color: CosmicPalette.placeholder-foreground; + margin: layout.padding-left + layout.padding-right; + } + } + + if root.has-focus && root.enabled : Rectangle { + width: parent.width + 2px; + height: parent.height + 2px; + border-radius: parent.border-radius + 2px; + border-color: CosmicPalette.state-focus; + border-width: 1px; + } + } +} diff --git a/demos/usecases/ui/widgets/icon.slint b/demos/usecases/ui/widgets/icon.slint new file mode 100644 index 00000000000..7846b3bcd8f --- /dev/null +++ b/demos/usecases/ui/widgets/icon.slint @@ -0,0 +1,9 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette } from "std-widgets.slint"; + +export component Icon inherits Image { + width: 16px; + colorize: Palette.foreground; +} diff --git a/demos/usecases/ui/widgets/icon_button.slint b/demos/usecases/ui/widgets/icon_button.slint new file mode 100644 index 00000000000..5c04fe92792 --- /dev/null +++ b/demos/usecases/ui/widgets/icon_button.slint @@ -0,0 +1,26 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette } from "std-widgets.slint"; +import { StateContainer } from "container.slint"; + +export component IconButton { + callback clicked <=> state-container.clicked; + + in property icon <=> icon.source; + + width: self.height; + min-height: 32px; + + vertical-stretch: 0; + horizontal-stretch: 0; + + state-container := StateContainer { + background: transparent; + border-radius: max(self.width, self.height) / 2; + icon := Image { + height: 16px; + colorize: Palette.foreground; + } + } +} diff --git a/demos/usecases/ui/widgets/navigation_list_view.slint b/demos/usecases/ui/widgets/navigation_list_view.slint new file mode 100644 index 00000000000..9fe2badb66a --- /dev/null +++ b/demos/usecases/ui/widgets/navigation_list_view.slint @@ -0,0 +1,90 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette, ListView, HorizontalBox } from "std-widgets.slint"; +import { CosmicPalette } from "styling.slint"; +import { Icon } from "icon.slint"; + +export component NavigationListItem { + private property foreground: Palette.foreground; + + callback select <=> touch-area.clicked; + + in property selected; + in property title <=> text.text; + in property message <=> message.text; + in property icon <=> icon.source; + + min-height: max(41px, layout.min-height); + + layout := HorizontalLayout { + padding-bottom: 8px; + + background := Rectangle { + border-radius: 16px; + + HorizontalLayout { + padding-left: 16px; + padding-right: 16px; + + spacing: 8px; + + icon := Icon { + y: (parent.height - self.height) / 2; + colorize: root.foreground; + } + + text := Text { + color: root.foreground; + horizontal-stretch: 1; + vertical-alignment: center; + font-size: 14px; + font-weight: 600; + } + + message := Text { + color: root.foreground; + horizontal-stretch: 0; + vertical-alignment: center; + font-size: 14px; + font-weight: 600; + } + } + + touch-area := TouchArea {} + } + } + + states [ + selected when root.selected : { + background.background: CosmicPalette.state-selected; + foreground: CosmicPalette.accent-text; + } + ] +} + +export struct NavigationListViewItem { + icon: image, + text: string, + message: string, +} + +export component NavigationListView inherits ListView { + in property <[NavigationListViewItem]> model; + in-out property current-item: -1; + + callback selected(/* current-item */ int); + + for item[index] in root.model : NavigationListItem { + height: self.min-height; + title: item.text; + icon: item.icon; + message: item.message; + selected: index == root.current-item; + + select => { + root.current-item = index; + selected(root.current-item); + } + } +} \ No newline at end of file diff --git a/demos/usecases/ui/widgets/segmented.slint b/demos/usecases/ui/widgets/segmented.slint new file mode 100644 index 00000000000..5d87a5f5fcf --- /dev/null +++ b/demos/usecases/ui/widgets/segmented.slint @@ -0,0 +1,4 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +export component Segmented {} \ No newline at end of file diff --git a/demos/usecases/ui/widgets/styling.slint b/demos/usecases/ui/widgets/styling.slint new file mode 100644 index 00000000000..efbf71fe276 --- /dev/null +++ b/demos/usecases/ui/widgets/styling.slint @@ -0,0 +1,39 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette } from "std-widgets.slint"; + +export global CosmicPalette { + out property state-selected: Palette.color-scheme == ColorScheme.dark ? #4D4D4D4D : #98989833; + out property accent-text: Palette.color-scheme == ColorScheme.dark ? #63D0DF : #00525A; + out property control-divider: Palette.color-scheme == ColorScheme.dark ? #DEDEDE33 : #3D3D3D33; + out property state-focus: Palette.color-scheme == ColorScheme.dark ? #63D0DF : #00525A; + out property placeholder-foreground: Palette.color-scheme == ColorScheme.dark ? #959595 : #585858; + out property state-hover: #63636333; + out property state-pressed: Palette.color-scheme == ColorScheme.dark ? #16161680 : #BEBEBE80; + out property bar-gradient: Palette.color-scheme == ColorScheme.dark ? @linear-gradient(180deg, #63D0DF 0%, #00525A 100%) : @linear-gradient(180deg, #00525A 0%, #63D0DF 100%); +} + +export struct TextStyle { + font-size: relative-font-size, + font-weight: int, +} + +export global CosmicFontSettings { + out property light-font-weight: 300; + out property regular-font-weight: 400; + out property semibold-font-weight: 600; + out property body: { + font-size: 14 * 0.0769rem, + font-weight: regular-font-weight + }; + out property body-strong: { + font-size: 14 * 0.0769rem, + font-weight: semibold-font-weight + }; + + out property title-2: { + font-size: 28 * 0.0769rem, + font-weight: regular-font-weight + }; +} diff --git a/demos/usecases/ui/widgets/tile.slint b/demos/usecases/ui/widgets/tile.slint new file mode 100644 index 00000000000..ff3b013edd2 --- /dev/null +++ b/demos/usecases/ui/widgets/tile.slint @@ -0,0 +1,191 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette } from "std-widgets.slint"; +import { CosmicFontSettings } from "styling.slint"; + +export struct BarTileModel { + title: string, + icon: image, + max: int, + min: int, + absolute-min: int, + absolute-max: int, + unit: string, +} + +component ValueLabel { + in property text; + in property unit; + + HorizontalLayout { + Text { + color: Palette.foreground; + vertical-stretch: 0; + horizontal-alignment: right; + text: root.text; + font-size: CosmicFontSettings.body-strong.font-size; + font-weight: CosmicFontSettings.body-strong.font-weight; + } + + Text { + color: Palette.foreground; + vertical-stretch: 0; + horizontal-alignment: left; + text: "°"; + font-size: CosmicFontSettings.body-strong.font-size; + font-weight: CosmicFontSettings.body-strong.font-weight; + } + } +} + +component BarTile { + in property title <=> i-title.text; + in property icon <=> i-icon.source; + in property max; + in property min; + in property unit; + in property absolute-min; + in property absolute-max; + + HorizontalLayout { + alignment: center; + + VerticalLayout { + spacing: 7px; + + i-title := Text { + color: Palette.foreground; + vertical-stretch: 0; + horizontal-alignment: center; + font-size: CosmicFontSettings.body-strong.font-size; + font-weight: CosmicFontSettings.body-strong.font-weight; + } + + i-icon := Image { + height: 20px; + vertical-stretch: 0; + colorize: Palette.accent-background; + } + + ValueLabel { + text: floor(max); + unit: unit; + } + + Rectangle { + private property range: root.absolute-max - root.absolute-min; + private property max-y: self.height * (root.max - root.absolute-min) / range; + private property min-y: self.height * (root.min - root.absolute-min) / range; + + vertical-stretch: 1; + + HorizontalLayout { + alignment: center; + y: parent.height - max-y; + height: max-y - min-y; + + Rectangle { + min_width: 12px; + border-radius: 6px; + + background: Palette.accent-background; + } + } + } + + ValueLabel { + text: floor(min); + unit: unit; + } + } + } +} + + + +export component BarTiles { + in property <[BarTileModel]> model; + in property active; + + horizontal-stretch: 1; + vertical-stretch: 1; + + HorizontalLayout { + padding-right: 16px; + padding-left: 16px; + padding-top: 8px; + padding-bottom: 8px; + + for tile in model : BarTile { + private property display-max: tile.max; + + horizontal-stretch: 1; + title: tile.title; + icon: tile.icon; + min: tile.min; + absolute-min: tile.absolute-min; + absolute-max: tile.absolute-max; + unit: tile.unit; + + states [ + active when active : { + max: display-max; + + in { + animate max { duration: 240ms; easing: cubic-bezier(0, 0, 0, 1); } + } + } + ] + } + } +} + +export component Tile { + in property icon <=> i-icon.source; + in property value <=> i-value.text; + in property text <=> i-text.text; + in property sub-text <=> i-sub-text.text; + + horizontal-stretch: 0; + vertical-stretch: 1; + + VerticalLayout { + padding-left: 16px; + padding-right: 16px; + padding-top: 8px; + padding-bottom: 8px; + spacing: 8px; + alignment: center; + + i-icon := Image { + height: 34px; + horizontal-alignment: center; + colorize: Palette.foreground; + image-fit: contain; + } + + i-value := Text { + horizontal-alignment: center; + color: Palette.foreground; + font-size: CosmicFontSettings.title-2.font-size; + font-weight: CosmicFontSettings.title-2.font-weight; + } + + VerticalLayout { + i-text := Text { + horizontal-alignment: center; + color: Palette.foreground; + font-size: CosmicFontSettings.body-strong.font-size; + font-weight: CosmicFontSettings.body-strong.font-weight; + } + + i-sub-text := Text { + horizontal-alignment: center; + color: Palette.accent-background; + font-size: CosmicFontSettings.body-strong.font-size; + font-weight: CosmicFontSettings.body-strong.font-weight; + } + } + } +} diff --git a/demos/usecases/ui/widgets/title_text.slint b/demos/usecases/ui/widgets/title_text.slint new file mode 100644 index 00000000000..1a84948cc05 --- /dev/null +++ b/demos/usecases/ui/widgets/title_text.slint @@ -0,0 +1,11 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { Palette } from "std-widgets.slint"; +import { CosmicFontSettings } from "styling.slint"; + +export component TitleText inherits Text { + color: Palette.foreground; + font-size: CosmicFontSettings.title-2.font-size; + font-weight: CosmicFontSettings.title-2.font-weight; +} diff --git a/demos/usecases/ui/widgets/value_display.slint b/demos/usecases/ui/widgets/value_display.slint new file mode 100644 index 00000000000..46e217c85dd --- /dev/null +++ b/demos/usecases/ui/widgets/value_display.slint @@ -0,0 +1,113 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +import { CosmicPalette, CosmicFontSettings } from "styling.slint"; +import { Palette } from "std-widgets.slint"; + +component ValueDelegate { + in property active; + in property title <=> title.text; + in property unit <=> unit.text; + in property value; + in property alternative-colors; + + private property display-value; + + states [ + active when active : { + display-value: value; + + in { + animate display-value { duration: 500ms; } + } + } + ] + + HorizontalLayout { + spacing: 15px; + + Rectangle { + min_width: 1px; + background: Palette.accent-background; + horizontal-stretch: 0; + } + + VerticalLayout { + alignment: center; + horizontal-stretch: 1; + + title := Text { + color: Palette.accent-background; + font-size: CosmicFontSettings.body-strong.font-size; + font-weight: CosmicFontSettings.body-strong.font-weight; + } + + HorizontalLayout { + alignment: start; + spacing: 5px; + + Text { + color: Palette.foreground; + text: round(display-value * 100) / 100; + font-size: CosmicFontSettings.body-strong.font-size; + font-weight: CosmicFontSettings.body-strong.font-weight; + vertical-alignment: center; + } + + unit := Text { + y: 4px; + vertical-alignment: center; + color: Palette.accent-background; + font-size: CosmicFontSettings.body.font-size; + font-weight: CosmicFontSettings.body.font-weight; + } + } + } + } +} + +export struct Value { + title: string, + value: float, + unit: string, +} + +export component ValueDisplay { + in property alternative-colors; + in property <[Value]> model; + in property active; + in property transparent-background; + in property vertical; + + min-height: 70px; + + + if(model.length > 0 && !vertical) : HorizontalLayout { + x: 15px; + width: parent.width - 30px; + height: 100%; + padding-top: 12px; + padding-bottom: 12px; + + for value in root.model : ValueDelegate { + width: parent.width / model.length; + horizontal-stretch: 1; + alternative-colors: root.alternative-colors; + title: value.title; + value: value.value; + unit: value.unit; + active: root.active; + } + } + + if(model.length > 0 && vertical) : VerticalLayout { + for value in root.model : ValueDelegate { + vertical-stretch: 1; + alternative-colors: root.alternative-colors; + title: value.title; + value: value.value; + unit: value.unit; + active: root.active; + } + } +} diff --git a/examples/virtual_keyboard/ui/assets/expand-more.svg b/examples/virtual_keyboard/ui/assets/expand-more.svg new file mode 100644 index 00000000000..a8197744dbe --- /dev/null +++ b/examples/virtual_keyboard/ui/assets/expand-more.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/virtual_keyboard/ui/icons.slint b/examples/virtual_keyboard/ui/icons.slint index 60ccd0519b4..a57a1f40433 100644 --- a/examples/virtual_keyboard/ui/icons.slint +++ b/examples/virtual_keyboard/ui/icons.slint @@ -8,4 +8,5 @@ export global Icons { out property chevron-left: @image-url("assets/chevron-left.svg"); out property arrow-circle-o-left: @image-url("assets/arrow-circle-o-left.svg"); out property globe: @image-url("assets/globe.svg"); -} \ No newline at end of file + out property expand-more: @image-url("assets/expand-more.svg"); +} diff --git a/examples/virtual_keyboard/ui/virtual_keyboard.slint b/examples/virtual_keyboard/ui/virtual_keyboard.slint index 646e59db952..622f37d226a 100644 --- a/examples/virtual_keyboard/ui/virtual_keyboard.slint +++ b/examples/virtual_keyboard/ui/virtual_keyboard.slint @@ -161,6 +161,8 @@ export global VirtualKeyboardHandler { export component VirtualKeyboard { private property shift; + callback close(); + preferred-width: 100%; TouchArea {} @@ -240,6 +242,14 @@ export component VirtualKeyboard { HorizontalLayout { spacing: 4px; + VirtualKeyboardButton { + icon: Icons.expand-more; + + key-pressed(key) => { + root.close(); + } + } + VirtualKeyboardButton { icon: Icons.globe; @@ -276,4 +286,4 @@ export component VirtualKeyboard { } animate y { duration: 500ms; easing: cubic-bezier(0.05, 0.7, 0.1, 1.0); } -} \ No newline at end of file +} diff --git a/tests/driver/interpreter/main.rs b/tests/driver/interpreter/main.rs index 7dcc2daa8ce..f02725011d5 100644 --- a/tests/driver/interpreter/main.rs +++ b/tests/driver/interpreter/main.rs @@ -31,6 +31,7 @@ macro_rules! test_example { } test_example!(example_printerdemo, "demos/printerdemo/ui/printerdemo.slint"); +test_example!(example_usecases, "demos/usecases/ui/app.slint"); test_example!(example_memory, "examples/memory/memory.slint"); test_example!(example_slide_puzzle, "examples/slide_puzzle/slide_puzzle.slint"); test_example!(example_todo, "examples/todo/ui/todo.slint");