diff --git a/.github/workflows/build_and_test_reusable.yaml b/.github/workflows/build_and_test_reusable.yaml index bbb544a5a55..c95acafed2e 100644 --- a/.github/workflows/build_and_test_reusable.yaml +++ b/.github/workflows/build_and_test_reusable.yaml @@ -87,6 +87,10 @@ jobs: key: x-v3 save_if: ${{ inputs.save_if }} cache: ${{ inputs.cache }} + - name: Cargo update + if: inputs.rust_version == 'stable' || inputs.rust_version == 'nightly' + # When runing with the lastest rust, ignore the rust_version field from the Cargo.toml because we want to test with the latest versions of the dependencies (that's what our users might get) + run: cargo update --ignore-rust-version - name: Run tests run: cargo test --verbose --all-features --workspace --timings ${{ inputs.extra_args }} --exclude slint-node --exclude pyslint --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude test-driver-python --exclude mcu-board-support --exclude mcu-embassy --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp --exclude slint-python -- --skip=_qt::t env: diff --git a/.github/workflows/build_docs.yaml b/.github/workflows/build_docs.yaml index 40807527401..577d14361d6 100644 --- a/.github/workflows/build_docs.yaml +++ b/.github/workflows/build_docs.yaml @@ -41,7 +41,7 @@ jobs: - name: Set up crate rustdoc link run: | rgb_version=`grep 'rgb = ' internal/core/Cargo.toml | sed 's/^.*"\(.*\)"/\1/'` - echo "RUSTDOCFLAGS=$RUSTDOCFLAGS --extern-html-root-url rgb=https://docs.rs/rgb/$rgb_version/ --extern-html-root-url android_activity=https://docs.rs/android-activity/0.5/ --extern-html-root-url raw_window_handle=https://docs.rs/raw_window_handle/0.6 --extern-html-root-url winit=https://docs.rs/winit/0.30 --extern-html-root-url wgpu=https://docs.rs/wgpu/26 --extern-html-root-url input=https://docs.rs/input/0.9" >> $GITHUB_ENV + echo "RUSTDOCFLAGS=$RUSTDOCFLAGS --extern-html-root-url rgb=https://docs.rs/rgb/$rgb_version/ --extern-html-root-url android_activity=https://docs.rs/android-activity/0.5/ --extern-html-root-url raw_window_handle=https://docs.rs/raw_window_handle/0.6 --extern-html-root-url winit=https://docs.rs/winit/0.30 --extern-html-root-url wgpu=https://docs.rs/wgpu/26 --extern-html-root-url input=https://docs.rs/input/0.9 --extern-html-root-url fontique=https://docs.rs/fontique/0.7" >> $GITHUB_ENV - uses: ./.github/actions/install-linux-dependencies - uses: ./.github/actions/setup-rust with: diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 16a2a77a268..fb26fd080f0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -481,6 +481,9 @@ jobs: --skip example_dial \ --skip example_fancy_switches \ --skip example_sprite_sheet \ + --skip example_grid_model_rows \ + --skip example_vector_as_grid \ + --skip example_vlayout \ --skip test_interpreter_elements_path_fit \ --skip test_interpreter_layout_path \ --skip test_interpreter_7guis_booker \ diff --git a/.github/workflows/upgrade_version.yaml b/.github/workflows/upgrade_version.yaml index 5dad8999e54..be7768112ff 100644 --- a/.github/workflows/upgrade_version.yaml +++ b/.github/workflows/upgrade_version.yaml @@ -50,6 +50,9 @@ jobs: # Version in the Android docs sed -i 's/^slint = { version = "[^"]+"/slint = { version = "${{ github.event.inputs.new_version }}"/' docs/astro/src/content/docs/guide/platforms/mobile/android.mdx + # Some documentation use the ~syntax + sed -i 's/\(slint.*version = \)"~[0-9]*\.[0-9]*"/\1"~'"$(echo ${{ github.event.inputs.new_version }} | sed 's/\([0-9]*\.[0-9]*\)\.[0-9]*/\1/')"'"/' api/rs/slint/Cargo.toml api/rs/slint/lib.rs + echo "Note that the version is not updated in the documentation and README yet" - name: Commit diff --git a/.mise/config.toml b/.mise/config.toml index c887edca1af..b9c3d669c71 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -2,10 +2,6 @@ # SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 # cspell:ignore pipx - -[settings] -disable_backends = ["aqua"] - [tools] ## C++: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..a2d982f8197 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,139 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Slint is a declarative GUI toolkit for building native user interfaces across embedded systems, desktops, mobile, and web platforms. UIs are written in `.slint` markup files and connected to business logic in Rust, C++, JavaScript, or Python. + +## Build Commands + +### Rust (Primary) +```sh +cargo build # Build the workspace +cargo build --release # Release build +cargo test # Run tests (requires cargo build first!) +cargo build --workspace --exclude uefi-demo --release # Build all examples +``` + +### Running Examples +```sh +cargo run --release -p gallery # Run the gallery example +cargo run --release --bin slint-viewer -- path/to/file.slint # View a .slint file +``` + +### C++ Build +```sh +cargo build --lib -p slint-cpp # Build C++ library +mkdir cppbuild && cd cppbuild +cmake -GNinja .. +cmake --build . +``` + +### Node.js Build +```sh +cd api/node && npm install +``` + +## Testing + +**Important**: Run `cargo build` before `cargo test` - the dynamic library must exist first. + +### Test Drivers +```sh +cargo test -p test-driver-interpreter # Fast interpreter tests +cargo test -p test-driver-rust # Rust API tests +cargo test -p test-driver-cpp # C++ tests (build slint-cpp first) +cargo test -p test-driver-nodejs # Node.js tests +cargo test -p doctests # Documentation snippet tests +``` + +### Filtered Testing +```sh +SLINT_TEST_FILTER=layout cargo test -p test-driver-rust # Filter by name +``` + +### Syntax Tests (Compiler Errors) +```sh +cargo test -p i-slint-compiler --features display-diagnostics --test syntax_tests +SLINT_SYNTAX_TEST_UPDATE=1 cargo test -p i-slint-compiler --test syntax_tests # Update expected errors +``` + +### Screenshot Tests +```sh +cargo test -p test-driver-screenshots # Compare against references +SLINT_CREATE_SCREENSHOTS=1 cargo test -p test-driver-screenshots # Generate references +``` + +## Architecture + +### Core Components + +- **`internal/compiler/`** - Slint language compiler (lexer, parser, code generators) + - `parser/` - .slint syntax parsing using Rowan + - `passes/` - Optimization passes + - `generator/` - Code generators for C++, Rust, Python, JS + - `tests/syntax/` - Syntax error test cases + +- **`internal/core/`** - Runtime library (properties, layout, animations, accessibility) + +- **`internal/interpreter/`** - Dynamic compilation for scripting languages + +- **`internal/backends/`** - Platform windowing/input: + - `winit/` - Cross-platform (primary) + - `qt/` - Qt integration + - `android-activity/`, `linuxkms/` + +- **`internal/renderers/`** - Rendering engines: + - `femtovg/` - OpenGL ES 2.0 + - `skia/` - Skia graphics + - `software/` - CPU-only fallback + +### Language APIs (`api/`) + +- `rs/slint/` - Rust public crate +- `rs/macros/` - `slint!` procedural macro +- `rs/build/` - Build script support +- `cpp/` - C++ API with CMake integration +- `node/` - Node.js bindings (Neon) +- `python/` - Python bindings (PyO3) + +### Tools + +- `tools/lsp/` - Language Server Protocol for editor integration +- `tools/compiler/` - CLI compiler +- `tools/viewer/` - .slint file viewer with hot reload + +### Key Patterns + +- Internal crates (`internal/`) are not semver-stable - they use exact version pinning +- FFI modules are gated with `#[cfg(feature = "ffi")]` +- C++ headers generated via `cargo xtask cbindgen` +- Extensive Cargo features control renderers (`renderer-femtovg`, `renderer-skia`, `renderer-software`) and backends (`backend-winit`, `backend-qt`) + +## Code Style + +- Rust: `rustfmt` enforced in CI +- C++: `clang-format` enforced in CI +- Linear git history preferred (rebase/squash merges) + +## Platform Prerequisites + +### Linux +```sh +# Debian/Ubuntu +sudo apt install libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev \ + libfontconfig-dev libssl-dev clang libavcodec-dev libavformat-dev \ + libavutil-dev libavfilter-dev libavdevice-dev libasound2-dev pkg-config +``` + +### macOS +```sh +xcode-select --install +brew install pkg-config ffmpeg +``` + +### Windows +- Enable symlinks: `git clone -c core.symlinks=true https://github.com/slint-ui/slint` +- Install MSVC Build Tools +- FFMPEG via vcpkg or manual installation diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a5b9c3270..b3bd6ba4697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to this project are documented in this file. - Diagnostics now report range instead of just a position - Translations: allow to opt out of default context - Fixed debug performance overlay not working (#10198) + - Qt backend: worked around leak in Plasma when setting a window icon. ### Slint Language @@ -29,7 +30,9 @@ All notable changes to this project are documented in this file. - Added `stroke-line-join` for Path (#9912) - Added `from ` syntax to `@conic-gradient` - `row`, `col`, `colspan`, and `rowspan` properties can now be changed at runtime + - Support for `if` and `for` in `GridLayout` - New `StyledText` element for displaying rich text, as well as `styled-text` type + - Fixed missing dependency detection on `Image.source-clip` ### Widgets @@ -39,12 +42,14 @@ All notable changes to this project are documented in this file. - Fixed some widgets that could still be edited when disabled or read-only - SpinBox: added `read-only` property - TabWidget: added `orientation` property (#3688) - - TextEdit and LineEdit: Added `font-family` property + - TextEdit and LineEdit: Added `font-family` and `font-italic` properties ### Rust - - Added `slint::register_font_from_memory` + - Added `slint::fontique` module, guarded with `unstable-fontique-07` feature, to provide access + to fontique collection types for registering custom fonts at run-time. - Added `slint::language::ColorScheme` + - In live preview mode, fixed panic when custom Model access the component's property (#10278) ### C++ diff --git a/Cargo.toml b/Cargo.toml index a3f17d6f08a..858b41d8403 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -165,7 +165,7 @@ serde_json = { version = "1.0.96" } softbuffer = { version = "0.4.4", default-features = false } spin_on = { version = "0.1" } strum = { version = "0.27.1", default-features = false, features = ["derive"] } -toml_edit = { version = "0.23.0" } +toml_edit = { version = "0.24.0" } ttf-parser = { version = "0.25" } typed-index-collections = "3.2" web-sys = { version = "0.3.72", default-features = false } diff --git a/api/cpp/include/slint.h b/api/cpp/include/slint.h index a6fcdd13f13..455b9dbf1a8 100644 --- a/api/cpp/include/slint.h +++ b/api/cpp/include/slint.h @@ -124,10 +124,13 @@ inline SharedVector solve_box_layout(const cbindgen_private::BoxLayoutDat } inline SharedVector -organize_grid_layout(cbindgen_private::Slice input_data) +organize_grid_layout(cbindgen_private::Slice input_data, + cbindgen_private::Slice repeater_indices) { SharedVector result; - cbindgen_private::slint_organize_grid_layout(input_data, &result); + cbindgen_private::Slice ri = + make_slice(reinterpret_cast(repeater_indices.ptr), repeater_indices.len); + cbindgen_private::slint_organize_grid_layout(input_data, ri, &result); return result; } @@ -142,26 +145,32 @@ inline SharedVector organize_dialog_button_layout( inline SharedVector solve_grid_layout(const cbindgen_private::GridLayoutData &data, - cbindgen_private::Slice constraints, - cbindgen_private::Orientation orientation) + cbindgen_private::Slice constraints, + cbindgen_private::Orientation orientation, + cbindgen_private::Slice repeater_indices) { SharedVector result; - cbindgen_private::slint_solve_grid_layout(&data, constraints, orientation, &result); + cbindgen_private::Slice ri = + make_slice(reinterpret_cast(repeater_indices.ptr), repeater_indices.len); + cbindgen_private::slint_solve_grid_layout(&data, constraints, orientation, ri, &result); return result; } inline cbindgen_private::LayoutInfo grid_layout_info(const cbindgen_private::GridLayoutOrganizedData &organized_data, - cbindgen_private::Slice constraints, float spacing, + cbindgen_private::Slice constraints, + cbindgen_private::Slice repeater_indices, float spacing, const cbindgen_private::Padding &padding, cbindgen_private::Orientation orientation) { - return cbindgen_private::slint_grid_layout_info(&organized_data, constraints, spacing, &padding, - orientation); + cbindgen_private::Slice ri = + make_slice(reinterpret_cast(repeater_indices.ptr), repeater_indices.len); + return cbindgen_private::slint_grid_layout_info(&organized_data, constraints, ri, spacing, + &padding, orientation); } inline cbindgen_private::LayoutInfo -box_layout_info(cbindgen_private::Slice cells, float spacing, +box_layout_info(cbindgen_private::Slice cells, float spacing, const cbindgen_private::Padding &padding, cbindgen_private::LayoutAlignment alignment) { @@ -169,16 +178,18 @@ box_layout_info(cbindgen_private::Slice cel } inline cbindgen_private::LayoutInfo -box_layout_info_ortho(cbindgen_private::Slice cells, +box_layout_info_ortho(cbindgen_private::Slice cells, const cbindgen_private::Padding &padding) { return cbindgen_private::slint_box_layout_info_ortho(cells, &padding); } /// Access the layout cache of an item within a repeater -inline float layout_cache_access(const SharedVector &cache, int offset, int repeater_index) +template +inline T layout_cache_access(const SharedVector &cache, int offset, int repeater_index, + int entries_per_item) { - size_t idx = size_t(cache[offset]) + repeater_index * 2; + size_t idx = size_t(cache[offset]) + repeater_index * entries_per_item; return idx < cache.size() ? cache[idx] : 0; } diff --git a/api/node/Cargo.toml b/api/node/Cargo.toml index d460278e291..4243d1fddb7 100644 --- a/api/node/Cargo.toml +++ b/api/node/Cargo.toml @@ -15,6 +15,7 @@ rust-version.workspace = true version.workspace = true categories = ["gui", "development-tools"] build = "build.rs" +publish = false [lib] crate-type = ["cdylib"] diff --git a/api/node/README.md b/api/node/README.md index c29ae93e873..3e87994453e 100644 --- a/api/node/README.md +++ b/api/node/README.md @@ -30,7 +30,7 @@ npm install slint-ui You need to install the following components: - * **[Node.js](https://nodejs.org/download/release/)** (v16. or newer) + * **[Node.js](https://nodejs.org/download/release/)** (v20 or newer) * **[pnpm](https://www.pnpm.io/)** * **[Rust compiler](https://www.rust-lang.org/tools/install)** diff --git a/api/node/cover.md b/api/node/cover.md index 2052dc847b1..091eabd4bdd 100644 --- a/api/node/cover.md +++ b/api/node/cover.md @@ -21,7 +21,7 @@ in detail. To use Slint with Node.js, ensure the following programs are installed: - * **[Node.js](https://nodejs.org/download/release/)** (v16. or newer) + * **[Node.js](https://nodejs.org/download/release/)** (v20 or newer) * **[npm](https://www.npmjs.com/)** To use Slint with Deno, ensure the following programs are installed: diff --git a/api/rs/slint/Cargo.toml b/api/rs/slint/Cargo.toml index 1725582c556..15b95cb7fff 100644 --- a/api/rs/slint/Cargo.toml +++ b/api/rs/slint/Cargo.toml @@ -200,7 +200,7 @@ backend-android-activity-05 = [ ## in your `Cargo.toml` when enabling this feature: ## ## ```toml -## slint = { version = "~1.14", features = ["unstable-wgpu-26"] } +## slint = { version = "~1.15", features = ["unstable-wgpu-26"] } ## ``` unstable-wgpu-26 = [ "i-slint-core/unstable-wgpu-26", @@ -219,7 +219,7 @@ unstable-wgpu-26 = [ ## in your `Cargo.toml` when enabling this feature: ## ## ```toml -## slint = { version = "~1.14", features = ["unstable-wgpu-27"] } +## slint = { version = "~1.15", features = ["unstable-wgpu-27"] } ## ``` unstable-wgpu-27 = [ "i-slint-core/unstable-wgpu-27", @@ -236,7 +236,7 @@ unstable-wgpu-27 = [ ## in your `Cargo.toml` when enabling this feature: ## ## ```toml -## slint = { version = "~1.12", features = ["unstable-winit-030"] } +## slint = { version = "~1.15", features = ["unstable-winit-030"] } ## ``` unstable-winit-030 = ["backend-winit", "dep:i-slint-backend-winit", "i-slint-backend-selector/unstable-winit-030"] @@ -250,10 +250,23 @@ unstable-winit-030 = ["backend-winit", "dep:i-slint-backend-winit", "i-slint-bac ## in your `Cargo.toml` when enabling this feature: ## ## ```toml -## slint = { version = "~1.13", features = ["unstable-libinput-09"] } +## slint = { version = "~1.15", features = ["unstable-libinput-09"] } ## ``` unstable-libinput-09 = ["i-slint-backend-selector/unstable-libinput-09"] +## Enable support for exposing [fontique](https://docs.rs/fontique/0.7.0/fontique/) related APIs. +## +## APIs guarded with this feature are *NOT* subject to the usual Slint API stability guarantees. This feature as well as the APIs changed or removed +## in future minor releases of Slint, likely to be replaced by a feature with a similar name but the fontique version suffix being bumped. +## +## To avoid unintended compilation failures, we recommend to use the [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements) +## in your `Cargo.toml` when enabling this feature: +## +## ```toml +## slint = { version = "~1.15", features = ["unstable-fontique-07"] } +## ``` +unstable-fontique-07 = ["i-slint-common/shared-fontique"] + [dependencies] i-slint-core = { workspace = true } slint-macros = { workspace = true } @@ -321,5 +334,6 @@ features = [ "unstable-wgpu-27", "unstable-winit-030", "unstable-libinput-09", + "unstable-fontique-07", ] rustdoc-args = ["--generate-link-to-definition"] diff --git a/api/rs/slint/docs.rs b/api/rs/slint/docs.rs index 391901bbbad..be54a2681e5 100644 --- a/api/rs/slint/docs.rs +++ b/api/rs/slint/docs.rs @@ -76,9 +76,9 @@ pub mod generated_code { /// ```ignore /// let sample = SampleComponent::new().unwrap(); /// let sample_weak = sample.as_weak(); - /// sample.as_ref().on_hello(move || { + /// sample.on_hello(move || { /// let sample = sample_weak.unwrap(); - /// sample.as_ref().set_counter(42); + /// sample.set_counter(42); /// }); /// ``` pub fn on_hello(&self, f: impl Fn() + 'static) {} diff --git a/api/rs/slint/lib.rs b/api/rs/slint/lib.rs index d9c2a1375b0..66eac51dd19 100644 --- a/api/rs/slint/lib.rs +++ b/api/rs/slint/lib.rs @@ -211,33 +211,19 @@ compile_error!( pub use slint_macros::slint; +pub use i_slint_backend_selector::api::*; +pub use i_slint_core::api::*; #[doc(hidden)] #[deprecated(note = "Experimental type was made public by mistake")] pub use i_slint_core::component_factory::ComponentFactory; #[cfg(not(target_arch = "wasm32"))] pub use i_slint_core::graphics::{BorrowedOpenGLTextureBuilder, BorrowedOpenGLTextureOrigin}; -pub use i_slint_core::translations::{SelectBundledTranslationError, select_bundled_translation}; - -// keep in sync with internal/interpreter/api.rs -pub use i_slint_backend_selector::api::*; -#[cfg(feature = "std")] -pub use i_slint_common::sharedfontique::{ - FontHandle, RegisterFontError, register_font_from_memory, -}; -pub use i_slint_core::api::*; -pub use i_slint_core::graphics::{ - Brush, Color, Image, LoadImageError, Rgb8Pixel, Rgba8Pixel, RgbaColor, SharedPixelBuffer, -}; pub use i_slint_core::model::{ FilterModel, MapModel, Model, ModelExt, ModelNotify, ModelPeer, ModelRc, ModelTracker, ReverseModel, SortModel, StandardListViewItem, TableColumn, VecModel, }; -pub use i_slint_core::sharedvector::SharedVector; pub use i_slint_core::timers::{Timer, TimerMode}; -pub use i_slint_core::{ - format, - string::{SharedString, ToSharedString}, -}; +pub use i_slint_core::translations::{SelectBundledTranslationError, select_bundled_translation}; pub mod private_unstable_api; @@ -487,7 +473,7 @@ pub mod wgpu_26 { //! //! `Cargo.toml`: //! ```toml - //! slint = { version = "~1.13", features = ["unstable-wgpu-26"] } + //! slint = { version = "~1.15", features = ["unstable-wgpu-26"] } //! ``` //! //! `main.rs`: @@ -584,7 +570,7 @@ pub mod wgpu_27 { //! //! `Cargo.toml`: //! ```toml - //! slint = { version = "~1.14", features = ["unstable-wgpu-27"] } + //! slint = { version = "~1.15", features = ["unstable-wgpu-27"] } //! ``` //! //! `main.rs`: @@ -671,7 +657,7 @@ pub mod winit_030 { //! //! `Cargo.toml`: //! ```toml - //! slint = { version = "~1.12", features = ["unstable-winit-030"] } + //! slint = { version = "~1.15", features = ["unstable-winit-030"] } //! ``` //! //! `main.rs`: @@ -726,3 +712,55 @@ pub mod winit_030 { /// Deprecated alias to [`EventResult`] pub type WinitWindowEventResult = EventResult; } + +#[cfg(feature = "unstable-fontique-07")] +pub mod fontique { + //! Fontique 0.7 specific types and re-exports. + //! + //! *Note*: This module is behind a feature flag and may be removed or changed in future minor releases, + //! as new major Fontique releases become available. + //! + //! Use the types, functions, and re-exports in this module to register custom fonts at run-time for use + //! by Slint's renderers. + + pub use i_slint_common::sharedfontique::fontique; + + #[i_slint_core_macros::slint_doc] + /// Returns a clone of [`fontique::Collection`] that's used by Slint for text rendering. It's set up + /// with shared storage, so fonts registered with the returned collection or additionally configured font + /// fallbacks apply to the entire process. + /// + /// Note: The recommended way of including custom fonts is at compile time of Slint files. For details, + /// see also the [Font Handling](slint:FontHandling) documentation. + /// + /// The example below sketches out the steps for registering a downloaded font to add additional glyph + /// coverage for Japanese text: + /// + /// `Cargo.toml`: + /// ```toml + /// slint = { version = "~1.15", features = ["unstable-fontique-07"] } + /// ``` + /// + /// `main.rs`: + /// ```rust,no_run + /// use slint::fontique::fontique; + /// + /// fn main() { + /// // ... + /// let downloaded_font: Vec = todo!("Download https://somewebsite.com/font.ttf"); + /// let blob = fontique::Blob::new(std::sync::Arc::new(downloaded_font)); + /// let mut collection = slint::fontique::shared_collection(); + /// let fonts = collection.register_fonts(blob, None); + /// collection + /// .append_fallbacks(fontique::FallbackKey::new("Hira", None), fonts.iter().map(|x| x.0)); + /// collection + /// .append_fallbacks(fontique::FallbackKey::new("Kana", None), fonts.iter().map(|x| x.0)); + /// collection + /// .append_fallbacks(fontique::FallbackKey::new("Hani", None), fonts.iter().map(|x| x.0)); + /// // ... + /// } + /// ``` + pub fn shared_collection() -> fontique::Collection { + i_slint_common::sharedfontique::COLLECTION.inner.clone() + } +} diff --git a/docs/astro/astro.config.mjs b/docs/astro/astro.config.mjs index 04e25a7771d..8cb996cd2aa 100644 --- a/docs/astro/astro.config.mjs +++ b/docs/astro/astro.config.mjs @@ -63,6 +63,20 @@ export default defineConfig({ collapsed: true, items: [ "guide/tooling/vscode", + { + label: "Other Editors", + collapsed: true, + items: [ + "guide/tooling/manual-setup", + "guide/tooling/kate", + "guide/tooling/qt-creator", + "guide/tooling/helix", + "guide/tooling/neo-vim", + "guide/tooling/sublime-text", + "guide/tooling/jetbrains-ide", + "guide/tooling/zed", + ], + }, "guide/tooling/figma-inspector", ], }, @@ -319,6 +333,13 @@ export default defineConfig({ items: [ "reference/std-widgets/overview", "reference/std-widgets/style", + { + label: "Globals", + autogenerate: { + directory: + "reference/std-widgets/globals", + }, + }, { label: "Basic Widgets", autogenerate: { diff --git a/docs/astro/src/assets/guide/tooling/helix-show-preview.webp b/docs/astro/src/assets/guide/tooling/helix-show-preview.webp new file mode 100644 index 00000000000..28756ec061b Binary files /dev/null and b/docs/astro/src/assets/guide/tooling/helix-show-preview.webp differ diff --git a/docs/astro/src/assets/guide/tooling/kate-lsp-setup.webp b/docs/astro/src/assets/guide/tooling/kate-lsp-setup.webp new file mode 100644 index 00000000000..faf9952e5b1 Binary files /dev/null and b/docs/astro/src/assets/guide/tooling/kate-lsp-setup.webp differ diff --git a/docs/astro/src/assets/guide/tooling/nvim-show-preview.webp b/docs/astro/src/assets/guide/tooling/nvim-show-preview.webp new file mode 100644 index 00000000000..f78955ee438 Binary files /dev/null and b/docs/astro/src/assets/guide/tooling/nvim-show-preview.webp differ diff --git a/docs/astro/src/assets/guide/tooling/qt-creator-lsp-setup.webp b/docs/astro/src/assets/guide/tooling/qt-creator-lsp-setup.webp new file mode 100644 index 00000000000..3c5b9a2b3dd Binary files /dev/null and b/docs/astro/src/assets/guide/tooling/qt-creator-lsp-setup.webp differ diff --git a/docs/astro/src/assets/guide/tooling/slint-viewer.webp b/docs/astro/src/assets/guide/tooling/slint-viewer.webp new file mode 100644 index 00000000000..304661bfb8a Binary files /dev/null and b/docs/astro/src/assets/guide/tooling/slint-viewer.webp differ diff --git a/docs/astro/src/content/docs/guide/tooling/helix.mdx b/docs/astro/src/content/docs/guide/tooling/helix.mdx new file mode 100644 index 00000000000..3acdc05598d --- /dev/null +++ b/docs/astro/src/content/docs/guide/tooling/helix.mdx @@ -0,0 +1,34 @@ +--- +// cSpell: ignore Textobject +title: Helix +description: Helix Configuration for Slint +--- + +import Link from '@slint/common-files/src/components/Link.astro'; + +To install the Slint Language server, check the . + +[Helix](https://helix-editor.com/) works out of the box without further configuration. To check if Helix detects Slint Language server successfully, run this command: + +```sh +hx --health slint +``` + +The output should be like: + +``` +Configured language servers: + ✓ slint-lsp: /home/user/.local/bin/slint-lsp +Configured debug adapter: None +Configured formatter: None +Highlight queries: ✓ +Textobject queries: ✓ +Indent queries: ✓ +``` + +### Live Preview + +To open the live preview, place the caret over a component name and trigger the code actions (bound to `a` by default). +Depending on your configuration, this action might be bound to something else, so please check your configuration for the appropriate key binding (`code_action`). + +![Opening a live Preview from Helix](../../../../assets/guide/tooling/helix-show-preview.webp) diff --git a/docs/astro/src/content/docs/guide/tooling/jetbrains-ide.mdx b/docs/astro/src/content/docs/guide/tooling/jetbrains-ide.mdx new file mode 100644 index 00000000000..be7bf3f2605 --- /dev/null +++ b/docs/astro/src/content/docs/guide/tooling/jetbrains-ide.mdx @@ -0,0 +1,9 @@ +--- +// cSpell: ignore kizeevov +title: JetBrains IDE +description: JetBrains IDE Configuration for Slint +--- + +[kizeevov/slint-idea-plugin](https://github.com/kizeevov/slint-idea-plugin) has a plugin for the Intellij platform. + +_Note: This plugin is developed by [@kizeevov](https://github.com/kizeevov)._ diff --git a/docs/astro/src/content/docs/guide/tooling/kate.mdx b/docs/astro/src/content/docs/guide/tooling/kate.mdx new file mode 100644 index 00000000000..1c8125be7df --- /dev/null +++ b/docs/astro/src/content/docs/guide/tooling/kate.mdx @@ -0,0 +1,54 @@ +--- +// cSpell: ignore ksyntaxhighlighter +title: Kate +description: Kate Configuration for Slint +--- + +import Link from '@slint/common-files/src/components/Link.astro'; + +Before we start, it's important to note that Kate relies on the presence of syntax highlighting file for the usage of the LSP. +Therefore, we'll set up the syntax highlighting first. + +### Syntax Highlighting + +The file [slint.ksyntaxhighlighter.xml][syntax-xml] needs to be copied into a location where Kate can find it. +See the [kate documentation](https://docs.kde.org/stable5/en/kate/katepart/highlight.html#katehighlight-xml-format) + +On Linux, this can be done by running this command + +```sh +mkdir -p ~/.local/share/org.kde.syntax-highlighting/syntax/ +wget https://raw.githubusercontent.com/slint-ui/slint/master/editors/kate/slint.ksyntaxhighlighter.xml -O ~/.local/share/org.kde.syntax-highlighting/syntax/slint.xml +``` + +On Windows, download [slint.ksyntaxhighlighter.xml][syntax-xml] into `%USERPROFILE%\AppData\Local\org.kde.syntax-highlighting\syntax` + +### LSP + +After setting up the syntax highlighting, you can now install the Slint Language server. Check the for instructions. + +Once it is installed, go to *Settings > Configure Kate*. In the *Plugins* section, enable the *LSP-Client* plugin. This will add a *LSP Client* section in the settings dialog. In that *LSP Client* section, go to the *User Server Settings*, and enter the following in the text area: + +```json +{ + "servers": { + "Slint": { + "path": ["%{ENV:HOME}/.cargo/bin", "%{ENV:USERPROFILE}/.cargo/bin"], + "command": ["slint-lsp"], + "highlightingModeRegex": "Slint" + } + } +} +``` + +![Kate LSP Setup](../../../../assets/guide/tooling/kate-lsp-setup.webp) + +### Live Preview + +Once the LSP is correctly set up, to preview a component, first, position your cursor on the name definition of the component you want to preview +(for instance, `MainWindow` in `component MainWindow inherits Window {`). +Then, activate the *Show Preview* code action. +You can do this by using the Alt+Enter shortcut to bring up the code action menu, +or find it in the menu bar at *LSP Client > Code Action > Show Preview* + +[syntax-xml]: diff --git a/docs/astro/src/content/docs/guide/tooling/manual-setup.mdx b/docs/astro/src/content/docs/guide/tooling/manual-setup.mdx new file mode 100644 index 00000000000..17ae921365a --- /dev/null +++ b/docs/astro/src/content/docs/guide/tooling/manual-setup.mdx @@ -0,0 +1,108 @@ +--- +// cSpell: ignore libx libxcb libxkbcommon libinput libgbm +title: Manual Setup +description: Manually set up Slint for your editor +--- + +import Link from '@slint/common-files/src/components/Link.astro'; + +We provide extensions or configuration files for different editors to better support .slint files. + +### Editors + +- +- +- +- +- +- +- +- + +If your favorite editor is not in the list of supported editors, it just means we did not test it, not that it doesn't work. We do provide a language server for Slint that should work with most editor that supports the Language Server Protocol (LSP). If you do test your editor with it, we would be happy to accept a pull request that adds instructions here. + +## Slint Language Server (`slint-lsp`) + +Most modern editors use the [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/) to add support for different programming languages. +Slint provides an LSP implementation with the `slint-lsp` binary. + +### Installation + +If you have Rust installed, you can install the binary by running the following command: + +```sh +cargo install slint-lsp +``` + +This makes the latest released version available in `$HOME/.cargo/bin`. If you would like to try a development version, you can also point `cargo install` to the git repository: +for the released version. Or, to install the development version: + +```sh +cargo install slint-lsp --git https://github.com/slint-ui/slint --force +``` + + +Alternatively, you can download one of our pre-built binaries for Linux or Windows: + +1. Go to [the latest Slint release](https://github.com/slint-ui/slint/releases/latest) +2. From "Assets" download either `slint-lsp-linux.tar.gz` for a Linux x86-64 binary + or `slint-lsp-windows-x86_64.zip` for a Windows x86-64 binary. +3. Uncompress the downloaded archive into a location of your choice. + +Make sure the required dependencies are found. On Debian-like systems install them with the following command: + +```shell +sudo apt install -y build-essential libx11-xcb1 libx11-dev libxcb1-dev libxkbcommon0 libinput10 libinput-dev libgbm1 libgbm-dev +``` + +### Editor configuration + +Once you have `slint-lsp` installed, configure your editor to use the binary, no arguments are required. +See our [list of editor configurations](#editors) for details. +If your editor is not on this list, please refer to your editors documentation for details on how to set up language servers. + +### Formatting Slint files + +Slint files can be auto-formatted with `slint-lsp` in the following ways: + +- `slint-lsp format ` - reads the file and outputs the formatted version to stdout +- `slint-lsp format -i ` - reads the file and saves the output to the same file +- `slint-lsp format /dev/stdin` - using /dev/stdin you can achieve the special behavior + of reading from stdin and writing to stdout + +Note that `.slint` files are formatted, while `.md` and `.rs` files are searched for `.slint` blocks. +All other files are left untouched. + +If you have `slint-lsp` configured in your editor, you should be able to format .slint files via your editor as well. + +## Slint Live Preview (`slint-viewer`) + +Slint's live preview feature lets you see your code changes in real-time. +This is a really powerful way to iterate quickly on your UI without having to recompile anything. + +If you have slint-lsp configured in your editor, you can launch the live preview directly from your editor. + +**Example in Neovim:** + +![Opening Live Preview from a Neovim Popup](../../../../assets/guide/tooling/nvim-show-preview.webp) + +### Running `slint-viewer` from the terminal + +To open the live preview from a terminal, you can use the `slint-viewer` binary. + +To install it either: + +* [Download the binary](https://github.com/slint-ui/slint/releases/latest) +* Run `cargo install slint-viewer` if you have a Rust installation + +Then run `slint-viewer --auto-reload `. + +**Example on the [Printer Demo](https://github.com/slint-ui/slint/blob/master/demos/printerdemo/ui/printerdemo.slint)** + +![Slint Printer Demo](../../../../assets/guide/tooling/slint-viewer.webp) + +## Additional resources + +* [Slint Plugin for (Neo)vim](https://github.com/slint-ui/vim-slint) +* [Slint TreeSitter Grammar](https://github.com/slint-ui/tree-sitter-slint) +* [Editor Configuration Source](https://github.com/slint-ui/slint/tree/master/editors) diff --git a/docs/astro/src/content/docs/guide/tooling/neo-vim.mdx b/docs/astro/src/content/docs/guide/tooling/neo-vim.mdx new file mode 100644 index 00000000000..30127d070fd --- /dev/null +++ b/docs/astro/src/content/docs/guide/tooling/neo-vim.mdx @@ -0,0 +1,78 @@ +--- +// cSpell: ignore vimrc nvim lspconfig kosayoda +title: (Neo-)Vim +description: Vim and Neovim Configuration for Slint +--- + +import Link from '@slint/common-files/src/components/Link.astro'; + +## Vim + +To install the Slint Language server, check the . + +Vim support the Language Server Protocol via its [Conquer of Completion](https://github.com/neoclide/coc.nvim) +plugin. Together with the Slint LSP server, this enables inline diagnostics and code completion when +editing `.slint` files. + +After installing the extension, for example via [vim-plug](https://github.com/junegunn/vim-plug), +two additional configuration changes are needed to integrate the LSP server with vim: + +1. Make vim recognize the `.slint` files with the correct file type + +Install the [`slint-ui/vim-slint`](https://github.com/slint-ui/vim-slint) plugin. + +Alternatively you can add the following to your vim configuration file (e.g. `vimrc`) to +enable automatic recognition of `.slint` files: + +{/* cSpell: disable */} +``` +autocmd BufEnter *.slint :setlocal filetype=slint +``` +{/* cSpell: enable */} + +2. Make sure the slint language server is installed and can be found in PATH. + +3. Configure Conquer of Completion to use the Slint LSP server + +Start `vim` and run the `:CocConfig` command to bring up the buffer that allows editing +the JSON configuration file (`coc-settings.json`), and make sure the following mapping +exists under the `language` server section: + +```json +{ + "languageserver": { + "slint": { + "command": "slint-lsp", + "filetypes": ["slint"] + } + } +} +``` + +## Neovim + +Follow step 1. of the Vim section to get support for `.slint` files. + +The easiest way to use the language server in Neovim is via the [`neovim/nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig) plugin. + +To install the language server you can: + +- +- install [Mason](https://github.com/mason-org/mason.nvim) and run `:MasonInstall slint-lsp` (on Windows, Linux and macOS) + +### Live Preview + +Once the `slint-lsp` language server is installed and running, you can open the live preview +by placing the caret over a component name and triggering the code actions (bound to `gra` by default). +Depending on your configuration, this action might be bound to something else, so please check your configuration for the appropriate key binding (`vim.lsp.buf.code_action()`). + +You may also want to install a plugin that indicates when code actions are available like [kosayoda/nvim-lightbulb](https://github.com/kosayoda/nvim-lightbulb). + +**Example with nvim-lightbulb**: + +![Opening Live Preview from Neovim](../../../../assets/guide/tooling/nvim-show-preview.webp) + +### Tree-sitter + +If you use `nvim-treesitter` you can install the Tree Sitter parser for Slint using `TSInstall slint` +for syntax highlighting and indentation support. diff --git a/docs/astro/src/content/docs/guide/tooling/qt-creator.mdx b/docs/astro/src/content/docs/guide/tooling/qt-creator.mdx new file mode 100644 index 00000000000..dfd187c2fc7 --- /dev/null +++ b/docs/astro/src/content/docs/guide/tooling/qt-creator.mdx @@ -0,0 +1,36 @@ +--- +title: Qt Creator +description: Qt Creator Configuration for Slint +--- + +import Link from '@slint/common-files/src/components/Link.astro'; + +### Syntax Highlighting + +For the **syntax highlighting**, QtCreator supports the same format as Kate, with +the [xml file][syntax-xml] at the same location. +Refer to the instruction from the to enable syntax highlighting. + +### LSP + +To install the Slint Language server, check the . + +To setup the lsp: + + 1. Install the `slint-lsp` binary + 2. Then in Qt creator, go to *Tools > Option* and select the *Language Client* section. + 3. Click *Add* + 4. As a name, use "Slint" + 5. use `*.slint` as a file pattern. (don't use MIME types) + 6. As executable, select the `slint-lsp` binary (no arguments required) + 7. Click *Apply* or *Ok* + +![Qt Creator LSP Configuration](../../../../assets/guide/tooling/qt-creator-lsp-setup.webp) + +### Live Preview + +Once you have set up the LSP, in order to **preview a component**, when you have a .slint file open, place your cursor to +the name of the component you would like to preview and press *Alt + Enter* to open +the code action menu. Select *Show Preview* from that menu. + +[syntax-xml]: diff --git a/docs/astro/src/content/docs/guide/tooling/sublime-text.mdx b/docs/astro/src/content/docs/guide/tooling/sublime-text.mdx new file mode 100644 index 00000000000..1f0ba06e323 --- /dev/null +++ b/docs/astro/src/content/docs/guide/tooling/sublime-text.mdx @@ -0,0 +1,30 @@ +--- +// cSpell: ignore sublimelsp +title: Sublime Text +description: Sublime Text Configuration for Slint +--- + +import Link from '@slint/common-files/src/components/Link.astro'; + +To install the Slint Language server, check the . + +To setup the LSP: + +1. Make sure the slint language server is installed +2. Using Package Control in Sublime Text, install the LSP package ([sublimelsp/LSP](https://github.com/sublimelsp/LSP)) +3. Download the Slint syntax highlighting files into your User Package folder, + e.g. on macOS `~/Library/Application Support/Sublime Text/Packages/User/` : + https://raw.githubusercontent.com/slint-ui/slint/master/editors/sublime/Slint.sublime-syntax + https://raw.githubusercontent.com/slint-ui/slint/master/editors/sublime/Slint.tmPreferences +4. Download the LSP package settings file into your User Package folder: + https://raw.githubusercontent.com/slint-ui/slint/master/editors/sublime/LSP.sublime-settings +5. Modify the slint-lsp command path in `LSP.sublime-settings` to point to the cargo installation path in your home folder (**Replace YOUR_USER by your username**): + `"command": ["/home/YOUR_USER/.cargo/bin/slint-lsp"]` +6. Run "LSP: Enable Language Server Globally" or "LSP: Enable Language Server in Project" from Sublime's Command Palette to allow the server to start. +7. Open a .slint file - if the server starts its name will be in the left side of the status bar. + +### Live Preview + +In order to **preview a component**, when you have a .slint file open, place your cursor to +the name of the component you would like to preview and select the "Show preview" button that +will appear on the right of the editor pane. diff --git a/docs/astro/src/content/docs/guide/tooling/vscode.mdx b/docs/astro/src/content/docs/guide/tooling/vscode.mdx index 920d6d9eea5..476bda89db6 100644 --- a/docs/astro/src/content/docs/guide/tooling/vscode.mdx +++ b/docs/astro/src/content/docs/guide/tooling/vscode.mdx @@ -6,6 +6,7 @@ description: Get going VS Code import { Steps, Tabs, TabItem } from '@astrojs/starlight/components'; import { YouTube } from 'astro-embed'; +import Link from '@slint/common-files/src/components/Link.astro'; @@ -17,7 +18,7 @@ it's also the easiest to get started with. :::note[Note] -We support many other tools and editors, see [here](https://github.com/slint-ui/slint/blob/master/editors/README.md#editors). +We support many other tools and editors, see . ::: diff --git a/docs/astro/src/content/docs/guide/tooling/zed.mdx b/docs/astro/src/content/docs/guide/tooling/zed.mdx new file mode 100644 index 00000000000..2a208500e0f --- /dev/null +++ b/docs/astro/src/content/docs/guide/tooling/zed.mdx @@ -0,0 +1,12 @@ +--- +title: Zed +description: Zed Configuration for Slint +--- + +import Link from '@slint/common-files/src/components/Link.astro'; + +[Zed](https://zed.dev) is a high-performance, multiplayer code editor. The [zed-slint extension](https://github.com/slint-ui/slint/tree/master/editors/zed), originally developed and donated by Luke Jones, now lives under the slint organization. It integrates the latest release of the into Zed, offering code completion and syntax highlighting. Install the extension via the following steps: + +1. Open the extensions tab via the Zed -> Extensions menu. +2. In the search field, enter "slint". +3. Click on "Install" for the "Slint" extension. diff --git a/docs/astro/src/content/docs/reference/std-widgets/globals/palette.mdx b/docs/astro/src/content/docs/reference/std-widgets/globals/palette.mdx new file mode 100644 index 00000000000..18abbd21593 --- /dev/null +++ b/docs/astro/src/content/docs/reference/std-widgets/globals/palette.mdx @@ -0,0 +1,79 @@ +--- +// cSpell: ignore DSLINT +title: Palette +description: Property reference for the Palette global. +--- + +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; +import Link from '@slint/common-files/src/components/Link.astro'; + +Use `Palette` to create custom widgets that match the colors of +the selected style e.g. fluent, cupertino, material, or qt. + +See for details on the available styles. + +## Properties + +### background + +Defines the default background brush. Use this if none of the more specialized background brushes apply. + + +### foreground + +Defines the foreground brush that is used for content that is displayed on `background` brush. + + +### alternate-background + +Defines an alternate background brush that is used for example for text input controls or panels like a side bar. + + +### alternate-foreground + +Defines the foreground brush that is used for content that is displayed on `alternate-background` brush. + + +### control-background + +Defines the default background brush for controls, such as push buttons, combo boxes, etc. + + +### control-foreground + +Defines the foreground brush that is used for content that is displayed on `control-background` brush. + + +### accent-background + +Defines the background brush for highlighted controls such as primary buttons. + + +### accent-foreground + +Defines the foreground brush that is used for content that is displayed on `accent-background` brush. + + +### selection-background + +Defines the background brush that is used to highlight a selection such as a text selection. + + +### selection-foreground + +Defines the foreground brush that is used for content that is displayed on `selection-background` brush. + + +### border + +Defines the brush that is used for borders such as separators and widget borders. + + +### color-scheme + +Read this property to determine the color scheme used by the palette. +Set this property to force a dark or light color scheme. All styles +except for the Qt style support setting a dark or light color scheme. + + + diff --git a/docs/astro/src/content/docs/reference/std-widgets/globals/stylemetrics.mdx b/docs/astro/src/content/docs/reference/std-widgets/globals/stylemetrics.mdx new file mode 100644 index 00000000000..93dca71dd25 --- /dev/null +++ b/docs/astro/src/content/docs/reference/std-widgets/globals/stylemetrics.mdx @@ -0,0 +1,25 @@ +--- +// cSpell: ignore DSLINT +title: StyleMetrics +description: Property reference for the StyleMetrics global. +--- + +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; +import Link from '@slint/common-files/src/components/Link.astro'; + +Use `StyleMetrics` to create custom widgets that match the layout settings of +the selected style e.g. fluent, cupertino, material, or qt. + +See for details on the available styles. + +## Properties + +### layout-spacing + +Defines the default layout spacing. This spacing is also used by `VerticalBox`, `HorizontalBox` and `GridBox`. + + +### layout-padding + +Defines the default layout padding. This padding is also used by `VerticalBox`, `HorizontalBox` and `GridBox`. + diff --git a/docs/astro/src/content/docs/reference/std-widgets/overview.mdx b/docs/astro/src/content/docs/reference/std-widgets/overview.mdx index 249b5564931..255d8cfd409 100644 --- a/docs/astro/src/content/docs/reference/std-widgets/overview.mdx +++ b/docs/astro/src/content/docs/reference/std-widgets/overview.mdx @@ -4,7 +4,6 @@ description: Widgets Overview. --- import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; -import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; import Link from '@slint/common-files/src/components/Link.astro'; ```slint playground @@ -29,90 +28,7 @@ export component MyCustomWidget { Slint provides a series of built-in widgets that can be imported from `"std-widgets.slint"`. The widget appearance depends on the selected style. -See for details how to select the style. If no style is selected, `native` is the default. If `native` isn't available, `fluent` is the default. +See for details how to select the style and how to use the `Palette` and `StyleMetrics` properties. If no style is selected, `native` is the default. If `native` isn't available, `fluent` is the default. All widgets support all . - -## Palette Properties - -Use `Palette` to create custom widgets that match the colors of -the selected style e.g. fluent, cupertino, material, or qt. - -### background - -Defines the default background brush. Use this if none of the more specialized background brushes apply. - - -### foreground - -Defines the foreground brush that is used for content that is displayed on `background` brush. - - -### alternate-background - -Defines an alternate background brush that is used for example for text input controls or panels like a side bar. - - -### alternate-foreground - -Defines the foreground brush that is used for content that is displayed on `alternate-background` brush. - - -### control-background - -Defines the default background brush for controls, such as push buttons, combo boxes, etc. - - -### control-foreground - -Defines the foreground brush that is used for content that is displayed on `control-background` brush. - - -### accent-background - -Defines the background brush for highlighted controls such as primary buttons. - - -### accent-foreground - -Defines the foreground brush that is used for content that is displayed on `accent-background` brush. - - -### selection-background - -Defines the background brush that is used to highlight a selection such as a text selection. - - -### selection-foreground - -Defines the foreground brush that is used for content that is displayed on `selection-background` brush. - - -### border - -Defines the brush that is used for borders such as separators and widget borders. - - -### color-scheme - -Read this property to determine the color scheme used by the palette. -Set this property to force a dark or light color scheme. All styles -except for the Qt style support setting a dark or light color scheme. - - - -## StyleMetrics Properties - -Use `StyleMetrics` to create custom widgets that match the layout settings of -the selected style e.g. fluent, cupertino, material, or qt. - -### layout-spacing - -Defines the default layout spacing. This spacing is also used by `VerticalBox`, `HorizontalBox` and `GridBox`. - - -### layout-padding - -Defines the default layout padding. This padding is also used by `VerticalBox`, `HorizontalBox` and `GridBox`. - diff --git a/docs/astro/src/content/docs/reference/std-widgets/style.mdx b/docs/astro/src/content/docs/reference/std-widgets/style.mdx index f83154d483b..de1a97fb92c 100644 --- a/docs/astro/src/content/docs/reference/std-widgets/style.mdx +++ b/docs/astro/src/content/docs/reference/std-widgets/style.mdx @@ -6,6 +6,8 @@ description: std-widgets Style. import { Tabs, TabItem } from '@astrojs/starlight/components'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; +import Link from '@slint/common-files/src/components/Link.astro'; You can modify the look of these widgets by choosing a style. @@ -81,7 +83,7 @@ main_window.run() ## Using Style Properties In Your Own Components -The global `Palette` and `StyleMetrics` properties can be accessed and will be set to the appropriate values of the current style. +The global and properties can be accessed and will be set to the appropriate values of the current style. ```slint playground import { Palette, StyleMetrics } from "std-widgets.slint"; diff --git a/docs/astro/src/content/docs/reference/std-widgets/views/textedit.mdx b/docs/astro/src/content/docs/reference/std-widgets/views/textedit.mdx index 7493a9ab2e2..11c5b5378e9 100644 --- a/docs/astro/src/content/docs/reference/std-widgets/views/textedit.mdx +++ b/docs/astro/src/content/docs/reference/std-widgets/views/textedit.mdx @@ -39,6 +39,11 @@ The size of the font of the input text. The name of the font family selected for rendering the text. +### font-italic + +The italic state of the font of the input text + + ### text The text being edited diff --git a/docs/building.md b/docs/building.md index b2c2c6bef27..6b4c85e2a6e 100644 --- a/docs/building.md +++ b/docs/building.md @@ -83,7 +83,7 @@ To use Slint from C++, the following extra dependencies are needed: To use Slint from Node.js, the following extra dependencies are needed. -- **[Node.js](https://nodejs.org/en/)** (including npm) At this time you will need to use the version 16. +- **[Node.js](https://nodejs.org/en/)** (including npm) Version 20 or newer is recommended. - **[Python](https://www.python.org)** ### Symlinks in the repository (Windows) diff --git a/docs/common/src/components/CodeSnippetMD.astro b/docs/common/src/components/CodeSnippetMD.astro index 8b1ce4916ba..69bd95bc6ae 100644 --- a/docs/common/src/components/CodeSnippetMD.astro +++ b/docs/common/src/components/CodeSnippetMD.astro @@ -14,16 +14,27 @@ interface Props { skip?: boolean; scale?: number; } -const { imagePath, imageAlt, skip, needsBackground } = Astro.props as Props; +const { + imagePath, + imageAlt, + skip, + needsBackground, + imageWidth: _imageWidth, + imageHeight: _imageHeight, +} = Astro.props as Props; const images = import.meta.glob<{ default: ImageMetadata }>( "/src/assets/generated/*.{jpeg,jpg,png,gif}", ); -if (!images[imagePath]) { - throw new Error( - `"${imagePath}" does not exist in glob: "src/assets/generated/*.{jpeg,jpg,png,gif}"`, - ); + +let imageMeta: ImageMetadata | undefined = undefined; +if (imagePath.length > 0) { + if (!images[imagePath]) { + throw new Error( + `"${imagePath}" does not exist in glob: "src/assets/generated/*.{jpeg,jpg,png,gif}"`, + ); + } + imageMeta = (await images[imagePath]()).default; } -const imageSrc = images[imagePath](); const imageCSS = `image-block ${needsBackground ? "screenshot-container" : ""}`; --- @@ -31,8 +42,14 @@ const imageCSS = `image-block ${needsBackground ? "screenshot-container" : ""}`;
-
- {imageAlt} -
+ + {imageMeta &&
+ {imageAlt} +
} } diff --git a/docs/common/src/utils/utils.ts b/docs/common/src/utils/utils.ts index 9ce3fbb775e..684ffa83661 100644 --- a/docs/common/src/utils/utils.ts +++ b/docs/common/src/utils/utils.ts @@ -211,3 +211,15 @@ export function removeLeadingSpaces(input: string, spaces = 4): string { }); return modifiedLines.join("\n"); } + +export const trim = (str = "", ch?: string) => { + let start = 0; + let end = str.length || 0; + while (start < end && str[start] === ch) { + ++start; + } + while (end > start && str[end - 1] === ch) { + --end; + } + return start > 0 || end < str.length ? str.substring(start, end) : str; +}; diff --git a/docs/development.md b/docs/development.md index f0b57c2132f..7d877d3ee75 100644 --- a/docs/development.md +++ b/docs/development.md @@ -74,6 +74,9 @@ HTML documentation can be generated with something like cargo doc --document-private-items --no-deps --open ``` +The documentation that lives on is rendered with Astro Starlight. +See the ./astro/README.md file for details. + ## Rust to C++ bindings We use a rather complex mechanism to expose internal data structures implemented in Rust to C++, in a way that allows us to provide a nice C++ API. diff --git a/editors/README.md b/editors/README.md index 1bfb037fca1..376c7134291 100644 --- a/editors/README.md +++ b/editors/README.md @@ -2,218 +2,16 @@ # Editor Configuration for Slint This folder contains extensions or configuration files for different editor to better support .slint files. -This README contains information on how to configure various editors. - -If your favorite editor is not in this list, it just means we did not test it, not that it doesn't work. -We do provide a [language server for Slint](../tools/lsp) that should work with most editor that supports -the Language Server Protocol (LSP) -(see its [README.md](../tools/lsp/README.md) for more info on how to install it). -If you do test your editor with it, we would be happy to accept a pull request that adds instructions here. +See [the Slint documentation](https://snapshots.slint.dev/master/docs/slint/guide/tooling/manual-setup/) for details on how to configure your editor of choice. ## Editors -- [Visual Studio Code](#visual-studio-code) -- [Kate](#kate) -- [Qt Creator](#qtcreator) -- [Helix](#helix) -- [Vim](#vim) -- [Neovim](#neovim) -- [Sublime Text](#sublime-text) -- [JetBrains IDE](#jetbrains-ide) -- [Zed](#zed) - -## Visual Studio Code - -For VSCode, we have an [extension in this repository](vscode), you can install it -directly from the market place. This includes the Slint language server and is a one-stop shop to -get you started. - -## Kate - -Before we start, it's important to note that Kate relies on the presence of syntax highlighting file for the usage of the LSP. -Therefore, we'll set up the syntax highlighting first. - -### Syntax Highlighting - -The file [slint.ksyntaxhighlighter.xml](kate/slint.ksyntaxhighlighter.xml) needs to be copied into a location where Kate can find it. -See the [kate documentation](https://docs.kde.org/stable5/en/kate/katepart/highlight.html#katehighlight-xml-format) - -On Linux, this can be done by running this command - -```sh -mkdir -p ~/.local/share/org.kde.syntax-highlighting/syntax/ -wget https://raw.githubusercontent.com/slint-ui/slint/master/editors/kate/slint.ksyntaxhighlighter.xml -O ~/.local/share/org.kde.syntax-highlighting/syntax/slint.xml -``` - -On Windows, download [slint.ksyntaxhighlighter.xml](./slint.ksyntaxhighlighter.xml) into `%USERPROFILE%\AppData\Local\org.kde.syntax-highlighting\syntax` - -### LSP - -After setting up the syntax highlighting, you can now install the Slint Language server. Check the [LSP README.md](../tools/lsp/README.md) for instructions. - -Once it is installed, go to *Settings > Configure Kate*. In the *Plugins* section, enable the *LSP-Client* plugin. This will add a *LSP Client* section in the settings dialog. In that *LSP Client* section, go to the *User Server Settings*, and enter the following in the text area: - -```json -{ - "servers": { - "Slint": { - "path": ["%{ENV:HOME}/.cargo/bin", "%{ENV:USERPROFILE}/.cargo/bin"], - "command": ["slint-lsp"], - "highlightingModeRegex": "Slint" - } - } -} -``` - -To preview a component, first, position your cursor on the name definition of the component you want to preview -(for instance, `MainWindow` in `component MainWindow inherits Window {`). -Then, activate the *Show Preview* code action. -You can do this by using the Alt+Enter shortcut to bring up the code action menu, -or find it in the menu bar at *LSP Client > Code Action > Show Preview* - - - - -## QtCreator - -### Syntax Highlighting - -For the **syntax highlighting**, QtCreator supports the same format as Kate, with -the [xml file](kate/slint.ksyntaxhighlighter.xml) at the same location. -Refer to the instruction from the [previous section](#syntax-highlighting) to enable syntax highlighting. - -### LSP - -To install the Slint Language server, check the [LSP README.md](../tools/lsp/README.md). - -To setup the lsp: - - 1. Install the `slint-lsp` binary - 2. Then in Qt creator, go to *Tools > Option* and select the *Language Client* section. - 3. Click *Add* - 4. As a name, use "Slint" - 5. use `*.slint` as a file pattern. (don't use MIME types) - 6. As executable, select the `slint-lsp` binary (no arguments required) - 7. Click *Apply* or *Ok* - - - -In order to **preview a component**, when you have a .slint file open, place your cursor to -the name of the component you would like to preview and press *Alt + Enter* to open -the code action menu. Select *Show Preview* from that menu. - -## Helix - -To install the Slint Language server, check the [LSP README.md](../tools/lsp/README.md). - -[Helix](https://helix-editor.com/) works out of the box without further configuration. To check if Helix detects Slint Language server successfully, run this command: - -```sh -hx --health slint -``` - -The output should be like: - -``` -Configured language servers: - ✓ slint-lsp: /home/user/.local/bin/slint-lsp -Configured debug adapter: None -Configured formatter: None -Highlight queries: ✓ -Textobject queries: ✓ -Indent queries: ✓ -``` - -## Vim - -To install the Slint Language server, check the [LSP README.md](../tools/lsp/README.md). - -Vim support the Language Server Protocol via its [Conquer of Completion](https://github.com/neoclide/coc.nvim) -plugin. Together with the Slint LSP server, this enables inline diagnostics and code completion when -editing `.slint` files. - -After installing the extension, for example via [vim-plug](https://github.com/junegunn/vim-plug), -two additional configuration changes are needed to integrate the LSP server with vim: - -1. Make vim recognize the `.slint` files with the correct file type - -Install the `slint-ui/vim-slint` plugin. - -Alternatively you can add the following to your vim configuration file (e.g. `vimrc`) to -enable automatic recognition of `.slint` files: - -``` -autocmd BufEnter *.slint :setlocal filetype=slint -``` - -2. Make sure the slint language server is installed and can be found in PATH. - -3. Configure Conquer of Completion to use the Slint LSP server - -Start `vim` and run the `:CocConfig` command to bring up the buffer that allows editing -the JSON configuration file (`coc-settings.json`), and make sure the following mapping -exists under the `language` server section: - -```json -{ - "languageserver": { - "slint": { - "command": "slint-lsp", - "filetypes": ["slint"] - } - } -} -``` - -### Neovim - -Follow step 1. of the Vim section to get support for `.slint` files. - -The easiest way to use the language server itself in Neovim is via the `neovim/nvim-lspconfig` -and `williamboman/nvim-lsp-installer` plugins. Once these are installed -you can run `:LspInstall slint_lsp` to install the lsp binary (on Windows, Linux, and macOS). - -Once the slint_lsp language server is installed and running, you can trigger the live preview -via the code actions. Unfortunately there are several ways to trigger these, so please check your -configuration. - -Also, if you use `nvim-treesitter` you can install the Tree Sitter parser for Slint using `TSInstall slint` -for syntax highlighting and indentation support. - -## Sublime Text - -To install the Slint Language server, check the [LSP README.md](../tools/lsp/README.md). - -To setup the LSP: - -1. Make sure the slint language server is installed -2. Using Package Control in Sublime Text, install the LSP package (sublimelsp/LSP) -3. Download the Slint syntax highlighting files into your User Package folder, - e.g. on macOS `~/Library/Application Support/Sublime Text/Packages/User/` : - https://raw.githubusercontent.com/slint-ui/slint/master/editors/sublime/Slint.sublime-syntax - https://raw.githubusercontent.com/slint-ui/slint/master/editors/sublime/Slint.tmPreferences -4. Download the LSP package settings file into your User Package folder: - https://raw.githubusercontent.com/slint-ui/slint/master/editors/sublime/LSP.sublime-settings -5. Modify the slint-lsp command path in `LSP.sublime-settings` to point to the cargo installation path in your home folder (**Replace YOUR_USER by your username**): - `"command": ["/home/YOUR_USER/.cargo/bin/slint-lsp"]` -6. Run "LSP: Enable Language Server Globally" or "LSP: Enable Language Server in Project" from Sublime's Command Palette to allow the server to start. -7. Open a .slint file - if the server starts its name will be in the left side of the status bar. - -In order to **preview a component**, when you have a .slint file open, place your cursor to -the name of the component you would like to preview and select the "Show preview" button that -will appear on the right of the editor pane. - -## JetBrains IDE - -https://github.com/kizeevov/slint-idea-plugin has a plugin for the Intellij -platform. - -_Note: This plugin is developed by @kizeevov._ - -## Zed - -[Zed](https://zed.dev) is a high-performance, multiplayer code editor. The [zed-slint extension](https://github.com/slint-ui/slint/tree/master/editors/zed), originally developed and donated by Luke Jones, now lives under the slint organization. It integrates the latest release of the [slint language server](../tools/lsp/README.md) into Zed, offering code completion and syntax highlighting. Install the extension via the following steps: - -1. Open the extensions tab via the Zed -> Extensions menu. -2. In the search field, enter "slint". -3. Click on "Install" for the "Slint" extension. +- [Visual Studio Code](https://snapshots.slint.dev/master/docs/slint/guide/tooling/vscode/) +- [Kate](https://snapshots.slint.dev/master/docs/slint/guide/tooling/kate/) +- [Qt Creator](https://snapshots.slint.dev/master/docs/slint/guide/tooling/qt-creator/) +- [Helix](https://snapshots.slint.dev/master/docs/slint/guide/tooling/helix/) +- [(Neo-)Vim](https://snapshots.slint.dev/master/docs/slint/guide/tooling/neo-vim/) +- [Sublime Text](https://snapshots.slint.dev/master/docs/slint/guide/tooling/sublime-text/) +- [JetBrains IDE](https://snapshots.slint.dev/master/docs/slint/guide/tooling/jetbrains-ide/) +- [Zed](https://snapshots.slint.dev/master/docs/slint/guide/tooling/zed/) +- [Manual Setup](https://snapshots.slint.dev/master/docs/slint/guide/tooling/manual-setup/) diff --git a/examples/README.md b/examples/README.md index f64cb805832..1bec900fa3e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -25,6 +25,38 @@ These examples demonstrate the main features of Slint and how to use them in dif | [7GUIs![7 GUI's demo image](https://user-images.githubusercontent.com/22800467/169002497-5b90e63b-5717-4290-8ac7-c618d9e2a4f1.png "7 GUI's demo image")](./7guis/) | Our implementations of the ["7GUIs"](https://7guis.github.io/7guis/) Tasks.
[Project...](./7guis/) | | | [Slint & Bevy![Bevy demo image](https://github.com/user-attachments/assets/69785864-b6ae-40e1-8f62-4f70677d930e "Bevy demo image")](./7guis/) | A demo that shows how to embed [Bevy](https://bevyengine.org) into Slint
[Project...](./bevy/) | | +### Additional Examples + +These examples demonstrate specialized features or integrations: + +| Example | Description | +| --- | --- | +| [fancy_demo](./fancy_demo/) | Custom widget implementations built from scratch (buttons, sliders, checkboxes, MDI windows) | +| [fancy-switches](./fancy-switches/) | Fancy toggle switch animations | +| [dial](./dial/) | Rotary dial control | +| [speedometer](./speedometer/) | Animated speedometer gauge | +| [orbit-animation](./orbit-animation/) | Orbital animation effects | +| [sprite-sheet](./sprite-sheet/) | Sprite sheet animation | +| [repeater](./repeater/) | Demonstrates the `for` repeater element | +| [gstreamer-player](./gstreamer-player/) | Video playback using GStreamer | +| [wgpu_texture](./wgpu_texture/) | Custom rendering with wgpu into a Slint texture | + +### Embedded/MCU Examples + +| Example | Description | +| --- | --- | +| [mcu-board-support](./mcu-board-support/) | Board support packages for various MCU targets | +| [mcu-embassy](./mcu-embassy/) | Embassy async runtime integration for MCUs | +| [uefi-demo](./uefi-demo/) | Running Slint in a UEFI environment | + +### Platform Integration Examples + +| Example | Description | +| --- | --- | +| [cpp](./cpp/) | C++ platform API examples (native WIN32, Qt integration, Qt viewer) | +| [servo](./servo/) | Integration with the Servo web engine | +| [safe-ui](./safe-ui/) | Safety-critical UI patterns | + #### External examples | Thumbnail | Description | | --- | --- | diff --git a/examples/cpp/README.md b/examples/cpp/README.md new file mode 100644 index 00000000000..ce6c46b7ef2 --- /dev/null +++ b/examples/cpp/README.md @@ -0,0 +1,48 @@ +# C++ Platform Integration Examples + +These examples demonstrate different ways to integrate Slint into C++ applications using the platform API. + +## Examples + +### platform_native + +Shows how to use the Slint C++ platform API to integrate into a native Windows application using the WIN32 API directly. + +**Use case:** Embedding Slint in existing native Windows applications without Qt or other frameworks. + +**Key files:** +- `main.cpp` - Native WIN32 application shell +- `windowadapter_win.h` - Slint platform implementation using WIN32 API +- `appview.h/cpp` - Interface between the application and Slint UI + +### platform_qt + +Shows how to use the Slint platform API to render a Slint scene inside a Qt window. + +**Use case:** Using Slint for specific UI components within a larger Qt application, with full control over the rendering integration. + +### qt_viewer + +Demonstrates embedding a dynamically loaded `.slint` file into a Qt (QWidgets) application using `slint::interpreter::ComponentInstance::qwidget()`. + +**Use case:** Loading and displaying Slint UI files at runtime within a Qt application, useful for plugin systems or dynamic UI loading. + +## Comparison + +| Example | Qt Required | Dynamic Loading | Platform | +|---------|-------------|-----------------|----------| +| platform_native | No | No | Windows only | +| platform_qt | Yes | No | Cross-platform | +| qt_viewer | Yes | Yes | Cross-platform | + +## Building + +Each example has its own CMakeLists.txt. Build from the example directory: + +```sh +mkdir build && cd build +cmake .. +cmake --build . +``` + +For Qt examples, ensure Qt is installed and `qmake` is in your PATH. diff --git a/examples/fancy_demo/README.md b/examples/fancy_demo/README.md new file mode 100644 index 00000000000..37016e42b65 --- /dev/null +++ b/examples/fancy_demo/README.md @@ -0,0 +1,40 @@ +# Fancy Demo + +A showcase of custom widget implementations built entirely in Slint without using the built-in widget library. + +## What It Demonstrates + +This example implements a complete widget set from scratch, demonstrating how to build custom UI components using Slint's primitives: + +### Custom Widgets + +- **MdiWindow** - Draggable, collapsible MDI-style windows with close buttons +- **Button** - Custom styled buttons with hover effects +- **CheckBox** - Animated checkbox with custom graphics +- **RadioButton** - Radio button implementation +- **SelectableLabel** - Clickable label that acts like a radio button +- **Slider** - Custom slider with track and handle +- **Hyperlink** - Clickable text that opens URLs +- **DragValue** - Numeric input that can be adjusted by dragging +- **ProgressBar** - Animated progress indicator +- **LineEdit** - Text input field (uses built-in TextInput) + +### UI Patterns + +- MDI (Multiple Document Interface) window management +- Custom theming via a `Palette` global +- Touch/mouse interaction handling +- Animations and state transitions +- Path-based vector graphics for icons + +## Running + +```sh +cargo run -p fancy_demo +``` + +Or with the viewer: + +```sh +cargo run --bin slint-viewer -- examples/fancy_demo/main.slint +``` diff --git a/examples/gallery/Cargo.toml b/examples/gallery/Cargo.toml index f4a2d287750..fc5f0a33a18 100644 --- a/examples/gallery/Cargo.toml +++ b/examples/gallery/Cargo.toml @@ -31,8 +31,11 @@ slint-build = { path = "../../api/rs/build" } #wasm# #wasm# [target.'cfg(target_arch = "wasm32")'.dependencies] #wasm# wasm-bindgen = { version = "0.2" } -#wasm# web-sys = { version = "0.3", features=["console", "Window", "Navigator"] } +#wasm# web-sys = { version = "0.3", features = ["console", "Window", "Navigator"] } +#wasm# js-sys = { version = "0.3.57" } #wasm# console_error_panic_hook = "0.1.5" +#wasm# slint = { path = "../../api/rs/slint", features = ["unstable-fontique-07"] } +#wasm# icu_locale_core = { version = "2.1.1" } [package.metadata.bundle] identifier = "com.slint.examples.gallery" diff --git a/examples/gallery/index.html b/examples/gallery/index.html index 451f5c9ded1..93fabc3505f 100644 --- a/examples/gallery/index.html +++ b/examples/gallery/index.html @@ -82,7 +82,7 @@

Slint Gallery

try { const fontData = await fetchFont(fontUrl); const uint8Array = new Uint8Array(fontData); - const result = await module.load_font_from_bytes(uint8Array); + const result = await module.load_font_from_bytes(uint8Array, lang); } catch (error) { console.error(`Failed to load font for language ${lang}:`, error); } diff --git a/examples/gallery/main.rs b/examples/gallery/main.rs index e5a1c542da2..b41909770dd 100644 --- a/examples/gallery/main.rs +++ b/examples/gallery/main.rs @@ -10,10 +10,50 @@ slint::include_modules!(); #[cfg(target_arch = "wasm32")] #[wasm_bindgen] -pub fn load_font_from_bytes(font_data: &[u8]) -> Result<(), JsValue> { - slint::register_font_from_memory(font_data.to_vec()) - .map(|_| ()) - .map_err(|e| JsValue::from_str(&format!("Failed to register font: {}", e))) +pub fn load_font_from_bytes(font_data: js_sys::Uint8Array, locale: &str) -> Result<(), JsValue> { + use slint::fontique::fontique; + + let font_data = font_data.to_vec(); + let blob = fontique::Blob::new(std::sync::Arc::new(font_data)); + let mut collection = slint::fontique::shared_collection(); + let fonts = collection.register_fonts(blob, None); + + scripts_for_locale(locale, |script| { + collection + .append_fallbacks(fontique::FallbackKey::new(*script, None), fonts.iter().map(|x| x.0)); + }); + + Ok(()) +} + +#[cfg(target_arch = "wasm32")] +fn scripts_for_locale(locale: &str, mut callback: impl FnMut(&slint::fontique::fontique::Script)) { + use slint::fontique::fontique; + + let Ok(locale) = icu_locale_core::Locale::try_from_str(locale) else { + return; + }; + + let scripts: &[fontique::Script] = match locale.id.language.as_str() { + "ja" => &[ + fontique::Script::from("Hira"), + fontique::Script::from("Kana"), + fontique::Script::from("Hani"), + ], + "ko" => &[fontique::Script::from("Hang"), fontique::Script::from("Hani")], + "zh" => &[fontique::Script::from("Hans"), fontique::Script::from("Hant")], + _ => { + if let Some(script) = locale.id.script { + &[fontique::Script::from(script.into_raw())] + } else { + &[] + } + } + }; + + for script in scripts { + callback(script); + } } use std::rc::Rc; diff --git a/examples/layouts/grid-with-model-in-rows.slint b/examples/layouts/grid-with-model-in-rows.slint new file mode 100644 index 00000000000..c2b1220e59f --- /dev/null +++ b/examples/layouts/grid-with-model-in-rows.slint @@ -0,0 +1,63 @@ +// Copyright © Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure +// SPDX-License-Identifier: MIT + +component Cell inherits Rectangle { + border-width: 1phx; + border-color: #888; + HorizontalLayout { + padding: 10phx; + Text { + text: cell_text; + vertical-alignment: center; + horizontal-alignment: left; + } + } + + in property cell_text; +} + +component HeaderCell inherits Cell { + background: #ccc; +} + +export component MainWindow inherits Window { + + default-font-size: 20px; + + property <[{ name: string, account: string, animal: string }]> model: [ + { name: "Olivier", account: "ogoffart", animal: "cat" }, + { name: "Simon", account: "tronical", animal: "dog" }, + { name: "David", account: "dfaure", animal: "bird" }, + { name: "Alice", account: "wonderland", animal: "white rabbit" }, + { name: "Randriana Tzimabazafi", account: "rt", animal: "lizard" }, + { name: "Verne", account: "jules", animal: "octopus" }, + { name: "Pratchett", account: "terry", animal: "turtles (all the way down)" } + ]; + + grid := GridLayout { + Row { + HeaderCell { + cell_text: "Name"; + } + for person in model: Cell { + cell_text: person.name; + } + } + Row { + HeaderCell { + cell_text: "Account"; + } + for person in model: Cell { + cell_text: person.account; + } + } + Row { + HeaderCell { + cell_text: "Animal"; + } + for person in model: Cell { + cell_text: person.animal; + } + } + } +} diff --git a/examples/layouts/vector-as-grid.slint b/examples/layouts/vector-as-grid.slint new file mode 100644 index 00000000000..293ed58c2e5 --- /dev/null +++ b/examples/layouts/vector-as-grid.slint @@ -0,0 +1,37 @@ +// Copyright © Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure +// SPDX-License-Identifier: MIT + +component Cell inherits Rectangle { + border-width: 1phx; + border-color: #888; + HorizontalLayout { + padding: 10phx; + Text { + text: cell_text; + vertical-alignment: center; + horizontal-alignment: left; + } + } + + in property cell_text; +} + +export component MainWindow inherits Window { + + default-font-size: 20px; + + property <[string]> names: ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]; + property columns: 4; + + GridLayout { + Row { + for text[i] in names: Cell { + cell_text: text; + width: 100phx; + height: 50phx; + row: i / columns; + col: mod(i, columns); + } + } + } +} diff --git a/examples/layouts/vertical-layout-with-model.slint b/examples/layouts/vertical-layout-with-model.slint new file mode 100644 index 00000000000..69887c77f9a --- /dev/null +++ b/examples/layouts/vertical-layout-with-model.slint @@ -0,0 +1,60 @@ +// Copyright © Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure +// SPDX-License-Identifier: MIT + +component Item inherits Rectangle { + border-width: 1phx; + border-color: #888; + HorizontalLayout { + padding: 10phx; + Text { + text: name + " (" + account + ") has " + animal; + vertical-alignment: center; + horizontal-alignment: left; + } + } + + in property name; + in property account; + in property animal; +} + +component HeaderCell inherits Rectangle { + background: #ccc; + border-width: 1phx; + border-color: #888; + Text { + text: cell_text; + vertical-alignment: center; + horizontal-alignment: center; + } + + in property cell_text; +} + +export component MainWindow inherits Window { + + default-font-size: 20px; + + property <[{ name: string, account: string, animal: string}]> model: [ + { name: "Olivier", account: "ogoffart", animal: "a cat" }, + { name: "Simon", account: "tronical", animal: "a dog" }, + { name: "David", account: "dfaure", animal: "a bird" }, + { name: "Alice", account: "wonderland", animal: "a white rabbit" }, + { name: "Randriana Tzimabazafi", account: "rt", animal: "a lizard" }, + { name: "Verne", account: "jules", animal: "an octopus" }, + { name: "Pratchett", account: "terry", animal: "turtles (all the way down)" } + ]; + + VerticalLayout { + HeaderCell { + cell_text: "User Information"; + height: 50phx; + } + + for person in model: Item { + name: person.name; + account: person.account; + animal: person.animal; + } + } +} diff --git a/examples/safe-ui/Cargo.toml b/examples/safe-ui/Cargo.toml index e81f05d36ab..1f1ba02b17b 100644 --- a/examples/safe-ui/Cargo.toml +++ b/examples/safe-ui/Cargo.toml @@ -8,6 +8,7 @@ name = "slint-safeui" version = "1.15.0" edition = "2024" build = "build.rs" +publish = false [lib] path = "src/lib.rs" diff --git a/internal/backends/qt/qt_window.rs b/internal/backends/qt/qt_window.rs index 23c341e78e8..7f96e85c002 100644 --- a/internal/backends/qt/qt_window.rs +++ b/internal/backends/qt/qt_window.rs @@ -1227,7 +1227,13 @@ impl QtItemRenderer<'_> { size: LogicalSize, image: Pin<&dyn i_slint_core::item_rendering::RenderImage>, ) { - let source_rect = image.source_clip(); + let source_rect = image.source_clip().filter(|rect| { + let source_size = image.source().size().cast(); + rect.origin.x != 0 + || rect.origin.y != 0 + || rect.size.width != source_size.width + || rect.size.height != source_size.height + }); let pixmap: qttypes::QPixmap = self.cache.get_or_update_cache_entry(item_rc, || { let source = image.source(); @@ -1236,23 +1242,15 @@ impl QtItemRenderer<'_> { // Query target_width/height here again to ensure that changes will invalidate the item rendering cache. let scale_factor = ScaleFactor::new(self.scale_factor()); - let t = (image.target_size() * scale_factor).cast(); - let source_size = if source.is_svg() { - let has_source_clipping = source_rect.map_or(false, |rect| { - rect.origin.x != 0 - || rect.origin.y != 0 - || !rect.size.width != t.width - || !rect.size.height != t.height - }); - if has_source_clipping { + if source_rect.is_some() { // Source size & clipping is not implemented yet None } else { Some( i_slint_core::graphics::fit( image.image_fit(), - t.cast(), + (image.target_size() * scale_factor).cast(), IntRect::from_size(origin.cast()), scale_factor, Default::default(), // We only care about the size, so alignments don't matter diff --git a/internal/backends/winit/Cargo.toml b/internal/backends/winit/Cargo.toml index 2929fa4a4dd..6a4fd7376da 100644 --- a/internal/backends/winit/Cargo.toml +++ b/internal/backends/winit/Cargo.toml @@ -109,8 +109,8 @@ wasm-bindgen = { version = "0.2" } glutin = { workspace = true, optional = true, default-features = false, features = ["egl", "wgl"] } glutin-winit = { version = "0.5", optional = true, default-features = false, features = ["egl", "wgl"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -accesskit = { version = "0.21", optional = true } -accesskit_winit = { version = "0.29", optional = true, default-features = false, features = ["accesskit_unix", "async-io", "rwh_06"] } +accesskit = { version = "0.22", optional = true } +accesskit_winit = { version = "0.30", optional = true, default-features = false, features = ["accesskit_unix", "async-io", "rwh_06"] } copypasta = { version = "0.10", default-features = false } [target.'cfg(not(any(target_family = "windows", target_vendor = "apple", target_arch = "wasm32", target_os = "android")))'.dependencies] diff --git a/internal/common/lib.rs b/internal/common/lib.rs index eef7efdc47a..a8c9e0ac181 100644 --- a/internal/common/lib.rs +++ b/internal/common/lib.rs @@ -46,3 +46,8 @@ pub fn get_native_style(has_qt: bool, target: &str) -> &'static str { /// /// Use a private unicode character so we are sure it is not used in the user's code pub const MENU_SEPARATOR_PLACEHOLDER_TITLE: &str = "\u{E001}⸺"; + +/// Internal "magic" value for row and col numbers, to mean "auto", in GridLayoutInputData +/// Use the value 65536, so it's outside u16 range and not as likely as -1 +/// (we can catch it as a literal at compile time, but not if it's a runtime value) +pub const ROW_COL_AUTO: f32 = u16::MAX as f32 + 1.; diff --git a/internal/common/sharedfontique.rs b/internal/common/sharedfontique.rs index 068afd394e2..6399da04afe 100644 --- a/internal/common/sharedfontique.rs +++ b/internal/common/sharedfontique.rs @@ -141,83 +141,6 @@ impl std::ops::DerefMut for Collection { } } -/// Handle to a registered font that can be used for future operations. -/// -/// Returned by [`register_font_from_memory()`]. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct FontHandle {} - -/// Error type for font registration failures. -/// -/// Returned by [`register_font_from_memory()`]. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub enum RegisterFontError { - /// The provided font data was empty - EmptyData, - /// No valid fonts could be extracted from the data - NoFontsFound, - /// The font data could not be parsed - InvalidFontData, -} - -impl std::fmt::Display for RegisterFontError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - RegisterFontError::EmptyData => write!(f, "Font data is empty"), - RegisterFontError::NoFontsFound => { - write!(f, "No valid fonts found in the provided data") - } - RegisterFontError::InvalidFontData => write!(f, "Invalid font data"), - } - } -} - -impl std::error::Error for RegisterFontError {} - -/// Register a font from byte data dynamically. -/// -/// The argument is the owner of a slice of byte that contains the data of the font. -/// It can for example be a `&'static [u8]` obtained from [`include_bytes!()`] or a `Vec` -/// obtained from [`std::fs::read()`]. -/// -/// The data must be in the [TrueType (TTF) format](https://en.wikipedia.org/wiki/TrueType). -/// -/// # Example -/// -/// ``` -/// # use i_slint_common::sharedfontique as slint; -/// let font_bytes = include_bytes!("sharedfontique/DejaVuSans.ttf"); -/// slint::register_font_from_memory(font_bytes).expect("Failed to register font"); -/// ``` -pub fn register_font_from_memory( - font_data: impl AsRef<[u8]> + Send + Sync + 'static, -) -> Result { - let data = font_data.as_ref(); - - if data.is_empty() { - return Err(RegisterFontError::EmptyData); - } - - let blob = fontique::Blob::new(Arc::new(font_data)); - - let mut collection = get_collection(); - let fonts = collection.register_fonts(blob, None); - - if fonts.is_empty() { - return Err(RegisterFontError::NoFontsFound); - } - - // Set up fallbacks for all scripts - for script in fontique::Script::all_samples().iter().map(|(script, _)| *script) { - collection - .append_fallbacks(fontique::FallbackKey::new(script, None), fonts.iter().map(|x| x.0)); - } - - Ok(FontHandle {}) -} - /// Font metrics in design space. Scale with desired pixel size and divided by units_per_em /// to obtain pixel metrics. #[derive(Clone)] diff --git a/internal/compiler/expression_tree.rs b/internal/compiler/expression_tree.rs index 9d845c1841e..9627240a990 100644 --- a/internal/compiler/expression_tree.rs +++ b/internal/compiler/expression_tree.rs @@ -752,11 +752,16 @@ pub enum Expression { ReturnStatement(Option>), LayoutCacheAccess { + /// This property holds an array of entries layout_cache_prop: NamedReference, + /// The index into that array. If repeater_index is None, then the code will be `layout_cache_prop[index]` index: usize, /// When set, this is the index within a repeater, and the index is then the location of another offset. - /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index]` + /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index * entries_per_item]` repeater_index: Option>, + /// The number of entries per item (2 for LayoutCache, 4 for GridLayoutInputData) + /// This is only used when repeater_index is set + entries_per_item: usize, }, /// Organize a grid layout, i.e. decide what goes where @@ -1881,14 +1886,21 @@ pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std write!(f, "return ")?; e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(())) } - Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { - write!( - f, - "{:?}[{}{}]", - layout_cache_prop, - index, - if repeater_index.is_some() { " + $index" } else { "" } - ) + Expression::LayoutCacheAccess { + layout_cache_prop, + index, + repeater_index, + entries_per_item, + } => { + if repeater_index.is_some() { + write!( + f, + "{:?}[{:?}[{}] + $repeater_index * {}]", + layout_cache_prop, layout_cache_prop, index, entries_per_item + ) + } else { + write!(f, "{:?}[{}]", layout_cache_prop, index) + } } Expression::OrganizeGridLayout(..) => write!(f, "organize_grid_layout(..)"), Expression::ComputeLayoutInfo(..) => write!(f, "layout_info(..)"), diff --git a/internal/compiler/generator/cpp.rs b/internal/compiler/generator/cpp.rs index 6413fc51539..608c7b8d131 100644 --- a/internal/compiler/generator/cpp.rs +++ b/internal/compiler/generator/cpp.rs @@ -2522,18 +2522,19 @@ fn generate_sub_component( fn generate_repeated_component( repeated: &llr::RepeatedElement, - root: &llr::CompilationUnit, + unit: &llr::CompilationUnit, parent_ctx: ParentScope, model_data_type: Option<&Type>, file: &mut File, conditional_includes: &ConditionalIncludes, ) { - let repeater_id = ident(&root.sub_components[repeated.sub_tree.root].name); + let root_sc = &unit.sub_components[repeated.sub_tree.root]; + let repeater_id = ident(&root_sc.name); let mut repeater_struct = Struct { name: repeater_id.clone(), ..Default::default() }; generate_item_tree( &mut repeater_struct, &repeated.sub_tree, - root, + unit, Some(&parent_ctx), false, repeater_id.clone(), @@ -2543,7 +2544,7 @@ fn generate_repeated_component( ); let ctx = EvaluationContext { - compilation_unit: root, + compilation_unit: unit, current_scope: EvaluationScope::SubComponent(repeated.sub_tree.root, Some(&parent_ctx)), generator_state: CppGeneratorContext { global_access: "self".into(), conditional_includes }, argument_types: &[], @@ -2611,13 +2612,30 @@ fn generate_repeated_component( repeater_struct.members.push(( Access::Public, // Because Repeater accesses it Declaration::Function(Function { - name: "box_layout_data".into(), - signature: "(slint::cbindgen_private::Orientation o) const -> slint::cbindgen_private::BoxLayoutCellData".to_owned(), + name: "layout_item_info".into(), + signature: "(slint::cbindgen_private::Orientation o) const -> slint::cbindgen_private::LayoutItemInfo".to_owned(), statements: Some(vec!["return { layout_info({&static_vtable, const_cast(static_cast(this))}, o) };".into()]), ..Function::default() }), )); + root_sc.grid_layout_input_for_repeated.as_ref().map(|expr| { + repeater_struct.members.push(( + Access::Public, // Because Repeater accesses it + Declaration::Function(Function { + name: "grid_layout_input_for_repeated".into(), + signature: + "(bool new_row) const -> slint::cbindgen_private::GridLayoutInputData" + .to_owned(), + + statements: Some(vec![ + "[[maybe_unused]] auto self = this;".into(), + format!("return {};", compile_expression(&expr.borrow(), &ctx)), + ]), + ..Function::default() + }), + )); + }); } if let Some(index_prop) = repeated.index_prop { @@ -3716,28 +3734,34 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String ident(&value.to_pascal_case()), ) } - Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { + Expression::LayoutCacheAccess { + layout_cache_prop, + index, + repeater_index, + entries_per_item, + } => { let cache = access_member(layout_cache_prop, ctx); cache.map_or_default(|cache| { if let Some(ri) = repeater_index { format!( - "slint::private_api::layout_cache_access({}.get(), {}, {})", + "slint::private_api::layout_cache_access({}.get(), {}, {}, {})", cache, index, - compile_expression(ri, ctx) + compile_expression(ri, ctx), + entries_per_item ) } else { format!("{cache}.get()[{index}]") } }) } - Expression::BoxLayoutFunction { + Expression::WithLayoutItemInfo { cells_variable, repeater_indices, elements, orientation, sub_expression, - } => box_layout_function( + } => generate_with_layout_item_info( cells_variable, repeater_indices.as_ref().map(SmolStr::as_str), elements.as_ref(), @@ -3745,6 +3769,18 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String sub_expression, ctx, ), + Expression::WithGridInputData { + cells_variable, + repeater_indices, + elements, + sub_expression, + } => generate_with_grid_input_data( + cells_variable, + repeater_indices.as_ref().map(SmolStr::as_str), + elements.as_ref(), + sub_expression, + ctx, + ), Expression::MinMax { ty, op, lhs, rhs } => { let ident = match op { MinMaxOp::Min => "min", @@ -4305,7 +4341,7 @@ fn compile_builtin_function_call( } } -fn box_layout_function( +fn generate_with_layout_item_info( cells_variable: &str, repeated_indices: Option<&str>, elements: &[Either], @@ -4315,7 +4351,7 @@ fn box_layout_function( ) -> String { let repeated_indices = repeated_indices.map(ident); let mut push_code = - "std::vector cells_vector;".to_owned(); + "std::vector cells_vector;".to_owned(); let mut repeater_idx = 0usize; for item in elements { @@ -4347,7 +4383,7 @@ fn box_layout_function( repeater_idx += 1; write!( push_code, - "self->repeater_{id}.for_each([&](const auto &sub_comp){{ cells_vector.push_back(sub_comp->box_layout_data({o})); }});", + "self->repeater_{id}.for_each([&](const auto &sub_comp){{ cells_vector.push_back(sub_comp->layout_item_info({o})); }});", id = repeater, o = to_cpp_orientation(orientation), ) @@ -4365,7 +4401,80 @@ fn box_layout_function( format!("std::array {}_array;", 2 * repeater_idx, ri) }); format!( - "[&]{{ {} {} slint::cbindgen_private::Slice{} = slint::private_api::make_slice(std::span(cells_vector)); return {}; }}()", + "[&]{{ {} {} slint::cbindgen_private::Slice{} = slint::private_api::make_slice(std::span(cells_vector)); return {}; }}()", + ri, + push_code, + ident(cells_variable), + compile_expression(sub_expression, ctx) + ) +} + +fn generate_with_grid_input_data( + cells_variable: &str, + repeated_indices: Option<&str>, + elements: &[Either], + sub_expression: &llr::Expression, + ctx: &llr_EvaluationContext, +) -> String { + let mut push_code = + "std::vector cells_vector;".to_owned(); + let mut repeater_idx = 0usize; + let mut has_new_row_bool = false; + + for item in elements { + match item { + Either::Left(value) => { + write!( + push_code, + "cells_vector.push_back({{ {} }});", + compile_expression(value, ctx) + ) + .unwrap(); + } + Either::Right(repeater) => { + let repeater_id = format!("repeater_{}", usize::from(repeater.repeater_index)); + write!(push_code, "self->{repeater_id}.ensure_updated(self);").unwrap(); + + if let Some(ri) = &repeated_indices { + write!(push_code, "{}_array[{}] = cells_vector.size();", ri, repeater_idx * 2) + .unwrap(); + write!( + push_code, + "{ri}_array[{c}] = self->{id}.len();", + ri = ri, + c = repeater_idx * 2 + 1, + id = repeater_id, + ) + .unwrap(); + } + repeater_idx += 1; + write!( + push_code, + "{maybe_bool} new_row = {new_row}; + self->{id}.for_each([&](const auto &sub_comp) {{ + cells_vector.push_back(sub_comp->grid_layout_input_for_repeated(new_row)); + new_row = false; + }});", + new_row = repeater.new_row, + maybe_bool = if has_new_row_bool { "" } else { "bool " }, + id = repeater_id, + ) + .unwrap(); + has_new_row_bool = true; + } + } + } + + let ri = repeated_indices.as_ref().map_or(String::new(), |ri| { + write!( + push_code, + "slint::cbindgen_private::Slice {ri} = slint::private_api::make_slice(std::span({ri}_array));" + ) + .unwrap(); + format!("std::array {}_array;", 2 * repeater_idx, ri) + }); + format!( + "[&]{{ {} {} slint::cbindgen_private::Slice{} = slint::private_api::make_slice(std::span(cells_vector)); return {}; }}()", ri, push_code, ident(cells_variable), diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index c11ffc8fc99..a6cd357bfdf 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -1183,6 +1183,20 @@ fn generate_sub_component( let layout_info_h = compile_expression_no_parenthesis(&component.layout_info_h.borrow(), &ctx); let layout_info_v = compile_expression_no_parenthesis(&component.layout_info_v.borrow(), &ctx); + let grid_layout_input_for_repeated_fn = + component.grid_layout_input_for_repeated.as_ref().map(|expr| { + let expr = compile_expression_no_parenthesis(&expr.borrow(), &ctx); + quote! { + fn grid_layout_input_for_repeated( + self: ::core::pin::Pin<&Self>, + new_row: bool, + ) -> sp::GridLayoutInputData { + #![allow(unused)] + let _self = self; + #expr + } + } + }); // FIXME! this is only public because of the ComponentHandle::WeakInner. we should find another way let visibility = parent_ctx.is_none().then(|| quote!(pub)); @@ -1294,6 +1308,8 @@ fn generate_sub_component( } } + #grid_layout_input_for_repeated_fn + fn subtree_range(self: ::core::pin::Pin<&Self>, dyn_index: u32) -> sp::IndexRange { #![allow(unused)] let _self = self; @@ -1888,6 +1904,17 @@ fn generate_repeated_component( let root_sc = &unit.sub_components[repeated.sub_tree.root]; let inner_component_id = self::inner_component_id(root_sc); + let grid_layout_input_data_fn = root_sc.grid_layout_input_for_repeated.as_ref().map(|_| { + quote! { + fn grid_layout_input_data( + self: ::core::pin::Pin<&Self>, + new_row: bool) -> sp::GridLayoutInputData + { + self.as_ref().grid_layout_input_for_repeated(new_row) + } + } + }); + let extra_fn = if let Some(listview) = &repeated.listview { let p_y = access_member(&listview.prop_y, &ctx).unwrap(); let p_height = access_member(&listview.prop_height, &ctx).unwrap(); @@ -1903,13 +1930,18 @@ fn generate_repeated_component( } } } else { - // TODO: we could generate this code only if we know that this component is in a box layout - quote! { - fn box_layout_data(self: ::core::pin::Pin<&Self>, o: sp::Orientation) - -> sp::BoxLayoutCellData - { - sp::BoxLayoutCellData { constraint: self.as_ref().layout_info(o) } + let layout_item_info_fn = root_sc.child_of_layout.then(|| { + quote! { + fn layout_item_info(self: ::core::pin::Pin<&Self>, o: sp::Orientation) + -> sp::LayoutItemInfo + { + sp::LayoutItemInfo { constraint: self.as_ref().layout_info(o) } + } } + }); + quote! { + #layout_item_info_fn + #grid_layout_input_data_fn } }; @@ -2693,26 +2725,26 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream quote!(sp::#base_ident::#value_ident) } } - Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { + Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index, entries_per_item } => { access_member(layout_cache_prop, ctx).map_or_default(|cache| { if let Some(ri) = repeater_index { let offset = compile_expression(ri, ctx); quote!({ let cache = #cache.get(); - *cache.get((cache[#index] as usize) + #offset as usize * 2).unwrap_or(&(0 as sp::Coord)) + *cache.get((cache[#index] as usize) + #offset as usize * #entries_per_item).unwrap_or(&(0 as _)) }) } else { quote!(#cache.get()[#index]) } }) } - Expression::BoxLayoutFunction { + Expression::WithLayoutItemInfo { cells_variable, repeater_indices, elements, orientation, sub_expression, - } => box_layout_function( + } => generate_with_layout_item_info( cells_variable, repeater_indices.as_ref().map(SmolStr::as_str), elements.as_ref(), @@ -2720,6 +2752,17 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream sub_expression, ctx, ), + + Expression::WithGridInputData { + cells_variable, repeater_indices, elements, sub_expression + } => generate_with_grid_input_data( + cells_variable, + repeater_indices.as_ref().map(SmolStr::as_str), + elements.as_ref(), + sub_expression, + ctx, + ), + Expression::MinMax { ty, op, lhs, rhs } => { let lhs = compile_expression(lhs, ctx); let t = rust_primitive_type(ty); @@ -2832,7 +2875,7 @@ fn compile_builtin_function_call( let popup = ¤t_sub_component.popup_windows[*popup_index as usize]; let popup_window_id = inner_component_id(&ctx.compilation_unit.sub_components[popup.item_tree.root]); - let parent_component = access_item_rc(parent_ref, ctx); + let parent_item = access_item_rc(parent_ref, ctx); let parent_ctx = ParentScope::new(ctx, None); let popup_ctx = EvaluationContext::new_sub_component( @@ -2847,6 +2890,7 @@ fn compile_builtin_function_call( let window_adapter_tokens = access_window_adapter_field(ctx); let popup_id_name = internal_popup_id(*popup_index as usize); component_access_tokens.then(|component_access_tokens| quote!({ + let parent_item = #parent_item; let popup_instance = #popup_window_id::new(#component_access_tokens.self_weak.get().unwrap().clone()).unwrap(); let popup_instance_vrc = sp::VRc::map(popup_instance.clone(), |x| x); let position = { let _self = popup_instance_vrc.as_pin_ref(); #position }; @@ -2858,7 +2902,7 @@ fn compile_builtin_function_call( &sp::VRc::into_dyn(popup_instance.into()), position, #close_policy, - #parent_component, + parent_item, false, // is_menu )) ); @@ -3420,7 +3464,76 @@ fn struct_name_to_tokens(name: &StructName) -> Option } } -fn box_layout_function( +fn generate_with_grid_input_data( + cells_variable: &str, + repeated_indices: Option<&str>, + elements: &[Either], + sub_expression: &Expression, + ctx: &EvaluationContext, +) -> TokenStream { + let repeated_indices = repeated_indices.map(ident); + let inner_component_id = self::inner_component_id(ctx.current_sub_component().unwrap()); + let mut fixed_count = 0usize; + let mut repeated_count = quote!(); + let mut push_code = Vec::new(); + let mut repeater_idx = 0usize; + for item in elements { + match item { + Either::Left(value) => { + let value = compile_expression(value, ctx); + fixed_count += 1; + push_code.push(quote!(items_vec.push(#value);)) + } + Either::Right(repeater) => { + let repeater_id = format_ident!("repeater{}", usize::from(repeater.repeater_index)); + let rep_inner_component_id = self::inner_component_id( + &ctx.compilation_unit.sub_components[ctx + .current_sub_component() + .unwrap() + .repeated[repeater.repeater_index] + .sub_tree + .root], + ); + repeated_count = quote!(#repeated_count + _self.#repeater_id.len()); + let ri = repeated_indices.as_ref().map(|ri| { + quote!( + #ri[#repeater_idx * 2] = items_vec.len() as u32; + #ri[#repeater_idx * 2 + 1] = internal_vec.len() as u32; + ) + }); + repeater_idx += 1; + let new_row = repeater.new_row; + push_code.push(quote!( + #inner_component_id::FIELD_OFFSETS.#repeater_id.apply_pin(_self).ensure_updated( + || { #rep_inner_component_id::new(_self.self_weak.get().unwrap().clone()).unwrap().into() } + ); + let internal_vec = _self.#repeater_id.instances_vec(); + #ri + let mut new_row = #new_row; + for sub_comp in &internal_vec { + items_vec.push(sub_comp.as_pin_ref().grid_layout_input_data(new_row)); + new_row = false; + } + )); + } + } + } + let ri = repeated_indices.as_ref().map(|ri| quote!(let mut #ri = [0u32; 2 * #repeater_idx];)); + let ri2 = repeated_indices.map(|ri| quote!(let #ri = sp::Slice::from_slice(&#ri);)); + let cells_variable = ident(cells_variable); + let sub_expression = compile_expression(sub_expression, ctx); + + quote! { { + #ri + let mut items_vec = sp::Vec::with_capacity(#fixed_count #repeated_count); + #(#push_code)* + let #cells_variable = sp::Slice::from_slice(&items_vec); + #ri2 + #sub_expression + } } +} + +fn generate_with_layout_item_info( cells_variable: &str, repeated_indices: Option<&str>, elements: &[Either], @@ -3462,7 +3575,7 @@ fn box_layout_function( let internal_vec = _self.#repeater_id.instances_vec(); #ri for sub_comp in &internal_vec { - items_vec.push(sub_comp.as_pin_ref().box_layout_data(#orientation)) + items_vec.push(sub_comp.as_pin_ref().layout_item_info(#orientation)) } )); } diff --git a/internal/compiler/generator/rust_live_preview.rs b/internal/compiler/generator/rust_live_preview.rs index 76c00840340..13a0b991d16 100644 --- a/internal/compiler/generator/rust_live_preview.rs +++ b/internal/compiler/generator/rust_live_preview.rs @@ -116,7 +116,7 @@ fn generate_public_component( #[allow(dead_code)] pub fn #on_ident(&self, f: impl FnMut(#(#callback_args),*) -> #return_type + 'static) { let f = ::core::cell::RefCell::new(f); - self.0.borrow_mut().set_callback(#prop_name, sp::Rc::new(move |values| { + self.0.borrow().set_callback(#prop_name, sp::Rc::new(move |values| { let [#(#args_name,)*] = values else { panic!("invalid number of argument for callback {}::{}", #component_name, #prop_name) }; (*f.borrow_mut())(#(#args_name.clone().try_into().unwrap_or_else(|_| panic!("invalid argument for callback {}::{}", #component_name, #prop_name)),)*).into() })) @@ -155,7 +155,7 @@ fn generate_public_component( property_and_callback_accessors.push(quote!( #[allow(dead_code)] pub fn #setter_ident(&self, value: #rust_property_type) { - self.0.borrow_mut().set_property(#prop_name, #convert_to_value(value)) + self.0.borrow().set_property(#prop_name, #convert_to_value(value)) } )); } else { @@ -274,7 +274,7 @@ fn generate_global(global: &llr::GlobalComponent, root: &llr::CompilationUnit) - #[allow(dead_code)] pub fn #on_ident(&self, f: impl FnMut(#(#callback_args),*) -> #return_type + 'static) { let f = ::core::cell::RefCell::new(f); - self.0.borrow_mut().set_global_callback(#global_name, #prop_name, sp::Rc::new(move |values| { + self.0.borrow().set_global_callback(#global_name, #prop_name, sp::Rc::new(move |values| { let [#(#args_name,)*] = values else { panic!("invalid number of argument for callback {}::{}", #global_name, #prop_name) }; (*f.borrow_mut())(#(#args_name.clone().try_into().unwrap_or_else(|_| panic!("invalid argument for callback {}::{}", #global_name, #prop_name)),)*).into() })) @@ -313,7 +313,7 @@ fn generate_global(global: &llr::GlobalComponent, root: &llr::CompilationUnit) - property_and_callback_accessors.push(quote!( #[allow(dead_code)] pub fn #setter_ident(&self, value: #rust_property_type) { - self.0.borrow_mut().set_global_property(#global_name, #prop_name, #convert_to_value(value)) + self.0.borrow().set_global_property(#global_name, #prop_name, #convert_to_value(value)) } )); } else { diff --git a/internal/compiler/langtype.rs b/internal/compiler/langtype.rs index 37d17ca6008..2a76c0af4a5 100644 --- a/internal/compiler/langtype.rs +++ b/internal/compiler/langtype.rs @@ -657,7 +657,7 @@ pub enum BuiltinPrivateStruct { GridLayoutData, GridLayoutInputData, BoxLayoutData, - BoxLayoutCellData, + LayoutItemInfo, Padding, LayoutInfo, FontMetrics, diff --git a/internal/compiler/layout.rs b/internal/compiler/layout.rs index 456ca5b83d4..332ff3aa801 100644 --- a/internal/compiler/layout.rs +++ b/internal/compiler/layout.rs @@ -279,24 +279,32 @@ impl LayoutConstraints { pub enum RowColExpr { Named(NamedReference), Literal(u16), + Auto, } -/// An element in a GridLayout #[derive(Debug, Clone)] -pub struct GridLayoutElement { +pub struct GridLayoutCell { pub new_row: bool, pub col_expr: RowColExpr, pub row_expr: RowColExpr, pub colspan_expr: RowColExpr, pub rowspan_expr: RowColExpr, +} + +/// An element in a GridLayout +#[derive(Debug, Clone)] +pub struct GridLayoutElement { + // Rc> because shared with the repeated component's element + pub cell: Rc>, pub item: LayoutItem, } impl GridLayoutElement { - pub fn span(&self, orientation: Orientation) -> &RowColExpr { + pub fn span(&self, orientation: Orientation) -> RowColExpr { + let cell = self.cell.borrow(); match orientation { - Orientation::Horizontal => &self.colspan_expr, - Orientation::Vertical => &self.rowspan_expr, + Orientation::Horizontal => cell.colspan_expr.clone(), + Orientation::Vertical => cell.rowspan_expr.clone(), } } } @@ -474,7 +482,8 @@ pub struct GridLayout { impl GridLayout { pub fn visit_rowcol_named_references(&mut self, visitor: &mut impl FnMut(&mut NamedReference)) { - for cell in &mut self.elems { + for elem in &mut self.elems { + let mut cell = elem.cell.borrow_mut(); if let RowColExpr::Named(ref mut e) = cell.col_expr { visitor(e); } diff --git a/internal/compiler/lib.rs b/internal/compiler/lib.rs index 193c0effd3b..daa022ae16c 100644 --- a/internal/compiler/lib.rs +++ b/internal/compiler/lib.rs @@ -304,6 +304,9 @@ impl CompilerConfiguration { } } +/// Prepare for compilation of the source file +/// - storing parser configuration +/// - setting up the parser fn prepare_for_compile( diagnostics: &mut diagnostics::BuildDiagnostics, #[allow(unused_mut)] mut compiler_config: CompilerConfiguration, diff --git a/internal/compiler/llr/expression.rs b/internal/compiler/llr/expression.rs index 5cdb247fa78..b59a0f5c0f7 100644 --- a/internal/compiler/llr/expression.rs +++ b/internal/compiler/llr/expression.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 use super::{ - GlobalIdx, LocalMemberIndex, LocalMemberReference, MemberReference, RepeatedElementIdx, - SubComponentIdx, SubComponentInstanceIdx, + GlobalIdx, GridLayoutRepeatedElement, LocalMemberIndex, LocalMemberReference, MemberReference, + RepeatedElementIdx, SubComponentIdx, SubComponentInstanceIdx, }; use crate::expression_tree::{BuiltinFunction, MinMaxOp, OperatorClass}; use crate::langtype::Type; @@ -167,25 +167,37 @@ pub enum Expression { EnumerationValue(crate::langtype::EnumerationValue), + /// See LayoutCacheAccess in expression_tree.rs LayoutCacheAccess { layout_cache_prop: MemberReference, index: usize, - /// When set, this is the index within a repeater, and the index is then the location of another offset. - /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index]` repeater_index: Option>, + entries_per_item: usize, }, - /// Will call the sub_expression, with the cell variable set to the - /// array of BoxLayoutCellData from the elements - BoxLayoutFunction { - /// The local variable (as read with [`Self::ReadLocalVariable`]) that contains the sell + /// Will call the sub_expression, with the cells variable set to the + /// array of LayoutItemInfo from the elements + WithLayoutItemInfo { + /// The local variable (as read with [`Self::ReadLocalVariable`]) that contains the cells cells_variable: String, /// The name for the local variable that contains the repeater indices repeater_indices: Option, - /// Either an expression of type BoxLayoutCellData, or an index to the repeater + /// Either an expression of type LayoutItemInfo, or an index to the repeater elements: Vec>, orientation: Orientation, sub_expression: Box, }, + /// Will call the sub_expression, with the cells variable set to the + /// array of GridLayoutInputData from the elements + WithGridInputData { + /// The local variable (as read with [`Self::ReadLocalVariable`]) that contains the cells + cells_variable: String, + /// The name for the local variable that contains the repeater indices + repeater_indices: Option, + /// Either an expression of type GridLayoutInputData, or information about the repeated element + elements: Vec>, + sub_expression: Box, + }, + MinMax { ty: Type, op: MinMaxOp, @@ -314,7 +326,8 @@ impl Expression { Self::ConicGradient { .. } => Type::Brush, Self::EnumerationValue(e) => Type::Enumeration(e.enumeration.clone()), Self::LayoutCacheAccess { .. } => Type::LogicalLength, - Self::BoxLayoutFunction { sub_expression, .. } => sub_expression.ty(ctx), + Self::WithLayoutItemInfo { sub_expression, .. } => sub_expression.ty(ctx), + Self::WithGridInputData { sub_expression, .. } => sub_expression.ty(ctx), Self::MinMax { ty, .. } => ty.clone(), Self::EmptyComponentFactory => Type::ComponentFactory, Self::TranslationReference { .. } => Type::String, @@ -395,7 +408,11 @@ macro_rules! visit_impl { $visitor(repeater_index); } } - Expression::BoxLayoutFunction { elements, sub_expression, .. } => { + Expression::WithLayoutItemInfo { elements, sub_expression, .. } => { + $visitor(sub_expression); + elements.$iter().filter_map(|x| x.$as_ref().left()).for_each($visitor); + } + Expression::WithGridInputData { elements, sub_expression, .. } => { $visitor(sub_expression); elements.$iter().filter_map(|x| x.$as_ref().left()).for_each($visitor); } diff --git a/internal/compiler/llr/item_tree.rs b/internal/compiler/llr/item_tree.rs index ee5ef28c483..9ec3bb00bc1 100644 --- a/internal/compiler/llr/item_tree.rs +++ b/internal/compiler/llr/item_tree.rs @@ -27,6 +27,12 @@ pub struct ItemInstanceIdx(usize); #[derive(Debug, Clone, Copy, Into, From, Hash, PartialEq, Eq)] pub struct RepeatedElementIdx(usize); +#[derive(Debug, Clone)] +pub struct GridLayoutRepeatedElement { + pub new_row: bool, + pub repeater_index: RepeatedElementIdx, +} + impl PropertyIdx { pub const REPEATER_DATA: Self = Self(0); pub const REPEATER_INDEX: Self = Self(1); @@ -357,6 +363,8 @@ pub struct SubComponent { pub layout_info_h: MutExpression, pub layout_info_v: MutExpression, + pub child_of_layout: bool, + pub grid_layout_input_for_repeated: Option, /// Maps (item_index, property) to an expression pub accessible_prop: BTreeMap<(u32, String), MutExpression>, @@ -511,6 +519,9 @@ impl CompilationUnit { } visitor(&sc.layout_info_h, ctx); visitor(&sc.layout_info_v, ctx); + if let Some(e) = &sc.grid_layout_input_for_repeated { + visitor(e, ctx); + } for e in sc.accessible_prop.values() { visitor(e, ctx); } diff --git a/internal/compiler/llr/lower_expression.rs b/internal/compiler/llr/lower_expression.rs index 4a2cb0f55da..6d968a29940 100644 --- a/internal/compiler/llr/lower_expression.rs +++ b/internal/compiler/llr/lower_expression.rs @@ -9,10 +9,13 @@ use itertools::Either; use smol_str::{SmolStr, format_smolstr}; use super::lower_to_item_tree::{LoweredElement, LoweredSubComponentMapping, LoweringState}; -use super::{Animation, LocalMemberReference, MemberReference, PropertyIdx, RepeatedElementIdx}; +use super::{ + Animation, GridLayoutRepeatedElement, LocalMemberReference, MemberReference, PropertyIdx, + RepeatedElementIdx, +}; use crate::expression_tree::{BuiltinFunction, Callable, Expression as tree_Expression}; use crate::langtype::{BuiltinPrivateStruct, EnumerationValue, Struct, StructName, Type}; -use crate::layout::{Orientation, RowColExpr}; +use crate::layout::{GridLayoutCell, Orientation, RowColExpr}; use crate::llr::Expression as llr_Expression; use crate::namedreference::NamedReference; use crate::object_tree::{Element, ElementRc, PropertyAnimation}; @@ -39,7 +42,13 @@ impl ExpressionLoweringCtx<'_> { let mut map = &self.inner; if !enclosing.is_global() { while !Rc::ptr_eq(enclosing, map.component) { - map = map.parent.unwrap(); + map = map.parent.unwrap_or_else(|| { + panic!( + "Could not find component for property reference {from:?} in component {:?}. Started with enclosing={:?}", + self.component.id, + enclosing.id + ) + }); level += 1; } } @@ -238,13 +247,17 @@ pub fn lower_expression( tree_Expression::ReturnStatement(..) => { panic!("The remove return pass should have removed all return") } - tree_Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { - llr_Expression::LayoutCacheAccess { - layout_cache_prop: ctx.map_property_reference(layout_cache_prop), - index: *index, - repeater_index: repeater_index.as_ref().map(|e| lower_expression(e, ctx).into()), - } - } + tree_Expression::LayoutCacheAccess { + layout_cache_prop, + index, + repeater_index, + entries_per_item, + } => llr_Expression::LayoutCacheAccess { + layout_cache_prop: ctx.map_property_reference(layout_cache_prop), + index: *index, + repeater_index: repeater_index.as_ref().map(|e| lower_expression(e, ctx).into()), + entries_per_item: *entries_per_item, + }, tree_Expression::OrganizeGridLayout(l) => organize_grid_layout(l, ctx), tree_Expression::ComputeLayoutInfo(l, o) => compute_layout_info(l, *o, ctx), tree_Expression::ComputeGridLayoutInfo { @@ -601,21 +614,43 @@ fn compute_grid_layout_info( ) -> llr_Expression { let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, o, ctx); let organized_cells = ctx.map_property_reference(layout_organized_data_prop); - let constraints = grid_layout_cell_constraints(layout, o, ctx); + let constraints_result = grid_layout_cell_constraints(layout, o, ctx); let orientation_literal = llr_Expression::EnumerationValue(EnumerationValue { value: o as _, enumeration: crate::typeregister::BUILTIN.with(|b| b.enums.Orientation.clone()), }); - llr_Expression::ExtraBuiltinFunctionCall { + let sub_expression = llr_Expression::ExtraBuiltinFunctionCall { function: "grid_layout_info".into(), arguments: vec![ llr_Expression::PropertyReference(organized_cells), - constraints, + constraints_result.cells, + if constraints_result.compute_cells.is_none() { + llr_Expression::Array { + element_ty: Type::Int32, + values: Vec::new(), + as_model: false, + } + } else { + llr_Expression::ReadLocalVariable { + name: "repeated_indices".into(), + ty: Type::Array(Type::Int32.into()), + } + }, spacing, padding, orientation_literal, ], return_ty: crate::typeregister::layout_info_type().into(), + }; + match constraints_result.compute_cells { + Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo { + cells_variable, + repeater_indices: Some("repeated_indices".into()), + elements, + orientation: o, + sub_expression: Box::new(sub_expression), + }, + None => sub_expression, } } @@ -645,7 +680,7 @@ fn compute_layout_info( } }; match bld.compute_cells { - Some((cells_variable, elements)) => llr_Expression::BoxLayoutFunction { + Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo { cells_variable, repeater_indices: None, elements, @@ -662,7 +697,7 @@ fn organize_grid_layout( layout: &crate::layout::GridLayout, ctx: &mut ExpressionLoweringCtx, ) -> llr_Expression { - let cells = grid_layout_input_data(layout, ctx); + let input_data = grid_layout_input_data(layout, ctx); if let Some(button_roles) = &layout.dialog_button_roles { let e = crate::typeregister::BUILTIN.with(|e| e.enums.DialogButtonRole.clone()); @@ -682,14 +717,38 @@ fn organize_grid_layout( }; llr_Expression::ExtraBuiltinFunctionCall { function: "organize_dialog_button_layout".into(), - arguments: vec![cells, roles_expr], + arguments: vec![input_data.cells, roles_expr], return_ty: Type::Array(Type::Int32.into()), } } else { - llr_Expression::ExtraBuiltinFunctionCall { + let sub_expression = llr_Expression::ExtraBuiltinFunctionCall { function: "organize_grid_layout".into(), - arguments: vec![cells], + arguments: vec![ + input_data.cells, + if input_data.compute_cells.is_none() { + llr_Expression::Array { + element_ty: Type::Int32, + values: Vec::new(), + as_model: false, + } + } else { + llr_Expression::ReadLocalVariable { + name: "repeated_indices".into(), + ty: Type::Array(Type::Int32.into()), + } + }, + ], return_ty: Type::Array(Type::Int32.into()), + }; + if let Some((cells_variable, elements)) = input_data.compute_cells { + llr_Expression::WithGridInputData { + cells_variable, + repeater_indices: Some("repeated_indices".into()), + elements, + sub_expression: Box::new(sub_expression), + } + } else { + sub_expression } } } @@ -707,22 +766,55 @@ fn solve_grid_layout( value: o as _, enumeration: crate::typeregister::BUILTIN.with(|b| b.enums.Orientation.clone()), }); - llr_Expression::ExtraBuiltinFunctionCall { - function: "solve_grid_layout".into(), - arguments: vec![ - make_struct( - BuiltinPrivateStruct::GridLayoutData, - [ - ("size", Type::Float32, size), - ("spacing", Type::Float32, spacing), - ("padding", padding.ty(ctx), padding), - ("organized_data", Type::ArrayOfU16, llr_Expression::PropertyReference(cells)), - ], - ), - grid_layout_cell_constraints(layout, o, ctx), - orientation_expr, + let data = make_struct( + BuiltinPrivateStruct::GridLayoutData, + [ + ("size", Type::Float32, size), + ("spacing", Type::Float32, spacing), + ("padding", padding.ty(ctx), padding), + ("organized_data", Type::ArrayOfU16, llr_Expression::PropertyReference(cells)), ], - return_ty: Type::LayoutCache, + ); + let constraints_result = grid_layout_cell_constraints(layout, o, ctx); + + match constraints_result.compute_cells { + Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo { + cells_variable: cells_variable.clone(), + repeater_indices: Some("repeated_indices".into()), + elements, + orientation: o, + sub_expression: Box::new(llr_Expression::ExtraBuiltinFunctionCall { + function: "solve_grid_layout".into(), + arguments: vec![ + data, + llr_Expression::ReadLocalVariable { + name: cells_variable.into(), + ty: constraints_result.cells.ty(ctx), + }, + orientation_expr, + llr_Expression::ReadLocalVariable { + name: "repeated_indices".into(), + ty: Type::Array(Type::Int32.into()), + }, + ], + return_ty: Type::LayoutCache, + }), + }, + None => llr_Expression::ExtraBuiltinFunctionCall { + function: "solve_grid_layout".into(), + arguments: vec![ + data, + constraints_result.cells, + orientation_expr, + llr_Expression::Array { + // empty array of repeated indices + element_ty: Type::Int32, + values: Vec::new(), + as_model: false, + }, + ], + return_ty: Type::LayoutCache, + }, } } @@ -752,7 +844,7 @@ fn solve_layout( ], ); match bld.compute_cells { - Some((cells_variable, elements)) => llr_Expression::BoxLayoutFunction { + Some((cells_variable, elements)) => llr_Expression::WithLayoutItemInfo { cells_variable, repeater_indices: Some("repeated_indices".into()), elements, @@ -790,11 +882,18 @@ fn solve_layout( struct BoxLayoutDataResult { alignment: llr_Expression, cells: llr_Expression, - /// When there are repeater involved, we need to do a BoxLayoutFunction with the + /// When there are repeater involved, we need to do a WithLayoutItemInfo with the /// given cell variable and elements compute_cells: Option<(String, Vec>)>, } +fn make_layout_cell_data_struct(layout_info: llr_Expression) -> llr_Expression { + make_struct( + BuiltinPrivateStruct::LayoutItemInfo, + [("constraint", crate::typeregister::layout_info_type().into(), layout_info)], + ) +} + fn box_layout_data( layout: &crate::layout::BoxLayout, orientation: Orientation, @@ -823,14 +922,7 @@ fn box_layout_data( .map(|li| { let layout_info = get_layout_info(&li.element, ctx, &li.constraints, orientation); - make_struct( - BuiltinPrivateStruct::BoxLayoutCellData, - [( - "constraint", - crate::typeregister::layout_info_type().into(), - layout_info, - )], - ) + make_layout_cell_data_struct(layout_info) }) .collect(), element_ty, @@ -850,10 +942,7 @@ fn box_layout_data( } else { let layout_info = get_layout_info(&item.element, ctx, &item.constraints, orientation); - elements.push(Either::Left(make_struct( - BuiltinPrivateStruct::BoxLayoutCellData, - [("constraint", crate::typeregister::layout_info_type().into(), layout_info)], - ))); + elements.push(Either::Left(make_layout_cell_data_struct(layout_info))); } } let cells = llr_Expression::ReadLocalVariable { @@ -864,56 +953,160 @@ fn box_layout_data( } } +struct GridLayoutCellConstraintsResult { + cells: llr_Expression, + /// When there are repeater involved, we need to do a WithLayoutItemInfo with the + /// given cell variable and elements + compute_cells: Option<(String, Vec>)>, +} + fn grid_layout_cell_constraints( layout: &crate::layout::GridLayout, orientation: Orientation, ctx: &mut ExpressionLoweringCtx, -) -> llr_Expression { - llr_Expression::Array { - element_ty: crate::typeregister::layout_info_type().into(), - values: layout - .elems - .iter() - .map(|c| get_layout_info(&c.item.element, ctx, &c.item.constraints, orientation)) - .collect(), - as_model: false, +) -> GridLayoutCellConstraintsResult { + let repeater_count = + layout.elems.iter().filter(|i| i.item.element.borrow().repeated.is_some()).count(); + + let element_ty = crate::typeregister::box_layout_cell_data_type(); + + if repeater_count == 0 { + let cells = llr_Expression::Array { + element_ty: element_ty, + values: layout + .elems + .iter() + .map(|li| { + let layout_info = + get_layout_info(&li.item.element, ctx, &li.item.constraints, orientation); + make_layout_cell_data_struct(layout_info) + }) + .collect(), + as_model: false, + }; + GridLayoutCellConstraintsResult { cells, compute_cells: None } + } else { + let mut elements = vec![]; + for item in &layout.elems { + if item.item.element.borrow().repeated.is_some() { + let repeater_index = match ctx + .mapping + .element_mapping + .get(&item.item.element.clone().into()) + .unwrap() + { + LoweredElement::Repeated { repeated_index } => *repeated_index, + _ => panic!(), + }; + elements.push(Either::Right(repeater_index)) + } else { + let layout_info = + get_layout_info(&item.item.element, ctx, &item.item.constraints, orientation); + elements.push(Either::Left(make_layout_cell_data_struct(layout_info))); + } + } + let cells = llr_Expression::ReadLocalVariable { + name: "cells".into(), + ty: Type::Array(Rc::new(crate::typeregister::layout_info_type().into())), + }; + GridLayoutCellConstraintsResult { cells, compute_cells: Some(("cells".into(), elements)) } } } +struct GridLayoutInputDataResult { + cells: llr_Expression, + /// When there are repeaters involved, we need to do a WithGridInputData with the + /// given cell variable and elements + compute_cells: Option<(String, Vec>)>, +} + +// helper for organize_grid_layout() fn grid_layout_input_data( layout: &crate::layout::GridLayout, ctx: &mut ExpressionLoweringCtx, -) -> llr_Expression { - llr_Expression::Array { - element_ty: grid_layout_input_data_ty(), - values: layout - .elems - .iter() - .map(|c| { - let propref_or_default = |named_ref: &RowColExpr| match named_ref { - RowColExpr::Literal(n) => llr_Expression::NumberLiteral((*n).into()), - RowColExpr::Named(e) => { - llr_Expression::PropertyReference(ctx.map_property_reference(e)) +) -> GridLayoutInputDataResult { + let propref = |named_ref: &RowColExpr| match named_ref { + RowColExpr::Literal(n) => llr_Expression::NumberLiteral((*n).into()), + RowColExpr::Named(nr) => llr_Expression::PropertyReference(ctx.map_property_reference(nr)), + RowColExpr::Auto => llr_Expression::NumberLiteral(i_slint_common::ROW_COL_AUTO as _), + }; + let input_data_for_cell = |elem: &crate::layout::GridLayoutElement, + new_row_expr: llr_Expression| { + let row_expr = propref(&elem.cell.borrow().row_expr); + let col_expr = propref(&elem.cell.borrow().col_expr); + let rowspan_expr = propref(&elem.cell.borrow().rowspan_expr); + let colspan_expr = propref(&elem.cell.borrow().colspan_expr); + + make_struct( + BuiltinPrivateStruct::GridLayoutInputData, + [ + ("new_row", Type::Bool, new_row_expr), + ("row", Type::Float32, row_expr), + ("col", Type::Float32, col_expr), + ("rowspan", Type::Float32, rowspan_expr), + ("colspan", Type::Float32, colspan_expr), + ], + ) + }; + let repeater_count = + layout.elems.iter().filter(|i| i.item.element.borrow().repeated.is_some()).count(); + + let element_ty = grid_layout_input_data_ty(); + + if repeater_count == 0 { + let cells = llr_Expression::Array { + element_ty, + values: layout + .elems + .iter() + .map(|elem| { + input_data_for_cell( + elem, + llr_Expression::BoolLiteral(elem.cell.borrow().new_row), + ) + }) + .collect(), + as_model: false, + }; + GridLayoutInputDataResult { cells, compute_cells: None } + } else { + let mut elements = vec![]; + let mut after_repeater_in_same_row = false; + for item in &layout.elems { + let new_row = item.cell.borrow().new_row; + if new_row { + after_repeater_in_same_row = false; + } + if item.item.element.borrow().repeated.is_some() { + let repeater_index = match ctx + .mapping + .element_mapping + .get(&item.item.element.clone().into()) + .unwrap() + { + LoweredElement::Repeated { repeated_index } => *repeated_index, + _ => panic!(), + }; + let repeated_element = GridLayoutRepeatedElement { new_row, repeater_index }; + elements.push(Either::Right(repeated_element)); + after_repeater_in_same_row = true; + } else { + let new_row_expr = if new_row || !after_repeater_in_same_row { + llr_Expression::BoolLiteral(new_row) + } else { + llr_Expression::ReadLocalVariable { + name: SmolStr::new_static("new_row"), + ty: Type::Bool, } }; - let row_expr = propref_or_default(&c.row_expr); - let col_expr = propref_or_default(&c.col_expr); - let rowspan_expr = propref_or_default(&c.rowspan_expr); - let colspan_expr = propref_or_default(&c.colspan_expr); - - make_struct( - BuiltinPrivateStruct::GridLayoutInputData, - [ - ("new_row", Type::Bool, llr_Expression::BoolLiteral(c.new_row)), - ("row", Type::Int32, row_expr), - ("col", Type::Int32, col_expr), - ("rowspan", Type::Int32, rowspan_expr), - ("colspan", Type::Int32, colspan_expr), - ], - ) - }) - .collect(), - as_model: false, + elements.push(Either::Left(input_data_for_cell(item, new_row_expr))); + } + } + let cells = llr_Expression::ReadLocalVariable { + name: "cells".into(), + ty: Type::Array(Rc::new(element_ty)), + }; + GridLayoutInputDataResult { cells, compute_cells: Some(("cells".into(), elements)) } } } @@ -1013,6 +1206,35 @@ pub fn get_layout_info( } } +pub fn get_grid_layout_input_for_repeated( + ctx: &mut ExpressionLoweringCtx, + grid_cell: &GridLayoutCell, +) -> llr_Expression { + let new_row_expr = + llr_Expression::ReadLocalVariable { name: SmolStr::new_static("new_row"), ty: Type::Bool }; + + fn convert_row_col_expr(expr: &RowColExpr, ctx: &mut ExpressionLoweringCtx) -> llr_Expression { + match expr { + RowColExpr::Literal(n) => llr_Expression::NumberLiteral((*n).into()), + RowColExpr::Named(nr) => { + llr_Expression::PropertyReference(ctx.map_property_reference(nr)) + } + RowColExpr::Auto => llr_Expression::NumberLiteral(i_slint_common::ROW_COL_AUTO as _), + } + } + + make_struct( + BuiltinPrivateStruct::GridLayoutInputData, + [ + ("new_row", Type::Bool, new_row_expr), + ("row", Type::Float32, convert_row_col_expr(&grid_cell.row_expr, ctx)), + ("col", Type::Float32, convert_row_col_expr(&grid_cell.col_expr, ctx)), + ("rowspan", Type::Float32, convert_row_col_expr(&grid_cell.rowspan_expr, ctx)), + ("colspan", Type::Float32, convert_row_col_expr(&grid_cell.colspan_expr, ctx)), + ], + ) +} + fn compile_path( path: &crate::expression_tree::Path, ctx: &mut ExpressionLoweringCtx, diff --git a/internal/compiler/llr/lower_to_item_tree.rs b/internal/compiler/llr/lower_to_item_tree.rs index f30233b12b6..e7a4e7d4e9c 100644 --- a/internal/compiler/llr/lower_to_item_tree.rs +++ b/internal/compiler/llr/lower_to_item_tree.rs @@ -172,7 +172,13 @@ impl LoweredSubComponentMapping { }, }, }, - LoweredElement::Repeated { .. } => unreachable!(), + LoweredElement::Repeated { .. } => { + panic!( + "Trying to map property {from:?} on a repeated element {} of type {:?}", + element.borrow().id, + element.borrow().base_type + ); + } LoweredElement::ComponentPlaceholder { .. } => unreachable!(), } } @@ -256,6 +262,8 @@ fn lower_sub_component( // just initialize to dummy expression right now and it will be set later layout_info_h: super::Expression::BoolLiteral(false).into(), layout_info_v: super::Expression::BoolLiteral(false).into(), + child_of_layout: component.root_element.borrow().child_of_layout, + grid_layout_input_for_repeated: None, accessible_prop: Default::default(), element_infos: Default::default(), prop_analysis: Default::default(), @@ -545,6 +553,15 @@ fn lower_sub_component( crate::layout::Orientation::Vertical, ) .into(); + if let Some(grid_layout_cell) = component.root_element.borrow().grid_layout_cell.as_ref() { + sub_component.grid_layout_input_for_repeated = Some( + super::lower_expression::get_grid_layout_input_for_repeated( + &mut ctx, + &grid_layout_cell.borrow(), + ) + .into(), + ); + } sub_component.accessible_prop = accessible_prop .into_iter() diff --git a/internal/compiler/llr/optim_passes/count_property_use.rs b/internal/compiler/llr/optim_passes/count_property_use.rs index 3c6efa54ff8..62ebb4ce9f5 100644 --- a/internal/compiler/llr/optim_passes/count_property_use.rs +++ b/internal/compiler/llr/optim_passes/count_property_use.rs @@ -69,6 +69,9 @@ pub fn count_property_use(root: &CompilationUnit) { // 5. the layout info sc.layout_info_h.borrow().visit_property_references(ctx, &mut visit_property); sc.layout_info_v.borrow().visit_property_references(ctx, &mut visit_property); + if let Some(e) = &sc.grid_layout_input_for_repeated { + e.borrow().visit_property_references(ctx, &mut visit_property); + } // 6. accessibility props and geometries for b in sc.accessible_prop.values() { diff --git a/internal/compiler/llr/optim_passes/inline_expressions.rs b/internal/compiler/llr/optim_passes/inline_expressions.rs index 4e95699dad0..5d17b7b2292 100644 --- a/internal/compiler/llr/optim_passes/inline_expressions.rs +++ b/internal/compiler/llr/optim_passes/inline_expressions.rs @@ -66,7 +66,8 @@ fn expression_cost(exp: &Expression, ctx: &EvaluationContext) -> isize { Expression::ConicGradient { .. } => ALLOC_COST, Expression::EnumerationValue(_) => 0, Expression::LayoutCacheAccess { .. } => PROPERTY_ACCESS_COST, - Expression::BoxLayoutFunction { .. } => return isize::MAX, + Expression::WithLayoutItemInfo { .. } => return isize::MAX, + Expression::WithGridInputData { .. } => return isize::MAX, Expression::MinMax { .. } => 10, Expression::EmptyComponentFactory => 10, Expression::TranslationReference { .. } => PROPERTY_ACCESS_COST + 2 * ALLOC_COST, diff --git a/internal/compiler/llr/optim_passes/remove_unused.rs b/internal/compiler/llr/optim_passes/remove_unused.rs index c44a52c0364..4e7423b1ae4 100644 --- a/internal/compiler/llr/optim_passes/remove_unused.rs +++ b/internal/compiler/llr/optim_passes/remove_unused.rs @@ -294,6 +294,8 @@ mod visitor { geometries, layout_info_h, layout_info_v, + child_of_layout: _, + grid_layout_input_for_repeated, accessible_prop, element_infos: _, prop_analysis, @@ -377,6 +379,9 @@ mod visitor { } visit_expression(layout_info_h.get_mut(), &scope, state, visitor); visit_expression(layout_info_v.get_mut(), &scope, state, visitor); + if let Some(e) = grid_layout_input_for_repeated { + visit_expression(e.get_mut(), &scope, state, visitor); + } for a in accessible_prop.values_mut() { visit_expression(a.get_mut(), &scope, state, visitor); diff --git a/internal/compiler/llr/pretty_print.rs b/internal/compiler/llr/pretty_print.rs index 1c8bac69fa0..eaf8c922c1d 100644 --- a/internal/compiler/llr/pretty_print.rs +++ b/internal/compiler/llr/pretty_print.rs @@ -392,17 +392,31 @@ impl<'a, T> Display for DisplayExpression<'a, T> { stops.iter().map(|(e1, e2)| format!("{} {}", e(e1), e(e2))).join(", ") ), Expression::EnumerationValue(x) => write!(f, "{x}"), - Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index: None } => { + Expression::LayoutCacheAccess { + layout_cache_prop, + index, + repeater_index: None, + .. + } => { write!(f, "{}[{}]", DisplayPropertyRef(layout_cache_prop, ctx), index) } Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index: Some(ri), + entries_per_item, } => { - write!(f, "{}[{} % {}]", DisplayPropertyRef(layout_cache_prop, ctx), index, e(ri)) + write!( + f, + "{}[{} % {} * {}]", + DisplayPropertyRef(layout_cache_prop, ctx), + index, + e(ri), + entries_per_item + ) } - Expression::BoxLayoutFunction { .. } => write!(f, "BoxLayoutFunction(TODO)",), + Expression::WithLayoutItemInfo { .. } => write!(f, "WithLayoutItemInfo(TODO)",), + Expression::WithGridInputData { .. } => write!(f, "WithGridInputData(TODO)",), Expression::MinMax { ty: _, op, lhs, rhs } => match op { MinMaxOp::Min => write!(f, "min({}, {})", e(lhs), e(rhs)), MinMaxOp::Max => write!(f, "max({}, {})", e(lhs), e(rhs)), diff --git a/internal/compiler/namedreference.rs b/internal/compiler/namedreference.rs index 0d91dfc274a..24cfc099c4a 100644 --- a/internal/compiler/namedreference.rs +++ b/internal/compiler/namedreference.rs @@ -50,7 +50,10 @@ impl NamedReference { } #[track_caller] pub fn element(&self) -> ElementRc { - self.0.element.upgrade().expect("NamedReference to a dead element") + self.0 + .element + .upgrade() + .unwrap_or_else(|| panic!("{}: NamedReference to a dead element", self.0.name)) } pub fn ty(&self) -> Type { self.element().borrow().lookup_property(self.name()).property_type diff --git a/internal/compiler/object_tree.rs b/internal/compiler/object_tree.rs index 6105ee575d3..3df557da901 100644 --- a/internal/compiler/object_tree.rs +++ b/internal/compiler/object_tree.rs @@ -551,10 +551,7 @@ impl Component { /// This is an interface introduced with the "interface" keyword pub fn is_interface(&self) -> bool { - match &self.root_element.borrow().base_type { - ElementType::Interface => true, - _ => false, - } + matches!(&self.root_element.borrow().base_type, ElementType::Interface) } /// Returns the names of aliases to global singletons, exactly as @@ -873,6 +870,9 @@ pub struct Element { /// How many times the element was inlined pub inline_depth: i32, + /// Information about the grid cell containing this element, if applicable + pub grid_layout_cell: Option>>, + /// Debug information about this element. /// /// There can be several in case of inlining or optimization (child merged into their parent). @@ -944,9 +944,12 @@ pub fn pretty_print( } } for (name, expr) in &e.bindings { - let expr = expr.borrow(); indent!(); write!(f, "{name}: ")?; + let Ok(expr) = expr.try_borrow() else { + writeln!(f, "")?; + continue; + }; expression_tree::pretty_print(f, &expr.expression)?; if expr.analysis.as_ref().is_some_and(|a| a.is_const) { write!(f, "/*const*/")?; @@ -1084,11 +1087,11 @@ fn expected_relationship_to_parent(node: &syntax_nodes::Element) -> Option Some(ParentRelationship::Inherits), "implements" => Some(ParentRelationship::Implements), _ => None, - }; + } } impl Element { @@ -1126,7 +1129,7 @@ impl Element { (Ok(ElementType::Component(c)), Some(ParentRelationship::Implements)) => { if !diag.enable_experimental { diag.push_error( - format!("'implements' is an experimental feature"), + "'implements' is an experimental feature".into(), &base_node, ); ElementType::Error @@ -1146,7 +1149,7 @@ impl Element { (Ok(ElementType::Builtin(_bt)), Some(ParentRelationship::Implements)) => { if !diag.enable_experimental { diag.push_error( - format!("'implements' is an experimental feature"), + "'implements' is an experimental feature".into(), &base_node, ); } else { diff --git a/internal/compiler/parser.rs b/internal/compiler/parser.rs index a38f82ee7f9..055df28aa03 100644 --- a/internal/compiler/parser.rs +++ b/internal/compiler/parser.rs @@ -328,7 +328,8 @@ declare_syntax! { Pipe -> "|", Percent -> "%", } - // syntax kind + // Syntax Nodes. The list after the `->` is the possible child nodes, + // see the documentation of `declare_syntax!` macro for details. { Document -> [ *Component, *ExportsList, *ImportSpecifier, *StructDeclaration, *EnumDeclaration ], /// `DeclaredIdentifier := Element { ... }` @@ -541,6 +542,7 @@ mod parser_trait { } /// Peek the `n`th token, not including whitespace and comments fn nth(&mut self, n: usize) -> Token; + /// Consume the token and point to the next token fn consume(&mut self); fn error(&mut self, e: impl Into); fn warning(&mut self, e: impl Into); @@ -616,7 +618,9 @@ pub use parser_trait::*; pub struct DefaultParser<'a> { builder: rowan::GreenNodeBuilder<'static>, + /// tokens from the lexer tokens: Vec, + /// points on the current token of the token list cursor: usize, diags: &'a mut BuildDiagnostics, source_file: SourceFile, @@ -633,7 +637,8 @@ impl<'a> DefaultParser<'a> { } } - /// Constructor that create a parser from the source code + /// Constructor that create a parser from the source code. + /// It creates the tokens by lexing the code pub fn new(source: &str, diags: &'a mut BuildDiagnostics) -> Self { Self::from_tokens(crate::lexer::lex(source), diags) } @@ -670,7 +675,7 @@ impl Parser for DefaultParser<'_> { self.builder.finish_node(); } - /// Peek the `n`th token, not including whitespace and comments + /// Peek the `n`th token starting from the cursor position, not including whitespace and comments fn nth(&mut self, mut n: usize) -> Token { self.consume_ws(); let mut c = self.cursor; @@ -686,7 +691,7 @@ impl Parser for DefaultParser<'_> { self.tokens.get(c).cloned().unwrap_or_default() } - /// Consume the current token + /// Adds the current token to the node builder and increments the cursor to point on the next token fn consume(&mut self) { let t = self.current_token(); self.builder.token(t.kind.into(), t.text.as_str()); diff --git a/internal/compiler/parser/document.rs b/internal/compiler/parser/document.rs index 1e37c792cec..13b3f3afe8f 100644 --- a/internal/compiler/parser/document.rs +++ b/internal/compiler/parser/document.rs @@ -141,12 +141,12 @@ pub fn parse_component(p: &mut impl Parser) -> bool { } if is_global { if p.peek().kind() == SyntaxKind::ColonEqual { - p.warning(format!("':=' to declare a global is deprecated. Remove the ':='")); + p.warning("':=' to declare a global is deprecated. Remove the ':='"); p.consume(); } } else if is_interface { if p.peek().kind() == SyntaxKind::ColonEqual { - p.error(format!("':=' to declare an interface is not supported. Remove the ':='")); + p.error("':=' to declare an interface is not supported. Remove the ':='"); p.consume(); } } else if !is_new_component { diff --git a/internal/compiler/passes/binding_analysis.rs b/internal/compiler/passes/binding_analysis.rs index 6a36c2d875d..42e7407772d 100644 --- a/internal/compiler/passes/binding_analysis.rs +++ b/internal/compiler/passes/binding_analysis.rs @@ -680,8 +680,13 @@ fn visit_implicit_layout_info_dependencies( match base_type.as_str() { "Image" => { vis(&NamedReference::new(item, SmolStr::new_static("source")).into(), N); + vis(&NamedReference::new(item, SmolStr::new_static("source-clip-width")).into(), N); if orientation == Orientation::Vertical { vis(&NamedReference::new(item, SmolStr::new_static("width")).into(), N); + vis( + &NamedReference::new(item, SmolStr::new_static("source-clip-height")).into(), + N, + ); } } "Text" | "TextInput" => { diff --git a/internal/compiler/passes/ensure_window.rs b/internal/compiler/passes/ensure_window.rs index 93a5ca4c19d..72c6569947a 100644 --- a/internal/compiler/passes/ensure_window.rs +++ b/internal/compiler/passes/ensure_window.rs @@ -60,6 +60,7 @@ pub fn ensure_window( is_flickable_viewport: false, item_index: Default::default(), item_index_of_first_children: Default::default(), + grid_layout_cell: None, debug: std::mem::take(&mut win_elem_mut.debug), inline_depth: 0, diff --git a/internal/compiler/passes/inlining.rs b/internal/compiler/passes/inlining.rs index 99c76d94879..99300633a60 100644 --- a/internal/compiler/passes/inlining.rs +++ b/internal/compiler/passes/inlining.rs @@ -392,6 +392,7 @@ fn duplicate_element_with_mapping( has_popup_child: elem.has_popup_child, is_legacy_syntax: elem.is_legacy_syntax, inline_depth: elem.inline_depth + 1, + grid_layout_cell: elem.grid_layout_cell.clone(), })); mapping.insert(element_key(element.clone()), new.clone()); if let ElementType::Component(c) = &mut new.borrow_mut().base_type diff --git a/internal/compiler/passes/lower_layout.rs b/internal/compiler/passes/lower_layout.rs index 9dc3bf45034..ac2ce42ff2a 100644 --- a/internal/compiler/passes/lower_layout.rs +++ b/internal/compiler/passes/lower_layout.rs @@ -108,17 +108,14 @@ fn lower_element_layout( match layout_type.as_ref().unwrap().as_str() { "Row" => { - // We shouldn't lower layout if we have a Row in there. Unless the Row is the root of a repeated item, - // in which case another error has been reported - assert!( - diag.has_errors() - && Rc::ptr_eq(&component.root_element, elem) - && component - .parent_element - .upgrade() - .is_some_and(|e| e.borrow().repeated.is_some()), - "Error should have been caught at element lookup time" + if Rc::ptr_eq(&component.root_element, elem) + && component.parent_element.upgrade().is_some_and(|e| e.borrow().repeated.is_some()) + { + diag.push_error( + "'if' or 'for' expressions are not currently supported for Row elements in grid layouts".to_string(), + &*elem.borrow(), ); + } return None; } "GridLayout" => lower_grid_layout(component, elem, diag, type_register), @@ -391,15 +388,25 @@ impl GridLayout { check_numbering_consistency(col_expr_type, "col"); } - let propref_or_default = |name: &'static str| -> Option { - crate::layout::binding_reference(item_element, name).map(RowColExpr::Named) + let propref = |name: &'static str| -> Option { + let nr = crate::layout::binding_reference(item_element, name).map(|nr| { + // similar to adjust_references in repeater_component.rs (which happened before these references existed) + let e = nr.element(); + let mut nr = nr.clone(); + if e.borrow().repeated.is_some() + && let crate::langtype::ElementType::Component(c) = e.borrow().base_type.clone() + { + nr = NamedReference::new(&c.root_element, nr.name().clone()) + }; + nr + }); + nr.map(RowColExpr::Named) }; - // MAX means "auto", see to_layout_data() - let row_expr = propref_or_default("row"); - let col_expr = propref_or_default("col"); - let rowspan_expr = propref_or_default("rowspan"); - let colspan_expr = propref_or_default("colspan"); + let row_expr = propref("row"); + let col_expr = propref("col"); + let rowspan_expr = propref("rowspan"); + let colspan_expr = propref("colspan"); self.add_element_with_coord_as_expr( item_element, @@ -449,49 +456,63 @@ impl GridLayout { let index = self.elems.len(); let result = create_layout_item(item_element, diag); if let Some(ref layout_item) = result { - if layout_item.repeater_index.is_some() { - diag.push_error( - "'if' or 'for' expressions are not currently supported in grid layouts" - .to_string(), - &*item_element.borrow(), - ); - return; - } - + let rep_idx = &layout_item.repeater_index; let e = &layout_item.elem; - set_prop_from_cache(e, "x", layout_cache_prop_h, index * 2, &None, diag); + set_prop_from_cache(e, "x", layout_cache_prop_h, index * 2, rep_idx, 2, diag); if !layout_item.item.constraints.fixed_width { - set_prop_from_cache(e, "width", layout_cache_prop_h, index * 2 + 1, &None, diag); + set_prop_from_cache( + e, + "width", + layout_cache_prop_h, + index * 2 + 1, + rep_idx, + 2, + diag, + ); } - set_prop_from_cache(e, "y", layout_cache_prop_v, index * 2, &None, diag); + set_prop_from_cache(e, "y", layout_cache_prop_v, index * 2, rep_idx, 2, diag); if !layout_item.item.constraints.fixed_height { - set_prop_from_cache(e, "height", layout_cache_prop_v, index * 2 + 1, &None, diag); + set_prop_from_cache( + e, + "height", + layout_cache_prop_v, + index * 2 + 1, + rep_idx, + 2, + diag, + ); } let org_index = index * 4; if col_expr.is_none() { - set_prop_from_cache(e, "col", organized_data_prop, org_index, &None, diag); + set_prop_from_cache(e, "col", organized_data_prop, org_index, rep_idx, 4, diag); } if row_expr.is_none() { - set_prop_from_cache(e, "row", organized_data_prop, org_index + 2, &None, diag); + set_prop_from_cache(e, "row", organized_data_prop, org_index + 2, rep_idx, 4, diag); } - let expr_or_default = |expr: &Option, default: u16| -> RowColExpr { + let expr_or_default = |expr: &Option, default: RowColExpr| -> RowColExpr { match expr { Some(RowColExpr::Literal(v)) => RowColExpr::Literal(*v), Some(RowColExpr::Named(nr)) => RowColExpr::Named(nr.clone()), - None => RowColExpr::Literal(default), + Some(RowColExpr::Auto) => RowColExpr::Auto, + None => default, } }; - self.elems.push(GridLayoutElement { + let grid_layout_cell = Rc::new(RefCell::new(GridLayoutCell { new_row, - col_expr: expr_or_default(col_expr, u16::MAX), // MAX means "auto" - row_expr: expr_or_default(row_expr, u16::MAX), - colspan_expr: expr_or_default(colspan_expr, 1), - rowspan_expr: expr_or_default(rowspan_expr, 1), + col_expr: expr_or_default(col_expr, RowColExpr::Auto), + row_expr: expr_or_default(row_expr, RowColExpr::Auto), + colspan_expr: expr_or_default(colspan_expr, RowColExpr::Literal(1)), + rowspan_expr: expr_or_default(rowspan_expr, RowColExpr::Literal(1)), + })); + let grid_layout_element = GridLayoutElement { + cell: grid_layout_cell.clone(), item: layout_item.item.clone(), - }); + }; + layout_item.elem.borrow_mut().grid_layout_cell = Some(grid_layout_cell); + self.elems.push(grid_layout_element); } } } @@ -563,7 +584,7 @@ fn lower_box_layout( } }; let actual_elem = &item.elem; - set_prop_from_cache(actual_elem, pos, &layout_cache_prop, index, rep_idx, diag); + set_prop_from_cache(actual_elem, pos, &layout_cache_prop, index, rep_idx, 2, diag); if !fixed_size { set_prop_from_cache( actual_elem, @@ -571,6 +592,7 @@ fn lower_box_layout( &layout_cache_prop, index + 1, rep_idx, + 2, diag, ); } @@ -961,6 +983,7 @@ fn set_prop_from_cache( layout_cache_prop: &NamedReference, index: usize, repeater_index: &Option, + entries_per_item: usize, diag: &mut BuildDiagnostics, ) { let old = elem.borrow_mut().bindings.insert( @@ -970,6 +993,7 @@ fn set_prop_from_cache( layout_cache_prop: layout_cache_prop.clone(), index, repeater_index: repeater_index.as_ref().map(|x| Box::new(x.clone())), + entries_per_item, }, layout_cache_prop.element().borrow().to_source_location(), ) diff --git a/internal/compiler/passes/lower_menus.rs b/internal/compiler/passes/lower_menus.rs index e7289f4a556..b65a8402bd9 100644 --- a/internal/compiler/passes/lower_menus.rs +++ b/internal/compiler/passes/lower_menus.rs @@ -248,8 +248,6 @@ fn process_context_menu( .clone() .into(); - context_menu_elem.borrow_mut().base_type = components.context_menu_internal.clone(); - let mut menu_elem = None; context_menu_elem.borrow_mut().children.retain(|x| { if x.borrow().base_type == menu_element_type { @@ -282,9 +280,10 @@ fn process_context_menu( } let children = std::mem::take(&mut menu_elem.borrow_mut().children); - let c = lower_menu_items(context_menu_elem, children, components); + let c = lower_menu_items(context_menu_elem, children, components, diag); let item_tree_root = Expression::ElementReference(Rc::downgrade(&c.root_element)); + context_menu_elem.borrow_mut().base_type = components.context_menu_internal.clone(); for (name, _) in &components.context_menu_internal.property_list() { if let Some(decl) = context_menu_elem.borrow().property_declarations.get(name) { diag.push_error(format!("Cannot re-define internal property '{name}'"), &decl.node); @@ -341,9 +340,8 @@ fn process_window( no_native_menu: bool, diag: &mut BuildDiagnostics, ) -> bool { - let mut window = win.borrow_mut(); let mut menu_bar = None; - window.children.retain(|x| { + win.borrow_mut().children.retain(|x| { if matches!(&x.borrow().base_type, ElementType::Builtin(b) if b.name == "MenuBar") { if menu_bar.is_some() { diag.push_error("Only one MenuBar is allowed in a Window".into(), &*x.borrow()); @@ -370,7 +368,7 @@ fn process_window( // Lower MenuItem's into a tree root let children = std::mem::take(&mut menu_bar.borrow_mut().children); - let c = lower_menu_items(&menu_bar, children, components); + let c = lower_menu_items(&menu_bar, children, components, diag); let item_tree_root = Expression::ElementReference(Rc::downgrade(&c.root_element)); if !no_native_menu { @@ -393,6 +391,7 @@ fn process_window( }; } + let mut window = win.borrow_mut(); let menubar_impl = Element { id: format_smolstr!("{}-menulayout", window.id), base_type: components.menubar_impl.clone(), @@ -517,6 +516,7 @@ fn lower_menu_items( parent: &ElementRc, children: Vec, components: &UsefulMenuComponents, + diag: &mut BuildDiagnostics, ) -> Rc { let component = Rc::new_cyclic(|component_weak| { let root_element = Rc::new(RefCell::new(Element { @@ -556,13 +556,17 @@ fn lower_menu_items( ..Default::default() } }); - parent - .borrow() - .enclosing_component - .upgrade() - .unwrap() - .menu_item_tree - .borrow_mut() - .push(component.clone()); + let enclosing = parent.borrow().enclosing_component.upgrade().unwrap(); + + super::lower_popups::check_no_reference_to_popup( + parent, + &enclosing, + &Rc::downgrade(&component), + &NamedReference::new(parent, SmolStr::new_static("x")), + diag, + ); + + enclosing.menu_item_tree.borrow_mut().push(component.clone()); + component } diff --git a/internal/compiler/passes/lower_popups.rs b/internal/compiler/passes/lower_popups.rs index 094c47a6875..0a35318a8c4 100644 --- a/internal/compiler/passes/lower_popups.rs +++ b/internal/compiler/passes/lower_popups.rs @@ -112,10 +112,6 @@ fn lower_popup_window( parent_cip.insertion_index -= 1; } - if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) { - popup_window_element.borrow_mut().base_type = window_type.clone(); - } - let map_close_on_click_value = |b: &BindingExpression| { let Expression::BoolLiteral(v) = super::ignore_debug_hooks(&b.expression) else { assert!(diag.has_errors()); @@ -213,28 +209,11 @@ fn lower_popup_window( popup_mut.geometry_props.as_mut().unwrap().y = dummy2; } - // Throw error when accessing the popup from outside - // FIXME: - // - the span is the span of the PopupWindow, that's wrong, we should have the span of the reference - // - There are other object reference than in the NamedReference - // - Maybe this should actually be allowed - visit_all_named_references(&parent_component, &mut |nr| { - let element = &nr.element(); - if check_element(element, &weak, diag, popup_window_element) { - // just set it to whatever is a valid NamedReference, otherwise we'll panic later - *nr = coord_x.clone(); - } - }); - visit_all_expressions(&parent_component, |exp, _| { - exp.visit_recursive_mut(&mut |exp| { - if let Expression::ElementReference(element) = exp { - let elem = element.upgrade().unwrap(); - if !Rc::ptr_eq(&elem, popup_window_element) { - check_element(&elem, &weak, diag, popup_window_element); - } - } - }); - }); + check_no_reference_to_popup(popup_window_element, &parent_component, &weak, &coord_x, diag); + + if matches!(popup_window_element.borrow().base_type, ElementType::Builtin(_)) { + popup_window_element.borrow_mut().base_type = window_type.clone(); + } super::focus_handling::call_focus_on_init(&popup_comp); @@ -251,15 +230,63 @@ fn report_const_error(prop: &str, span: &Option, diag: &mut Buil diag.push_error(format!("The {prop} property only supports constants at the moment"), span); } +/// Throw error when accessing the popup from outside +// FIXME: +// - the span is the span of the PopupWindow, that's wrong, we should have the span of the reference +// - There are other object reference than in the NamedReference +// - Maybe this should actually be allowed +pub fn check_no_reference_to_popup( + popup_window_element: &ElementRc, + parent_component: &Rc, + new_weak: &Weak, + random_valid_ref: &NamedReference, + diag: &mut BuildDiagnostics, +) { + visit_all_named_references(parent_component, &mut |nr| { + let element = &nr.element(); + if check_element(element, new_weak, diag, popup_window_element, nr.name()) { + // just set it to whatever is a valid NamedReference, otherwise we'll panic later + *nr = random_valid_ref.clone(); + } + }); + visit_all_expressions(parent_component, |exp, _| { + exp.visit_recursive_mut(&mut |exp| { + if let Expression::ElementReference(element) = exp { + let elem = element.upgrade().unwrap(); + if !Rc::ptr_eq(&elem, popup_window_element) { + check_element(&elem, new_weak, diag, popup_window_element, &""); + } + } + }); + }); +} + fn check_element( element: &ElementRc, popup_comp: &Weak, diag: &mut BuildDiagnostics, popup_window_element: &ElementRc, + prop_name: &str, ) -> bool { if Weak::ptr_eq(&element.borrow().enclosing_component, popup_comp) { + let element_name = popup_window_element + .borrow() + .builtin_type() + .map(|t| t.name.clone()) + .unwrap_or_else(|| SmolStr::new_static("PopupWindow")); + let id = element.borrow().id.clone(); + let what = if prop_name.is_empty() { + if id.is_empty() { "something".into() } else { format!("element '{id}'") } + } else { + if id.is_empty() { + format!("property or callback '{prop_name}'") + } else { + format!("property or callback '{id}.{prop_name}'") + } + }; + diag.push_error( - "Cannot access the inside of a PopupWindow from enclosing component".into(), + format!("Cannot access {what} inside of a {element_name} from enclosing component"), &*popup_window_element.borrow(), ); true diff --git a/internal/compiler/passes/materialize_fake_properties.rs b/internal/compiler/passes/materialize_fake_properties.rs index 3ec2e3d1a53..df6c9eb8255 100644 --- a/internal/compiler/passes/materialize_fake_properties.rs +++ b/internal/compiler/passes/materialize_fake_properties.rs @@ -26,6 +26,14 @@ pub fn materialize_fake_properties(component: &Rc) { && let Some(ty) = should_materialize(&elem.property_declarations, &elem.base_type, nr.name()) { + // This only brings more trouble down the line + if elem.repeated.is_some() { + panic!( + "Cannot materialize fake property {} on repeated element {}", + nr.name(), + elem.id + ); + } to_materialize.insert(nr.clone(), ty); } }); diff --git a/internal/compiler/passes/move_declarations.rs b/internal/compiler/passes/move_declarations.rs index 65e186c46a3..63359421195 100644 --- a/internal/compiler/passes/move_declarations.rs +++ b/internal/compiler/passes/move_declarations.rs @@ -52,7 +52,7 @@ fn do_move_declarations(component: &Rc) { return; } - // take the bindings so we do nt keep the borrow_mut of the element + // take the bindings so we do not keep the borrow_mut of the element let bindings = core::mem::take(&mut elem.borrow_mut().bindings); let mut new_bindings = BindingsMap::default(); for (k, e) in bindings { @@ -165,7 +165,7 @@ fn simplify_optimized_items_recursive(component: &Rc) { }); } -/// Optimized item are not used for the fact that they are items, but their properties +/// Optimized items are not used for the fact that they are items, but their properties /// might still be used. So we must pretend all the properties are declared in the /// item itself so the move_declaration pass can move the declaration in the component root fn simplify_optimized_items(items: &[ElementRc]) { diff --git a/internal/compiler/passes/repeater_component.rs b/internal/compiler/passes/repeater_component.rs index 0495034f70e..985362e0bde 100644 --- a/internal/compiler/passes/repeater_component.rs +++ b/internal/compiler/passes/repeater_component.rs @@ -63,6 +63,7 @@ fn create_repeater_components(component: &Rc) { item_index_of_first_children: Default::default(), is_legacy_syntax: elem.is_legacy_syntax, inline_depth: 0, + grid_layout_cell: elem.grid_layout_cell.clone(), })), parent_element, ..Component::default() @@ -92,7 +93,7 @@ fn create_repeater_components(component: &Rc) { e.borrow_mut().enclosing_component = weak.clone() }); - // Move all the menus that belong to the new crated component + // Move all the menus that belong to the newly created component // Could use Vec::extract_if if MSRV >= 1.87 component.menu_item_tree.borrow_mut().retain(|x| { if x.parent_element @@ -121,7 +122,7 @@ fn create_repeater_components(component: &Rc) { } } -/// Make sure that references to property within the repeated element actually point to the reference +/// Make sure that references to properties within the repeated element actually point to the reference /// to the root of the newly created component fn adjust_references(comp: &Rc) { visit_all_named_references(comp, &mut |nr| { diff --git a/internal/compiler/tests/syntax/analysis/binding_loop_image.slint b/internal/compiler/tests/syntax/analysis/binding_loop_image.slint new file mode 100644 index 00000000000..55f8b52e8cc --- /dev/null +++ b/internal/compiler/tests/syntax/analysis/binding_loop_image.slint @@ -0,0 +1,26 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +// Issue 10342 + +export component MyPanel { +// >error{The binding for the property 'layoutinfo-v' is part of a binding loop (fb.source-clip-height -> layoutinfo-v -> preferred-height -> height)} + fb := Image { + width: 100%; + source-clip-width: root.width / 0.531px; + source-clip-height: root.height / 0.531px; +// > layoutinfo-v -> preferred-height -> height)} + } + +} +//<< layoutinfo-v -> preferred-height -> height)} + +export component MainWindow inherits Window { + MyPanel { +// > layoutinfo-v -> preferred-height -> height)} +// > <^error{The binding for the property 'height' is part of a binding loop (fb.source-clip-height -> layoutinfo-v -> preferred-height -> height)} + x: 30px; + y: 30px; + width: 200px; + } +} diff --git a/internal/compiler/tests/syntax/basic/popup.slint b/internal/compiler/tests/syntax/basic/popup.slint index b251881a144..05dd80d36fb 100644 --- a/internal/compiler/tests/syntax/basic/popup.slint +++ b/internal/compiler/tests/syntax/basic/popup.slint @@ -9,7 +9,7 @@ component Foo { component Issue5852 { p := PopupWindow { -// > +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +export component A { + cb := ContextMenuArea { +// > { + inner-menu.title = "Hi"; + } + } +} + diff --git a/internal/compiler/tests/syntax/elements/menubar4.slint b/internal/compiler/tests/syntax/elements/menubar4.slint new file mode 100644 index 00000000000..a9688e3a655 --- /dev/null +++ b/internal/compiler/tests/syntax/elements/menubar4.slint @@ -0,0 +1,15 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +export component B inherits Window { + callback activate <=> item.activated; + MenuBar { +// > -// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 - -export Test := Rectangle { -// > condition; - - GridLayout { - Row { - if (condition): Text { -// > Expression::ReturnStatement( expr.as_ref().map(|e| Box::new(self.snapshot_expression(e))), ), - Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { - Expression::LayoutCacheAccess { - layout_cache_prop: layout_cache_prop.snapshot(self), - index: *index, - repeater_index: repeater_index - .as_ref() - .map(|e| Box::new(self.snapshot_expression(e))), - } - } + Expression::LayoutCacheAccess { + layout_cache_prop, + index, + repeater_index, + entries_per_item, + } => Expression::LayoutCacheAccess { + layout_cache_prop: layout_cache_prop.snapshot(self), + index: *index, + repeater_index: repeater_index + .as_ref() + .map(|e| Box::new(self.snapshot_expression(e))), + entries_per_item: *entries_per_item, + }, Expression::MinMax { ty, op, lhs, rhs } => Expression::MinMax { ty: ty.clone(), lhs: Box::new(self.snapshot_expression(lhs)), diff --git a/internal/compiler/typeregister.rs b/internal/compiler/typeregister.rs index b539ed7f299..8164bf60f2d 100644 --- a/internal/compiler/typeregister.rs +++ b/internal/compiler/typeregister.rs @@ -41,10 +41,10 @@ pub const RESERVED_LAYOUT_PROPERTIES: &[(&str, Type)] = &[ ]; pub const RESERVED_GRIDLAYOUT_PROPERTIES: &[(&str, Type)] = &[ - ("col", Type::Int32), - ("row", Type::Int32), - ("colspan", Type::Int32), - ("rowspan", Type::Int32), + ("col", Type::Float32), + ("row", Type::Float32), + ("colspan", Type::Float32), + ("rowspan", Type::Float32), ]; macro_rules! declare_enums { @@ -142,7 +142,7 @@ impl BuiltinTypes { box_layout_cell_data_type: Type::Struct(Rc::new(Struct { fields: IntoIterator::into_iter([("constraint".into(), layout_info_type.into())]) .collect(), - name: BuiltinPrivateStruct::BoxLayoutCellData.into(), + name: BuiltinPrivateStruct::LayoutItemInfo.into(), })), gridlayout_input_data_type: Type::Struct(Rc::new(Struct { fields: IntoIterator::into_iter([ @@ -332,6 +332,7 @@ pub fn reserved_member_function(name: &str) -> Option { None } +/// All types (datatypes, internal elements, properties, ...) are stored in this type #[derive(Debug, Default)] pub struct TypeRegister { /// The set of property types. @@ -746,7 +747,7 @@ pub fn path_element_type() -> Type { BUILTIN.with(|types| types.path_element_type.clone()) } -/// The [`Type`] for a runtime BoxLayoutCellData structure +/// The [`Type`] for a runtime LayoutItemInfo structure pub fn box_layout_cell_data_type() -> Type { BUILTIN.with(|types| types.box_layout_cell_data_type.clone()) } diff --git a/internal/compiler/widgets/common/textedit-base.slint b/internal/compiler/widgets/common/textedit-base.slint index a13632c00fa..99216f74b12 100644 --- a/internal/compiler/widgets/common/textedit-base.slint +++ b/internal/compiler/widgets/common/textedit-base.slint @@ -11,6 +11,7 @@ export component TextEditBase inherits Rectangle { in property read-only <=> text-input.read-only; in property font-size <=> text-input.font-size; in property font-family <=> text-input.font-family; + in property font-italic <=> text-input.font-italic; in property enabled <=> text-input.enabled; out property has-focus: text-input.has-focus; out property visible-width <=> scroll-view.visible-width; diff --git a/internal/compiler/widgets/cosmic/textedit.slint b/internal/compiler/widgets/cosmic/textedit.slint index 2a82834b5f2..4cc19ac636d 100644 --- a/internal/compiler/widgets/cosmic/textedit.slint +++ b/internal/compiler/widgets/cosmic/textedit.slint @@ -11,6 +11,7 @@ export component TextEdit { in property read-only <=> base.read-only; in property font-size <=> base.font-size; in property font-family <=> base.font-family; + in property font-italic <=> base.font-italic; in property enabled <=> base.enabled; in property placeholder-text <=> base.placeholder-text; out property has-focus: base.has-focus; diff --git a/internal/compiler/widgets/cupertino/textedit.slint b/internal/compiler/widgets/cupertino/textedit.slint index 2586bbd9c42..b1841243fc9 100644 --- a/internal/compiler/widgets/cupertino/textedit.slint +++ b/internal/compiler/widgets/cupertino/textedit.slint @@ -65,6 +65,7 @@ export component TextEdit { in property read-only <=> text-input.read-only; in property font-size <=> text-input.font-size; in property font-family <=> text-input.font-family; + in property font-italic <=> text-input.font-italic; in property enabled <=> text-input.enabled; out property visible-width <=> scroll-view.visible-width; out property visible-height <=> scroll-view.visible-height; diff --git a/internal/compiler/widgets/fluent/textedit.slint b/internal/compiler/widgets/fluent/textedit.slint index 5829827a7e2..3b924bbfa08 100644 --- a/internal/compiler/widgets/fluent/textedit.slint +++ b/internal/compiler/widgets/fluent/textedit.slint @@ -11,6 +11,7 @@ export component TextEdit { in property read-only <=> base.read-only; in property font-size <=> base.font-size; in property font-family <=> base.font-family; + in property font-italic <=> base.font-italic; in property enabled <=> base.enabled; in property placeholder-text <=> base.placeholder-text; out property has-focus: base.has-focus; diff --git a/internal/compiler/widgets/material/textedit.slint b/internal/compiler/widgets/material/textedit.slint index 393f5ffece2..c5692e3b5e3 100644 --- a/internal/compiler/widgets/material/textedit.slint +++ b/internal/compiler/widgets/material/textedit.slint @@ -11,6 +11,7 @@ export component TextEdit { in property read-only <=> base.read-only; in property font-size <=> base.font-size; in property font-family <=> base.font-family; + in property font-italic <=> base.font-italic; in property enabled <=> base.enabled; in property placeholder-text <=> base.placeholder-text; out property has-focus: base.has-focus; diff --git a/internal/compiler/widgets/qt/textedit.slint b/internal/compiler/widgets/qt/textedit.slint index 4a1bf503101..d14993cef22 100644 --- a/internal/compiler/widgets/qt/textedit.slint +++ b/internal/compiler/widgets/qt/textedit.slint @@ -10,6 +10,7 @@ export component TextEdit { in property read-only <=> base.read-only; in property font-size <=> base.font-size; in property font-family <=> base.font-family; + in property font-italic <=> base.font-italic; in property enabled <=> base.enabled; in property placeholder-text <=> base.placeholder-text; out property has-focus: base.has-focus; diff --git a/internal/core-macros/link-data.json b/internal/core-macros/link-data.json index b547823825a..8231d72f384 100644 --- a/internal/core-macros/link-data.json +++ b/internal/core-macros/link-data.json @@ -117,7 +117,7 @@ "href": "reference/primitive-types/#numeric-types" }, "Palette": { - "href": "reference/std-widgets/overview/#palette-properties" + "href": "reference/std-widgets/globals/palette/" }, "Path": { "href": "reference/elements/path/" @@ -164,6 +164,9 @@ "StyleWidgets": { "href": "reference/std-widgets/style/" }, + "StyleMetrics": { + "href": "reference/std-widgets/globals/stylemetrics/" + }, "Text": { "href": "reference/elements/text/" }, @@ -185,6 +188,9 @@ "VerticalLayout": { "href": "reference/layouts/verticallayout/" }, + "VSCode": { + "href": "guide/tooling/vscode/" + }, "QtBackend": { "href": "guide/backends-and-renderers/backend_qt/" }, @@ -236,6 +242,36 @@ "modules": { "href": "guide/language/coding/file/#modules" }, + "ManualEditorSetup": { + "href": "guide/tooling/manual-setup/" + }, + "Kate": { + "href": "guide/tooling/kate/" + }, + "QtCreator": { + "href": "guide/tooling/qt-creator/" + }, + "Helix": { + "href": "guide/tooling/helix/" + }, + "NeoVim": { + "href": "guide/tooling/neo-vim/" + }, + "SublimeText": { + "href": "guide/tooling/sublime-text/" + }, + "JetBrains": { + "href": "guide/tooling/jetbrains-ide/" + }, + "Zed": { + "href": "guide/tooling/zed/" + }, + "SlintLSP": { + "href": "guide/tooling/manual-setup/#slint-lsp" + }, + "KateSyntaxHighlighting": { + "href": "guide/tooling/kate/#syntax-highlighting" + }, "index": { "href": "" } diff --git a/internal/core/api.rs b/internal/core/api.rs index d3310ec04b1..8e41f5b87ee 100644 --- a/internal/core/api.rs +++ b/internal/core/api.rs @@ -7,15 +7,20 @@ This module contains types that are public and re-exported in the slint-rs as we #![warn(missing_docs)] -#[cfg(target_has_atomic = "ptr")] -pub use crate::future::*; -use crate::graphics::{Rgba8Pixel, SharedPixelBuffer}; use crate::input::{KeyEventType, MouseEvent}; -pub use crate::styled_text::StyledText; use crate::window::{WindowAdapter, WindowInner}; use alloc::boxed::Box; use alloc::string::String; +#[cfg(target_has_atomic = "ptr")] +pub use crate::future::*; +pub use crate::graphics::{ + Brush, Color, Image, LoadImageError, Rgb8Pixel, Rgba8Pixel, RgbaColor, SharedPixelBuffer, +}; +pub use crate::sharedvector::SharedVector; +pub use crate::styled_text::StyledText; +pub use crate::{format, string::SharedString, string::ToSharedString}; + /// A position represented in the coordinate space of logical pixels. That is the space before applying /// a display device specific scale factor. #[derive(Debug, Default, Copy, Clone, PartialEq)] @@ -792,8 +797,6 @@ impl Window { } } -pub use crate::SharedString; - #[i_slint_core_macros::slint_doc] /// This trait is used to obtain references to global singletons exported in `.slint` /// markup. Alternatively, you can use [`ComponentHandle::global`] to obtain access. diff --git a/internal/core/context.rs b/internal/core/context.rs index 3d10e601a4c..746afbd1167 100644 --- a/internal/core/context.rs +++ b/internal/core/context.rs @@ -117,6 +117,11 @@ pub fn with_global_context( GLOBAL_CONTEXT.with(|p| match p.get() { Some(ctx) => Ok(f(ctx)), None => { + if crate::platform::with_event_loop_proxy(|proxy| proxy.is_some()) { + return Err(PlatformError::SetPlatformError( + crate::platform::SetPlatformError::AlreadySet, + )); + } crate::platform::set_platform(factory()?).map_err(PlatformError::SetPlatformError)?; Ok(f(p.get().unwrap())) } diff --git a/internal/core/items.rs b/internal/core/items.rs index f1851d4e978..a65f47b8411 100644 --- a/internal/core/items.rs +++ b/internal/core/items.rs @@ -1150,6 +1150,7 @@ declare_item_vtable! { pub struct PropertyAnimation { #[rtti_field] pub delay: i32, + /// duration in millisecond #[rtti_field] pub duration: i32, #[rtti_field] diff --git a/internal/core/layout.rs b/internal/core/layout.rs index 14587771128..d9d2d433c78 100644 --- a/internal/core/layout.rs +++ b/internal/core/layout.rs @@ -7,6 +7,8 @@ use crate::items::{DialogButtonRole, LayoutAlignment}; use crate::{Coord, SharedVector, slice::Slice}; +use alloc::format; +use alloc::string::String; use alloc::vec::Vec; pub use crate::items::Orientation; @@ -309,26 +311,29 @@ mod grid_internal { /// Used by both solve_grid_layout() and grid_layout_info() pub fn to_layout_data( organized_data: &GridLayoutOrganizedData, - constraints: Slice, + constraints: Slice, orientation: Orientation, + repeater_indices: Slice, spacing: Coord, size: Option, ) -> Vec { - assert!(organized_data.len() % 4 == 0); - assert!(constraints.len() * 4 == organized_data.len()); - let num = organized_data.max_value(orientation) as usize; + assert!(organized_data.len().is_multiple_of(4)); + let num = + organized_data.max_value(constraints.len(), orientation, &repeater_indices) as usize; if num < 1 { return Default::default(); } let mut layout_data = alloc::vec![grid_internal::LayoutData { stretch: 1., ..Default::default() }; num]; let mut has_spans = false; - for (idx, constraint) in constraints.iter().enumerate() { + for (idx, cell_data) in constraints.iter().enumerate() { + let constraint = &cell_data.constraint; let mut max = constraint.max; if let Some(size) = size { max = max.min(size * constraint.max_percent / 100 as Coord); } - let (col_or_row, span) = organized_data.col_or_row_and_span(idx, orientation); + let (col_or_row, span) = + organized_data.col_or_row_and_span(idx, orientation, &repeater_indices); for c in 0..(span as usize) { let cdata = &mut layout_data[col_or_row as usize + c]; cdata.max = cdata.max.min(max); @@ -348,8 +353,10 @@ mod grid_internal { } } if has_spans { - for (idx, constraint) in constraints.iter().enumerate() { - let (col_or_row, span) = organized_data.col_or_row_and_span(idx, orientation); + for (idx, cell_data) in constraints.iter().enumerate() { + let constraint = &cell_data.constraint; + let (col_or_row, span) = + organized_data.col_or_row_and_span(idx, orientation, &repeater_indices); if span > 1 { let span_data = &mut layout_data[(col_or_row as usize)..(col_or_row + span) as usize]; @@ -434,12 +441,15 @@ pub struct GridLayoutData { pub struct GridLayoutInputData { /// whether this cell is the first one in a Row element pub new_row: bool, - /// col and row number (u16::MAX means auto). - pub col: u16, - pub row: u16, + /// col and row number. + /// Only ROW_COL_AUTO and the u16 range are valid, values outside of + /// that will be clamped with a warning at runtime + pub col: f32, + pub row: f32, /// colspan and rowspan - pub colspan: u16, - pub rowspan: u16, + /// Only the u16 range is valid, values outside of that will be clamped with a warning at runtime + pub colspan: f32, + pub rowspan: f32, } /// The organized layout data for a GridLayout, after row/col determination: @@ -454,21 +464,114 @@ impl GridLayoutOrganizedData { self.push(rowspan); } - fn col_or_row_and_span(&self, index: usize, orientation: Orientation) -> (u16, u16) { + fn col_or_row_and_span( + &self, + cell_number: usize, + orientation: Orientation, + repeater_indices: &Slice, + ) -> (u16, u16) { + // For every cell, we have 4 entries, each at their own index + // But we also need to take into account indirections for repeated items + let mut final_idx = 0; + let mut cell_nr_adj = 0i32; // needs to be signed in case we start with an empty repeater + let cell_number = cell_number as i32; + // repeater_indices is a list of (start_cell, count) pairs + for rep_idx in 0..(repeater_indices.len() / 2) { + let ri_start_cell = repeater_indices[rep_idx * 2] as i32; + let ri_cell_count = repeater_indices[rep_idx * 2 + 1] as i32; + if cell_number < ri_start_cell { + break; + } + if ri_cell_count > 0 + && cell_number >= ri_start_cell + && cell_number < ri_start_cell + ri_cell_count + { + // read the jump index + final_idx = self[(ri_start_cell - cell_nr_adj) as usize * 4] as usize + + ((cell_number - ri_start_cell) * 4) as usize; + break; + } + // If self contains [cell0] [repeater item pointing to 3 items] [cell4] + // then cell4 is at position 2, we need to adjust by 3-1=2 cells + // -1 is correct for an empty repeater (e.g. if false), which takes one position, for 0 real cells + cell_nr_adj += ri_cell_count - 1; + } + if final_idx == 0 { + final_idx = ((cell_number - cell_nr_adj) * 4) as usize; + } let offset = if orientation == Orientation::Horizontal { 0 } else { 2 }; - (self[index * 4 + offset], self[index * 4 + offset + 1]) + (self[final_idx + offset], self[final_idx + offset + 1]) } - fn max_value(&self, orientation: Orientation) -> u16 { + fn max_value( + &self, + num_cells: usize, + orientation: Orientation, + repeater_indices: &Slice, + ) -> u16 { let mut max = 0; - for idx in 0..self.len() / 4 { - let (col_or_row, span) = self.col_or_row_and_span(idx, orientation); + // This could be rewritten more efficiently to avoid a loop calling a loop, by keeping track of the repeaters we saw until now + // Not sure it's worth the complexity though + for idx in 0..num_cells { + let (col_or_row, span) = self.col_or_row_and_span(idx, orientation, repeater_indices); max = max.max(col_or_row + span.max(1)); } max } } +struct OrganizedDataGenerator<'a> { + // Input + repeater_indices: &'a [u32], + // An always increasing counter, the index of the cell being added + counter: usize, + // The index/4 in result in which we should add the next repeated item + repeat_offset: usize, + // The index/4 in repeater_indices + next_rep: usize, + // The index/4 in result in which we should add the next non-repeated item + current_offset: usize, + // Output + result: &'a mut GridLayoutOrganizedData, +} + +impl<'a> OrganizedDataGenerator<'a> { + fn new(repeater_indices: &'a [u32], result: &'a mut GridLayoutOrganizedData) -> Self { + let repeat_offset = + result.len() / 4 - repeater_indices.iter().skip(1).step_by(2).sum::() as usize; + Self { repeater_indices, counter: 0, repeat_offset, next_rep: 0, current_offset: 0, result } + } + fn add(&mut self, col: u16, colspan: u16, row: u16, rowspan: u16) { + let res = self.result.make_mut_slice(); + let o = loop { + if let Some(nr) = self.repeater_indices.get(self.next_rep * 2) { + let nr = *nr as usize; + if nr == self.counter { + for o in 0..4 { + res[self.current_offset * 4 + o] = (self.repeat_offset * 4 + o) as _; + } + self.current_offset += 1; + } + if self.counter >= nr { + if self.counter - nr == self.repeater_indices[self.next_rep * 2 + 1] as usize { + self.next_rep += 1; + continue; + } + self.repeat_offset += 1; + break self.repeat_offset - 1; + } + } + self.current_offset += 1; + break self.current_offset - 1; + }; + res[o * 4] = col; + res[o * 4 + 1] = colspan; + res[o * 4 + 2] = row; + res[o * 4 + 3] = rowspan; + self.counter += 1; + } +} + /// Given the cells of a layout of a Dialog, re-order the buttons according to the platform /// This function assume that the `roles` contains the roles of the button which are the first cells in `input_data` pub fn organize_dialog_button_layout( @@ -555,42 +658,86 @@ pub fn organize_dialog_button_layout( for (input_index, cell) in input_data.as_slice().iter().enumerate() { let col = column_for_input.iter().position(|&x| x == input_index); if let Some(col) = col { - organized_data.push_cell(col as u16, cell.colspan, cell.row, cell.rowspan); + organized_data.push_cell(col as _, cell.colspan as _, cell.row as _, cell.rowspan as _); } else { // This is used for the main window (which is the only cell which isn't a button) // Given lower_dialog_layout(), this will always be a single cell at 0,0 with a colspan of number_of_buttons - organized_data.push_cell(cell.col, cell.colspan, cell.row, cell.rowspan); + organized_data.push_cell( + cell.col as _, + cell.colspan as _, + cell.row as _, + cell.rowspan as _, + ); } } organized_data } +type Errors = Vec; + +pub fn organize_grid_layout( + input_data: Slice, + repeater_indices: Slice, +) -> GridLayoutOrganizedData { + let (organized_data, errors) = organize_grid_layout_impl(input_data, repeater_indices); + for error in errors { + crate::debug_log!("Slint layout error: {}", error); + } + organized_data +} + // Implement "auto" behavior for row/col numbers (unless specified in the slint file). -pub fn organize_grid_layout(input_data: Slice) -> GridLayoutOrganizedData { +fn organize_grid_layout_impl( + input_data: Slice, + repeater_indices: Slice, +) -> (GridLayoutOrganizedData, Errors) { let mut organized_data = GridLayoutOrganizedData::default(); - organized_data.reserve(input_data.len() * 4); + organized_data.resize(input_data.len() * 4 + repeater_indices.len() * 2, 0 as _); + let mut generator = + OrganizedDataGenerator::new(repeater_indices.as_slice(), &mut organized_data); + let mut errors = Vec::new(); + + fn clamp_to_u16(value: f32, field_name: &str, errors: &mut Vec) -> u16 { + if value < 0.0 { + errors.push(format!("cell {field_name} {value} is negative, clamping to 0")); + 0 + } else if value > u16::MAX as f32 { + errors + .push(format!("cell {field_name} {value} is too large, clamping to {}", u16::MAX)); + u16::MAX + } else { + value as u16 + } + } + let mut row = 0; let mut col = 0; let mut first = true; - let auto = u16::MAX; for cell in input_data.as_slice().iter() { if cell.new_row && !first { row += 1; col = 0; } first = false; - if cell.row != auto && row != cell.row { - row = cell.row; - col = 0; + + if cell.row != i_slint_common::ROW_COL_AUTO { + let cell_row = clamp_to_u16(cell.row, "row", &mut errors); + if row != cell_row { + row = cell_row; + col = 0; + } } - if cell.col != auto { - col = cell.col; + if cell.col != i_slint_common::ROW_COL_AUTO { + col = clamp_to_u16(cell.col, "col", &mut errors); } - organized_data.push_cell(col, cell.colspan, row, cell.rowspan); - col += 1; + let colspan = clamp_to_u16(cell.colspan, "colspan", &mut errors); + let rowspan = clamp_to_u16(cell.rowspan, "rowspan", &mut errors); + col = col.min(u16::MAX - colspan); // ensure col + colspan doesn't overflow + generator.add(col, colspan, row, rowspan); + col += colspan; } - organized_data + (organized_data, errors) } /// The layout cache generator inserts the pos and size into the result array (which becomes the layout cache property), @@ -653,13 +800,15 @@ impl<'a> LayoutCacheGenerator<'a> { /// pos (x or y), size (width or height) pub fn solve_grid_layout( data: &GridLayoutData, - constraints: Slice, + constraints: Slice, orientation: Orientation, + repeater_indices: Slice, ) -> SharedVector { let mut layout_data = grid_internal::to_layout_data( &data.organized_data, constraints, orientation, + repeater_indices, data.spacing, Some(data.size), ); @@ -675,31 +824,41 @@ pub fn solve_grid_layout( data.spacing, ); - let mut result = SharedVector::with_capacity(2 * constraints.len()); + let mut result = SharedVector::::default(); + result.resize(2 * constraints.len() + repeater_indices.len(), 0 as _); + let mut generator = LayoutCacheGenerator::new(&repeater_indices, &mut result); + for idx in 0..constraints.len() { - let (col_or_row, span) = data.organized_data.col_or_row_and_span(idx, orientation); + let (col_or_row, span) = + data.organized_data.col_or_row_and_span(idx, orientation, &repeater_indices); let cdata = &layout_data[col_or_row as usize]; - result.push(cdata.pos); - result.push(if span > 0 { - let first_cell = &layout_data[col_or_row as usize]; + let size = if span > 0 { let last_cell = &layout_data[col_or_row as usize + span as usize - 1]; - last_cell.pos + last_cell.size - first_cell.pos + last_cell.pos + last_cell.size - cdata.pos } else { 0 as Coord - }); + }; + generator.add(cdata.pos, size); } result } pub fn grid_layout_info( organized_data: GridLayoutOrganizedData, // not & because the code generator doesn't support it in ExtraBuiltinFunctionCall - constraints: Slice, + constraints: Slice, + repeater_indices: Slice, spacing: Coord, padding: &Padding, orientation: Orientation, ) -> LayoutInfo { - let layout_data = - grid_internal::to_layout_data(&organized_data, constraints, orientation, spacing, None); + let layout_data = grid_internal::to_layout_data( + &organized_data, + constraints, + orientation, + repeater_indices, + spacing, + None, + ); if layout_data.is_empty() { return Default::default(); } @@ -721,12 +880,14 @@ pub struct BoxLayoutData<'a> { pub spacing: Coord, pub padding: Padding, pub alignment: LayoutAlignment, - pub cells: Slice<'a, BoxLayoutCellData>, + pub cells: Slice<'a, LayoutItemInfo>, } #[repr(C)] #[derive(Default, Debug, Clone)] -pub struct BoxLayoutCellData { +/// The information about a single item in a layout +/// For now this only contains the LayoutInfo constraints, but could be extended in the future +pub struct LayoutItemInfo { pub constraint: LayoutInfo, } @@ -816,7 +977,7 @@ pub fn solve_box_layout(data: &BoxLayoutData, repeater_indices: Slice) -> S /// Return the LayoutInfo for a BoxLayout with the given cells. pub fn box_layout_info( - cells: Slice, + cells: Slice, spacing: Coord, padding: &Padding, alignment: LayoutAlignment, @@ -844,7 +1005,7 @@ pub fn box_layout_info( LayoutInfo { min, max, min_percent: 0 as _, max_percent: 100 as _, preferred, stretch } } -pub fn box_layout_info_ortho(cells: Slice, padding: &Padding) -> LayoutInfo { +pub fn box_layout_info_ortho(cells: Slice, padding: &Padding) -> LayoutInfo { let extra_w = padding.begin + padding.end; let mut fold = cells.iter().fold(LayoutInfo { stretch: f32::MAX, ..Default::default() }, |a, b| { @@ -867,9 +1028,10 @@ pub(crate) mod ffi { #[unsafe(no_mangle)] pub extern "C" fn slint_organize_grid_layout( input_data: Slice, + repeater_indices: Slice, result: &mut GridLayoutOrganizedData, ) { - *result = super::organize_grid_layout(input_data); + *result = super::organize_grid_layout(input_data, repeater_indices); } #[unsafe(no_mangle)] @@ -884,22 +1046,31 @@ pub(crate) mod ffi { #[unsafe(no_mangle)] pub extern "C" fn slint_solve_grid_layout( data: &GridLayoutData, - constraints: Slice, + constraints: Slice, orientation: Orientation, + repeater_indices: Slice, result: &mut SharedVector, ) { - *result = super::solve_grid_layout(data, constraints, orientation) + *result = super::solve_grid_layout(data, constraints, orientation, repeater_indices) } #[unsafe(no_mangle)] pub extern "C" fn slint_grid_layout_info( organized_data: &GridLayoutOrganizedData, - constraints: Slice, + constraints: Slice, + repeater_indices: Slice, spacing: Coord, padding: &Padding, orientation: Orientation, ) -> LayoutInfo { - super::grid_layout_info(organized_data.clone(), constraints, spacing, padding, orientation) + super::grid_layout_info( + organized_data.clone(), + constraints, + repeater_indices, + spacing, + padding, + orientation, + ) } #[unsafe(no_mangle)] @@ -914,7 +1085,7 @@ pub(crate) mod ffi { #[unsafe(no_mangle)] /// Return the LayoutInfo for a BoxLayout with the given cells. pub extern "C" fn slint_box_layout_info( - cells: Slice, + cells: Slice, spacing: Coord, padding: &Padding, alignment: LayoutAlignment, @@ -925,7 +1096,7 @@ pub(crate) mod ffi { #[unsafe(no_mangle)] /// Return the LayoutInfo for a BoxLayout with the given cells. pub extern "C" fn slint_box_layout_info_ortho( - cells: Slice, + cells: Slice, padding: &Padding, ) -> LayoutInfo { super::box_layout_info_ortho(cells, padding) @@ -936,6 +1107,185 @@ pub(crate) mod ffi { mod tests { use super::*; + fn collect_from_organized_data( + organized_data: &GridLayoutOrganizedData, + num_cells: usize, + repeater_indices: Slice, + ) -> Vec<(u16, u16, u16, u16)> { + let mut result = Vec::new(); + for i in 0..num_cells { + let col_and_span = + organized_data.col_or_row_and_span(i, Orientation::Horizontal, &repeater_indices); + let row_and_span = + organized_data.col_or_row_and_span(i, Orientation::Vertical, &repeater_indices); + result.push((col_and_span.0, col_and_span.1, row_and_span.0, row_and_span.1)); + } + result + } + + #[test] + fn test_organized_data_generator_2_fixed_cells() { + // 2 fixed cells + let mut result = GridLayoutOrganizedData::default(); + let num_cells = 2; + result.resize(num_cells * 4, 0 as _); + let mut generator = OrganizedDataGenerator::new(&[], &mut result); + generator.add(0, 1, 0, 1); + generator.add(1, 2, 0, 3); + assert_eq!(result.as_slice(), &[0, 1, 0, 1, 1, 2, 0, 3]); + + let repeater_indices = Slice::from_slice(&[]); + let collected_data = collect_from_organized_data(&result, num_cells, repeater_indices); + assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1), (1, 2, 0, 3)]); + + assert_eq!(result.max_value(num_cells, Orientation::Horizontal, &repeater_indices), 3); + assert_eq!(result.max_value(num_cells, Orientation::Vertical, &repeater_indices), 3); + } + + #[test] + fn test_organized_data_generator_1_fixed_cell_1_repeater() { + // 4 cells: 1 fixed cell, 1 repeater with 3 repeated cells + let mut result = GridLayoutOrganizedData::default(); + let num_cells = 4; + let repeater_indices = &[1u32, 3u32]; + result.resize(num_cells * 4 + 2 * repeater_indices.len(), 0 as _); + let mut generator = OrganizedDataGenerator::new(repeater_indices, &mut result); + generator.add(0, 1, 0, 2); // fixed + generator.add(1, 2, 1, 3); // repeated + generator.add(1, 1, 2, 4); + generator.add(2, 2, 3, 5); + assert_eq!( + result.as_slice(), + &[ + 0, 1, 0, 2, // fixed + 8, 9, 10, 11, // jump to repeater data + 1, 2, 1, 3, 1, 1, 2, 4, 2, 2, 3, 5 // repeater data + ] + ); + let repeater_indices = Slice::from_slice(repeater_indices); + let collected_data = collect_from_organized_data(&result, num_cells, repeater_indices); + assert_eq!( + collected_data.as_slice(), + &[(0, 1, 0, 2), (1, 2, 1, 3), (1, 1, 2, 4), (2, 2, 3, 5)] + ); + + assert_eq!(result.max_value(num_cells, Orientation::Horizontal, &repeater_indices), 4); + assert_eq!(result.max_value(num_cells, Orientation::Vertical, &repeater_indices), 8); + } + + #[test] + + fn test_organize_data_with_auto_and_spans() { + let auto = i_slint_common::ROW_COL_AUTO; + let input = std::vec![ + GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: -1. }, + GridLayoutInputData { new_row: false, col: auto, row: auto, colspan: 1., rowspan: 2. }, + GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 2., rowspan: 1. }, + GridLayoutInputData { new_row: true, col: -2., row: 80000., colspan: 2., rowspan: 1. }, + ]; + let repeater_indices = Slice::from_slice(&[]); + let (organized_data, errors) = + organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices); + assert_eq!( + organized_data.as_slice(), + &[ + 0, 2, 0, 0, // row 0, col 0, rowspan 0 (see below) + 2, 1, 0, 2, // row 0, col 2 (due to colspan of first cell) + 0, 2, 1, 1, // row 1, col 0 + 0, 2, 65535, 1, // row 65535, col 0 + ] + ); + assert_eq!(errors.len(), 3); + // Note that a rowspan of 0 is valid, it means the cell doesn't occupy any row + assert_eq!(errors[0], "cell rowspan -1 is negative, clamping to 0"); + assert_eq!(errors[1], "cell row 80000 is too large, clamping to 65535"); + assert_eq!(errors[2], "cell col -2 is negative, clamping to 0"); + let collected_data = + collect_from_organized_data(&organized_data, input.len(), repeater_indices); + assert_eq!( + collected_data.as_slice(), + &[(0, 2, 0, 0), (2, 1, 0, 2), (0, 2, 1, 1), (0, 2, 65535, 1)] + ); + assert_eq!(organized_data.max_value(3, Orientation::Horizontal, &repeater_indices), 3); + assert_eq!(organized_data.max_value(3, Orientation::Vertical, &repeater_indices), 2); + } + + #[test] + fn test_organize_data_1_empty_repeater() { + // Row { Text {} if false: Text {} }, this test shows why we need i32 for cell_nr_adj + let auto = i_slint_common::ROW_COL_AUTO; + let cell = + GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. }; + let input = std::vec![cell]; + let repeater_indices = Slice::from_slice(&[1u32, 0u32]); + let (organized_data, errors) = + organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices); + assert_eq!( + organized_data.as_slice(), + &[ + 0, 1, 0, 1, // fixed + 0, 0, 0, 0 + ] // jump to repeater data (not used) + ); + assert_eq!(errors.len(), 0); + let collected_data = + collect_from_organized_data(&organized_data, input.len(), repeater_indices); + assert_eq!(collected_data.as_slice(), &[(0, 1, 0, 1)]); + assert_eq!(organized_data.max_value(1, Orientation::Horizontal, &repeater_indices), 1); + } + + #[test] + fn test_organize_data_4_repeaters() { + let auto = i_slint_common::ROW_COL_AUTO; + let mut cell = + GridLayoutInputData { new_row: true, col: auto, row: auto, colspan: 1., rowspan: 1. }; + let mut input = std::vec![cell.clone()]; + for _ in 0..8 { + cell.new_row = false; + input.push(cell.clone()); + } + let repeater_indices = Slice::from_slice(&[0u32, 0u32, 1u32, 4u32, 6u32, 2u32, 8u32, 0u32]); + let (organized_data, errors) = + organize_grid_layout_impl(Slice::from_slice(&input), repeater_indices); + assert_eq!( + organized_data.as_slice(), + &[ + 28, 29, 30, 31, // jump to first (empty) repeater (not used) + 0, 1, 0, 1, // first row, first column + 28, 29, 30, 31, // jump to first repeater data + 5, 1, 0, 1, // fixed + 44, 45, 46, 47, // jump to second repeater data + 52, 53, 54, 55, // slot for jumping to 3rd repeater (out of bounds, not used) + 8, 1, 0, 1, // final fixed element + 1, 1, 0, 1, // first repeater data + 2, 1, 0, 1, 3, 1, 0, 1, 4, 1, 0, 1, // end of first repeater + 6, 1, 0, 1, // second repeater data + 7, 1, 0, 1 // end of second repeater + ] + ); + assert_eq!(errors.len(), 0); + let collected_data = + collect_from_organized_data(&organized_data, input.len(), repeater_indices); + assert_eq!( + collected_data.as_slice(), + &[ + (0, 1, 0, 1), + (1, 1, 0, 1), + (2, 1, 0, 1), + (3, 1, 0, 1), + (4, 1, 0, 1), + (5, 1, 0, 1), + (6, 1, 0, 1), + (7, 1, 0, 1), + (8, 1, 0, 1), + ] + ); + assert_eq!( + organized_data.max_value(input.len(), Orientation::Horizontal, &repeater_indices), + 9 + ); + } + #[test] fn test_layout_cache_generator_2_fixed_cells() { // 2 fixed cells diff --git a/internal/core/model.rs b/internal/core/model.rs index 14e0c40d10c..d43ed268183 100644 --- a/internal/core/model.rs +++ b/internal/core/model.rs @@ -821,12 +821,20 @@ pub trait RepeatedItemTree: LogicalLength::default() } - /// Returns what's needed to perform the layout if this ItemTrees is in a box layout - fn box_layout_data( + /// Returns what's needed to perform the layout if this ItemTree is in a layout + fn layout_item_info( self: Pin<&Self>, _orientation: Orientation, - ) -> crate::layout::BoxLayoutCellData { - crate::layout::BoxLayoutCellData::default() + ) -> crate::layout::LayoutItemInfo { + crate::layout::LayoutItemInfo::default() + } + + /// Returns what's needed to call organize_grid_layout layout if this ItemTree is in a grid layout + fn grid_layout_input_data( + self: Pin<&Self>, + new_row: bool, + ) -> crate::layout::GridLayoutInputData { + crate::layout::GridLayoutInputData { new_row, ..Default::default() } } } diff --git a/internal/core/model/adapters.rs b/internal/core/model/adapters.rs index 45ba2d508b8..cdcd4194aaf 100644 --- a/internal/core/model/adapters.rs +++ b/internal/core/model/adapters.rs @@ -912,11 +912,6 @@ where Self(container) } - - /// Returns a reference to the inner model - pub fn source_model(&self) -> &M { - &self.0.as_ref().get().get_ref().wrapped_model - } } impl SortModel @@ -944,6 +939,17 @@ where Self(container) } +} + +impl SortModel +where + M: Model + 'static, + S: SortHelper, +{ + /// Returns a reference to the inner model + pub fn source_model(&self) -> &M { + &self.0.as_ref().get().get_ref().wrapped_model + } /// Manually reapply the sorting. You need to run this e.g. if the sort function depends /// on mutable state and it has changed. @@ -1131,6 +1137,10 @@ mod sort_tests { for (i, v) in expected.iter().enumerate() { assert_eq!(model.row_data(i), Some(*v), "Expected {v} at index {i}"); } + + assert!(Rc::ptr_eq(model.source_model(), &wrapped_rc)); + model.reset(); + assert_eq!(*observer.reset.borrow(), 1); } #[test] @@ -1173,6 +1183,8 @@ mod sort_tests { assert_eq!(model.row_data(5), Some("a")); assert_eq!(model.row_data(6), None); assert_eq!(model.row_data(7), None); + + assert!(Rc::ptr_eq(model.source_model(), &wrapped_rc)); } } diff --git a/internal/core/properties.rs b/internal/core/properties.rs index 7b9d532b790..6c0679cbf91 100644 --- a/internal/core/properties.rs +++ b/internal/core/properties.rs @@ -509,6 +509,10 @@ struct PropertyHandle { handle: Cell, } +const BINDING_BORROWED: usize = 0b01; +const BINDING_POINTER_TO_BINDING: usize = 0b10; +const BINDING_POINTER_MASK: usize = !(BINDING_POINTER_TO_BINDING | BINDING_BORROWED); + impl core::fmt::Debug for PropertyHandle { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let handle = self.handle.get(); @@ -516,21 +520,48 @@ impl core::fmt::Debug for PropertyHandle { f, "PropertyHandle {{ handle: 0x{:x}, locked: {}, binding: {} }}", handle & !0b11, - (handle & 0b01) == 0b01, - (handle & 0b10) == 0b10 + self.lock_flag(), + PropertyHandle::is_pointer_to_binding(handle) ) } } impl PropertyHandle { /// The lock flag specifies that we can get a reference to the Cell or unsafe cell + #[inline] fn lock_flag(&self) -> bool { - self.handle.get() & 0b1 == 1 + self.handle.get() & BINDING_BORROWED == BINDING_BORROWED } /// Sets the lock_flag. /// Safety: the lock flag must not be unset if there exist references to what's inside the cell unsafe fn set_lock_flag(&self, set: bool) { - self.handle.set(if set { self.handle.get() | 0b1 } else { self.handle.get() & !0b1 }) + self.handle.set(if set { + self.handle.get() | BINDING_BORROWED + } else { + self.handle.get() & !BINDING_BORROWED + }) + } + + #[inline] + fn is_pointer_to_binding(handle: usize) -> bool { + handle & BINDING_POINTER_TO_BINDING == BINDING_POINTER_TO_BINDING + } + + /// Get the pointer **without locking** if the handle points to a pointer otherwise None + #[inline] + fn pointer_to_binding(handle: usize) -> Option<*mut BindingHolder> { + if Self::is_pointer_to_binding(handle) { + Some((handle & BINDING_POINTER_MASK) as *mut BindingHolder) + } else { + None + } + } + + /// The handle is not borrowed to any other binding + /// and the handle does not point to another binding + #[inline] + fn has_no_binding_or_lock(handle: usize) -> bool { + (handle as usize) & (BINDING_BORROWED | BINDING_POINTER_TO_BINDING) == 0 } /// Access the value. @@ -540,8 +571,8 @@ impl PropertyHandle { if self.lock_flag() { unsafe { let handle = self.handle.get(); - if handle & 0b10 == 0b10 { - let binding = &mut *((handle & !0b11) as *mut BindingHolder); + if let Some(binding_pointer) = Self::pointer_to_binding(handle) { + let binding = &mut *(binding_pointer); let debug_name = &binding.debug_name; panic!("Recursion detected with property {debug_name}"); } @@ -552,8 +583,8 @@ impl PropertyHandle { self.set_lock_flag(true); scopeguard::defer! { self.set_lock_flag(false); } let handle = self.handle.get(); - let binding = if handle & 0b10 == 0b10 { - Some(Pin::new_unchecked(&mut *((handle & !0b11) as *mut BindingHolder))) + let binding = if let Some(pointer) = Self::pointer_to_binding(handle) { + Some(Pin::new_unchecked(&mut *(pointer))) } else { None }; @@ -563,11 +594,10 @@ impl PropertyHandle { fn remove_binding(&self) { assert!(!self.lock_flag(), "Recursion detected"); - let val = self.handle.get(); - if val & 0b10 == 0b10 { + + if let Some(binding) = Self::pointer_to_binding(self.handle.get()) { unsafe { self.set_lock_flag(true); - let binding = (val & !0b11) as *mut BindingHolder; let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize; if (*binding).dependencies.get() == const_sentinel { self.handle.set(const_sentinel); @@ -580,8 +610,8 @@ impl PropertyHandle { } ((*binding).vtable.drop)(binding); } - debug_assert!(self.handle.get() & 0b11 == 0); } + debug_assert!(Self::has_no_binding_or_lock(self.handle.get())); } /// Safety: the BindingCallable must be valid for the type of this property @@ -612,8 +642,8 @@ impl PropertyHandle { } self.remove_binding(); - debug_assert!((binding as usize) & 0b11 == 0); - debug_assert!(self.handle.get() & 0b11 == 0); + debug_assert!(Self::has_no_binding_or_lock(binding as usize)); + debug_assert!(Self::has_no_binding_or_lock(self.handle.get())); let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize; let is_constant = self.handle.get() == const_sentinel; unsafe { @@ -626,7 +656,7 @@ impl PropertyHandle { ); } } - self.handle.set((binding as usize) | 0b10); + self.handle.set((binding as usize) | BINDING_POINTER_TO_BINDING); if !is_constant { self.mark_dirty( #[cfg(slint_debug_property)] @@ -637,7 +667,7 @@ impl PropertyHandle { fn dependencies(&self) -> *mut DependencyListHead { assert!(!self.lock_flag(), "Recursion detected"); - if (self.handle.get() & 0b10) != 0 { + if Self::is_pointer_to_binding(self.handle.get()) { self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead) } else { self.handle.as_ptr() as *mut DependencyListHead @@ -746,7 +776,7 @@ impl PropertyHandle { impl Drop for PropertyHandle { fn drop(&mut self) { self.remove_binding(); - debug_assert!(self.handle.get() & 0b11 == 0); + debug_assert!(Self::has_no_binding_or_lock(self.handle.get())); if self.handle.get() as *const u32 != (&CONSTANT_PROPERTY_SENTINEL) as *const u32 { unsafe { DependencyListHead::drop(self.handle.as_ptr() as *mut _); @@ -1084,8 +1114,7 @@ impl Property { /// If the property is a two way binding, return the common property pub(crate) fn check_common_property(self: Pin<&Self>) -> Option>>> { let handle_val = self.handle.handle.get(); - if handle_val & 0b10 == 0b10 { - let holder = (handle_val & !0b11) as *const BindingHolder; + if let Some(holder) = PropertyHandle::pointer_to_binding(handle_val) { // Safety: the handle is a pointer to a binding if unsafe { *&raw const (*holder).is_two_way_binding } { // Safety: the handle is a pointer to a binding whose B is a TwoWayBinding @@ -1136,7 +1165,7 @@ impl Property { } let prop2_handle_val = prop2.handle.handle.get(); - let handle = if prop2_handle_val & 0b10 == 0b10 { + let handle = if PropertyHandle::is_pointer_to_binding(prop2_handle_val) { // If prop2 is a binding, just "steal it" prop2.handle.handle.set(0); PropertyHandle { handle: Cell::new(prop2_handle_val) } @@ -1181,7 +1210,7 @@ impl Property { common_property } else { let prop1_handle_val = prop1.handle.handle.get(); - let handle = if prop1_handle_val & 0b10 == 0b10 { + let handle = if PropertyHandle::is_pointer_to_binding(prop1_handle_val) { // If prop1 is a binding, just "steal it" prop1.handle.handle.set(0); PropertyHandle { handle: Cell::new(prop1_handle_val) } @@ -1309,8 +1338,8 @@ impl Property { ); let old_handle = prop2.handle.handle.get(); - let has_binding = old_handle & 0b11 == 0b10; - if has_binding { + let old_pointer = PropertyHandle::pointer_to_binding(old_handle); + if old_pointer.is_some() { prop2.handle.handle.set(0); } @@ -1321,8 +1350,8 @@ impl Property { debug_name.as_str(), ); - if has_binding { - prop2.handle.set_binding_impl((old_handle & !0b11) as *mut BindingHolder); + if let Some(binding) = old_pointer { + prop2.handle.set_binding_impl(binding); } }; } @@ -1813,6 +1842,17 @@ impl PropertyTracker { } } +#[test] +fn test_property_handler_binding() { + assert_eq!(PropertyHandle::has_no_binding_or_lock(BINDING_BORROWED), false); + assert_eq!(PropertyHandle::has_no_binding_or_lock(BINDING_POINTER_TO_BINDING), false); + assert_eq!( + PropertyHandle::has_no_binding_or_lock(BINDING_BORROWED | BINDING_POINTER_TO_BINDING), + false + ); + assert_eq!(PropertyHandle::has_no_binding_or_lock(0), true); +} + #[test] fn test_property_listener_scope() { let scope = Box::pin(PropertyTracker::default()); diff --git a/internal/core/properties/properties_animations.rs b/internal/core/properties/properties_animations.rs index c69f0b76cf5..6f04008f0c1 100644 --- a/internal/core/properties/properties_animations.rs +++ b/internal/core/properties/properties_animations.rs @@ -10,9 +10,15 @@ use crate::{ use num_traits::Float; enum AnimationState { + /// The animation will start after the delay is finished Delaying, - Animating { current_iteration: u64 }, - Done { iteration_count: u64 }, + /// Actual animation + Animating { + current_iteration: u64, + }, + Done { + iteration_count: u64, + }, } pub(super) struct PropertyValueAnimationData { @@ -30,6 +36,7 @@ impl PropertyValueAnimationData { Self { from_value, to_value, details, start_time, state: AnimationState::Delaying } } + /// Single iteration of the animation pub fn compute_interpolated_value(&mut self) -> (T, bool) { let new_tick = crate::animations::current_tick(); let mut time_progress = new_tick.duration_since(self.start_time).as_millis() as u64; diff --git a/internal/core/textlayout/sharedparley.rs b/internal/core/textlayout/sharedparley.rs index cbb7f971e1a..174f64d5a24 100644 --- a/internal/core/textlayout/sharedparley.rs +++ b/internal/core/textlayout/sharedparley.rs @@ -396,20 +396,24 @@ fn layout( let max_physical_width = options.max_width.map(|max_width| max_width * scale_factor); let max_physical_height = options.max_height.map(|max_height| max_height * scale_factor); - let elision_info = if let (TextOverflow::Elide, Some(max_physical_width)) = - (options.text_overflow, max_physical_width) - { + // Returned None if failed to get the elipsis glyph for some rare reason. + let get_elipsis_glyph = || { let mut layout = layout_builder.build("…", None, None, None); layout.break_all_lines(None); - let line = layout.lines().next().unwrap(); - let item = line.items().next().unwrap(); + let line = layout.lines().next()?; + let item = line.items().next()?; let run = match item { parley::layout::PositionedLayoutItem::GlyphRun(run) => Some(run), - _ => None, - } - .unwrap(); - let glyph = run.positioned_glyphs().next().unwrap(); - Some(ElisionInfo { elipsis_glyph: glyph, max_physical_width }) + _ => return None, + }?; + let glyph = run.positioned_glyphs().next()?; + Some(glyph) + }; + + let elision_info = if let (TextOverflow::Elide, Some(max_physical_width)) = + (options.text_overflow, max_physical_width) + { + get_elipsis_glyph().map(|elipsis_glyph| ElisionInfo { elipsis_glyph, max_physical_width }) } else { None }; diff --git a/internal/interpreter/api.rs b/internal/interpreter/api.rs index 43979b3296a..05075d3fb17 100644 --- a/internal/interpreter/api.rs +++ b/internal/interpreter/api.rs @@ -9,6 +9,7 @@ use i_slint_core::component_factory::ComponentFactory; use i_slint_core::component_factory::FactoryContext; use i_slint_core::graphics::euclid::approxeq::ApproxEq as _; use i_slint_core::items::*; +use i_slint_core::model::{Model, ModelExt, ModelRc}; #[cfg(feature = "internal")] use i_slint_core::window::WindowInner; use smol_str::SmolStr; @@ -20,26 +21,8 @@ use std::rc::Rc; #[doc(inline)] pub use i_slint_compiler::diagnostics::{Diagnostic, DiagnosticLevel}; -// keep in sync with api/rs/slint/lib.rs pub use i_slint_backend_selector::api::*; -#[cfg(feature = "std")] -pub use i_slint_common::sharedfontique::{ - FontHandle, RegisterFontError, register_font_from_memory, -}; pub use i_slint_core::api::*; -pub use i_slint_core::graphics::{ - Brush, Color, Image, LoadImageError, Rgb8Pixel, Rgba8Pixel, RgbaColor, SharedPixelBuffer, -}; -pub use i_slint_core::model::{ - FilterModel, MapModel, Model, ModelExt, ModelNotify, ModelPeer, ModelRc, ModelTracker, - ReverseModel, SortModel, StandardListViewItem, TableColumn, VecModel, -}; -pub use i_slint_core::sharedvector::SharedVector; -pub use i_slint_core::timers::{Timer, TimerMode}; -pub use i_slint_core::{ - format, - string::{SharedString, ToSharedString}, -}; /// Argument of [`Compiler::set_default_translation_context()`] /// @@ -1437,7 +1420,7 @@ impl ComponentInstance { generativity::make_guard!(guard); let comp = self.inner.unerase(guard); comp.description() - .get_global(comp.borrow(), &&normalize_identifier(global)) + .get_global(comp.borrow(), &normalize_identifier(global)) .map_err(|()| GetPropertyError::NoSuchProperty)? // FIXME: should there be a NoSuchGlobal error? .as_ref() .get_property(&normalize_identifier(property)) @@ -1454,7 +1437,7 @@ impl ComponentInstance { generativity::make_guard!(guard); let comp = self.inner.unerase(guard); comp.description() - .get_global(comp.borrow(), &&normalize_identifier(global)) + .get_global(comp.borrow(), &normalize_identifier(global)) .map_err(|()| SetPropertyError::NoSuchProperty)? // FIXME: should there be a NoSuchGlobal error? .as_ref() .set_property(&normalize_identifier(property), value) diff --git a/internal/interpreter/dynamic_item_tree.rs b/internal/interpreter/dynamic_item_tree.rs index 1e3bd369b2d..9d1ad70de8e 100644 --- a/internal/interpreter/dynamic_item_tree.rs +++ b/internal/interpreter/dynamic_item_tree.rs @@ -24,7 +24,7 @@ use i_slint_core::item_tree::{ use i_slint_core::items::{ AccessibleRole, ItemRef, ItemVTable, PopupClosePolicy, PropertyAnimation, }; -use i_slint_core::layout::{BoxLayoutCellData, LayoutInfo, Orientation}; +use i_slint_core::layout::{LayoutInfo, LayoutItemInfo, Orientation}; use i_slint_core::lengths::{LogicalLength, LogicalRect}; use i_slint_core::menus::MenuFromItemTree; use i_slint_core::model::{ModelRc, RepeatedItemTree, Repeater}; @@ -171,8 +171,8 @@ impl RepeatedItemTree for ErasedItemTreeBox { LogicalLength::new(self.borrow().as_ref().layout_info(Orientation::Horizontal).min) } - fn box_layout_data(self: Pin<&Self>, o: Orientation) -> BoxLayoutCellData { - BoxLayoutCellData { constraint: self.borrow().as_ref().layout_info(o) } + fn layout_item_info(self: Pin<&Self>, o: Orientation) -> LayoutItemInfo { + LayoutItemInfo { constraint: self.borrow().as_ref().layout_info(o) } } } @@ -761,14 +761,13 @@ fn ensure_repeater_updated<'id>( ) { let repeater = rep_in_comp.offset.apply_pin(instance_ref.instance); let init = || { - let instance = instantiate( + instantiate( rep_in_comp.item_tree_to_repeat.clone(), instance_ref.self_weak().get().cloned(), None, None, Default::default(), - ); - instance + ) }; if let Some(lv) = &rep_in_comp .item_tree_to_repeat @@ -1286,7 +1285,7 @@ pub(crate) fn generate_item_tree<'id>( .insert(name.clone(), builder.type_builder.add_field_type::()); continue; } - let Some((prop, type_info)) = property_info_for_type(&decl.property_type, &name) else { + let Some((prop, type_info)) = property_info_for_type(&decl.property_type, name) else { continue; }; custom_properties.insert( @@ -1294,33 +1293,24 @@ pub(crate) fn generate_item_tree<'id>( PropertiesWithinComponent { offset: builder.type_builder.add_field(type_info), prop }, ); } - if let Some(parent_element) = component.parent_element.upgrade() { - if let Some(r) = &parent_element.borrow().repeated { - if !r.is_conditional_element { - let (prop, type_info) = property_info::(); - custom_properties.insert( - SPECIAL_PROPERTY_INDEX.into(), - PropertiesWithinComponent { - offset: builder.type_builder.add_field(type_info), - prop, - }, - ); - - let model_ty = Expression::RepeaterModelReference { - element: component.parent_element.clone(), - } - .ty(); - let (prop, type_info) = - property_info_for_type(&model_ty, &SPECIAL_PROPERTY_MODEL_DATA).unwrap(); - custom_properties.insert( - SPECIAL_PROPERTY_MODEL_DATA.into(), - PropertiesWithinComponent { - offset: builder.type_builder.add_field(type_info), - prop, - }, - ); - } - } + if let Some(parent_element) = component.parent_element.upgrade() + && let Some(r) = &parent_element.borrow().repeated + && !r.is_conditional_element + { + let (prop, type_info) = property_info::(); + custom_properties.insert( + SPECIAL_PROPERTY_INDEX.into(), + PropertiesWithinComponent { offset: builder.type_builder.add_field(type_info), prop }, + ); + + let model_ty = + Expression::RepeaterModelReference { element: component.parent_element.clone() }.ty(); + let (prop, type_info) = + property_info_for_type(&model_ty, SPECIAL_PROPERTY_MODEL_DATA).unwrap(); + custom_properties.insert( + SPECIAL_PROPERTY_MODEL_DATA.into(), + PropertiesWithinComponent { offset: builder.type_builder.add_field(type_info), prop }, + ); } let parent_item_tree_offset = @@ -1587,10 +1577,10 @@ pub fn instantiate( { continue; } - if let Some(b) = description.original.root_element.borrow().bindings.get(prop_name) { - if b.borrow().two_way_bindings.is_empty() { - continue; - } + if let Some(b) = description.original.root_element.borrow().bindings.get(prop_name) + && b.borrow().two_way_bindings.is_empty() + { + continue; } let p = description.custom_properties.get(prop_name).unwrap(); unsafe { @@ -1811,13 +1801,12 @@ fn prepare_for_two_way_binding( eval::ComponentInstance::InstanceRef(enclosing_component) => { let element = element.borrow(); if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id + && let Some(x) = enclosing_component.description.custom_properties.get(name) { - if let Some(x) = enclosing_component.description.custom_properties.get(name) { - let item = unsafe { Pin::new_unchecked(&*instance_ref.as_ptr().add(x.offset)) }; - let common = x.prop.prepare_for_two_way_binding(item); - return (common, map); - }; - }; + let item = unsafe { Pin::new_unchecked(&*instance_ref.as_ptr().add(x.offset)) }; + let common = x.prop.prepare_for_two_way_binding(item); + return (common, map); + } let item_info = enclosing_component .description .items @@ -1851,10 +1840,9 @@ pub(crate) fn get_property_ptr(nr: &NamedReference, instance: InstanceRef) -> *c eval::ComponentInstance::InstanceRef(enclosing_component) => { let element = element.borrow(); if element.id == element.enclosing_component.upgrade().unwrap().root_element.borrow().id + && let Some(x) = enclosing_component.description.custom_properties.get(nr.name()) { - if let Some(x) = enclosing_component.description.custom_properties.get(nr.name()) { - return unsafe { enclosing_component.as_ptr().add(x.offset).cast() }; - }; + return unsafe { enclosing_component.as_ptr().add(x.offset).cast() }; }; let item_info = enclosing_component .description @@ -2289,7 +2277,7 @@ extern "C" fn supported_accessibility_actions( ) -> SupportedAccessibilityAction { generativity::make_guard!(guard); let instance_ref = unsafe { InstanceRef::from_pin_ref(component, guard) }; - let val = instance_ref.description.original_elements[item_index as usize] + instance_ref.description.original_elements[item_index as usize] .borrow() .accessibility_props .0 @@ -2301,8 +2289,7 @@ extern "C" fn supported_accessibility_actions( )) .unwrap_or_else(|| panic!("Not an accessible action: {value:?}")) | acc - }); - val + }) } #[cfg_attr(not(feature = "ffi"), i_slint_core_macros::remove_extern)] @@ -2439,7 +2426,7 @@ impl<'a, 'id> InstanceRef<'a, 'id> { let extra_data = description.extra_data_offset.apply(instance); let window_adapter = // We are the root: Create a window adapter i_slint_backend_selector::with_platform(|_b| { - return _b.create_window_adapter(); + _b.create_window_adapter() })?; let comp_rc = extra_data.self_weak.get().unwrap().upgrade().unwrap(); @@ -2480,19 +2467,18 @@ impl<'a, 'id> InstanceRef<'a, 'id> { ) -> Option> { // we need a 'static guard in order to be able to re-borrow with lifetime 'a. // Safety: This is the only 'static Id in scope. - if let Some(parent_offset) = self.description.parent_item_tree_offset { - if let Some(parent) = + if let Some(parent_offset) = self.description.parent_item_tree_offset + && let Some(parent) = parent_offset.apply(self.as_ref()).get().and_then(vtable::VWeak::upgrade) - { - let parent_instance = parent.unerase(_guard); - // And also assume that the parent lives for at least 'a. FIXME: this may not be sound - let parent_instance = unsafe { - std::mem::transmute::, InstanceRef<'a, 'id2>>( - parent_instance.borrow_instance(), - ) - }; - return Some(parent_instance); + { + let parent_instance = parent.unerase(_guard); + // And also assume that the parent lives for at least 'a. FIXME: this may not be sound + let parent_instance = unsafe { + std::mem::transmute::, InstanceRef<'a, 'id2>>( + parent_instance.borrow_instance(), + ) }; + return Some(parent_instance); } None } diff --git a/internal/interpreter/eval.rs b/internal/interpreter/eval.rs index 1634516f773..a5dc272dda1 100644 --- a/internal/interpreter/eval.rs +++ b/internal/interpreter/eval.rs @@ -450,7 +450,12 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon } local_context.return_value.clone().unwrap() } - Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { + Expression::LayoutCacheAccess { + layout_cache_prop, + index, + repeater_index, + entries_per_item, + } => { let cache = load_property_helper( &ComponentInstance::InstanceRef(local_context.component_instance), &layout_cache_prop.element(), @@ -462,7 +467,7 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon let offset: usize = eval_expression(ri, local_context).try_into().unwrap(); Value::Number( cache - .get((cache[*index] as usize) + offset * 2) + .get((cache[*index] as usize) + offset * entries_per_item) .copied() .unwrap_or(0.) .into(), @@ -475,7 +480,7 @@ pub fn eval_expression(expression: &Expression, local_context: &mut EvalLocalCon let offset: usize = eval_expression(ri, local_context).try_into().unwrap(); Value::Number( cache - .get((cache[*index] as usize) + offset * 2) + .get((cache[*index] as usize) + offset * entries_per_item) .copied() .unwrap_or(0) .into(), @@ -1303,12 +1308,13 @@ fn call_builtin_function( rest.first(), ); - if let Some(w) = component.window_adapter().internal(i_slint_core::InternalToken) { - if !no_native && w.supports_native_menu_bar() { - let menubar = vtable::VRc::into_dyn(menu_item_tree); - w.setup_menubar(menubar); - return Value::Void; - } + if let Some(w) = component.window_adapter().internal(i_slint_core::InternalToken) + && !no_native + && w.supports_native_menu_bar() + { + let menubar = vtable::VRc::into_dyn(menu_item_tree); + w.setup_menubar(menubar); + return Value::Void; } let (entries, sub_menu, activated) = menu_item_tree_properties(menu_item_tree); @@ -1328,7 +1334,7 @@ fn call_builtin_function( set_callback_handler(i, &activated_nr.element(), activated_nr.name(), activated) .unwrap(); - return Value::Void; + Value::Void } BuiltinFunction::MonthDayCount => { let m: u32 = eval_expression(&arguments[0], local_context).try_into().unwrap(); @@ -1605,19 +1611,17 @@ fn eval_assignment(lhs: &Expression, op: char, rhs: Value, local_context: &mut E } let component = element.borrow().enclosing_component.upgrade().unwrap(); - if element.borrow().id == component.root_element.borrow().id { - if let Some(x) = + if element.borrow().id == component.root_element.borrow().id + && let Some(x) = enclosing_component.description.custom_properties.get(nr.name()) - { - unsafe { - let p = Pin::new_unchecked( - &*enclosing_component.as_ptr().add(x.offset), - ); - x.prop.set(p, eval(x.prop.get(p).unwrap()), None).unwrap(); - } - return; + { + unsafe { + let p = + Pin::new_unchecked(&*enclosing_component.as_ptr().add(x.offset)); + x.prop.set(p, eval(x.prop.get(p).unwrap()), None).unwrap(); } - }; + return; + } let item_info = &enclosing_component.description.items[element.borrow().id.as_str()]; let item = diff --git a/internal/interpreter/eval_layout.rs b/internal/interpreter/eval_layout.rs index e166742d7c1..26d5906eaa3 100644 --- a/internal/interpreter/eval_layout.rs +++ b/internal/interpreter/eval_layout.rs @@ -44,10 +44,12 @@ pub(crate) fn compute_grid_layout_info( eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap() }; let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval); + let repeater_indices = grid_repeater_indices(grid_layout, local_context); let constraints = grid_layout_constraints(grid_layout, orientation, local_context); core_layout::grid_layout_info( organized_data.clone(), Slice::from_slice(constraints.as_slice()), + Slice::from_slice(repeater_indices.as_slice()), spacing, &padding, to_runtime(orientation), @@ -92,12 +94,8 @@ pub(crate) fn organize_grid_layout( layout: &GridLayout, local_context: &mut EvalLocalContext, ) -> Value { - let component = local_context.component_instance; - let expr_eval = |nr: &NamedReference| -> f32 { - eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap() - }; - let cells = grid_layout_input_data(layout, &expr_eval); - + let cells = grid_layout_input_data(layout, local_context); + let repeater_indices = grid_repeater_indices(layout, local_context); if let Some(buttons_roles) = &layout.dialog_button_roles { let roles = buttons_roles .iter() @@ -109,7 +107,11 @@ pub(crate) fn organize_grid_layout( ) .into() } else { - core_layout::organize_grid_layout(Slice::from_slice(cells.as_slice())).into() + core_layout::organize_grid_layout( + Slice::from_slice(cells.as_slice()), + Slice::from_slice(repeater_indices.as_slice()), + ) + .into() } } @@ -123,6 +125,7 @@ pub(crate) fn solve_grid_layout( let expr_eval = |nr: &NamedReference| -> f32 { eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap() }; + let repeater_indices = grid_repeater_indices(grid_layout, local_context); let constraints = grid_layout_constraints(grid_layout, orientation, local_context); let (padding, spacing) = padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval); @@ -139,6 +142,7 @@ pub(crate) fn solve_grid_layout( &data, Slice::from_slice(constraints.as_slice()), to_runtime(orientation), + Slice::from_slice(repeater_indices.as_slice()), ) .into() } @@ -207,54 +211,104 @@ fn repeater_instances( let rep = crate::dynamic_item_tree::get_repeater_by_name(component, elem.borrow().id.as_str(), guard); rep.0.as_ref().ensure_updated(|| { - let instance = crate::dynamic_item_tree::instantiate( + crate::dynamic_item_tree::instantiate( rep.1.clone(), component.self_weak().get().cloned(), None, None, Default::default(), - ); - instance + ) }); rep.0.as_ref().instances_vec() } fn grid_layout_input_data( grid_layout: &i_slint_compiler::layout::GridLayout, - expr_eval: &impl Fn(&NamedReference) -> f32, + ctx: &EvalLocalContext, ) -> Vec { - grid_layout - .elems - .iter() - .map(|cell| { - let eval_or_default = |expr: &RowColExpr| match expr { - RowColExpr::Literal(value) => *value, - RowColExpr::Named(e) => { - let value = expr_eval(e); - if value >= 0.0 && value <= u16::MAX as f32 { - value as u16 - } else { - panic!( - "Expected a positive integer, but got {:?} while evaluating {:?}", - value, e - ); - } - } - }; - let row = eval_or_default(&cell.row_expr); - let col = eval_or_default(&cell.col_expr); - let rowspan = eval_or_default(&cell.rowspan_expr); - let colspan = eval_or_default(&cell.colspan_expr); - core_layout::GridLayoutInputData { new_row: cell.new_row, col, row, colspan, rowspan } - }) - .collect() + let component = ctx.component_instance; + let mut result = Vec::with_capacity(grid_layout.elems.len()); + let mut after_repeater_in_same_row = false; + let mut new_row = true; + for elem in grid_layout.elems.iter() { + let eval_or_default = |expr: &RowColExpr, component: InstanceRef| match expr { + RowColExpr::Literal(value) => *value as f32, + RowColExpr::Auto => i_slint_common::ROW_COL_AUTO, + RowColExpr::Named(nr) => { + // we could check for out-of-bounds here, but organize_grid_layout will also do it + eval::load_property(component, &nr.element(), nr.name()) + .unwrap() + .try_into() + .unwrap() + } + }; + + let cell_new_row = elem.cell.borrow().new_row; + if cell_new_row { + after_repeater_in_same_row = false; + } + if elem.item.element.borrow().repeated.is_some() { + let component_vec = repeater_instances(component, &elem.item.element); + new_row = cell_new_row; + for erased_sub_comp in &component_vec { + // Evaluate the row/col/rowspan/colspan expressions in the context of the sub-component + generativity::make_guard!(guard); + let sub_comp = erased_sub_comp.as_pin_ref(); + let sub_instance_ref = + unsafe { InstanceRef::from_pin_ref(sub_comp.borrow(), guard) }; + let row = eval_or_default(&elem.cell.borrow().row_expr, sub_instance_ref); + let col = eval_or_default(&elem.cell.borrow().col_expr, sub_instance_ref); + let rowspan = eval_or_default(&elem.cell.borrow().rowspan_expr, sub_instance_ref); + let colspan = eval_or_default(&elem.cell.borrow().colspan_expr, sub_instance_ref); + result.push(core_layout::GridLayoutInputData { + new_row, + col, + row, + colspan, + rowspan, + }); + new_row = false; + } + after_repeater_in_same_row = true; + } else { + let new_row = + if cell_new_row || !after_repeater_in_same_row { cell_new_row } else { new_row }; + let row = eval_or_default(&elem.cell.borrow().row_expr, component); + let col = eval_or_default(&elem.cell.borrow().col_expr, component); + let rowspan = eval_or_default(&elem.cell.borrow().rowspan_expr, component); + let colspan = eval_or_default(&elem.cell.borrow().colspan_expr, component); + result.push(core_layout::GridLayoutInputData { new_row, col, row, colspan, rowspan }); + } + } + result +} + +fn grid_repeater_indices( + grid_layout: &i_slint_compiler::layout::GridLayout, + ctx: &mut EvalLocalContext, +) -> Vec { + let component = ctx.component_instance; + let mut repeater_indices = Vec::new(); + + let mut num_cells = 0; + for cell in grid_layout.elems.iter() { + if cell.item.element.borrow().repeated.is_some() { + let component_vec = repeater_instances(component, &cell.item.element); + repeater_indices.push(num_cells as _); + repeater_indices.push(component_vec.len() as _); + num_cells += component_vec.len(); + } else { + num_cells += 1; + } + } + repeater_indices } fn grid_layout_constraints( grid_layout: &i_slint_compiler::layout::GridLayout, orientation: Orientation, ctx: &mut EvalLocalContext, -) -> Vec { +) -> Vec { let component = ctx.component_instance; let expr_eval = |nr: &NamedReference| -> f32 { eval::load_property(component, &nr.element(), nr.name()).unwrap().try_into().unwrap() @@ -262,19 +316,28 @@ fn grid_layout_constraints( let mut constraints = Vec::with_capacity(grid_layout.elems.len()); for cell in grid_layout.elems.iter() { - let mut layout_info = get_layout_info( - &cell.item.element, - component, - &component.window_adapter(), - orientation, - ); - fill_layout_info_constraints( - &mut layout_info, - &cell.item.constraints, - orientation, - &expr_eval, - ); - constraints.push(layout_info); + if cell.item.element.borrow().repeated.is_some() { + let component_vec = repeater_instances(component, &cell.item.element); + constraints.extend( + component_vec + .iter() + .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation))), + ); + } else { + let mut layout_info = get_layout_info( + &cell.item.element, + component, + &component.window_adapter(), + orientation, + ); + fill_layout_info_constraints( + &mut layout_info, + &cell.item.constraints, + orientation, + &expr_eval, + ); + constraints.push(core_layout::LayoutItemInfo { constraint: layout_info }); + } } constraints } @@ -285,7 +348,7 @@ fn box_layout_data( component: InstanceRef, expr_eval: &impl Fn(&NamedReference) -> f32, mut repeater_indices: Option<&mut Vec>, -) -> (Vec, i_slint_core::items::LayoutAlignment) { +) -> (Vec, i_slint_core::items::LayoutAlignment) { let window_adapter = component.window_adapter(); let mut cells = Vec::with_capacity(box_layout.elems.len()); for cell in &box_layout.elems { @@ -298,7 +361,7 @@ fn box_layout_data( cells.extend( component_vec .iter() - .map(|x| x.as_pin_ref().box_layout_data(to_runtime(orientation))), + .map(|x| x.as_pin_ref().layout_item_info(to_runtime(orientation))), ); } else { let mut layout_info = @@ -309,7 +372,7 @@ fn box_layout_data( orientation, &expr_eval, ); - cells.push(core_layout::BoxLayoutCellData { constraint: layout_info }); + cells.push(core_layout::LayoutItemInfo { constraint: layout_info }); } } let alignment = box_layout diff --git a/internal/interpreter/global_component.rs b/internal/interpreter/global_component.rs index 3a90a011c20..3829ed3a917 100644 --- a/internal/interpreter/global_component.rs +++ b/internal/interpreter/global_component.rs @@ -115,8 +115,7 @@ impl CompiledGlobal { CompiledGlobal::Component { component, .. } => { generativity::make_guard!(guard); let component = component.unerase(guard); - let is_exported = !component.original.exported_global_names.borrow().is_empty(); - is_exported + !component.original.exported_global_names.borrow().is_empty() } } } diff --git a/internal/interpreter/lib.rs b/internal/interpreter/lib.rs index 150f9b119f8..722805cb247 100644 --- a/internal/interpreter/lib.rs +++ b/internal/interpreter/lib.rs @@ -99,9 +99,5 @@ pub use api::*; #[doc(hidden)] pub use eval::default_value_for_type; -/// (Re-export from corelib.) -#[doc(inline)] -pub use i_slint_core::{Brush, Color, SharedString, SharedVector}; - #[cfg(test)] mod tests; diff --git a/internal/interpreter/live_preview.rs b/internal/interpreter/live_preview.rs index 9578dfcf040..69a08515825 100644 --- a/internal/interpreter/live_preview.rs +++ b/internal/interpreter/live_preview.rs @@ -23,8 +23,8 @@ pub struct LiveReloadingComponent { compiler: Compiler, file_name: PathBuf, component_name: String, - properties: HashMap, - callbacks: HashMap Value + 'static>>, + properties: RefCell>, + callbacks: RefCell Value + 'static>>>, } impl LiveReloadingComponent { @@ -123,9 +123,13 @@ impl LiveReloadingComponent { eprintln!("Component {} not found", self.component_name); return false; } + true + } + /// Reload the properties and callbacks after a reload() + pub fn reload_properties_and_callbacks(&self) { // Set the properties - for (name, value) in self.properties.iter() { + for (name, value) in self.properties.borrow_mut().iter() { if let Some((global, prop)) = name.split_once('.') { self.instance() .set_global_property(global, prop, value.clone()) @@ -136,7 +140,7 @@ impl LiveReloadingComponent { .unwrap_or_else(|e| panic!("Cannot set property {name}: {e}")); } } - for (name, callback) in self.callbacks.iter() { + for (name, callback) in self.callbacks.borrow_mut().iter() { let callback = callback.clone(); if let Some((global, prop)) = name.split_once('.') { self.instance() @@ -150,8 +154,6 @@ impl LiveReloadingComponent { } eprintln!("Reloaded component {} from {}", self.component_name, self.file_name.display()); - - true } /// Return the instance @@ -160,8 +162,8 @@ impl LiveReloadingComponent { } /// Set a property and remember its value for when the component is reloaded - pub fn set_property(&mut self, name: &str, value: Value) { - self.properties.insert(name.into(), value.clone()); + pub fn set_property(&self, name: &str, value: Value) { + self.properties.borrow_mut().insert(name.into(), value.clone()); self.instance() .set_property(&name, value) .unwrap_or_else(|e| panic!("Cannot set property {name}: {e}")) @@ -182,16 +184,16 @@ impl LiveReloadingComponent { } /// Forward to set_callback - pub fn set_callback(&mut self, name: &str, callback: Rc Value + 'static>) { - self.callbacks.insert(name.into(), callback.clone()); + pub fn set_callback(&self, name: &str, callback: Rc Value + 'static>) { + self.callbacks.borrow_mut().insert(name.into(), callback.clone()); self.instance() .set_callback(&name, move |args| callback(args)) .unwrap_or_else(|e| panic!("Cannot set callback {name}: {e}")); } /// forward to set_global_property - pub fn set_global_property(&mut self, global_name: &str, name: &str, value: Value) { - self.properties.insert(format!("{global_name}.{name}"), value.clone()); + pub fn set_global_property(&self, global_name: &str, name: &str, value: Value) { + self.properties.borrow_mut().insert(format!("{global_name}.{name}"), value.clone()); self.instance() .set_global_property(global_name, name, value) .unwrap_or_else(|e| panic!("Cannot set property {global_name}::{name}: {e}")) @@ -213,12 +215,12 @@ impl LiveReloadingComponent { /// Forward to set_global_callback pub fn set_global_callback( - &mut self, + &self, global_name: &str, name: &str, callback: Rc Value + 'static>, ) { - self.callbacks.insert(format!("{global_name}.{name}"), callback.clone()); + self.callbacks.borrow_mut().insert(format!("{global_name}.{name}"), callback.clone()); self.instance() .set_global_callback(global_name, name, move |args| callback(args)) .unwrap_or_else(|e| panic!("Cannot set callback {global_name}::{name}: {e}")); @@ -261,7 +263,10 @@ impl Watcher { WatcherState::Waiting(cx.waker().clone()), ); if matches!(state, WatcherState::Changed) { - instance.borrow_mut().reload(); + let success = instance.borrow_mut().reload(); + if success { + instance.borrow().reload_properties_and_callbacks(); + }; }; std::task::Poll::Pending })); diff --git a/internal/renderers/femtovg/wgpu.rs b/internal/renderers/femtovg/wgpu.rs index 6ea3a353627..4351d3cf0ef 100644 --- a/internal/renderers/femtovg/wgpu.rs +++ b/internal/renderers/femtovg/wgpu.rs @@ -151,7 +151,9 @@ impl FemtoVGRenderer { let swapchain_format = swapchain_capabilities .formats .iter() - .find(|f| !f.is_srgb()) + .find(|f| { + matches!(f, wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm) + }) .copied() .unwrap_or_else(|| swapchain_capabilities.formats[0]); surface_config.format = swapchain_format; diff --git a/internal/renderers/skia/itemrenderer.rs b/internal/renderers/skia/itemrenderer.rs index b764efb3b79..2e1686ad912 100644 --- a/internal/renderers/skia/itemrenderer.rs +++ b/internal/renderers/skia/itemrenderer.rs @@ -334,8 +334,8 @@ impl<'a> SkiaItemRenderer<'a> { ) }) }) { - let _saved_canvas = self.pixel_align_origin(); self.canvas.translate(skia_safe::Vector::from((layer_offset.x, layer_offset.y))); + let _saved_canvas = self.pixel_align_origin(); self.canvas.draw_image_with_sampling_options( layer_image, skia_safe::Point::default(), diff --git a/internal/renderers/skia/wgpu_26_surface.rs b/internal/renderers/skia/wgpu_26_surface.rs index bcb3df9423a..f64f6f9bc41 100644 --- a/internal/renderers/skia/wgpu_26_surface.rs +++ b/internal/renderers/skia/wgpu_26_surface.rs @@ -60,7 +60,9 @@ impl super::Surface for WGPUSurface { let swapchain_format = swapchain_capabilities .formats .iter() - .find(|f| !f.is_srgb()) + .find(|f| { + matches!(f, wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm) + }) .copied() .unwrap_or_else(|| swapchain_capabilities.formats[0]); surface_config.format = swapchain_format; diff --git a/internal/renderers/skia/wgpu_26_surface/vulkan.rs b/internal/renderers/skia/wgpu_26_surface/vulkan.rs index 0583f25be64..1b62c358a66 100644 --- a/internal/renderers/skia/wgpu_26_surface/vulkan.rs +++ b/internal/renderers/skia/wgpu_26_surface/vulkan.rs @@ -121,45 +121,44 @@ pub unsafe fn make_vulkan_context( queue: &wgpu::Queue, ) -> Option { unsafe { - let maybe_vulkan_device = device.as_hal::(); - let maybe_vulkan_queue = queue.as_hal::(); - - maybe_vulkan_device.and_then(|vulkan_device| { - maybe_vulkan_queue.map(|vulkan_queue| { - let vulkan_queue_raw = vulkan_queue.as_raw(); - - let get_proc = |of| { - let result = match of { - skia_safe::gpu::vk::GetProcOf::Instance(instance, name) => { - vulkan_device.shared_instance().entry().get_instance_proc_addr( - ash::vk::Instance::from_raw(instance as _), - name, - ) - } - skia_safe::gpu::vk::GetProcOf::Device(device, name) => vulkan_device - .shared_instance() - .raw_instance() - .get_device_proc_addr(ash::vk::Device::from_raw(device as _), name), - }; - - match result { - Some(f) => f as _, - None => { - //println!("resolve of {} failed", of.name().to_str().unwrap()); - core::ptr::null() - } - } - }; - - let backend = vk::BackendContext::new( - vulkan_device.shared_instance().raw_instance().handle().as_raw() as _, - vulkan_device.raw_physical_device().as_raw() as _, - vulkan_device.raw_device().handle().as_raw() as _, - (vulkan_queue_raw.as_raw() as _, vulkan_device.queue_family_index() as _), - &get_proc, - ); - skia_safe::gpu::direct_contexts::make_vulkan(&backend, None) - }) - })? + let vulkan_device = device.as_hal::()?; + let vulkan_queue = queue.as_hal::()?; + + let vulkan_queue_raw = vulkan_queue.as_raw(); + + let get_proc = |of| { + let result = match of { + skia_safe::gpu::vk::GetProcOf::Instance(instance, name) => vulkan_device + .shared_instance() + .entry() + .get_instance_proc_addr(ash::vk::Instance::from_raw(instance as _), name), + skia_safe::gpu::vk::GetProcOf::Device(device, name) => vulkan_device + .shared_instance() + .raw_instance() + .get_device_proc_addr(ash::vk::Device::from_raw(device as _), name), + }; + + match result { + Some(f) => f as _, + None => { + //println!("resolve of {} failed", of.name().to_str().unwrap()); + core::ptr::null() + } + } + }; + + let mut backend = vk::BackendContext::new( + vulkan_device.shared_instance().raw_instance().handle().as_raw() as _, + vulkan_device.raw_physical_device().as_raw() as _, + vulkan_device.raw_device().handle().as_raw() as _, + (vulkan_queue_raw.as_raw() as _, vulkan_device.queue_family_index() as _), + &get_proc, + ); + + // WGPU 26 is locked to vulkan 1.3 and skia assumes the highest vulkan API version of the physical device is chosen, + // causing it to ask for unsupported features/functions + backend.set_max_api_version(vk::Version::new(1, 3, 0)); + + skia_safe::gpu::direct_contexts::make_vulkan(&backend, None) } } diff --git a/internal/renderers/skia/wgpu_27_surface.rs b/internal/renderers/skia/wgpu_27_surface.rs index df70aa36eaf..96b32d31b53 100644 --- a/internal/renderers/skia/wgpu_27_surface.rs +++ b/internal/renderers/skia/wgpu_27_surface.rs @@ -60,7 +60,9 @@ impl super::Surface for WGPUSurface { let swapchain_format = swapchain_capabilities .formats .iter() - .find(|f| !f.is_srgb()) + .find(|f| { + matches!(f, wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm) + }) .copied() .unwrap_or_else(|| swapchain_capabilities.formats[0]); surface_config.format = swapchain_format; diff --git a/internal/renderers/skia/wgpu_27_surface/vulkan.rs b/internal/renderers/skia/wgpu_27_surface/vulkan.rs index 7b259a514f0..3870d554fc9 100644 --- a/internal/renderers/skia/wgpu_27_surface/vulkan.rs +++ b/internal/renderers/skia/wgpu_27_surface/vulkan.rs @@ -121,45 +121,44 @@ pub unsafe fn make_vulkan_context( queue: &wgpu::Queue, ) -> Option { unsafe { - let maybe_vulkan_device = device.as_hal::(); - let maybe_vulkan_queue = queue.as_hal::(); - - maybe_vulkan_device.and_then(|vulkan_device| { - maybe_vulkan_queue.map(|vulkan_queue| { - let vulkan_queue_raw = vulkan_queue.as_raw(); - - let get_proc = |of| { - let result = match of { - skia_safe::gpu::vk::GetProcOf::Instance(instance, name) => { - vulkan_device.shared_instance().entry().get_instance_proc_addr( - ash::vk::Instance::from_raw(instance as _), - name, - ) - } - skia_safe::gpu::vk::GetProcOf::Device(device, name) => vulkan_device - .shared_instance() - .raw_instance() - .get_device_proc_addr(ash::vk::Device::from_raw(device as _), name), - }; - - match result { - Some(f) => f as _, - None => { - //println!("resolve of {} failed", of.name().to_str().unwrap()); - core::ptr::null() - } - } - }; - - let backend = vk::BackendContext::new( - vulkan_device.shared_instance().raw_instance().handle().as_raw() as _, - vulkan_device.raw_physical_device().as_raw() as _, - vulkan_device.raw_device().handle().as_raw() as _, - (vulkan_queue_raw.as_raw() as _, vulkan_device.queue_family_index() as _), - &get_proc, - ); - skia_safe::gpu::direct_contexts::make_vulkan(&backend, None) - }) - })? + let vulkan_device = device.as_hal::()?; + let vulkan_queue = queue.as_hal::()?; + + let vulkan_queue_raw = vulkan_queue.as_raw(); + + let get_proc = |of| { + let result = match of { + skia_safe::gpu::vk::GetProcOf::Instance(instance, name) => vulkan_device + .shared_instance() + .entry() + .get_instance_proc_addr(ash::vk::Instance::from_raw(instance as _), name), + skia_safe::gpu::vk::GetProcOf::Device(device, name) => vulkan_device + .shared_instance() + .raw_instance() + .get_device_proc_addr(ash::vk::Device::from_raw(device as _), name), + }; + + match result { + Some(f) => f as _, + None => { + //println!("resolve of {} failed", of.name().to_str().unwrap()); + core::ptr::null() + } + } + }; + + let mut backend = vk::BackendContext::new( + vulkan_device.shared_instance().raw_instance().handle().as_raw() as _, + vulkan_device.raw_physical_device().as_raw() as _, + vulkan_device.raw_device().handle().as_raw() as _, + (vulkan_queue_raw.as_raw() as _, vulkan_device.queue_family_index() as _), + &get_proc, + ); + + // WGPU 27 is locked to vulkan 1.3 and skia assumes the highest vulkan API version of the physical device is chosen, + // causing it to ask for unsupported features/functions + backend.set_max_api_version(vk::Version::new(1, 3, 0)); + + skia_safe::gpu::direct_contexts::make_vulkan(&backend, None) } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67295e14d58..5458114460f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,8 +13,8 @@ catalogs: specifier: 0.37.1 version: 0.37.1 '@biomejs/biome': - specifier: 2.3.10 - version: 2.3.10 + specifier: 2.3.11 + version: 2.3.11 '@expressive-code/plugin-line-numbers': specifier: 0.41.5 version: 0.41.5 @@ -28,8 +28,8 @@ catalogs: specifier: 5.16.6 version: 5.16.6 astro-embed: - specifier: 0.9.2 - version: 0.9.2 + specifier: 0.10.0 + version: 0.10.0 cspell: specifier: 9.4.0 version: 9.4.0 @@ -71,7 +71,7 @@ importers: devDependencies: '@biomejs/biome': specifier: 'catalog:' - version: 2.3.10 + version: 2.3.11 '@types/capture-console': specifier: 1.0.5 version: 1.0.5 @@ -98,7 +98,7 @@ importers: dependencies: '@biomejs/biome': specifier: 'catalog:' - version: 2.3.10 + version: 2.3.11 slint-ui: specifier: workspace:* version: link:../../../api/node @@ -131,7 +131,7 @@ importers: version: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) astro-embed: specifier: 'catalog:' - version: 0.9.2(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 0.10.0(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) playwright-ctrf-json-reporter: specifier: 'catalog:' version: 0.0.27 @@ -177,7 +177,7 @@ importers: devDependencies: '@biomejs/biome': specifier: 'catalog:' - version: 2.3.10 + version: 2.3.11 '@rauschma/env-var': specifier: 1.0.1 version: 1.0.1 @@ -305,7 +305,7 @@ importers: devDependencies: '@biomejs/biome': specifier: 'catalog:' - version: 2.3.10 + version: 2.3.11 '@codingame/monaco-vscode-api': specifier: ~8.0.4 version: 8.0.4 @@ -413,7 +413,7 @@ importers: version: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) astro-embed: specifier: 'catalog:' - version: 0.9.2(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) + version: 0.10.0(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) astro-icon: specifier: 1.1.5 version: 1.1.5 @@ -450,7 +450,7 @@ importers: version: 6.0.2(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2))(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.9.3)))(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.9.3)) '@biomejs/biome': specifier: 'catalog:' - version: 2.3.10 + version: 2.3.11 '@expressive-code/core': specifier: 0.41.5 version: 0.41.5 @@ -458,8 +458,8 @@ importers: specifier: 1.2.3 version: 1.2.3 '@iconify-json/tabler': - specifier: 1.2.25 - version: 1.2.25 + specifier: 1.2.26 + version: 1.2.26 '@playwright/test': specifier: 'catalog:' version: 1.57.0 @@ -521,41 +521,31 @@ packages: '@antfu/utils@8.1.1': resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} - '@astro-community/astro-embed-baseline-status@0.2.1': - resolution: {integrity: sha512-IGEcAzjQ3OVVEbB0yB7GDlYz3TpY1X4ZBDz2+ejRP0yW3VZuPeWyoIffuKm9iGUomnqXs6hoVR6/vK0OmLXZRA==} - peerDependencies: - astro: ^4.0.0-beta || ^5.0.0-beta + '@astro-community/astro-embed-baseline-status@0.2.2': + resolution: {integrity: sha512-07TBEb+xQWWZfMuoHohcZv/r2VSB80/1xN5iLhzSqavLmdsMyebEnbc6tvw3yMkxvX9IBLduNA5SxvVkpmowNQ==} - '@astro-community/astro-embed-bluesky@0.1.5': - resolution: {integrity: sha512-/0wruqqgcbB/z8KnUGETURvNwct5cKBcPit/gJus7oOQctT8+wUjWcIlCn3uyqaZUq6ghpbRsj2eSD75rJZzSQ==} - peerDependencies: - astro: ^4.0.0 || ^5.0.0-beta.0 + '@astro-community/astro-embed-bluesky@0.1.6': + resolution: {integrity: sha512-3y6Y3cRelLnR9AYMItmEAjcr83KAEa6WvsxQ1eHq1cPBzICXknuzphaZlmQZ+QG5NTtmEJD+2lQWrFba/BfM1A==} - '@astro-community/astro-embed-integration@0.8.3': - resolution: {integrity: sha512-lJfPOiol8lTay5kJHT3C4CmM6shF6mF2YZR2tSpM4F+D1tj26PZ937r0iHhUcOLPeQPmczZbs9Tx1WwDY4SjOQ==} + '@astro-community/astro-embed-integration@0.9.0': + resolution: {integrity: sha512-E/rLvgofFgHbvLoMARQrbTQ+WUWqkamqCt00LNFqFqL6KIXWo3QoqI3vSzZkA+wMErfHaMLdVz3Z938s7RbsgA==} peerDependencies: - astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta + astro: ^5.0.0 || ^6.0.0-alpha - '@astro-community/astro-embed-link-preview@0.2.3': - resolution: {integrity: sha512-TLnZOihoQhXOCybvbzE/ImqFkGgG5zSJeWIj+PytM41Q/uhU6w19LD571qmWADf0Grv/u7LtorR1PB6ijQnazQ==} + '@astro-community/astro-embed-link-preview@0.3.1': + resolution: {integrity: sha512-TI++efm08+kJqxqA7bvxBr7+Zt4yCceA6s3wvAQJ87eiaxbLqAFUSQ+paQD66ET9dIC+IuKzHOMwsoDfqBidYw==} - '@astro-community/astro-embed-twitter@0.5.9': - resolution: {integrity: sha512-bTIP/2LB3iEzlZ58L7dFyLJuWLeFDXgzZUQZKlWIfsXiKYqKIfLTQ01U10sh9UiHpm1M+4kOVPpue5LbUpJXHw==} - peerDependencies: - astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta + '@astro-community/astro-embed-twitter@0.5.11': + resolution: {integrity: sha512-6cmyQY4LVVJj6x7qC6XrhWcxNffLvR+QGE/iw5HTOtAn60AStr6u+IX2Txpy6N6bta0DLjGqhTBhkC3NxmVKJg==} - '@astro-community/astro-embed-utils@0.1.5': - resolution: {integrity: sha512-0RlP7J1YEWrguWDfEDsm4uDCXk4FKn0HHakmSOSwHLg6YR8WNEN/LGMGhhsxLc/mDqO2lRh1VqfJy+yPLLkzsQ==} + '@astro-community/astro-embed-utils@0.2.0': + resolution: {integrity: sha512-Ia70AMCFOUOSoaMfMaK7Ovk7VyIY4opwzBJoA6GeL+omkvpFwDbSWmA8MOiMF4gJC0j/1dgrEir+txIb+WvsCA==} - '@astro-community/astro-embed-vimeo@0.3.11': - resolution: {integrity: sha512-uvTLmG5z9WGoyKac86Fxh6YnmBwlEQOalbi1/BatUy9zfQ/5x8rFs+U5xiM1nW38dGmDw/Hj7Nq3ljnZxy6PMA==} - peerDependencies: - astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta + '@astro-community/astro-embed-vimeo@0.3.12': + resolution: {integrity: sha512-VLNcsniT5qZ/7GaSGFWnX4ar0qcGyAYB1HQnAH362Zjqs0QI2he9u1nWv1kEx4xr3fZVxl6D2QuNN4xKtd8/ig==} - '@astro-community/astro-embed-youtube@0.5.9': - resolution: {integrity: sha512-8Uk2SKbyZVb+jxwqSAMoEpQo+063XYwCI3yRy9cbkyHpu09mDabGZNTF5XrL8CKr3NtR5haBkeYK/kSuKUkJ/g==} - peerDependencies: - astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta + '@astro-community/astro-embed-youtube@0.5.10': + resolution: {integrity: sha512-hVlx77KQLjKzElVQnrU5znQ5/E60keVSAPrhuWvQQHuqva5auJtt8YBpOThkwDMuEKXjQybEF1/3C07RZ8MAOQ==} '@astrojs/check@0.9.6': resolution: {integrity: sha512-jlaEu5SxvSgmfGIFfNgcn5/f+29H61NJzEMfAZ82Xopr4XBchXB1GVlcJsE+elUlsYSbXlptZLX+JMG3b/wZEA==} @@ -733,55 +723,55 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.3.10': - resolution: {integrity: sha512-/uWSUd1MHX2fjqNLHNL6zLYWBbrJeG412/8H7ESuK8ewoRoMPUgHDebqKrPTx/5n6f17Xzqc9hdg3MEqA5hXnQ==} + '@biomejs/biome@2.3.11': + resolution: {integrity: sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.3.10': - resolution: {integrity: sha512-M6xUjtCVnNGFfK7HMNKa593nb7fwNm43fq1Mt71kpLpb+4mE7odO8W/oWVDyBVO4ackhresy1ZYO7OJcVo/B7w==} + '@biomejs/cli-darwin-arm64@2.3.11': + resolution: {integrity: sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.3.10': - resolution: {integrity: sha512-Vae7+V6t/Avr8tVbFNjnFSTKZogZHFYl7MMH62P/J1kZtr0tyRQ9Fe0onjqjS2Ek9lmNLmZc/VR5uSekh+p1fg==} + '@biomejs/cli-darwin-x64@2.3.11': + resolution: {integrity: sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.3.10': - resolution: {integrity: sha512-B9DszIHkuKtOH2IFeeVkQmSMVUjss9KtHaNXquYYWCjH8IstNgXgx5B0aSBQNr6mn4RcKKRQZXn9Zu1rM3O0/A==} + '@biomejs/cli-linux-arm64-musl@2.3.11': + resolution: {integrity: sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.3.10': - resolution: {integrity: sha512-hhPw2V3/EpHKsileVOFynuWiKRgFEV48cLe0eA+G2wO4SzlwEhLEB9LhlSrVeu2mtSn205W283LkX7Fh48CaxA==} + '@biomejs/cli-linux-arm64@2.3.11': + resolution: {integrity: sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.3.10': - resolution: {integrity: sha512-QTfHZQh62SDFdYc2nfmZFuTm5yYb4eO1zwfB+90YxUumRCR171tS1GoTX5OD0wrv4UsziMPmrePMtkTnNyYG3g==} + '@biomejs/cli-linux-x64-musl@2.3.11': + resolution: {integrity: sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.3.10': - resolution: {integrity: sha512-wwAkWD1MR95u+J4LkWP74/vGz+tRrIQvr8kfMMJY8KOQ8+HMVleREOcPYsQX82S7uueco60L58Wc6M1I9WA9Dw==} + '@biomejs/cli-linux-x64@2.3.11': + resolution: {integrity: sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.3.10': - resolution: {integrity: sha512-o7lYc9n+CfRbHvkjPhm8s9FgbKdYZu5HCcGVMItLjz93EhgJ8AM44W+QckDqLA9MKDNFrR8nPbO4b73VC5kGGQ==} + '@biomejs/cli-win32-arm64@2.3.11': + resolution: {integrity: sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.3.10': - resolution: {integrity: sha512-pHEFgq7dUEsKnqG9mx9bXihxGI49X+ar+UBrEIj3Wqj3UCZp1rNgV+OoyjFgcXsjCWpuEAF4VJdkZr3TrWdCbQ==} + '@biomejs/cli-win32-x64@2.3.11': + resolution: {integrity: sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -1514,8 +1504,8 @@ packages: '@iconify-json/flat-color-icons@1.2.3': resolution: {integrity: sha512-KcmJ7CY0TKFv5GuBjiS4/v++jEcNXna1jfY+yq014tnw/MN/jMI2oYpoMHGuIpYtUqDu7eove+WySnOYO8nS3w==} - '@iconify-json/tabler@1.2.25': - resolution: {integrity: sha512-yU0aipjN4moGTqeCbdA9m7Wl7CZ9PUqnlwQXng/iGXod43FiSZHNUyMbc1+mu0cR8BV7gf8rkQZJy+as8gWAEw==} + '@iconify-json/tabler@1.2.26': + resolution: {integrity: sha512-92G+ZD70AZgeJf07JfQzH+isnai6DwPcMBuF/qL1F+xAxdXCJzGd3w2RmsRvOmB+w1ImmWEEDms50QivQIjd6g==} '@iconify/tools@4.1.3': resolution: {integrity: sha512-guPw9jvkrCCGFUvPr+NgUcQIpQcIll38NQzUzrEMK/1vrDmeJ9jstsp/Dx5LIP2na9BUBLHKOKXA6cERTpnGFw==} @@ -2014,6 +2004,11 @@ packages: cpu: [x64] os: [win32] + '@parse5/tools@0.7.0': + resolution: {integrity: sha512-JDvrGhc8kYBq7/SM4obJkpgwWo6pRjF/fo9CCaiJyVOkDf203Ciq2UF6TjzCFXKs7Q/zS2sS4deyBx0XzRvh9Q==} + peerDependencies: + parse5: 7.x || 8.x + '@playform/pipe@0.1.3': resolution: {integrity: sha512-cjRcaj6b8XZMS+N51In78EuD9e0x0M3gYxi2g+qUGk1iya2uxcS+aSrXxfBUZueOjxADQwpyS4zLEhlbHCGcDA==} @@ -3103,10 +3098,10 @@ packages: astro-compress@2.3.8: resolution: {integrity: sha512-XajjEtSTJuVRBOrtZ/Siavd4KcH47SEHo0XoZZeYVRF6BODSBqxVdtlfkqYJKM+F4XRUmLDS5ncVTILnQYvvXw==} - astro-embed@0.9.2: - resolution: {integrity: sha512-MUeNrfnNgcrV9E8WqEW9IYK8+Y3etDLyyi8Uf35rM5WJ53wkh511ye9oi15taJuqOaYRk2hQ9P5G2+/JS1Mjxg==} + astro-embed@0.10.0: + resolution: {integrity: sha512-kKxaXyN6Iz4fxv944aJtXewyyotUbTOI+53wxW7SwoGOWFAA6BfKyATcjqvuYPUaQm+1Fp1/3kdz7UJdLAwTBw==} peerDependencies: - astro: ^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta + astro: ^5.0.0 || ^6.0.0-alpha astro-expressive-code@0.41.3: resolution: {integrity: sha512-u+zHMqo/QNLE2eqYRCrK3+XMlKakv33Bzuz+56V1gs8H0y6TZ0hIi3VNbIxeTn51NLn+mJfUV/A0kMNfE4rANw==} @@ -3541,9 +3536,6 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - cssom@0.5.0: - resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -4402,15 +4394,6 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - linkedom@0.18.12: - resolution: {integrity: sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==} - engines: {node: '>=16'} - peerDependencies: - canvas: '>= 2' - peerDependenciesMeta: - canvas: - optional: true - linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} @@ -4924,6 +4907,9 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} @@ -5674,9 +5660,6 @@ packages: '@swc/wasm': optional: true - ts-pattern@5.8.0: - resolution: {integrity: sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==} - tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} @@ -5726,9 +5709,6 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - uhyphen@0.2.0: - resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} - uint8arrays@3.0.0: resolution: {integrity: sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA==} @@ -6349,62 +6329,44 @@ snapshots: '@antfu/utils@8.1.1': {} - '@astro-community/astro-embed-baseline-status@0.2.1(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2))': + '@astro-community/astro-embed-baseline-status@0.2.2': dependencies: - '@astro-community/astro-embed-utils': 0.1.5 - astro: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) - transitivePeerDependencies: - - canvas + '@astro-community/astro-embed-utils': 0.2.0 - '@astro-community/astro-embed-bluesky@0.1.5(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2))': + '@astro-community/astro-embed-bluesky@0.1.6': dependencies: '@atproto/api': 0.13.35 - astro: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) - ts-pattern: 5.8.0 - '@astro-community/astro-embed-integration@0.8.3(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2))': + '@astro-community/astro-embed-integration@0.9.0(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2))': dependencies: - '@astro-community/astro-embed-bluesky': 0.1.5(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) - '@astro-community/astro-embed-link-preview': 0.2.3 - '@astro-community/astro-embed-twitter': 0.5.9(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) - '@astro-community/astro-embed-vimeo': 0.3.11(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) - '@astro-community/astro-embed-youtube': 0.5.9(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) + '@astro-community/astro-embed-bluesky': 0.1.6 + '@astro-community/astro-embed-link-preview': 0.3.1 + '@astro-community/astro-embed-twitter': 0.5.11 + '@astro-community/astro-embed-vimeo': 0.3.12 + '@astro-community/astro-embed-youtube': 0.5.10 '@types/unist': 2.0.11 astro: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) astro-auto-import: 0.4.5(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) unist-util-select: 4.0.3 - transitivePeerDependencies: - - canvas - '@astro-community/astro-embed-link-preview@0.2.3': + '@astro-community/astro-embed-link-preview@0.3.1': dependencies: - '@astro-community/astro-embed-utils': 0.1.5 - transitivePeerDependencies: - - canvas + '@astro-community/astro-embed-utils': 0.2.0 + '@parse5/tools': 0.7.0(parse5@8.0.0) + parse5: 8.0.0 - '@astro-community/astro-embed-twitter@0.5.9(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2))': + '@astro-community/astro-embed-twitter@0.5.11': dependencies: - '@astro-community/astro-embed-utils': 0.1.5 - astro: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) - transitivePeerDependencies: - - canvas + '@astro-community/astro-embed-utils': 0.2.0 - '@astro-community/astro-embed-utils@0.1.5': - dependencies: - linkedom: 0.18.12 - transitivePeerDependencies: - - canvas + '@astro-community/astro-embed-utils@0.2.0': {} - '@astro-community/astro-embed-vimeo@0.3.11(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2))': + '@astro-community/astro-embed-vimeo@0.3.12': dependencies: - '@astro-community/astro-embed-utils': 0.1.5 - astro: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) - transitivePeerDependencies: - - canvas + '@astro-community/astro-embed-utils': 0.2.0 - '@astro-community/astro-embed-youtube@0.5.9(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2))': + '@astro-community/astro-embed-youtube@0.5.10': dependencies: - astro: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) lite-youtube-embed: 0.3.4 '@astrojs/check@0.9.6(prettier@3.6.2)(typescript@5.9.3)': @@ -6724,39 +6686,39 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@biomejs/biome@2.3.10': + '@biomejs/biome@2.3.11': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.3.10 - '@biomejs/cli-darwin-x64': 2.3.10 - '@biomejs/cli-linux-arm64': 2.3.10 - '@biomejs/cli-linux-arm64-musl': 2.3.10 - '@biomejs/cli-linux-x64': 2.3.10 - '@biomejs/cli-linux-x64-musl': 2.3.10 - '@biomejs/cli-win32-arm64': 2.3.10 - '@biomejs/cli-win32-x64': 2.3.10 + '@biomejs/cli-darwin-arm64': 2.3.11 + '@biomejs/cli-darwin-x64': 2.3.11 + '@biomejs/cli-linux-arm64': 2.3.11 + '@biomejs/cli-linux-arm64-musl': 2.3.11 + '@biomejs/cli-linux-x64': 2.3.11 + '@biomejs/cli-linux-x64-musl': 2.3.11 + '@biomejs/cli-win32-arm64': 2.3.11 + '@biomejs/cli-win32-x64': 2.3.11 - '@biomejs/cli-darwin-arm64@2.3.10': + '@biomejs/cli-darwin-arm64@2.3.11': optional: true - '@biomejs/cli-darwin-x64@2.3.10': + '@biomejs/cli-darwin-x64@2.3.11': optional: true - '@biomejs/cli-linux-arm64-musl@2.3.10': + '@biomejs/cli-linux-arm64-musl@2.3.11': optional: true - '@biomejs/cli-linux-arm64@2.3.10': + '@biomejs/cli-linux-arm64@2.3.11': optional: true - '@biomejs/cli-linux-x64-musl@2.3.10': + '@biomejs/cli-linux-x64-musl@2.3.11': optional: true - '@biomejs/cli-linux-x64@2.3.10': + '@biomejs/cli-linux-x64@2.3.11': optional: true - '@biomejs/cli-win32-arm64@2.3.10': + '@biomejs/cli-win32-arm64@2.3.11': optional: true - '@biomejs/cli-win32-x64@2.3.10': + '@biomejs/cli-win32-x64@2.3.11': optional: true '@capsizecss/unpack@3.0.1': @@ -7398,7 +7360,7 @@ snapshots: dependencies: '@iconify/types': 2.0.0 - '@iconify-json/tabler@1.2.25': + '@iconify-json/tabler@1.2.26': dependencies: '@iconify/types': 2.0.0 @@ -7971,6 +7933,10 @@ snapshots: '@pagefind/windows-x64@1.4.0': optional: true + '@parse5/tools@0.7.0(parse5@8.0.0)': + dependencies: + parse5: 8.0.0 + '@playform/pipe@0.1.3': dependencies: '@types/node': 22.13.14 @@ -9112,18 +9078,16 @@ snapshots: - uploadthing - yaml - astro-embed@0.9.2(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)): + astro-embed@0.10.0(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: - '@astro-community/astro-embed-baseline-status': 0.2.1(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) - '@astro-community/astro-embed-bluesky': 0.1.5(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) - '@astro-community/astro-embed-integration': 0.8.3(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) - '@astro-community/astro-embed-link-preview': 0.2.3 - '@astro-community/astro-embed-twitter': 0.5.9(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) - '@astro-community/astro-embed-vimeo': 0.3.11(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) - '@astro-community/astro-embed-youtube': 0.5.9(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) + '@astro-community/astro-embed-baseline-status': 0.2.2 + '@astro-community/astro-embed-bluesky': 0.1.6 + '@astro-community/astro-embed-integration': 0.9.0(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)) + '@astro-community/astro-embed-link-preview': 0.3.1 + '@astro-community/astro-embed-twitter': 0.5.11 + '@astro-community/astro-embed-vimeo': 0.3.12 + '@astro-community/astro-embed-youtube': 0.5.10 astro: 5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2) - transitivePeerDependencies: - - canvas astro-expressive-code@0.41.3(astro@5.16.6(@types/node@20.16.10)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.52.5)(terser@5.39.0)(typescript@5.9.3)(yaml@2.8.2)): dependencies: @@ -9700,8 +9664,6 @@ snapshots: dependencies: css-tree: 2.2.1 - cssom@0.5.0: {} - csstype@3.2.3: {} debug@4.4.3: @@ -10727,14 +10689,6 @@ snapshots: lines-and-columns@1.2.4: {} - linkedom@0.18.12: - dependencies: - css-select: 5.2.2 - cssom: 0.5.0 - html-escaper: 3.0.3 - htmlparser2: 10.0.0 - uhyphen: 0.2.0 - linkify-it@5.0.0: dependencies: uc.micro: 2.1.0 @@ -11571,6 +11525,10 @@ snapshots: dependencies: entities: 6.0.1 + parse5@8.0.0: + dependencies: + entities: 6.0.1 + pascal-case@3.1.2: dependencies: no-case: 3.0.4 @@ -12516,8 +12474,6 @@ snapshots: yn: 3.1.1 optional: true - ts-pattern@5.8.0: {} - tsconfck@3.1.6(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -12551,8 +12507,6 @@ snapshots: ufo@1.6.1: {} - uhyphen@0.2.0: {} - uint8arrays@3.0.0: dependencies: multiformats: 9.9.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c8884f26039..8e97b2cb58f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,7 +16,7 @@ packages: - "ui-libraries/material/docs" catalog: - "@biomejs/biome": 2.3.10 + "@biomejs/biome": 2.3.11 "cspell": 9.4.0 "typescript": 5.9.3 "vite": 7.3.0 @@ -25,7 +25,7 @@ catalog: "@astrojs/starlight": 0.37.1 "starlight-links-validator": 0.19.2 "astro": 5.16.6 - "astro-embed": 0.9.2 + "astro-embed": 0.10.0 "sharp": 0.34.5 "@expressive-code/plugin-line-numbers": 0.41.5 "rehype-external-links": 3.0.0 diff --git a/tests/cases/issues/issue_10923_popupwindow-reshow.slint b/tests/cases/issues/issue_10923_popupwindow-reshow.slint new file mode 100644 index 00000000000..b4d70617624 --- /dev/null +++ b/tests/cases/issues/issue_10923_popupwindow-reshow.slint @@ -0,0 +1,69 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +import { Button } from "std-widgets.slint"; + +export component TestCase inherits Window { + in property foo: 0; + in-out property result; + + width: 100px; + height: 100px; + + Button { + text: "Show popup"; + + clicked => popup.show(); + } + + popup := PopupWindow { + width: rect.preferred-width; + height: rect.preferred-height; + + rect := Rectangle { + background: lightskyblue; + + VerticalLayout { + if foo == 0: Button { + text: "Click for crash"; + + clicked => { + result+=1; + popup.show(); + } + } + } + } + } +} + + +/* +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; +slint_testing::send_mouse_click(&instance, 50., 50.); +slint_testing::send_mouse_click(&instance, 5., 5.); +assert_eq(instance.get_result(), 1); +slint_testing::send_mouse_click(&instance, 5., 5.); +assert_eq(instance.get_result(), 2); +``` + +```rust +let instance = TestCase::new().unwrap(); +slint_testing::send_mouse_click(&instance, 50., 50.); +slint_testing::send_mouse_click(&instance, 5., 5.); +assert_eq!(instance.get_result(), 1); +slint_testing::send_mouse_click(&instance, 5., 5.); +assert_eq!(instance.get_result(), 2); +``` + +```js +var instance = new slint.TestCase(); +slintlib.private_api.send_mouse_click(instance, 50., 50.); +slintlib.private_api.send_mouse_click(instance, 5., 5.); +assert.equal(instance.result, 1); +slintlib.private_api.send_mouse_click(instance, 5., 5.); +assert.equal(instance.result, 2); +``` +*/ diff --git a/tests/cases/layout/grid_with_model_in_row.slint b/tests/cases/layout/grid_with_model_in_row.slint new file mode 100644 index 00000000000..c8d4ef13d20 --- /dev/null +++ b/tests/cases/layout/grid_with_model_in_row.slint @@ -0,0 +1,132 @@ +// Copyright © Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +export component TestCase inherits Window { + default-font-size: 20px; + width: 600phx; + height: 300phx; + + property <[{ name: string, colspan: int}]> model_with_colspan: [ + { name: "Jules", colspan: 1 }, + { name: "Emile", colspan: 2 }, + { name: "Charles", colspan: 1 }, + ]; + + out property clicked_text; + out property clicked: false; + + VerticalLayout { + Rectangle { + GridLayout { + padding-left: 10phx; + padding-top: 20phx; + header_row := Row { + Text { + colspan: 3; + text: "Click on one of the numbered cells"; + height: 50phx; + } + } + + Row { + for _ in 3: txt := Text { + width: 200phx; + height: 50phx; + text: txt.row + ", " + txt.col; + TouchArea { + clicked => { + clicked_text = txt.text; + clicked = true; + } + } + } + } + + Row { + // Optional item at the beginning of a row, to test that we set new_row correctly + if clicked: optionalText := Text { + width: 200phx; // make sure column 0 doesn't grow larger + height: 50phx; + text: "Clicked!"; + colspan: 2; + } + // This element exists to test whether the conditional Text above is created or absent (end.col changes accordingly) + end := Text { + text: "(end)"; + } + } + } + } + + Rectangle { + GridLayout { + before1 := Text { + text: "Cell before a repeater"; + } + // This one will have new_row=false, and shouldn't use a new_row local variable in the generated code + before2 := Text { + text: "Another cell before a repeater"; + } + + for entry in model_with_colspan: repeated := Text { + text: entry.name + " (" + entry.colspan + ")"; + colspan: entry.colspan; + } + // This tests that a variable colspan (different between repeated items) works correctly + after := Text { + text: "Cell after the repeater"; + } + } + } + } + + out property before_cells_ok: before1.row == 0 && before1.col == 0 && before2.row == 0 && before2.col == 1; + out property after_cell_ok: after.row == 0 && after.col == 6; + out property test: end.row == 2 && end.col == 0 && before_cells_ok && after_cell_ok; + out property test_after_clicked: end.row == 2 && end.col == 2 && clicked && before_cells_ok && after_cell_ok; +} + +/* + +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; +assert(instance.get_test()); +slint_testing::send_mouse_click(&instance, 10. + 5., 75.); +assert_eq(instance.get_clicked_text(), "1, 0"); +assert(instance.get_test_after_clicked()); +slint_testing::send_mouse_click(&instance, 10. + 200. + 5., 75.); +assert_eq(instance.get_clicked_text(), "1, 1"); +slint_testing::send_mouse_click(&instance, 10. + 400. + 5., 75.); +assert_eq(instance.get_clicked_text(), "1, 2"); +``` + + +```rust +let instance = TestCase::new().unwrap(); +assert!(instance.get_before_cells_ok()); +assert!(instance.get_after_cell_ok()); +assert!(instance.get_test()); +slint_testing::send_mouse_click(&instance, 10. + 5., 75.); +assert_eq!(instance.get_clicked_text(), "1, 0"); +assert!(instance.get_test_after_clicked()); +slint_testing::send_mouse_click(&instance, 10. + 200. + 5., 75.); +assert_eq!(instance.get_clicked_text(), "1, 1"); +slint_testing::send_mouse_click(&instance, 10. + 400. + 5., 75.); +assert_eq!(instance.get_clicked_text(), "1, 2"); + +``` + +```js +var instance = new slint.TestCase(); +assert(instance.test); +slintlib.private_api.send_mouse_click(instance, 10. + 5., 75.); +assert.deepEqual(instance.clicked_text, "1, 0"); +assert(instance.test_after_clicked); +slintlib.private_api.send_mouse_click(instance, 10. + 200. + 5., 75.); +assert.deepEqual(instance.clicked_text, "1, 1"); +slintlib.private_api.send_mouse_click(instance, 10. + 400. + 5., 75.); +assert.deepEqual(instance.clicked_text, "1, 2"); +``` + +*/ diff --git a/tests/cases/layout/grid_with_x_y_model.slint b/tests/cases/layout/grid_with_x_y_model.slint new file mode 100644 index 00000000000..06684b47100 --- /dev/null +++ b/tests/cases/layout/grid_with_x_y_model.slint @@ -0,0 +1,90 @@ +// Copyright © Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure +// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 + +export component TestCase inherits Window { + default-font-size: 16px; + width: 1000phx; + + out property clicked_text; + + property <[{ row: int, col: int}]> xy_model: [ + { row: 0, col: 2 }, + { row: 0, col: 1 }, + { row: 2, col: -1 }, // This one should warn and saturate to 2, 0 + { row: -500, col: 0 }, // This one should warn and saturate to 0, 0 + { row: 1, col: 0 }, + ]; + + GridLayout { + padding-left: 0phx; + padding-top: 0phx; + for entry in xy_model: txt := Text { + text: entry.row + ", " + entry.col; + row: entry.row; + col: entry.col; + colspan: entry.row == 2 && entry.col == -1 ? 2 : 1; + width: 200phx; + height: 50phx; + TouchArea { + clicked => { + clicked_text = txt.text; + } + } + } + next := Text { + text: "(next)"; + } + } + + out property test: next.row == 1 && next.col == 1; +} + +/* + +```cpp +auto handle = TestCase::create(); +const TestCase &instance = *handle; +slint_testing::send_mouse_click(&instance, 5., 5.); +assert_eq(instance.get_clicked_text(), "-500, 0"); +slint_testing::send_mouse_click(&instance, 200. + 5., 5.); +assert_eq(instance.get_clicked_text(), "0, 1"); +slint_testing::send_mouse_click(&instance, 400. + 5., 5.); +assert_eq(instance.get_clicked_text(), "0, 2"); +slint_testing::send_mouse_click(&instance, 5., 50. + 5.); +assert_eq(instance.get_clicked_text(), "1, 0"); +slint_testing::send_mouse_click(&instance, 5., 100. + 5.); +assert_eq(instance.get_clicked_text(), "2, -1"); +assert(instance.get_test()); +``` + +```rust +let instance = TestCase::new().unwrap(); +slint_testing::send_mouse_click(&instance, 5., 5.); +assert_eq!(instance.get_clicked_text(), "-500, 0"); +slint_testing::send_mouse_click(&instance, 200. + 5., 5.); +assert_eq!(instance.get_clicked_text(), "0, 1"); +slint_testing::send_mouse_click(&instance, 400. + 5., 5.); +assert_eq!(instance.get_clicked_text(), "0, 2"); +slint_testing::send_mouse_click(&instance, 5., 50. + 5.); +assert_eq!(instance.get_clicked_text(), "1, 0"); +slint_testing::send_mouse_click(&instance, 5., 100. + 5.); +assert_eq!(instance.get_clicked_text(), "2, -1"); +assert!(instance.get_test()); +``` + +```js +var instance = new slint.TestCase(); +slintlib.private_api.send_mouse_click(instance, 5., 5.); +assert.deepEqual(instance.clicked_text, "-500, 0"); +slintlib.private_api.send_mouse_click(instance, 200. + 5., 5.); +assert.deepEqual(instance.clicked_text, "0, 1"); +slintlib.private_api.send_mouse_click(instance, 400. + 5., 5.); +assert.deepEqual(instance.clicked_text, "0, 2"); +slintlib.private_api.send_mouse_click(instance, 5., 50. + 5.); +assert.deepEqual(instance.clicked_text, "1, 0"); +slintlib.private_api.send_mouse_click(instance, 5., 100. + 5.); +assert.deepEqual(instance.clicked_text, "2, -1"); +assert(instance.test); +``` + +*/ diff --git a/tests/cases/models/model.slint b/tests/cases/models/model.slint index 257991cf7e6..b04d339c644 100644 --- a/tests/cases/models/model.slint +++ b/tests/cases/models/model.slint @@ -2,10 +2,10 @@ // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 -TestCase := Rectangle { +export component TestCase inherits Window { width: 300phx; height: 300phx; - property<[{name: string, account: string, score: float}]> model: [ + in-out property<[{name: string, account: string, score: float}]> model: [ { name: "Olivier", account: "ogoffart", @@ -18,13 +18,14 @@ TestCase := Rectangle { } ]; - property clicked_score; - property clicked_name; - property clicked_internal_state; - property clicked_index; + in-out property clicked_score; + in-out property clicked_name; + in-out property clicked_internal_state; + in-out property clicked_index; for person[i] in model: TouchArea { x: i*10phx; + y: 0phx; width: 10phx; height: 10phx; property model; // this is not the model @@ -322,4 +323,36 @@ slint_testing::send_mouse_click(&instance, 35., 5.); assert_eq(instance.get_clicked_name(), "Olivier"); ``` +////////// Issue #10278 +```rust + +type ModelData = (slint::SharedString, slint::SharedString, f32); + +struct MyModel { + window_weak: slint::Weak, +} + +impl slint::Model for MyModel { + type Data = ModelData; + + fn row_count(&self) -> usize { + 42 + } + + fn row_data(&self, row: usize) -> Option { + let name = self.window_weak.unwrap().get_clicked_name(); + let account = format!("row: {row}").into(); + Some((name, account, row as f32)) + } + + fn model_tracker(&self) -> &dyn slint::ModelTracker { + &() + } +} + + +let instance = TestCase::new().unwrap(); +let model = std::rc::Rc::new(MyModel { window_weak: instance.as_weak() }); +instance.set_model(slint::ModelRc::from(model)); +``` */ diff --git a/tests/cases/widgets/textedit.slint b/tests/cases/widgets/textedit.slint index 806580617a6..826151a54fb 100644 --- a/tests/cases/widgets/textedit.slint +++ b/tests/cases/widgets/textedit.slint @@ -17,6 +17,7 @@ export component TestCase inherits Window { in-out property text <=> edit.text; in-out property read-only <=> edit.read-only; in-out property enabled <=> edit.enabled; + in-out property font-italic <=> edit.font-italic; public function paste() { edit.paste(); } @@ -108,6 +109,10 @@ instance.set_read_only(false); instance.set_enabled(false); instance.invoke_paste(); assert_eq!(instance.get_text(), "XxxHello👋"); + +assert_eq!(instance.get_font_italic(), false); +instance.set_font_italic(true); +assert_eq!(instance.get_font_italic(), true); ``` */ diff --git a/tests/driver/interpreter/main.rs b/tests/driver/interpreter/main.rs index 2b53890e3a0..174fef53896 100644 --- a/tests/driver/interpreter/main.rs +++ b/tests/driver/interpreter/main.rs @@ -46,6 +46,9 @@ test_example!(example_fancy_switches, "examples/fancy-switches/demo.slint"); test_example!(example_home_automation, "demos/home-automation/ui/demo.slint"); test_example!(example_energy_monitor, "demos/energy-monitor/ui/desktop_window.slint"); test_example!(example_weather, "demos/weather-demo/ui/main.slint"); +test_example!(example_grid_model_rows, "examples/layouts/grid-with-model-in-rows.slint"); +test_example!(example_vector_as_grid, "examples/layouts/vector-as-grid.slint"); +test_example!(example_vlayout, "examples/layouts/vertical-layout-with-model.slint"); fn main() { println!("Nothing to see here, please run me through cargo test :)"); diff --git a/tools/docsnapper/Cargo.toml b/tools/docsnapper/Cargo.toml index 71ceaf8c5bb..05a3f2ed5d7 100644 --- a/tools/docsnapper/Cargo.toml +++ b/tools/docsnapper/Cargo.toml @@ -13,6 +13,7 @@ rust-version.workspace = true version.workspace = true categories = ["gui", "development-tools", "command-line-utilities"] keywords = ["viewer", "gui", "ui", "toolkit"] +publish = false [dependencies] i-slint-compiler = { workspace = true } @@ -26,8 +27,7 @@ spin_on = { workspace = true } termcolor = { version = "1.4.1" } walkdir = { version = "2.5" } -# Enable image-rs' default features to make all image formats available for preview -image = { workspace = true, features = ["default"] } +image = { workspace = true } [[bin]] name = "slint-docsnapper" diff --git a/tools/docsnapper/README.md b/tools/docsnapper/README.md index ff9d8d3f51e..7a3cba62edc 100644 --- a/tools/docsnapper/README.md +++ b/tools/docsnapper/README.md @@ -1,10 +1,75 @@ +# Docsnapper -# Screenshot tool for the documentation examples +Automated screenshot generator for Slint documentation examples. Scans markdown files for Slint code snippets and renders them to images. -## Installation +## Disclaimer -Please don't :-) +This tool is internal to Slint development and used as part of the CI/CD pipeline to keep the documentation up to date. Use for other purposes at your peril :). + +## How It Works + +1. Walks through `.md` and `.mdx` files in a documentation folder +2. Finds `` tags containing ` ```slint ` code blocks +3. Compiles each snippet using the Slint interpreter +4. Renders the UI headlessly with the Skia renderer +5. Saves screenshots to the paths specified in the tags + +## Markdown Tag Format + +```markdown + +```slint +Button { text: "Click me"; } +``` + +``` + +### Tag Attributes + +| Attribute | Description | +|-----------|-------------| +| `imagePath` | Output path for the screenshot (relative to doc file or absolute from project root if starting with `/`) | +| `imageWidth` | Width in pixels | +| `imageHeight` | Height in pixels | +| `scale` | Scale factor (default: 1.0) | +| `noScreenShot` | Skip this snippet (no value needed) | + +If the code block doesn't contain a `component` declaration, the snippet is automatically wrapped in a window component. ## Usage -See the docs CI job +```sh +slint-docsnapper [options] +``` + +### Options + +| Option | Description | +|--------|-------------| +| `-I ` | Include path for `.slint` files or images | +| `-L ` | Library location (e.g., `-L std=/path/to/std`) | +| `--style ` | Style name (`native`, `fluent`, etc.) | +| `--overwrite` | Overwrite existing screenshot files | +| `--component ` | Specific component to render | + +### Example + +```sh +# Generate screenshots for all docs, overwriting existing ones +slint-docsnapper docs/astro/src/content/docs --style fluent --overwrite + +# With include paths +slint-docsnapper docs/astro/src/content/docs -I ../examples -I ../ui +``` + +## Build + +```sh +cargo build -p slint-docsnapper --release +``` + +## Notes + +- Requires a display server or headless environment (uses Skia with Wayland support) +- Primarily used in CI to generate/update documentation screenshots +- The tool finds the project root by looking for `astro.config.mjs` or `astro.config.ts` diff --git a/tools/figma-inspector/README.md b/tools/figma-inspector/README.md index eb1e54211a3..fd01f271b7d 100644 --- a/tools/figma-inspector/README.md +++ b/tools/figma-inspector/README.md @@ -1,85 +1,119 @@ +# Figma to Slint Property Inspector -## Figma to Slint property inspector +A Figma plugin that displays Slint code snippets in Figma's Dev mode inspector. When you select a design element, the plugin shows the equivalent Slint markup instead of CSS properties. -### Installing the plugin from Figma +## Features -The latest release of the Figma Inspector can be installed directly from the Figma website at +- Converts Figma elements to Slint component snippets +- Supports Figma variables (references them as Slint property paths) +- Works in Figma Desktop and VS Code extension - https://www.figma.com/community/plugin/1474418299182276871/figma-to-slint +### Supported Elements -or in Figma by searching for the "Figma To Slint" plugin. +| Figma Node Type | Slint Element | +|-----------------|---------------| +| Frame, Rectangle, Group | `Rectangle { }` | +| Component, Instance | `Rectangle { }` | +| Text | `Text { }` | +| Vector | `Path { }` | -### Installing the plugin via nightly snapshot. +### Converted Properties -Download the nightly snapshot [figma-plugin.zip](https://github.com/slint-ui/slint/releases/download/nightly/figma-plugin.zip). +**Layout:** `x`, `y`, `width`, `height` -The prerequisites are either the Figma Desktop App or the Figma VSCode extension. -A valid Figma subscription with at least 'Team Professional' is needed. +**Appearance:** +- `background` / `fill` (solid colors, linear and radial gradients) +- `opacity` +- `border-radius` (uniform or per-corner) +- `border-width`, `border-color` -In Figma Desktop or the VScode extension have a file open and right click on it. Select Plugins > Development > Import Plugin From Manifest.. and point it at the manifest.json file that you just unzipped. +**Text:** +- `text`, `color` +- `font-family`, `font-size`, `font-weight` +- `horizontal-alignment` -The Slint properties will now show in the Dev mode inspector in the same place the standard CSS properties -would have been shown. +**Path:** `commands` (extracted from SVG), `stroke`, `stroke-width` -### Build +### Figma Variables -Figma is a web app (Chromium) and the plugin is just javascript. As with other web apps in the repo -the prerequisite software needed to develop the plugin are: +When enabled, the plugin references Figma variables as Slint property paths: -You need to install the following components: -* **[Node.js](https://nodejs.org/download/release/)** (v20. or newer) -* **[pnpm](https://www.pnpm.io/)** -* **[Figma Desktop App](https://www.figma.com/downloads/)** +```slint,no-test +// Without variables +background: #3b82f6; -You also **MUST** have a valid Figma developer subscription as the plugin works in the Dev mode -and/or Figma VS Code extension. +// With variables enabled +background: Colors.current.primary; +``` -To try it out locally type this in this directory: +## Installation -```sh -## only need to run this once -pnpm install - -pnpm build -``` +### From Figma Community (Recommended) -Then in Figma on an open file right click and select `Plugins > Development > Import Plugin From Manifest..` and point it at the `dist/manifest.json` file that has now been created inside this project. +Install directly from [Figma Community](https://www.figma.com/community/plugin/1474418299182276871/figma-to-slint) or search for "Figma To Slint" in the Figma plugin browser. -You should also ensure `Plugins > Development > Hot Reload Plugin` is ticked. +### From Nightly Build -To develop in hot reload mode: +1. Download [figma-plugin.zip](https://github.com/slint-ui/slint/releases/download/nightly/figma-plugin.zip) +2. Extract the archive +3. In Figma: right-click → `Plugins` → `Development` → `Import Plugin From Manifest...` +4. Select the `manifest.json` from the extracted folder -```sh -pnpm dev -``` +### Requirements -As you save code changes the plugin is automatically recompiled and reloaded in Figma for Desktop and/or the Figma VS Code extension. +- Figma Desktop App or Figma VS Code extension +- Figma subscription with Dev mode access (Team Professional or higher) +## Development -### Testing +### Prerequisites -As of writing Figma has real test support. Testing is limited to unit testing some of the functions via `Vitest`. +- [Node.js](https://nodejs.org/) v20 or newer +- [pnpm](https://pnpm.io/) +- Figma Desktop App or VS Code extension -You can find the test files under `/tests`. This folder also includes the JSON export of a real Figma file -to test against. The easiest way to update the file is to to edit it in Figma and then use a personal access token to get a JSON version. +### Build -To get an access Token in Figma go to the home screen. Then top right click the logged in user name. Then `Settings` and then the `Security` tab. Scroll to the bottom and choose `Generate new token`. Then save the token in a secure private place. +```sh +pnpm install # Install dependencies (first time only) +pnpm build # Build the plugin +``` -You then need to get the file ID. Open figma.com, login and open the file. You will then have a url like -`https://www.figma.com/design/njC6jSUbrYpqLRJ2dyV6NT/energy-test-file?node-id=113-2294&p=f&t=5IDwrGIFUnri3Z17-0`. The ID is the part of the URL after `/design/` so in this example `njC6jSUbrYpqLRJ2dyV6NT`. +Import the plugin in Figma: right-click → `Plugins` → `Development` → `Import Plugin From Manifest...` → select `dist/manifest.json` -You can then use `curl` to download the JSON with +### Development Mode ```sh -curl -H 'X-Figma-Token: ' \ -'https://api.figma.com/v1/files/' \ --o figma_output.json +pnpm dev ``` -Vitest can then be run in hot reload mode for ease of test development with: +Enable hot reload in Figma: `Plugins` → `Development` → `Hot Reload Plugin` + +Changes are automatically recompiled and reloaded. + +### Testing + +Unit tests use Vitest with exported Figma JSON fixtures. ```sh -pnpm test +pnpm test # Run tests in watch mode ``` +#### Updating Test Fixtures + +1. Generate a Figma access token: + - Figma home → click username → `Settings` → `Security` → `Generate new token` + +2. Get the file ID from the Figma URL: + ``` + https://www.figma.com/design/njC6jSUbrYpqLRJ2dyV6NT/... + └─────────────────────┘ + File ID + ``` +3. Download the file as JSON: + ```sh + curl -H 'X-Figma-Token: ' \ + 'https://api.figma.com/v1/files/' \ + -o tests/figma_output.json + ``` diff --git a/tools/lsp/README.md b/tools/lsp/README.md index e13ff289ff5..3c9d0272518 100644 --- a/tools/lsp/README.md +++ b/tools/lsp/README.md @@ -8,42 +8,8 @@ featuring diagnostics, code completion, goto definition, and more importantly, l The LSP server consists of a binary, `slint-lsp` (or `slint-lsp.exe` on Windows). It provides all the functionality and allows any programming editor that also implements the standardized LSP protocol to communicate with it. - - -If you have Rust installed, you can install the binary by running the following command: - -```sh -cargo install slint-lsp -``` - -This makes the latest released version available in `$HOME/.cargo/bin`. If you would like to try a development version, you can also point `cargo install` to the git repository: -for the released version. Or, to install the development version: - -```sh -cargo install slint-lsp --git https://github.com/slint-ui/slint --force -``` - - -Alternatively, you can download one of our pre-built binaries for Linux or Windows: - -1. Open -2. Click on the latest release -3. From "Assets" download either `slint-lsp-linux.tar.gz` for a Linux x86-64 binary - or `slint-lsp-windows-x86_64.zip` for a Windows x86-64 binary. -4. Uncompress the downloaded archive into a location of your choice. - -As the next step, configure your editor to use the binary, no arguments are required - -Make sure the required dependencies are found. On Debian-like systems install them with the following command: - -```shell -sudo apt install -y build-essential libx11-xcb1 libx11-dev libxcb1-dev libxkbcommon0 libinput10 libinput-dev libgbm1 libgbm-dev -``` +For details on configuration see [the Slint documentation](https://snapshots.slint.dev/master/docs/guide/tooling/manual-setup/#slint-lsp). ## Code formatting -The slint code formatting tool is part of the lsp. To learn how to use it as a standalone tool, see [fmt README](./fmt/README.md) - -# Editor configuration - -Please check the [editors folder](../../editors/README.md) in the Slint repository for instructions on how to set up different editors to work with Slint. +The Slint code formatting tool is part of the lsp. To learn how to use it as a standalone tool, see [the Slint documentation](https://snapshots.slint.dev/master/docs/guide/tooling/manual-setup/#fmt) diff --git a/tools/lsp/fmt/README.md b/tools/lsp/fmt/README.md index 73bdaa8de3a..1528995ad68 100644 --- a/tools/lsp/fmt/README.md +++ b/tools/lsp/fmt/README.md @@ -1,4 +1,3 @@ - # Slint-fmt This tool for formatting .slint syntax is in a very early stage. @@ -8,36 +7,3 @@ If you find any such examples, please open an issue including the example and th ## Building This tool is part of slint-lsp. See the README file in `lsp` for building information. - -## Usage - -The built binary can be used in following ways: - -- `slint-lsp format ` - reads the file and outputs the formatted version to stdout -- `slint-lsp format -i ` - reads the file and saves the output to the same file -- `slint-lsp format /dev/stdin` - using /dev/stdin you can achieve the special behavior - of reading from stdin and writing to stdout - -Note that `.slint` files are formatted, while `.md` and `.rs` files are searched for `.slint` blocks. -All other files are left untouched. - -## Usage with VSCode - -While we don't yet have a proper VSCode integration for this formatter, -here is a simple way how you can get around it. - -1. Install the extension Custom Format by Vehmloewff. [Marketplace link](https://marketplace.visualstudio.com/items?itemName=Vehmloewff.custom-format) -2. Build slint-lsp locally. -3. Add a section like this to your vscode `settings.json`: -``` -{ - "custom-format.formatters": [ - { - "language": "slint", - "command": "/path/to/your/built/slint-lsp format /dev/stdin" - } - ] -} -``` -4. (Optional) Allow formatting or save, or set this formatter as default for .slint files. -5. Enjoy! Your .slint files are now formatted. diff --git a/tools/updater/README.md b/tools/updater/README.md index 088f6b8e90d..ebb1a5872f7 100644 --- a/tools/updater/README.md +++ b/tools/updater/README.md @@ -1,22 +1,96 @@ - # Slint Updater -This program is a tool to upgrade `.slint` files from the [Slint Project](https://slint.dev) to the latest syntax. +A syntax-aware migration tool that automatically upgrades `.slint` files to the latest Slint language syntax. -The Slint Design Language evolves, with new features being added and old ones marked for deprecation. Use this tool to -automatically upgrade your `.slint` files to the latest syntax. +As the Slint language evolves, some syntax patterns are deprecated in favor of new ones. This tool parses your `.slint` files using the Slint compiler and applies transformations to migrate deprecated patterns. ## Installation -The updater can be installed from crates.io: - ```bash cargo install slint-updater ``` -### Usage: +## Usage + +```bash +# Preview changes (prints to stdout) +slint-updater file.slint + +# Apply changes in-place +slint-updater -i file.slint + +# Update multiple files +slint-updater -i src/**/*.slint + +# Move property declarations to component root +slint-updater -i --move-declarations file.slint +``` + +### Options + +| Flag | Description | +|------|-------------| +| `-i, --inline` | Modify files in-place instead of printing to stdout | +| `--move-declarations` | Move all property declarations to the root of each component | + +## Supported File Types + +- `.slint` files +- `.rs` files containing `slint!` macros +- `.md` files with ` ```slint ` code blocks + +## Transformations + +### Active Transforms + +**Enum renames:** +- `PointerEventButton.none` → `PointerEventButton.other` +- `Keys.*` → `Key.*` + +### Experimental Transforms + +These transforms handle migrations from older Slint syntax versions: + +**Component declaration syntax:** +```slint,no-test +// Old syntax +MyComponent := Rectangle { } + +// New syntax +component MyComponent inherits Rectangle { } +``` + +**Property visibility:** +```slint,no-test +// Old syntax +property count: 0; + +// New syntax +in-out property count: 0; +``` + +**Struct declaration:** +```slint,no-test +// Old syntax +MyStruct := { field: int } +// New syntax +struct MyStruct { field: int } ``` -slint-updater -i /path/to/my/app/ui/**/*.slint + +## Examples + +Update all `.slint` files in a project: +```bash +slint-updater -i $(find . -name "*.slint") +``` + +Preview changes before applying: +```bash +slint-updater file.slint | diff file.slint - ``` +Update Slint code embedded in Rust: +```bash +slint-updater -i src/**/*.rs +``` diff --git a/tools/viewer/main.rs b/tools/viewer/main.rs index 29bd1c319b1..cd9a36ac068 100644 --- a/tools/viewer/main.rs +++ b/tools/viewer/main.rs @@ -135,6 +135,8 @@ fn main() -> Result<()> { if r.has_errors() { std::process::exit(-1); } + // If --component is used, r.compents contains only one element (filtered out in init_compiler()) + // If no component name is specified, the last defined component is shown let Some(c) = r.components().next() else { match args.component { Some(name) => { @@ -159,6 +161,7 @@ fn main() -> Result<()> { CURRENT_INSTANCE.with(|current| current.replace(Some(component.clone_strong()))); } + // Show the preview and running the event loop. Closing the window will make it continue component.run()?; if let Some(data_path) = args.save_data { @@ -237,7 +240,7 @@ fn watch_with_retry(path: &Path, watcher: &Arc notify::ErrorKind::PathNotFound | notify::ErrorKind::Generic(_) => { let path = path.to_path_buf(); let watcher = watcher.clone(); - static RETRY_DURATION: u64 = 100; + const RETRY_DURATION: u64 = 100; i_slint_core::timers::Timer::single_shot( std::time::Duration::from_millis(RETRY_DURATION), move || { @@ -260,6 +263,9 @@ fn watch_with_retry(path: &Path, watcher: &Arc }); } +/// Init dialog if `instance` is a Dialog +/// - Initializing the callbacks for `ok`, `yes`, `close`, `cancel` or `no` to quit the event loop +/// When one of those callbacks gets triggered the preview gets closed as well fn init_dialog(instance: &ComponentInstance) { for cb in instance.definition().callbacks() { let exit_code = match cb.as_str() { diff --git a/ui-libraries/material/docs/README.md b/ui-libraries/material/docs/README.md index 790d7da6d5b..8e232ede61f 100644 --- a/ui-libraries/material/docs/README.md +++ b/ui-libraries/material/docs/README.md @@ -44,13 +44,13 @@ The docs use a lot of autogenerated content. First create all the screenshots wh Generate the screenshots: ```bash -cd ui-libraries/material -cargo run -p slint-docsnapper -- -Lmaterial=$PWD/src/material.slint docs +cargo run -p slint-docsnapper -- -Lmaterial=$PWD/ui-libraries/material/src/material.slint ui-libraries/material/docs ``` Build the docs: ```bash +cd ui-libraries/material pnpm i pnpm run build ``` diff --git a/ui-libraries/material/docs/package.json b/ui-libraries/material/docs/package.json index fa271455946..7273a62b2e8 100644 --- a/ui-libraries/material/docs/package.json +++ b/ui-libraries/material/docs/package.json @@ -46,7 +46,7 @@ "@astrojs/partytown": "2.1.4", "@astrojs/tailwind": "6.0.2", "@iconify-json/flat-color-icons": "1.2.3", - "@iconify-json/tabler": "1.2.25", + "@iconify-json/tabler": "1.2.26", "@tailwindcss/typography": "0.5.19", "@types/js-yaml": "4.0.9", "@types/lodash.merge": "4.6.9", diff --git a/ui-libraries/material/docs/src/components/CodeSnippetMD.astro b/ui-libraries/material/docs/src/components/CodeSnippetMD.astro deleted file mode 100644 index da68f5b82aa..00000000000 --- a/ui-libraries/material/docs/src/components/CodeSnippetMD.astro +++ /dev/null @@ -1,54 +0,0 @@ ---- -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT -import "@astrojs/starlight/style/markdown.css"; -import type { ImageMetadata } from "astro"; -import { Image } from "astro:assets"; - -interface Props { - imagePath: string; - imageAlt: string; - imageWidth: number; - imageHeight: number; - needsBackground?: boolean; - skip?: boolean; - scale?: number; -} -const { - imagePath, - imageAlt, - skip, - needsBackground, - imageWidth: _imageWidth, - imageHeight: _imageHeight, -} = Astro.props as Props; -const images = import.meta.glob<{ default: ImageMetadata }>( - "/src/assets/generated/*.{jpeg,jpg,png,gif}", -); - -let imageMeta: ImageMetadata | undefined = undefined; -if (imagePath.length > 0) { - if (!images[imagePath]) { - throw new Error( - `"${imagePath}" does not exist in glob: "src/assets/generated/*.{jpeg,jpg,png,gif}"`, - ); - } - imageMeta = (await images[imagePath]()).default; -} -const imageCSS = `image-block ${needsBackground ? "screenshot-container" : ""}`; ---- - -{!skip &&
-
- -
- - {imageMeta &&
- {imageAlt} -
} -
} diff --git a/ui-libraries/material/docs/src/components/Link.astro b/ui-libraries/material/docs/src/components/Link.astro deleted file mode 100644 index 54167a75a00..00000000000 --- a/ui-libraries/material/docs/src/components/Link.astro +++ /dev/null @@ -1,29 +0,0 @@ ---- -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -import { linkMap } from "../utils/utils"; - -type LinkType = keyof typeof linkMap; - -interface Props { - label?: string; - type: LinkType; -} - -const { label, type } = Astro.props as Props; -const displayLabel = label || type; - -if (!(type in linkMap)) { - throw new Error( - `Invalid link type: ${type}. Maybe you forgot to add it to the linkMap?`, - ); -} - -// The base is set in astro.config.mjs -const base = import.meta.env.BASE_URL; -const fullHref = `${base}${linkMap[type].href}`; ---- - -
{displayLabel} - diff --git a/ui-libraries/material/docs/src/components/SlintProperty.astro b/ui-libraries/material/docs/src/components/SlintProperty.astro deleted file mode 100644 index f64f3b36763..00000000000 --- a/ui-libraries/material/docs/src/components/SlintProperty.astro +++ /dev/null @@ -1,93 +0,0 @@ ---- -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT -import "@astrojs/starlight/style/markdown.css"; -import { - type KnownType, - type PropertyVisibility, - getEnumContent, - getStructContent, - getTypeInfo, -} from "../utils/utils.ts"; - -import Type from "./Type.astro"; - -interface Props { - propName: string; - typeName: KnownType; - defaultValue?: string; - enumName?: string; - structName?: string; - propertyVisibility?: PropertyVisibility; -} -const { - propName, - typeName, - defaultValue, - enumName, - structName, - propertyVisibility, -} = Astro.props as Props; - -if (propName === "") { - console.error("No propName!!"); -} - -let fullTypeName: string = typeName; - -if (typeName === "enum") { - if (enumName === undefined || enumName === "") { - console.error("enum type without an enumName:", propName); - } else { - fullTypeName = typeName.toString() + " " + enumName!.toString(); - } -} else if (typeName === "struct") { - if (structName === undefined || structName === "") { - console.error("struct type without a structName:", propName); - } else { - fullTypeName = typeName.toString() + " " + structName!.toString(); - } -} else if (typeName === "[struct]") { - if (structName === undefined || structName === "") { - console.error("array of struct type without a structName:", propName); - } else { - fullTypeName = `[${typeName.toString().replace("[", "").replace("]", "")} ${structName.toString()}]`; - } -} else { - if (enumName && enumName !== "") { - console.error("Non-enum type with an enumName set:", propName); - } -} - -const typeInfo = getTypeInfo(typeName); -if (typeInfo.href !== "") { - const base = import.meta.env.BASE_URL; - typeInfo.href = `${base}${typeInfo.href}`; -} -const enumContent = await getEnumContent(enumName); -const structContent = await getStructContent(structName); - -const defaultValue_ = defaultValue ? defaultValue : typeInfo.defaultValue; - -if (!defaultValue_) { - console.error("No defaultValue for:", propName); -} ---- - - -
-

- {typeInfo.href === "" ? ( - - ) : ( - - - - )} - {propertyVisibility && {`(${propertyVisibility})`}} - default: {defaultValue_}
- {enumName && } - {structContent && } -

- -
diff --git a/ui-libraries/material/docs/src/content/docs/components/AppBars/app_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/AppBars/app_bar.mdx index 07e2eff4fe9..8efc6dcfe8c 100644 --- a/ui-libraries/material/docs/src/content/docs/components/AppBars/app_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/AppBars/app_bar.mdx @@ -3,8 +3,8 @@ title: AppBar description: AppBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/AppBars/bottom_app_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/AppBars/bottom_app_bar.mdx index dea7e0f8a19..7234373fbda 100644 --- a/ui-libraries/material/docs/src/content/docs/components/AppBars/bottom_app_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/AppBars/bottom_app_bar.mdx @@ -3,8 +3,8 @@ title: BottomAppBar description: BottomAppBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/AppBars/large_app_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/AppBars/large_app_bar.mdx index 3fe0e6e54c6..cd1fe148212 100644 --- a/ui-libraries/material/docs/src/content/docs/components/AppBars/large_app_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/AppBars/large_app_bar.mdx @@ -3,8 +3,8 @@ title: LargeAppBar description: LargeAppBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/AppBars/medium_app_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/AppBars/medium_app_bar.mdx index 74eacc78f7c..a118b44f222 100644 --- a/ui-libraries/material/docs/src/content/docs/components/AppBars/medium_app_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/AppBars/medium_app_bar.mdx @@ -3,8 +3,8 @@ title: MediumAppBar description: MediumAppBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/AppBars/navigation_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/AppBars/navigation_bar.mdx index bf4ee01a757..017616bebb6 100644 --- a/ui-libraries/material/docs/src/content/docs/components/AppBars/navigation_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/AppBars/navigation_bar.mdx @@ -3,8 +3,8 @@ title: NavigationBar description: NavigationBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/AppBars/search_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/AppBars/search_bar.mdx index fe348aed195..ccfbb194b3f 100644 --- a/ui-libraries/material/docs/src/content/docs/components/AppBars/search_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/AppBars/search_bar.mdx @@ -3,8 +3,8 @@ title: SearchBar description: SearchBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/AppBars/small_app_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/AppBars/small_app_bar.mdx index df70fa58ba4..d3f386a2987 100644 --- a/ui-libraries/material/docs/src/content/docs/components/AppBars/small_app_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/AppBars/small_app_bar.mdx @@ -3,8 +3,8 @@ title: SmallAppBar description: SmallAppBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/AppBars/tab_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/AppBars/tab_bar.mdx index 9438cf0588e..4ae7be52e6f 100644 --- a/ui-libraries/material/docs/src/content/docs/components/AppBars/tab_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/AppBars/tab_bar.mdx @@ -3,8 +3,8 @@ title: TabBar description: TabBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Badges/badge.mdx b/ui-libraries/material/docs/src/content/docs/components/Badges/badge.mdx index 71f732a91fe..0e25de3b8bd 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Badges/badge.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Badges/badge.mdx @@ -3,8 +3,8 @@ title: Badge description: Badge API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/elevated_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/elevated_button.mdx index 275e922b39c..4d85ab03c8e 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/elevated_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/elevated_button.mdx @@ -3,8 +3,8 @@ title: ElevatedButton description: ElevatedButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/filled_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/filled_button.mdx index c144660ed6e..34700391db6 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/filled_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/filled_button.mdx @@ -3,8 +3,8 @@ title: FilledButton description: FilledButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/filled_icon_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/filled_icon_button.mdx index 7e9e46feefc..98c08a47d6d 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/filled_icon_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/filled_icon_button.mdx @@ -3,8 +3,8 @@ title: FilledIconButton description: FilledIconButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/floating_action_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/floating_action_button.mdx index 33341e3f10d..af882523974 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/floating_action_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/floating_action_button.mdx @@ -3,8 +3,8 @@ title: FloatingActionButton (FAB) description: FloatingActionButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/icon_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/icon_button.mdx index da70839ec53..e815ce44ecd 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/icon_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/icon_button.mdx @@ -3,8 +3,8 @@ title: IconButton description: IconButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/outline_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/outline_button.mdx index 04e999a3130..14b3215d217 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/outline_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/outline_button.mdx @@ -3,8 +3,8 @@ title: OutlineButton description: OutlineButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/outline_icon_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/outline_icon_button.mdx index 5f40ebd79ec..660bae14def 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/outline_icon_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/outline_icon_button.mdx @@ -3,8 +3,8 @@ title: OutlineIconButton description: OutlineIconButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/segmented_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/segmented_button.mdx index e12adf4ebbc..647c9de1719 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/segmented_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/segmented_button.mdx @@ -3,8 +3,8 @@ title: SegmentedButton description: SegmentedButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/text_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/text_button.mdx index 392901e793e..572e6ca1e97 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/text_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/text_button.mdx @@ -3,8 +3,8 @@ title: TextButton description: TextButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/tonal_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/tonal_button.mdx index 747399b9e49..c99ba3f7343 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/tonal_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/tonal_button.mdx @@ -3,8 +3,8 @@ title: TonalButton description: TonalButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Buttons/tonal_icon_button.mdx b/ui-libraries/material/docs/src/content/docs/components/Buttons/tonal_icon_button.mdx index 12eea2b76da..1b4bf2a5619 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Buttons/tonal_icon_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Buttons/tonal_icon_button.mdx @@ -3,8 +3,8 @@ title: TonalIconButton description: TonalIconButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Cards/elevated_card.mdx b/ui-libraries/material/docs/src/content/docs/components/Cards/elevated_card.mdx index 7e8776c57d2..eea8eef7be5 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Cards/elevated_card.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Cards/elevated_card.mdx @@ -3,8 +3,8 @@ title: ElevatedCard description: ElevatedCard API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Cards/filled_card.mdx b/ui-libraries/material/docs/src/content/docs/components/Cards/filled_card.mdx index f6226e059f1..370887caf04 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Cards/filled_card.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Cards/filled_card.mdx @@ -3,8 +3,8 @@ title: FilledCard description: FilledCard API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Cards/outlined_card.mdx b/ui-libraries/material/docs/src/content/docs/components/Cards/outlined_card.mdx index f48e815c97e..55525f2f18b 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Cards/outlined_card.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Cards/outlined_card.mdx @@ -3,8 +3,8 @@ title: OutlinedCard description: OutlinedCard API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Checkboxes/check_box.mdx b/ui-libraries/material/docs/src/content/docs/components/Checkboxes/check_box.mdx index 5a0035c0f25..7f75876faa6 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Checkboxes/check_box.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Checkboxes/check_box.mdx @@ -3,8 +3,8 @@ title: CheckBox description: CheckBox API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Checkboxes/check_box_tile.mdx b/ui-libraries/material/docs/src/content/docs/components/Checkboxes/check_box_tile.mdx index 549fc87b385..9446f7b6f89 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Checkboxes/check_box_tile.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Checkboxes/check_box_tile.mdx @@ -3,8 +3,8 @@ title: CheckBoxTile description: CheckBoxTile API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Chips/action_chip.mdx b/ui-libraries/material/docs/src/content/docs/components/Chips/action_chip.mdx index e0a3655867a..a738195d97f 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Chips/action_chip.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Chips/action_chip.mdx @@ -3,8 +3,8 @@ title: ActionChip description: ActionChip API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Chips/filter_chip.mdx b/ui-libraries/material/docs/src/content/docs/components/Chips/filter_chip.mdx index ed44d944f5f..b48f783f378 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Chips/filter_chip.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Chips/filter_chip.mdx @@ -3,8 +3,8 @@ title: FilterChip description: FilterChip API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Chips/input_chip.mdx b/ui-libraries/material/docs/src/content/docs/components/Chips/input_chip.mdx index 2b0690c263b..687a548cbd0 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Chips/input_chip.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Chips/input_chip.mdx @@ -3,8 +3,8 @@ title: InputChip description: InputChip API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Dialogs/dialog.mdx b/ui-libraries/material/docs/src/content/docs/components/Dialogs/dialog.mdx index dba88ead67c..9f8bcc9c3ab 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Dialogs/dialog.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Dialogs/dialog.mdx @@ -3,8 +3,8 @@ title: Dialog description: Dialog API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Dialogs/fullscreen_dialog.mdx b/ui-libraries/material/docs/src/content/docs/components/Dialogs/fullscreen_dialog.mdx index 24e54af4279..605945c138c 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Dialogs/fullscreen_dialog.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Dialogs/fullscreen_dialog.mdx @@ -3,8 +3,8 @@ title: FullscreenDialog description: FullscreenDialog API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; A `FullscreenDialog` is a modal dialog that occupies the entire screen, typically used for complex forms or detailed content that requires more space than a standard dialog. It includes a header with a close button, title, and action buttons. diff --git a/ui-libraries/material/docs/src/content/docs/components/Layouts/grid.mdx b/ui-libraries/material/docs/src/content/docs/components/Layouts/grid.mdx index 6c88a8788b4..f323822c3d0 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Layouts/grid.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Layouts/grid.mdx @@ -3,8 +3,8 @@ title: Grid description: Grid API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; The `Grid` layout is a grid layout that arranges its children in a grid. diff --git a/ui-libraries/material/docs/src/content/docs/components/Layouts/horizontal.mdx b/ui-libraries/material/docs/src/content/docs/components/Layouts/horizontal.mdx index 0a319f3766c..e9d87425631 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Layouts/horizontal.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Layouts/horizontal.mdx @@ -3,8 +3,8 @@ title: Horizontal description: Horizontal API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; The `Horizontal` layout is a horizontal layout that arranges its children horizontally. diff --git a/ui-libraries/material/docs/src/content/docs/components/Layouts/vertical.mdx b/ui-libraries/material/docs/src/content/docs/components/Layouts/vertical.mdx index c0733d13896..445a8a406eb 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Layouts/vertical.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Layouts/vertical.mdx @@ -3,8 +3,8 @@ title: Vertical description: Vertical API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; The `Vertical` layout is a vertical layout that arranges its children vertically. diff --git a/ui-libraries/material/docs/src/content/docs/components/Navigation/modal_navigation_drawer.mdx b/ui-libraries/material/docs/src/content/docs/components/Navigation/modal_navigation_drawer.mdx index 8e0318bdf7a..7bf02f95212 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Navigation/modal_navigation_drawer.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Navigation/modal_navigation_drawer.mdx @@ -3,8 +3,8 @@ title: ModalNavigationDrawer description: ModalNavigationDrawer API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Navigation/navigation_drawer.mdx b/ui-libraries/material/docs/src/content/docs/components/Navigation/navigation_drawer.mdx index 9a35cbb9374..b38120d611c 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Navigation/navigation_drawer.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Navigation/navigation_drawer.mdx @@ -3,8 +3,8 @@ title: NavigationDrawer description: NavigationDrawer API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Navigation/navigation_rail.mdx b/ui-libraries/material/docs/src/content/docs/components/Navigation/navigation_rail.mdx index 306747d032b..bc1307ebf31 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Navigation/navigation_rail.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Navigation/navigation_rail.mdx @@ -3,8 +3,8 @@ title: NavigationRail description: NavigationRail API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Progress/circular_progress_indicator.mdx b/ui-libraries/material/docs/src/content/docs/components/Progress/circular_progress_indicator.mdx index ecac2adbd3f..46bdc7b7f67 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Progress/circular_progress_indicator.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Progress/circular_progress_indicator.mdx @@ -3,8 +3,8 @@ title: CircularProgressIndicator description: CircularProgressIndicator API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Progress/linear_progress_indicator.mdx b/ui-libraries/material/docs/src/content/docs/components/Progress/linear_progress_indicator.mdx index 35db8313493..30893c2b809 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Progress/linear_progress_indicator.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Progress/linear_progress_indicator.mdx @@ -3,8 +3,8 @@ title: LinearProgressIndicator description: LinearProgressIndicator API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/RadioButton/radio_button.mdx b/ui-libraries/material/docs/src/content/docs/components/RadioButton/radio_button.mdx index fe4c9fb7c31..5c5664db2fd 100644 --- a/ui-libraries/material/docs/src/content/docs/components/RadioButton/radio_button.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/RadioButton/radio_button.mdx @@ -3,8 +3,8 @@ title: RadioButton description: RadioButton API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/RadioButton/radio_button_tile.mdx b/ui-libraries/material/docs/src/content/docs/components/RadioButton/radio_button_tile.mdx index 2f6b00ba466..c2c2ab5062d 100644 --- a/ui-libraries/material/docs/src/content/docs/components/RadioButton/radio_button_tile.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/RadioButton/radio_button_tile.mdx @@ -3,8 +3,8 @@ title: RadioButtonTile description: RadioButtonTile API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/Sheets/modal_bottom_sheet.mdx b/ui-libraries/material/docs/src/content/docs/components/Sheets/modal_bottom_sheet.mdx index 6c73fb3aac5..a54f7f78a01 100644 --- a/ui-libraries/material/docs/src/content/docs/components/Sheets/modal_bottom_sheet.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/Sheets/modal_bottom_sheet.mdx @@ -3,8 +3,8 @@ title: ModalBottomSheet description: ModalBottomSheet API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/avatar.mdx b/ui-libraries/material/docs/src/content/docs/components/avatar.mdx index d797b9c46df..4e3b8c174df 100644 --- a/ui-libraries/material/docs/src/content/docs/components/avatar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/avatar.mdx @@ -3,8 +3,8 @@ title: Avatar description: Avatar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/date_picker.mdx b/ui-libraries/material/docs/src/content/docs/components/date_picker.mdx index df8148d421d..e4ae198df3c 100644 --- a/ui-libraries/material/docs/src/content/docs/components/date_picker.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/date_picker.mdx @@ -3,8 +3,8 @@ title: DatePickerPopup description: DatePickerPopup api. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; Use a date picker to let the user select a date. diff --git a/ui-libraries/material/docs/src/content/docs/components/divider.mdx b/ui-libraries/material/docs/src/content/docs/components/divider.mdx index 2a43ea0714e..c87d8c69ecb 100644 --- a/ui-libraries/material/docs/src/content/docs/components/divider.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/divider.mdx @@ -3,8 +3,8 @@ title: Divider description: Divider API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/drop_down_menu.mdx b/ui-libraries/material/docs/src/content/docs/components/drop_down_menu.mdx index 58ba9d7d80e..624f5337141 100644 --- a/ui-libraries/material/docs/src/content/docs/components/drop_down_menu.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/drop_down_menu.mdx @@ -3,8 +3,8 @@ title: DropDownMenu description: DropDownMenu API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/list_tile.mdx b/ui-libraries/material/docs/src/content/docs/components/list_tile.mdx index e71f40c3dc7..a404d6280e0 100644 --- a/ui-libraries/material/docs/src/content/docs/components/list_tile.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/list_tile.mdx @@ -3,8 +3,8 @@ title: ListTile description: ListTile API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/material_window.mdx b/ui-libraries/material/docs/src/content/docs/components/material_window.mdx index 2071e37ee6e..21a63f645ec 100644 --- a/ui-libraries/material/docs/src/content/docs/components/material_window.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/material_window.mdx @@ -3,8 +3,8 @@ title: MaterialWindow description: MaterialWindow API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/modal.mdx b/ui-libraries/material/docs/src/content/docs/components/modal.mdx index 6b20b43b1f9..afa01859a29 100644 --- a/ui-libraries/material/docs/src/content/docs/components/modal.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/modal.mdx @@ -3,8 +3,8 @@ title: Modal description: Modal API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/modal_drawer.mdx b/ui-libraries/material/docs/src/content/docs/components/modal_drawer.mdx index 83aea12b50e..2c3c0a49ce8 100644 --- a/ui-libraries/material/docs/src/content/docs/components/modal_drawer.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/modal_drawer.mdx @@ -3,8 +3,8 @@ title: ModalDrawer description: ModalDrawer API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/popup_menu.mdx b/ui-libraries/material/docs/src/content/docs/components/popup_menu.mdx index c220c749686..fa348ec80d7 100644 --- a/ui-libraries/material/docs/src/content/docs/components/popup_menu.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/popup_menu.mdx @@ -3,8 +3,8 @@ title: PopupMenu description: PopupMenu API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/scroll_view.mdx b/ui-libraries/material/docs/src/content/docs/components/scroll_view.mdx index 1046a63d9b0..7fda4aa5be6 100644 --- a/ui-libraries/material/docs/src/content/docs/components/scroll_view.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/scroll_view.mdx @@ -3,8 +3,8 @@ title: ScrollView description: ScrollView API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/slider.mdx b/ui-libraries/material/docs/src/content/docs/components/slider.mdx index bdbab63b5b9..988126a27b5 100644 --- a/ui-libraries/material/docs/src/content/docs/components/slider.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/slider.mdx @@ -3,8 +3,8 @@ title: Slider description: Slider API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/snack_bar.mdx b/ui-libraries/material/docs/src/content/docs/components/snack_bar.mdx index 9f30cb1338a..5ae9a8813ad 100644 --- a/ui-libraries/material/docs/src/content/docs/components/snack_bar.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/snack_bar.mdx @@ -3,8 +3,8 @@ title: SnackBar description: SnackBar API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/switch.mdx b/ui-libraries/material/docs/src/content/docs/components/switch.mdx index ffbd6639062..a0ac871a744 100644 --- a/ui-libraries/material/docs/src/content/docs/components/switch.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/switch.mdx @@ -3,8 +3,8 @@ title: Switch description: Switch API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/text_field.mdx b/ui-libraries/material/docs/src/content/docs/components/text_field.mdx index 6208c1fab56..2fed00cec05 100644 --- a/ui-libraries/material/docs/src/content/docs/components/text_field.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/text_field.mdx @@ -3,9 +3,9 @@ title: TextField description: TextField API. --- -import SlintProperty from '/src/components/SlintProperty.astro'; -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import Link from '/src/components/Link.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import Link from '@slint/common-files/src/components/Link.astro'; diff --git a/ui-libraries/material/docs/src/content/docs/components/time_picker.mdx b/ui-libraries/material/docs/src/content/docs/components/time_picker.mdx index cfbbfe02f2f..67585619b36 100644 --- a/ui-libraries/material/docs/src/content/docs/components/time_picker.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/time_picker.mdx @@ -3,8 +3,8 @@ title: TimePickerPopup description: TimePickerPopup API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/components/tooltip.mdx b/ui-libraries/material/docs/src/content/docs/components/tooltip.mdx index 8e996cf0786..934f410402e 100644 --- a/ui-libraries/material/docs/src/content/docs/components/tooltip.mdx +++ b/ui-libraries/material/docs/src/content/docs/components/tooltip.mdx @@ -3,8 +3,8 @@ title: ToolTip description: ToolTip API. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; ```slint diff --git a/ui-libraries/material/docs/src/content/docs/getting-started.mdx b/ui-libraries/material/docs/src/content/docs/getting-started.mdx index 4e5562a3b3e..1837596e770 100644 --- a/ui-libraries/material/docs/src/content/docs/getting-started.mdx +++ b/ui-libraries/material/docs/src/content/docs/getting-started.mdx @@ -3,9 +3,9 @@ title: Getting Started description: Getting Started with Slint Material UI. --- -import CodeSnippetMD from '/src/components/CodeSnippetMD.astro'; -import SlintProperty from '/src/components/SlintProperty.astro'; -import Link from '/src/components/Link.astro'; +import CodeSnippetMD from '@slint/common-files/src/components/CodeSnippetMD.astro'; +import SlintProperty from '@slint/common-files/src/components/SlintProperty.astro'; +import Link from '@slint/common-files/src/components/Link.astro'; import { Steps, Tabs, TabItem } from '@astrojs/starlight/components'; ![Slint Material UI](~/assets/images/material-tablet.webp) diff --git a/ui-libraries/material/docs/src/utils/permalinks.ts b/ui-libraries/material/docs/src/utils/permalinks.ts index e3ae64719d2..66b09976ca9 100644 --- a/ui-libraries/material/docs/src/utils/permalinks.ts +++ b/ui-libraries/material/docs/src/utils/permalinks.ts @@ -4,7 +4,7 @@ import slugify from "limax"; import { SITE, APP_BLOG } from "astrowind:config"; -import { trim } from "~/utils/utils"; +import { trim } from "@slint/common-files/src/utils/utils"; export const trimSlash = (s: string) => trim(trim(s, "/")); const createPath = (...params: string[]) => { diff --git a/ui-libraries/material/docs/src/utils/utils.ts b/ui-libraries/material/docs/src/utils/utils.ts deleted file mode 100644 index 77a131702ba..00000000000 --- a/ui-libraries/material/docs/src/utils/utils.ts +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright © SixtyFPS GmbH -// SPDX-License-Identifier: MIT - -export async function getEnumContent(enumName: string | undefined) { - if (enumName) { - try { - const module = await import( - `../content/collections/enums/${enumName}.md` - ); - return module.compiledContent(); - } catch (error) { - console.error(`Failed to load enum file for ${enumName}:`, error); - return ""; - } - } - return ""; -} - -export async function getStructContent( - structName: string | undefined, -): Promise { - if (structName === undefined) { - return ""; - } - const baseStruct = structName.replace(/[\[\]]/g, ""); - - if (baseStruct === "Time" || baseStruct === "Date") { - try { - const module = await import( - `../content/collections/std-widgets/${baseStruct}.md` - ); - return module.compiledContent(); - } catch (error) { - console.error(`Failed to load enum file for ${baseStruct}:`, error); - return ""; - } - } - - if (baseStruct) { - try { - const module = await import( - `../content/collections/structs/${baseStruct}.md` - ); - return module.compiledContent(); - } catch (error) { - console.error( - `Failed to load struct file for ${baseStruct}:`, - error, - ); - return ""; - } - } - return ""; -} - -export type KnownType = - | "angle" - | "bool" - | "brush" - | "color" - | "duration" - | "easing" - | "enum" - | "float" - | "image" - | "int" - | "length" - | "percent" - | "physical-length" - | "Point" - | "relative-font-size" - | "string" - | "struct" - | "[struct]" - | "IconButtonItem" - | "FABStyle" - | "SegmentedItem" - | "Time" - | "CheckState"; - -export type PropertyVisibility = "private" | "in" | "out" | "in-out"; - -export interface TypeInfo { - href: string; - defaultValue: string; -} - -export function getTypeInfo(typeName: KnownType): TypeInfo { - const baseType = typeName.replace(/[\[\]]/g, "") as KnownType; - switch (baseType) { - case "angle": - return { - href: linkMap.angle.href, - defaultValue: "0deg", - }; - case "bool": - return { - href: linkMap.bool.href, - defaultValue: "false", - }; - case "brush": - return { - href: linkMap.brush.href, - defaultValue: "a transparent brush", - }; - case "color": - return { - href: linkMap.color.href, - defaultValue: "a transparent color", - }; - case "duration": - return { - href: linkMap.duration.href, - defaultValue: "0ms", - }; - case "easing": - return { - href: linkMap.easing.href, - defaultValue: "linear", - }; - case "enum": - return { - href: linkMap.EnumType.href, - defaultValue: "the first enum value", - }; - case "float": - return { - href: linkMap.float.href, - defaultValue: "0.0", - }; - case "image": - return { - href: linkMap.ImageType.href, - defaultValue: "the empty image", - }; - case "int": - return { - href: linkMap.int.href, - defaultValue: "0", - }; - case "length": - return { - href: linkMap.length.href, - defaultValue: "0px", - }; - case "percent": - return { - href: linkMap.percent.href, - defaultValue: "0%", - }; - case "physical-length": - return { - href: linkMap.physicalLength.href, - defaultValue: "0phx", - }; - case "Point": - return { - href: linkMap.StructType.href, - defaultValue: "(0px, 0px)", - }; - case "relative-font-size": - return { - href: linkMap.relativeFontSize.href, - defaultValue: "0rem", - }; - case "string": - return { - href: linkMap.StringType.href, - defaultValue: '""', - }; - case "struct": - return { - href: linkMap.StructType.href, - defaultValue: "a struct with all default values", - }; - case "IconButtonItem": - return { - href: linkMap.StructType.href, - defaultValue: "a IconButtonItem with default values", - }; - case "FABStyle": - return { - href: linkMap.FABStyle.href, - defaultValue: "FABStyle.standard", - }; - case "SegmentedItem": - return { - href: linkMap.StructType.href, - defaultValue: "a SegmentedItem with default values", - }; - case "Time": - return { - href: linkMap.Time.href, - defaultValue: "Time { hour: 0, minute: 0, second: 0 }", - }; - case "CheckState": - return { - href: linkMap.CheckState.href, - defaultValue: "CheckState.unchecked", - }; - default: { - console.error("Unknown type: ", typeName); - return { - href: "", - defaultValue: "", - }; - } - } -} - -export function extractLines( - fileContent: string, - start: number, - end: number, -): string { - return fileContent - .split("\n") - .slice(start - 1, end) - .join("\n"); -} - -export function removeLeadingSpaces(input: string, spaces = 4): string { - const lines = input.split("\n"); - const modifiedLines = lines.map((line) => { - const leadingSpaces = line.match(/^ */)?.[0].length ?? 0; - if (leadingSpaces >= spaces) { - return line.slice(spaces); - } - return line; - }); - return modifiedLines.join("\n"); -} - -type LinkMapType = { - [K: string]: { - href: string; - }; -}; - -import linkMapData from "./link-data.json" with { type: "json" }; -export const linkMap: Readonly = linkMapData; - -import { I18N } from "astrowind:config"; - -export const formatter: Intl.DateTimeFormat = new Intl.DateTimeFormat( - I18N?.language, - { - year: "numeric", - month: "short", - day: "numeric", - timeZone: "UTC", - }, -); - -export const getFormattedDate = (date: Date): string => - date ? formatter.format(date) : ""; - -export const trim = (str = "", ch?: string) => { - let start = 0, - end = str.length || 0; - while (start < end && str[start] === ch) { - ++start; - } - while (end > start && str[end - 1] === ch) { - --end; - } - return start > 0 || end < str.length ? str.substring(start, end) : str; -}; - -// Function to format a number in thousands (K) or millions (M) format depending on its value -export const toUiAmount = (amount: number) => { - if (!amount) { - return 0; - } - - let value: string; - - if (amount >= 1000000000) { - const formattedNumber = (amount / 1000000000).toFixed(1); - if (Number(formattedNumber) === parseInt(formattedNumber)) { - value = parseInt(formattedNumber) + "B"; - } else { - value = formattedNumber + "B"; - } - } else if (amount >= 1000000) { - const formattedNumber = (amount / 1000000).toFixed(1); - if (Number(formattedNumber) === parseInt(formattedNumber)) { - value = parseInt(formattedNumber) + "M"; - } else { - value = formattedNumber + "M"; - } - } else if (amount >= 1000) { - const formattedNumber = (amount / 1000).toFixed(1); - if (Number(formattedNumber) === parseInt(formattedNumber)) { - value = parseInt(formattedNumber) + "K"; - } else { - value = formattedNumber + "K"; - } - } else { - value = Number(amount).toFixed(0); - } - - return value; -};