├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── build.yml.disabled │ └── test.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── COPYRIGHT ├── Cargo.lock.msrv ├── Cargo.toml ├── LICENSE ├── README.md ├── ROADMAP.md ├── clippy.toml ├── config-examples └── theme.yaml ├── crates ├── kas-core │ ├── COPYRIGHT │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── build.rs │ └── src │ │ ├── action.rs │ │ ├── config │ │ ├── config.rs │ │ ├── event.rs │ │ ├── factory.rs │ │ ├── font.rs │ │ ├── format.rs │ │ ├── mod.rs │ │ ├── shortcuts.rs │ │ └── theme.rs │ │ ├── core │ │ ├── collection.rs │ │ ├── data.rs │ │ ├── impls.rs │ │ ├── layout.rs │ │ ├── mod.rs │ │ ├── node.rs │ │ ├── scroll_traits.rs │ │ ├── tile.rs │ │ ├── widget.rs │ │ └── widget_id.rs │ │ ├── decorations.rs │ │ ├── dir.rs │ │ ├── draw │ │ ├── color.rs │ │ ├── draw.rs │ │ ├── draw_rounded.rs │ │ ├── draw_shared.rs │ │ └── mod.rs │ │ ├── event │ │ ├── components.rs │ │ ├── cx │ │ │ ├── config.rs │ │ │ ├── cx_pub.rs │ │ │ ├── mod.rs │ │ │ ├── platform.rs │ │ │ ├── press.rs │ │ │ └── press │ │ │ │ ├── mouse.rs │ │ │ │ ├── touch.rs │ │ │ │ └── velocity.rs │ │ ├── enums.rs │ │ ├── events.rs │ │ ├── mod.rs │ │ └── response.rs │ │ ├── geom.rs │ │ ├── geom │ │ └── vector.rs │ │ ├── hidden.rs │ │ ├── hidden │ │ └── with_any.rs │ │ ├── layout │ │ ├── align.rs │ │ ├── grid_solver.rs │ │ ├── mod.rs │ │ ├── row_solver.rs │ │ ├── single_solver.rs │ │ ├── size_rules.rs │ │ ├── size_types.rs │ │ ├── sizer.rs │ │ └── storage.rs │ │ ├── lib.rs │ │ ├── messages.rs │ │ ├── popup.rs │ │ ├── prelude.rs │ │ ├── root.rs │ │ ├── runner │ │ ├── common.rs │ │ ├── event_loop.rs │ │ ├── mod.rs │ │ ├── runner.rs │ │ ├── shared.rs │ │ └── window.rs │ │ ├── text │ │ ├── mod.rs │ │ ├── selection.rs │ │ └── string.rs │ │ ├── theme │ │ ├── anim.rs │ │ ├── colors.rs │ │ ├── dimensions.rs │ │ ├── draw.rs │ │ ├── flat_theme.rs │ │ ├── mod.rs │ │ ├── multi.rs │ │ ├── simple_theme.rs │ │ ├── size.rs │ │ ├── style.rs │ │ ├── text.rs │ │ ├── theme_dst.rs │ │ └── traits.rs │ │ └── util.rs ├── kas-dylib │ ├── COPYRIGHT │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ └── lib.rs ├── kas-macros │ ├── COPYRIGHT │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── collection.rs │ │ ├── extends.rs │ │ ├── lib.rs │ │ ├── make_layout.rs │ │ ├── scroll_traits.rs │ │ ├── visitors.rs │ │ ├── widget.rs │ │ ├── widget_args.rs │ │ └── widget_derive.rs ├── kas-resvg │ ├── COPYRIGHT │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── canvas.rs │ │ ├── lib.rs │ │ └── svg.rs ├── kas-view │ ├── COPYRIGHT │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ │ ├── data_traits.rs │ │ ├── driver.rs │ │ ├── filter │ │ ├── filter_list.rs │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── list_view.rs │ │ └── matrix_view.rs ├── kas-wgpu │ ├── COPYRIGHT │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── build.rs │ └── src │ │ ├── draw │ │ ├── atlases.rs │ │ ├── common.rs │ │ ├── custom.rs │ │ ├── draw_pipe.rs │ │ ├── flat_round.rs │ │ ├── images.rs │ │ ├── mod.rs │ │ ├── round_2col.rs │ │ ├── shaded_round.rs │ │ ├── shaded_square.rs │ │ ├── shaders.rs │ │ ├── shaders │ │ │ ├── flat_round.frag │ │ │ ├── flat_round.frag.spv │ │ │ ├── flat_round.vert │ │ │ ├── flat_round.vert.spv │ │ │ ├── glyph.frag │ │ │ ├── glyph.frag.spv │ │ │ ├── glyph.vert │ │ │ ├── glyph.vert.spv │ │ │ ├── image.frag │ │ │ ├── image.frag.spv │ │ │ ├── image.vert │ │ │ ├── image.vert.spv │ │ │ ├── round_2col.frag │ │ │ ├── round_2col.frag.spv │ │ │ ├── round_2col.vert │ │ │ ├── round_2col.vert.spv │ │ │ ├── shaded_round.frag │ │ │ ├── shaded_round.frag.spv │ │ │ ├── shaded_round.vert │ │ │ ├── shaded_round.vert.spv │ │ │ ├── shaded_square.frag │ │ │ ├── shaded_square.frag.spv │ │ │ ├── shaded_square.vert │ │ │ ├── shaded_square.vert.spv │ │ │ └── shaded_square.wgsl │ │ └── text_pipe.rs │ │ ├── draw_shaded.rs │ │ ├── lib.rs │ │ ├── options.rs │ │ ├── shaded_theme.rs │ │ └── surface.rs └── kas-widgets │ ├── COPYRIGHT │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ └── src │ ├── adapt │ ├── adapt.rs │ ├── adapt_cx.rs │ ├── adapt_events.rs │ ├── adapt_widget.rs │ ├── mod.rs │ ├── reserve.rs │ └── with_label.rs │ ├── button.rs │ ├── check_box.rs │ ├── combobox.rs │ ├── dialog.rs │ ├── edit.rs │ ├── event_config.rs │ ├── filler.rs │ ├── float.rs │ ├── frame.rs │ ├── grid.rs │ ├── grip.rs │ ├── image.rs │ ├── label.rs │ ├── lib.rs │ ├── list.rs │ ├── mark.rs │ ├── menu │ ├── menu_entry.rs │ ├── menubar.rs │ ├── mod.rs │ └── submenu.rs │ ├── nav_frame.rs │ ├── progress.rs │ ├── radio_box.rs │ ├── scroll.rs │ ├── scroll_bar.rs │ ├── scroll_label.rs │ ├── scroll_text.rs │ ├── separator.rs │ ├── slider.rs │ ├── spinner.rs │ ├── splitter.rs │ ├── stack.rs │ ├── tab_stack.rs │ └── text.rs ├── examples ├── README.md ├── calculator.rs ├── clock.rs ├── counter.rs ├── cursors.rs ├── data-list-view.rs ├── data-list.rs ├── gallery.rs ├── hello.rs ├── layout.rs ├── mandlebrot │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── mandlebrot.rs │ ├── shader.vert │ ├── shader.vert.spv │ ├── shader32.frag │ ├── shader32.frag.spv │ ├── shader64.frag │ └── shader64.frag.spv ├── proxy.rs ├── splitter.rs ├── stopwatch.rs ├── sync-counter.rs └── times-tables.rs ├── res ├── README.md ├── contrast-2-fill.svg ├── contrast-2-line.svg ├── error-warning-line.svg ├── gallery-line.svg └── rustacean-flat-happy.svg ├── src ├── lib.rs └── runner.rs └── tests └── layout_macros.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dhardy 4 | liberapay: dhardy 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yml.disabled: -------------------------------------------------------------------------------- 1 | name: Build artifacts 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | ubuntu: 9 | name: Ubuntu 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Install toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: stable 18 | override: true 19 | - name: Install dependencies 20 | run: sudo apt-get install -y libxkbcommon-dev libxcb-shape0-dev libxcb-xfixes0-dev 21 | - name: Build examples 22 | run: | 23 | cargo build --release --example layout --example gallery 24 | cargo build --release --manifest-path examples/mandlebrot/Cargo.toml 25 | - name: Prepare 26 | run: | 27 | strip target/release/examples/layout target/release/examples/gallery target/release/mandlebrot 28 | mv target/release/mandlebrot target/release/examples/ 29 | cp -a res target/release/examples/ 30 | - name: Upload 31 | uses: actions/upload-artifact@v2 32 | with: 33 | name: examples-ubuntu 34 | path: | 35 | target/release/examples/layout 36 | target/release/examples/gallery 37 | target/release/examples/mandlebrot 38 | target/release/examples/res/ 39 | 40 | macos: 41 | name: MacOS 42 | runs-on: macos-latest 43 | steps: 44 | - uses: actions/checkout@v2 45 | - name: Install toolchain 46 | uses: actions-rs/toolchain@v1 47 | with: 48 | profile: minimal 49 | toolchain: stable 50 | override: true 51 | - name: Build examples 52 | env: 53 | MACOSX_DEPLOYMENT_TARGET: 10.7 54 | WINIT_LINK_COLORSYNC: 1 55 | run: | 56 | cargo build --release --example layout --example gallery 57 | cargo build --release --manifest-path examples/mandlebrot/Cargo.toml 58 | - name: Prepare 59 | run: | 60 | strip target/release/examples/layout target/release/examples/gallery target/release/mandlebrot 61 | mv target/release/mandlebrot target/release/examples/ 62 | cp -a res target/release/examples/ 63 | - name: Upload 64 | uses: actions/upload-artifact@v2 65 | with: 66 | name: examples-macOS 67 | path: | 68 | target/release/examples/layout 69 | target/release/examples/gallery 70 | target/release/examples/mandlebrot 71 | target/release/examples/res/ 72 | 73 | windows: 74 | name: Windows 75 | runs-on: windows-latest 76 | steps: 77 | - uses: actions/checkout@v2 78 | - name: Install toolchain 79 | uses: actions-rs/toolchain@v1 80 | with: 81 | profile: minimal 82 | toolchain: stable 83 | override: true 84 | - name: Build examples 85 | run: | 86 | cargo build --release --example layout --example gallery 87 | cargo build --release --manifest-path examples/mandlebrot/Cargo.toml 88 | - name: Prepare 89 | run: | 90 | strip target/release/examples/layout.exe target/release/examples/gallery.exe target/release/mandlebrot.exe 91 | mv target/release/mandlebrot.exe target/release/examples/ 92 | xcopy res target\release\examples\res /e/k/c/i/y 93 | - name: Upload 94 | uses: actions/upload-artifact@v2 95 | with: 96 | name: examples-windows 97 | path: | 98 | target/release/examples/layout.exe 99 | target/release/examples/gallery.exe 100 | target/release/examples/mandlebrot.exe 101 | target/release/examples/res/ 102 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # This may be useful to run occasionally, but has issues: 2 | #format_code_in_doc_comments = true 3 | 4 | inline_attribute_width = 60 5 | overflow_delimited_expr = true 6 | single_line_if_else_max_width = 60 7 | use_field_init_shorthand = true 8 | use_try_shorthand = true 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ======== 3 | 4 | Contributions to KAS are welcome. It will be assumed that all 5 | contributions are made available under the terms of the Apache License v2.0. 6 | 7 | For very small changes, you may simply open a pull request; in other cases it 8 | is recommended to open an issue first. This project does not currently make use 9 | of third-party message boards. 10 | 11 | 12 | Code style 13 | --------- 14 | 15 | Code style is more art than science, but should: 16 | 17 | 1. Use `cargo fmt` 18 | 2. Make code clear and readable 19 | 3. Aim to reduce the line count, when this doesn't conflict with the above 20 | 21 | Sometimes introducing an extra `let` binding helps. Sometimes it makes sense 22 | to wrap a `match`. Sometimes it makes sense to use `return`. 23 | 24 | ### Spelling 25 | 26 | As is industry standard, APIs should use US-English spellings. 27 | This rule is not enforced for documentation or local variables, 28 | so long readability is not significantly impaired. 29 | 30 | Notes: 31 | 32 | - 'Config' is used as an abbreviation for 'configuration' (noun), *not* for 'configure' (verb) 33 | 34 | ### Nightly features 35 | 36 | KAS optionally uses several Rust nightly features, but is functional without 37 | (aside from some minor features). 38 | 39 | ### Unsafe 40 | 41 | Usage of `unsafe` is allowed, but not preferred. Current use cases: 42 | 43 | - Defining constants requiring `unwrap` (tracker: [`const_option`](https://github.com/rust-lang/rust/issues/58732)). Note that since 1.57, `panic!` in const fns is supported, hence a work-around using `match` is possible. 44 | - To get around lifetime restrictions on the theme API's `Theme::draw` and `Window::size` 45 | methods; this will no longer require `unsafe` once the 46 | `generic_associated_types` feature is stabilised. 47 | - `Id` uses `unsafe` code to support both inline and heap-allocated 48 | variants. 49 | - Implementing `bytemuck::Pod` and `Zeroable`, as required to assert that 50 | values may be copied to the GPU. 51 | - Constructing a `wgpu::Surface`, as required to assert validity of the window 52 | handle. 53 | 54 | Dependencies imply many more uses of `unsafe`; this includes: 55 | 56 | - Extern C APIs are commonly required (especially by `winit`) 57 | - GPU APIs 58 | - `smallvec` is widely used for in-place vectors; note that `tinyvec`'s 59 | restriction (`Item: Default`) makes it unsuitable for most of these uses 60 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | This work is copyrighted by the following contributors: 2 | 3 | Diggory Hardy 4 | 5 | This list may be incomplete. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KAS GUI 2 | ======= 3 | 4 | [![Test Status](https://github.com/kas-gui/kas/workflows/Tests/badge.svg?event=push)](https://github.com/kas-gui/kas/actions) 5 | [![Crates.io](https://img.shields.io/crates/v/kas.svg)](https://crates.io/crates/kas) 6 | [![kas-text](https://img.shields.io/badge/GitHub-kas--text-blueviolet)](https://github.com/kas-gui/kas-text/) 7 | [![Docs](https://docs.rs/kas/badge.svg)](https://docs.rs/kas) 8 | 9 | KAS is a stateful, pure-Rust GUI toolkit supporting: 10 | 11 | - [x] Mostly declarative UI descriptions 12 | - [x] Stateful widgets (e.g. selection range or a pure-UI counter) 13 | - [x] Virtual scrolling (list or matrix), including support for external data sources 14 | - [x] Theme abstraction including theme-driven animations and sizing 15 | - [ ] Multiple renderer backends 16 | - [ ] Integrated i18n support 17 | - [ ] Accessibility tool integration 18 | - [ ] Platform integration: persistent configuration, theme discovery, external menus, IME 19 | - [x] Most of the basics you'd expect: complex text, fractional scaling, automatic margins 20 | - [x] Extremely fast, monolithic binaries 21 | 22 | ### More 23 | 24 | - Docs: [Tutorials](https://kas-gui.github.io/tutorials/), 25 | [Wiki: Getting started](https://github.com/kas-gui/kas/wiki/Getting-started) 26 | - Prose: [Blog](https://kas-gui.github.io/blog/), 27 | [Design](https://github.com/kas-gui/design) 28 | - [API docs](https://docs.rs/kas) 29 | - Examples: [`examples` dir](examples), [kas-gui/7guis](https://github.com/kas-gui/7guis/). 30 | 31 | 32 | Crates and features 33 | ------------------- 34 | 35 | [kas] is a meta-package serving as the library's public API, but containing no real code. Other crates in this repo: 36 | 37 | - [kas-core](https://docs.rs/kas-core): the core library 38 | - [kas-widgets](https://docs.rs/kas-widgets): the main widget library 39 | - [kas-view](https://docs.rs/kas-view): view widgets supporting virtual scrolling 40 | - [kas-resvg](https://docs.rs/kas-resvg): extra widgets over [resvg](https://crates.io/crates/resvg) 41 | - [kas-dylib](https://crates.io/crates/kas-dylib): helper crate to support dynamic linking 42 | - kas-macros: proc-macro crate 43 | 44 | Significant external dependencies: 45 | 46 | - [kas-text](https://crates.io/crates/kas-text): complex text support 47 | - [impl-tools](https://crates.io/crates/impl-tools): `autoimpl` and `impl_scope` (extensible) macros 48 | - [winit](https://github.com/rust-windowing/winit): platform window integration 49 | - [wgpu](https://github.com/gfx-rs/wgpu): modern accelerated graphics API 50 | 51 | ### Feature flags 52 | 53 | The `kas` crate enables most important features by default, excepting those 54 | requiring nightly `rustc`. Other crates enable fewer features by default. 55 | See [Cargo.toml](https://github.com/kas-gui/kas/blob/master/Cargo.toml#L22). 56 | 57 | [kas]: https://docs.rs/kas 58 | 59 | 60 | Size 61 | ---- 62 | 63 | To reduce binary size, add this to your `Cargo.toml`: 64 | ```toml 65 | [profile.release] 66 | strip = true 67 | opt-level = "z" 68 | ``` 69 | 70 | You might also consider using feature `dynamic` if wishing to ship multiple 71 | binaries with shared libraries (Rust's `libstd` and `libkas_dylib`); note 72 | however that these are not ABI-stable. 73 | 74 | 75 | Copyright and Licence 76 | --------------------- 77 | 78 | The [COPYRIGHT](COPYRIGHT) file includes a list of contributors who claim 79 | copyright on this project. This list may be incomplete; new contributors may 80 | optionally add themselves to this list. 81 | 82 | The KAS library is published under the terms of the Apache License, Version 2.0. 83 | You may obtain a copy of this licence from the [LICENSE](LICENSE) file or on 84 | the following webpage: 85 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | single-char-binding-names-threshold = 7 2 | type-complexity-threshold = 600 3 | -------------------------------------------------------------------------------- /config-examples/theme.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | font_size: 10.0 3 | active_scheme: dark 4 | font_aliases: 5 | sans-serif: 6 | mode: Prepend 7 | list: [Calibri] 8 | Calibri: 9 | mode: Append 10 | list: [Carlito] 11 | fonts: 12 | Edit: 13 | families: 14 | - serif 15 | EditMulti: 16 | families: 17 | - serif 18 | MenuLabel: 19 | families: 20 | - sans-serif 21 | weight: 600 22 | raster: 23 | mode: 0 24 | subpixel_threshold: 0 25 | subpixel_steps: 1 26 | -------------------------------------------------------------------------------- /crates/kas-core/COPYRIGHT: -------------------------------------------------------------------------------- 1 | This work is copyrighted by the following contributors: 2 | 3 | Diggory Hardy 4 | 5 | This list may be incomplete. 6 | -------------------------------------------------------------------------------- /crates/kas-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kas-core" 3 | version = "0.15.0" 4 | authors = ["Diggory Hardy "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description = "KAS GUI / core" 8 | readme = "README.md" 9 | documentation = "https://docs.rs/kas-core/" 10 | keywords = ["gui"] 11 | categories = ["gui"] 12 | repository = "https://github.com/kas-gui/kas" 13 | exclude = ["/screenshots"] 14 | 15 | [package.metadata.docs.rs] 16 | features = ["stable"] 17 | rustdoc-args = ["--cfg", "docsrs"] 18 | 19 | [features] 20 | # The minimal feature set needed to build basic applications (with assumptions 21 | # about target platforms). 22 | minimal = ["winit", "wayland"] 23 | # All standard test target features 24 | stable = ["minimal", "clipboard", "markdown", "spawn", "x11", "serde", "toml", "yaml", "json", "ron", "macros_log", "image"] 25 | # Enables all "recommended" features for nightly rustc 26 | nightly = ["stable", "nightly-diagnostics"] 27 | # Additional, less recommendation-worthy features 28 | experimental = ["dark-light", "unsafe_node"] 29 | 30 | # Enables better proc-macro diagnostics (including warnings); nightly only. 31 | nightly-diagnostics = ["kas-macros/nightly"] 32 | 33 | # Use full specialization 34 | spec = [] 35 | 36 | # Enables documentation of APIs for graphics library and platform backends. 37 | # This API is not intended for use by end-user applications and 38 | # thus is omitted from built documentation by default. 39 | # This flag does not change the API, only built documentation. 40 | internal_doc = [] 41 | 42 | # Enable Markdown parsing 43 | markdown = ["kas-text/markdown"] 44 | 45 | # Enable support for YAML (de)serialisation 46 | yaml = ["serde", "dep:serde_yaml2"] 47 | 48 | # Enable support for JSON (de)serialisation 49 | json = ["serde", "dep:serde_json"] 50 | 51 | # Enable support for RON (de)serialisation 52 | ron = ["serde", "dep:ron"] 53 | 54 | # Enable support for TOML (de)serialisation 55 | toml = ["serde", "dep:toml"] 56 | 57 | # Enables clipboard read/write 58 | clipboard = ["dep:arboard", "dep:smithay-clipboard"] 59 | 60 | # Inject logging into macro-generated code. 61 | # Requires that all crates using these macros depend on the log crate. 62 | macros_log = ["kas-macros/log"] 63 | 64 | # Enable winit support 65 | winit = ["dep:winit"] 66 | 67 | # Support Wayland 68 | wayland = ["winit?/wayland", "winit?/wayland-dlopen"] 69 | 70 | # Support X11 71 | x11 = ["winit?/x11"] 72 | 73 | # Enable serde integration (mainly config read/write) 74 | serde = ["dep:serde", "kas-text/serde", "winit?/serde"] 75 | 76 | # Enable load_icon_from_path utility function 77 | image = ["dep:image"] 78 | 79 | # Automatically detect usage of dark theme 80 | dark-light = ["dep:dark-light"] 81 | 82 | # Support spawning async tasks 83 | spawn = ["dep:async-global-executor"] 84 | 85 | # Optimize Node using unsafe code 86 | unsafe_node = [] 87 | 88 | [build-dependencies] 89 | cfg_aliases = "0.2.0" 90 | 91 | [dependencies] 92 | log = "0.4" 93 | smallvec = "1.6.1" 94 | bitflags = "2.3.3" 95 | unicode-segmentation = "1.7" 96 | linear-map = "1.2.0" 97 | thiserror = "2.0.3" 98 | serde = { version = "1.0.123", features = ["derive"], optional = true } 99 | serde_json = { version = "1.0.61", optional = true } 100 | serde_yaml2 = { version = "0.1.2", optional = true } 101 | ron = { version = "0.10.1", package = "ron", optional = true } 102 | toml = { version = "0.8.2", package = "toml", optional = true } 103 | num_enum = "0.7.0" 104 | dark-light = { version = "2.0", optional = true } 105 | raw-window-handle = "0.6.0" 106 | async-global-executor = { version = "3.1.0", optional = true } 107 | cfg-if = "1.0.0" 108 | smol_str = "0.2.0" 109 | image = { version = "0.25.1", optional = true } 110 | 111 | [target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies] 112 | smithay-clipboard = { version = "0.7.0", optional = true } 113 | 114 | [target.'cfg(not(target_os = "android"))'.dependencies] 115 | arboard = { version = "3.2.0", optional = true, default-features = false } 116 | 117 | 118 | [dependencies.kas-macros] 119 | version = "0.15.0" 120 | path = "../kas-macros" 121 | 122 | [dependencies.kas-text] 123 | version = "0.8.0" 124 | 125 | [dependencies.easy-cast] 126 | version = "0.5.0" # used in doc links 127 | 128 | [dependencies.winit] 129 | # Provides translations for several winit types 130 | version = "0.30.1" 131 | optional = true 132 | default-features = false 133 | features = ["rwh_06"] 134 | 135 | [lints.clippy] 136 | module_inception = "allow" 137 | needless_lifetimes = "allow" 138 | unit_arg = "allow" 139 | match_like_matches_macro = "allow" 140 | needless_range_loop = "allow" 141 | too_many_arguments = "allow" 142 | -------------------------------------------------------------------------------- /crates/kas-core/README.md: -------------------------------------------------------------------------------- 1 | KAS Core 2 | ====== 3 | 4 | This is the core KAS crate. 5 | See also the [kas crate ](https://crates.io/crates/kas). 6 | 7 | For documentation of feature flags, see [Cargo.toml](Cargo.toml). 8 | 9 | 10 | Copyright and Licence 11 | ------- 12 | 13 | The [COPYRIGHT](COPYRIGHT) file includes a list of contributors who claim 14 | copyright on this project. This list may be incomplete; new contributors may 15 | optionally add themselves to this list. 16 | 17 | The KAS library is published under the terms of the Apache License, Version 2.0. 18 | You may obtain a copy of this licence from the [LICENSE](LICENSE) file or on 19 | the following webpage: 20 | -------------------------------------------------------------------------------- /crates/kas-core/build.rs: -------------------------------------------------------------------------------- 1 | // Script copied from winit 2 | 3 | use cfg_aliases::cfg_aliases; 4 | 5 | fn main() { 6 | // The script doesn't depend on our code 7 | println!("cargo:rerun-if-changed=build.rs"); 8 | 9 | // Setup cfg aliases 10 | cfg_aliases! { 11 | winit: { feature = "winit" }, 12 | 13 | // Systems. 14 | android_platform: { target_os = "android" }, 15 | wasm_platform: { target_arch = "wasm32" }, 16 | macos_platform: { target_os = "macos" }, 17 | ios_platform: { target_os = "ios" }, 18 | windows_platform: { target_os = "windows" }, 19 | apple: { any(target_os = "ios", target_os = "macos") }, 20 | free_unix: { all(unix, not(apple), not(android_platform)) }, 21 | redox: { target_os = "redox" }, 22 | 23 | // Native displays. 24 | x11_platform: { all(feature = "x11", free_unix, not(wasm), not(redox)) }, 25 | wayland_platform: { all(feature = "wayland", free_unix, not(wasm), not(redox)) }, 26 | orbital_platform: { redox }, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/kas-core/src/action.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Action enum 7 | 8 | bitflags! { 9 | /// Action required after processing 10 | /// 11 | /// Some methods operate directly on a context ([`ConfigCx`] or [`EventCx`]) 12 | /// while others don't reqiure a context but do require that some *action* 13 | /// is performed afterwards. This enum is used to convey that action. 14 | /// 15 | /// An `Action` produced at run-time should be passed to a context: 16 | /// `cx.action(self.id(), action)` (assuming `self` is a widget). 17 | /// An `Action` produced before starting the GUI may be discarded, for 18 | /// example: `let _ = runner.config_mut().font.set_size(24.0);`. 19 | /// 20 | /// Two `Action` values may be combined via bit-or (`a | b`). 21 | #[must_use] 22 | #[derive(Copy, Clone, Debug, Default)] 23 | pub struct Action: u32 { 24 | /// The whole window requires redrawing 25 | /// 26 | /// Note that [`event::EventCx::redraw`] can instead be used for more 27 | /// selective redrawing. 28 | const REDRAW = 1 << 0; 29 | /// Some widgets within a region moved 30 | /// 31 | /// Used when a pop-up is closed or a region adjusted (e.g. scroll or switch 32 | /// tab) to update which widget is under the mouse cursor / touch events. 33 | /// Identifier is that of the parent widget/window encapsulating the region. 34 | /// 35 | /// Implies window redraw. 36 | const REGION_MOVED = 1 << 4; 37 | /// A widget was scrolled 38 | /// 39 | /// This is used for inter-widget communication (see `EditBox`). If not 40 | /// handled locally, it is handled identially to [`Self::SET_RECT`]. 41 | const SCROLLED = 1 << 6; 42 | /// Reset size of all widgets without recalculating requirements 43 | const SET_RECT = 1 << 8; 44 | /// Resize all widgets in the window 45 | const RESIZE = 1 << 9; 46 | /// Update [`Dimensions`](crate::theme::dimensions::Dimensions) instances 47 | /// and theme configuration. 48 | /// 49 | /// Implies [`Action::RESIZE`]. 50 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 51 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 52 | const THEME_UPDATE = 1 << 10; 53 | /// Reload per-window cache of event configuration 54 | /// 55 | /// Implies [`Action::UPDATE`]. 56 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 57 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 58 | const EVENT_CONFIG = 1 << 11; 59 | /// Switch themes, replacing theme-window instances 60 | /// 61 | /// Implies [`Action::RESIZE`]. 62 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 63 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 64 | const THEME_SWITCH = 1 << 12; 65 | /// Reconfigure all widgets of the window 66 | /// 67 | /// *Configuring* widgets assigns [`Id`](crate::Id) identifiers and calls 68 | /// [`Events::configure`](crate::Events::configure). 69 | /// 70 | /// Implies [`Action::UPDATE`] since widgets are updated on configure. 71 | const RECONFIGURE = 1 << 16; 72 | /// Update all widgets 73 | /// 74 | /// This is a notification that input data has changed. 75 | const UPDATE = 1 << 17; 76 | /// The current window should be closed 77 | const CLOSE = 1 << 30; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/kas-core/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Configuration items and utilities 7 | 8 | mod config; 9 | pub use config::{Config, ConfigMsg, WindowConfig}; 10 | 11 | mod event; 12 | pub use event::{EventConfig, EventConfigMsg, EventWindowConfig, MousePan}; 13 | 14 | mod font; 15 | pub use font::{FontConfig, FontConfigMsg, RasterConfig}; 16 | 17 | mod format; 18 | pub use format::{Error, Format}; 19 | 20 | mod factory; 21 | pub use factory::*; 22 | 23 | mod shortcuts; 24 | pub use shortcuts::Shortcuts; 25 | 26 | mod theme; 27 | pub use theme::{ThemeConfig, ThemeConfigMsg}; 28 | -------------------------------------------------------------------------------- /crates/kas-core/src/core/data.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Widget data types 7 | 8 | use super::Id; 9 | #[allow(unused)] use super::Widget; 10 | use crate::geom::Rect; 11 | 12 | #[cfg(feature = "winit")] pub use winit::window::Icon; 13 | 14 | /// An icon used for the window titlebar, taskbar, etc. 15 | #[cfg(not(feature = "winit"))] 16 | #[derive(Clone)] 17 | pub struct Icon; 18 | #[cfg(not(feature = "winit"))] 19 | impl Icon { 20 | /// Creates an `Icon` from 32bpp RGBA data. 21 | /// 22 | /// The length of `rgba` must be divisible by 4, and `width * height` must equal 23 | /// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error. 24 | pub fn from_rgba( 25 | rgba: Vec, 26 | width: u32, 27 | height: u32, 28 | ) -> Result { 29 | let _ = (rgba, width, height); 30 | Result::::Ok(Icon) 31 | } 32 | } 33 | 34 | /// Common widget data 35 | /// 36 | /// This type may be used for a [`Widget`]'s `core: widget_core!()` field. 37 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 38 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 39 | #[derive(Default, Debug)] 40 | pub struct DefaultCoreType { 41 | pub _rect: Rect, 42 | pub _id: Id, 43 | #[cfg(debug_assertions)] 44 | pub status: WidgetStatus, 45 | } 46 | 47 | impl Clone for DefaultCoreType { 48 | fn clone(&self) -> Self { 49 | DefaultCoreType { 50 | _rect: self._rect, 51 | _id: Default::default(), 52 | #[cfg(debug_assertions)] 53 | status: self.status, 54 | } 55 | } 56 | } 57 | 58 | /// Widget state tracker 59 | /// 60 | /// This struct is used to track status of widget operations and panic in case 61 | /// of inappropriate call order (such cases are memory safe but may cause 62 | /// incorrect widget behaviour). 63 | /// 64 | /// It is not used in release builds. 65 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 66 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 67 | #[cfg(debug_assertions)] 68 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] 69 | pub enum WidgetStatus { 70 | #[default] 71 | New, 72 | Configured, 73 | SizeRulesX, 74 | SizeRulesY, 75 | SetRect, 76 | } 77 | 78 | #[cfg(debug_assertions)] 79 | impl WidgetStatus { 80 | fn require(&self, id: &Id, expected: Self) { 81 | if *self < expected { 82 | panic!("WidgetStatus of {id}: require {expected:?}, found {self:?}"); 83 | } 84 | } 85 | 86 | /// Configure 87 | /// 88 | /// Requires nothing. Re-configuration does not require repeating other actions. 89 | pub fn configure(&mut self, _id: &Id) { 90 | // re-configure does not require repeating other actions 91 | *self = (*self).max(WidgetStatus::Configured); 92 | } 93 | 94 | /// Update 95 | /// 96 | /// Requires configure. Does not affect status (note that widgets are always 97 | /// updated immediately after configure, hence `WidgetStatus::Configured` 98 | /// implies that `update` has been called or is just about to be called). 99 | pub fn update(&self, id: &Id) { 100 | self.require(id, WidgetStatus::Configured); 101 | 102 | // Update-after-configure is already guaranteed (see impls module). 103 | // NOTE: Update-after-data-change should be required but is hard to 104 | // detect; we could store a data hash but draw does not receive data. 105 | // As such we don't bother recording this operation. 106 | } 107 | 108 | /// Size rules 109 | /// 110 | /// Requires a prior call to `configure`. When `axis.is_vertical()`, 111 | /// requires a prior call to `size_rules` for the horizontal axis. 112 | /// 113 | /// Re-calling `size_rules` does not require additional actions. 114 | pub fn size_rules(&mut self, id: &Id, axis: crate::layout::AxisInfo) { 115 | if axis.is_horizontal() { 116 | self.require(id, WidgetStatus::Configured); 117 | *self = (*self).max(WidgetStatus::SizeRulesX); 118 | } else { 119 | self.require(id, WidgetStatus::SizeRulesX); 120 | *self = (*self).max(WidgetStatus::SizeRulesY); 121 | } 122 | } 123 | 124 | /// Set rect 125 | /// 126 | /// Requires calling `size_rules` for each axis. Re-calling `set_rect` does 127 | /// not require additional actions. 128 | pub fn set_rect(&mut self, id: &Id) { 129 | self.require(id, WidgetStatus::SizeRulesY); 130 | *self = WidgetStatus::SetRect; 131 | } 132 | 133 | /// Require that `set_rect` has been called 134 | pub fn require_rect(&self, id: &Id) { 135 | self.require(id, WidgetStatus::SetRect); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /crates/kas-core/src/core/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Core widget types 7 | 8 | mod collection; 9 | mod data; 10 | mod layout; 11 | mod node; 12 | mod scroll_traits; 13 | mod tile; 14 | mod widget; 15 | mod widget_id; 16 | 17 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 18 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 19 | pub mod impls; 20 | 21 | pub use collection::{CellCollection, Collection}; 22 | pub use data::*; 23 | pub use layout::*; 24 | pub use node::Node; 25 | pub use scroll_traits::*; 26 | pub use tile::*; 27 | pub use widget::*; 28 | pub use widget_id::*; 29 | -------------------------------------------------------------------------------- /crates/kas-core/src/core/scroll_traits.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Scroll bar traits 7 | 8 | use crate::event::EventCx; 9 | use crate::geom::{Offset, Size}; 10 | use crate::Widget; 11 | #[allow(unused)] use crate::{Events, Layout}; 12 | 13 | /// Additional functionality on scrollable widgets 14 | /// 15 | /// This trait should be implemented by widgets supporting scrolling, enabling 16 | /// a parent to control scrolling. 17 | /// 18 | /// If the widget scrolls itself it should set a scroll action via [`EventCx::set_scroll`]. 19 | pub trait Scrollable: Widget { 20 | /// Given size `size`, returns whether `(horiz, vert)` scrolling is required 21 | /// 22 | /// Note: this is called *before* [`Layout::set_rect`], thus must may need 23 | /// to perform independent calculation of the content size. 24 | fn scroll_axes(&self, size: Size) -> (bool, bool); 25 | 26 | /// Get the maximum scroll offset 27 | /// 28 | /// Note: the minimum scroll offset is always zero. 29 | /// 30 | /// Note: this is called immediately after [`Layout::set_rect`], thus should 31 | /// be updated there (as well as by [`Events::update`] if appropriate). 32 | fn max_scroll_offset(&self) -> Offset; 33 | 34 | /// Get the current scroll offset 35 | /// 36 | /// Contents of the scroll region are translated by this offset (to convert 37 | /// coordinates from the outer region to the scroll region, add this offset). 38 | /// 39 | /// The offset is restricted between [`Offset::ZERO`] and 40 | /// [`Self::max_scroll_offset`]. 41 | fn scroll_offset(&self) -> Offset; 42 | 43 | /// Set the scroll offset 44 | /// 45 | /// This may be used for programmatic scrolling, e.g. by a wrapping widget 46 | /// with scroll controls. Note that calling this method directly on the 47 | /// scrolling widget will not update any controls in a wrapping widget. 48 | /// 49 | /// The offset is clamped to the available scroll range and applied. The 50 | /// resulting offset is returned. 51 | fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset; 52 | } 53 | -------------------------------------------------------------------------------- /crates/kas-core/src/event/cx/config.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Configuration context 7 | 8 | use super::PendingNavFocus; 9 | use crate::event::{EventState, FocusSource}; 10 | use crate::geom::Rect; 11 | use crate::layout::AlignPair; 12 | use crate::text::format::FormattableText; 13 | use crate::theme::{Feature, SizeCx, Text, ThemeSize}; 14 | use crate::{Id, Node}; 15 | use std::ops::{Deref, DerefMut}; 16 | 17 | #[allow(unused)] use crate::event::{Event, EventCx}; 18 | #[allow(unused)] use crate::{Action, Events, Layout}; 19 | 20 | /// Widget configuration and update context 21 | /// 22 | /// This type supports easy access to [`EventState`] (via [`Deref`], 23 | /// [`DerefMut`] and [`Self::ev_state`]) as well as [`SizeCx`] 24 | /// ([`Self::size_cx`]). 25 | #[must_use] 26 | pub struct ConfigCx<'a> { 27 | sh: &'a dyn ThemeSize, 28 | pub(crate) ev: &'a mut EventState, 29 | } 30 | 31 | impl<'a> ConfigCx<'a> { 32 | /// Construct 33 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 34 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 35 | pub fn new(sh: &'a dyn ThemeSize, ev: &'a mut EventState) -> Self { 36 | ConfigCx { sh, ev } 37 | } 38 | 39 | /// Access a [`SizeCx`] 40 | #[inline] 41 | pub fn size_cx(&self) -> SizeCx<'a> { 42 | SizeCx::new(self.sh) 43 | } 44 | 45 | /// Access [`EventState`] 46 | #[inline] 47 | pub fn ev_state(&mut self) -> &mut EventState { 48 | self.ev 49 | } 50 | 51 | /// Disable or enable navigation focus 52 | /// 53 | /// When nav focus is disabled, [`EventState::nav_focus`] always returns 54 | /// `None`. Any existing focus is immediately cleared. Both 55 | /// [`EventState::set_nav_focus`] and [`EventState::next_nav_focus`] will fail to 56 | /// do anything. Input such as the Tab key and mouse click 57 | /// will not set navigation focus. 58 | pub fn disable_nav_focus(&mut self, disabled: bool) { 59 | self.ev.config.nav_focus = !disabled; 60 | if disabled { 61 | self.pending_nav_focus = PendingNavFocus::Set { 62 | target: None, 63 | source: FocusSource::Synthetic, 64 | }; 65 | } 66 | } 67 | 68 | /// Configure a widget 69 | /// 70 | /// All widgets must be configured after construction (see 71 | /// [widget lifecycle](Layout#widget-lifecycle)). 72 | /// This method performs complete configuration of the widget by calling 73 | /// [`Events::configure`], [`Events::update`], [`Events::configure_recurse`]. 74 | /// 75 | /// To trigger (re)-configuration of the entire widget tree, use 76 | /// [`Action::RECONFIGURE`]. 77 | /// 78 | /// Pass the `id` to assign to the widget. This is usually constructed with 79 | /// [`Events::make_child_id`]. 80 | #[inline] 81 | pub fn configure(&mut self, mut widget: Node<'_>, id: Id) { 82 | if id.is_valid() { 83 | widget._configure(self, id); 84 | } 85 | } 86 | 87 | /// Update a widget 88 | /// 89 | /// All widgets must be updated after input data changes. 90 | /// This method recursively updates the widget by calling 91 | /// [`Events::update`] and [`Events::update_recurse`]. 92 | #[inline] 93 | pub fn update(&mut self, mut widget: Node<'_>) { 94 | widget._update(self); 95 | } 96 | 97 | /// Align a feature's rect 98 | /// 99 | /// In case the input `rect` is larger than desired on either axis, it is 100 | /// reduced in size and offset within the original `rect` as is preferred. 101 | #[inline] 102 | pub fn align_feature(&self, feature: Feature, rect: Rect, align: AlignPair) -> Rect { 103 | self.sh.align_feature(feature, rect, align) 104 | } 105 | 106 | /// Configure a text object 107 | /// 108 | /// This selects a font given the [`TextClass`][crate::theme::TextClass], 109 | /// [theme configuration][crate::config::ThemeConfig] and 110 | /// the loaded [fonts][crate::text::fonts]. 111 | #[inline] 112 | pub fn text_configure(&self, text: &mut Text) { 113 | let class = text.class(); 114 | self.sh.text_configure(text, class); 115 | } 116 | } 117 | 118 | impl<'a> Deref for ConfigCx<'a> { 119 | type Target = EventState; 120 | fn deref(&self) -> &EventState { 121 | self.ev 122 | } 123 | } 124 | impl<'a> DerefMut for ConfigCx<'a> { 125 | fn deref_mut(&mut self) -> &mut EventState { 126 | self.ev 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /crates/kas-core/src/event/cx/press/velocity.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Event handling: mouse / touch velocity sampling 7 | //! 8 | //! The goal here is to yield estimates of velocity (in pixels per second) for 9 | //! mouse motion and touch swipe events. Estimates should be smooth (ideally at 10 | //! least two delta samples) and responsive (ideally no longer than the period 11 | //! of one frame). 12 | //! 13 | //! From a cursory web search and some tests on available devices: 14 | //! 15 | //! - Common (basic) mice usually report at 125 Hz while gaming mice commonly 16 | //! report at 1 kHz (potentially up to 8 kHz) 17 | //! - Some touchscreens report at ~100 Hz while modern phone touchscreens often 18 | //! report at twice the screen refresh rate (with some gaming phones using 19 | //! ~1-2 kHz). 20 | //! - A Windows Precision Touchpad is required to report at 125 Hz; my Elan 21 | //! touchpad reports at 150 Hz. 22 | //! - Troubleshooting requests attest that rates are sometimes much lower (as 23 | //! low as 30 Hz). 24 | //! 25 | //! A sample period of `3500 / screen_refresh_hz` ms should allow at least 3 26 | //! (delta) samples while providing 7 in the common 125 Hz mouse, 60 Hz screen 27 | //! case. Provided we ignore deltas of zero, we may limit our sample buffer to 28 | //! a relatively small size (e.g. 8 samples) without making results too jittery; 29 | //! this also improves responsiveness when the sample rate is high. 30 | 31 | use crate::geom::Vec2; 32 | use smallvec::SmallVec; 33 | use std::time::{Duration, Instant}; 34 | 35 | const MAX_SAMPLES: usize = 8; 36 | 37 | /// A buffer of recent delta samples used to estimate velocity 38 | #[derive(Clone, Debug, Default)] 39 | pub(super) struct Samples { 40 | samples: SmallVec<[(Instant, Vec2); MAX_SAMPLES]>, 41 | next: usize, // index of next insert 42 | } 43 | 44 | impl Samples { 45 | /// Clear all samples 46 | pub(super) fn clear(&mut self) { 47 | self.samples.clear(); 48 | self.next = 0; 49 | } 50 | 51 | /// Push a new sample 52 | pub(super) fn push_delta(&mut self, delta: Vec2) { 53 | let now = Instant::now(); 54 | if self.samples.len() < MAX_SAMPLES { 55 | self.samples.push((now, delta)); 56 | } else { 57 | self.samples[self.next] = (now, delta); 58 | self.next = (self.next + 1) % MAX_SAMPLES; 59 | } 60 | } 61 | 62 | /// Calculate average velocity over a given sample `period` 63 | /// 64 | /// Units: pixels per second. 65 | pub(super) fn velocity(&self, period: Duration) -> Vec2 { 66 | let now = Instant::now(); 67 | let start = now - period; // saturating_sub 68 | 69 | let mut delta = Vec2::ZERO; 70 | for sample in &self.samples { 71 | if sample.0 > start { 72 | delta += sample.1; 73 | } 74 | } 75 | 76 | delta / period.as_secs_f32() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/kas-core/src/event/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Event handling 7 | //! 8 | //! ## Event handling model 9 | //! 10 | //! Note: widgets are represented as an acyclic tree, with the *root* at the 11 | //! "top" of the tree. Each tree node is a [`Widget`] and has an [`Id`]. 12 | //! An [`Id`] represents a *path* and may be used to find the most 13 | //! direct root from the root to the target. 14 | //! 15 | //! An [`Event`] is sent to a target widget as follows: 16 | //! 17 | //! 1. Determine the target's [`Id`]. For example, this may be 18 | //! the [`nav_focus`](EventState::nav_focus) or may be determined from 19 | //! from mouse/touch coordinates by calling [`try_probe`](crate::Layout::try_probe). 20 | //! 2. If the target is [disabled](EventState::is_disabled), then find the 21 | //! top-most ancestor which is disabled and make that the target, but 22 | //! inhibit calling of [`Events::handle_event`] on this widget (but still 23 | //! unwind, calling [`Events::handle_event`] on ancestors)). 24 | //! 3. Traverse *down* the widget tree from its root to the target according to 25 | //! the [`Id`]. 26 | //! 4. In the normal case (when the target is not disabled and the event is 27 | //! not stolen), [`Events::handle_event`] is called on the target. 28 | //! 5. If the message stack is not empty, call [`Events::handle_messages`] on 29 | //! the current node. 30 | //! 6. Unwind, traversing back *up* the widget tree (towards the root). 31 | //! On each node (excluding the target), 32 | //! 33 | //! - If a non-empty scroll action is [set](EventCx::set_scroll), 34 | //! call [`Events::handle_scroll`] 35 | //! - If the event has not yet been [used](Used), 36 | //! call [`Events::handle_event`] 37 | //! - If the message stack is non-empty (see [`EventCx::push`]), 38 | //! call [`Events::handle_messages`]. 39 | //! 7. If the message stack is not empty, call 40 | //! [`AppData::handle_messages`](crate::runner::AppData::handle_messages). 41 | //! 8. Clear any messages still on the message stack, printing a warning to the 42 | //! log. Messages *should* be handled during unwinding, though not doing so 43 | //! is safe (and possibly useful during development). 44 | //! 45 | //! ### Pop-ups 46 | //! 47 | //! When a pop-up widget is created, the pop-up's parent takes priority for 48 | //! "press" (mouse / touch) input as well as receiving keyboard focus. 49 | //! 50 | //! If this input is unhandled, the pop-up is automatically closed and the event 51 | //! is re-sent to the next candidate, allowing handling of e.g. mouse clicks on 52 | //! widgets under a menu. This should be intuitive: UI which is in focus and 53 | //! not greyed-out should be interactive. 54 | //! 55 | //! [`Id`]: crate::Id 56 | 57 | pub mod components; 58 | mod cx; 59 | #[cfg(not(winit))] mod enums; 60 | mod events; 61 | mod response; 62 | 63 | pub use smol_str::SmolStr; 64 | #[cfg(winit)] 65 | pub use winit::event::{ElementState, KeyEvent, MouseButton}; 66 | #[cfg(winit)] 67 | pub use winit::keyboard::{Key, ModifiersState, NamedKey, PhysicalKey}; 68 | #[cfg(winit)] 69 | pub use winit::window::{CursorIcon, ImePurpose, ResizeDirection}; // used by Key 70 | 71 | #[allow(unused)] use crate::{Events, Widget}; 72 | pub use cx::*; 73 | #[cfg(not(winit))] pub use enums::*; 74 | pub use events::*; 75 | pub use response::{IsUsed, Scroll, Unused, Used}; 76 | -------------------------------------------------------------------------------- /crates/kas-core/src/event/response.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Event handling: IsUsed and Scroll types 7 | 8 | use crate::geom::{Offset, Rect}; 9 | 10 | pub use IsUsed::{Unused, Used}; 11 | 12 | use super::components::KineticStart; 13 | 14 | /// Return type of event-handling methods 15 | /// 16 | /// This type is convertible to/from `bool` and supports the expected bit-wise 17 | /// OR operator (`a | b`, `*a |= b`). 18 | /// 19 | /// The type also implements negation with output type `bool`, thus allowing 20 | /// `if is_used.into() { ... }` and `if !is_used { ... }`. An implementation of 21 | /// `Deref` would be preferred, but the trait can only output a reference. 22 | #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] 23 | pub enum IsUsed { 24 | /// Event was unused 25 | /// 26 | /// Unused events may be used by a parent/ancestor widget or passed to 27 | /// another handler until used. 28 | Unused, 29 | /// Event is used, no other result 30 | Used, 31 | } 32 | 33 | impl From for IsUsed { 34 | fn from(is_used: bool) -> Self { 35 | match is_used { 36 | false => Self::Unused, 37 | true => Self::Used, 38 | } 39 | } 40 | } 41 | 42 | impl From for bool { 43 | fn from(is_used: IsUsed) -> bool { 44 | is_used == Used 45 | } 46 | } 47 | 48 | impl std::ops::BitOr for IsUsed { 49 | type Output = Self; 50 | #[inline] 51 | fn bitor(self, rhs: Self) -> Self { 52 | match (self, rhs) { 53 | (Unused, Unused) => Unused, 54 | _ => Used, 55 | } 56 | } 57 | } 58 | impl std::ops::BitOrAssign for IsUsed { 59 | #[inline] 60 | fn bitor_assign(&mut self, rhs: Self) { 61 | *self = *self | rhs; 62 | } 63 | } 64 | 65 | impl std::ops::Not for IsUsed { 66 | type Output = bool; 67 | #[inline] 68 | fn not(self) -> bool { 69 | self != Used 70 | } 71 | } 72 | 73 | /// Request to / notification of scrolling from a child 74 | /// 75 | /// See: [`EventCx::set_scroll`](super::EventCx::set_scroll). 76 | #[derive(Clone, Debug, Default, PartialEq)] 77 | #[must_use] 78 | pub enum Scroll { 79 | /// No scrolling 80 | #[default] 81 | None, 82 | /// Child has scrolled; no further scrolling needed 83 | /// 84 | /// External scroll bars use this as a notification to update self. 85 | Scrolled, 86 | /// Pan region by the given offset 87 | /// 88 | /// This may be returned to scroll the closest scrollable ancestor region. 89 | /// This region should attempt to scroll self by this offset, then, if all 90 | /// the offset was used, return `Scroll::Scrolled`, otherwise return 91 | /// `Scroll::Offset(delta)` with the unused offset `delta`. 92 | /// 93 | /// With the usual scroll offset conventions, this delta must be subtracted 94 | /// from the scroll offset. 95 | Offset(Offset), 96 | /// Start kinetic scrolling 97 | Kinetic(KineticStart), 98 | /// Focus the given rect 99 | Rect(Rect), 100 | } 101 | -------------------------------------------------------------------------------- /crates/kas-core/src/hidden/with_any.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Input data: map any to `()` 7 | -------------------------------------------------------------------------------- /crates/kas-core/src/layout/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Layout utilities 7 | //! 8 | //! For documentation of layout resolution, see the [`Layout`] trait. 9 | //! 10 | //! Size units are physical (real) pixels. This applies to most of KAS. 11 | //! 12 | //! ## Data types 13 | //! 14 | //! [`SizeRules`] is the "heart" of widget layout, used to specify a widget's 15 | //! size requirements. It provides various methods to compute derived rules 16 | //! and [`SizeRules::solve_seq`], the "muscle" of the layout engine. 17 | //! 18 | //! [`AxisInfo`], [`Margins`] and [`Stretch`] are auxilliary data types. 19 | //! 20 | //! ## Solvers 21 | //! 22 | //! The [`RulesSolver`] and [`RulesSetter`] traits define interfaces for 23 | //! layout engines: 24 | //! 25 | //! - [`SingleSolver`] and [`SingleSetter`] are trivial implementations for 26 | //! single-child parents 27 | //! - [`RowSolver`] and [`RowSetter`] set out a row or column of children. 28 | //! These are parametrised over `S: RowStorage` allowing both efficient 29 | //! operation on a small fixed number of children with [`FixedRowStorage`] 30 | //! and operation on a over a `Vec` with [`DynRowStorage`]. 31 | //! - [`GridSolver`] and [`GridSetter`] set out children assigned to grid 32 | //! cells with optional cell-spans. This is the most powerful and flexible 33 | //! layout engine. 34 | //! 35 | //! [`RowPositionSolver`] may be used with widgets set out by [`RowSetter`] 36 | //! to quickly locate children from a `coord` or `rect`. 37 | //! 38 | //! [`Layout`]: crate::Layout 39 | 40 | mod align; 41 | mod grid_solver; 42 | mod row_solver; 43 | mod single_solver; 44 | mod size_rules; 45 | mod size_types; 46 | mod sizer; 47 | mod storage; 48 | 49 | use crate::dir::{Direction, Directional, Directions}; 50 | 51 | pub use align::{Align, AlignHints, AlignPair}; 52 | pub use grid_solver::{DefaultWithLen, GridCellInfo, GridDimensions, GridSetter, GridSolver}; 53 | pub use row_solver::{RowPositionSolver, RowSetter, RowSolver}; 54 | pub use single_solver::{SingleSetter, SingleSolver}; 55 | pub use size_rules::SizeRules; 56 | pub use size_types::*; 57 | pub use sizer::{solve_size_rules, RulesSetter, RulesSolver, SolveCache}; 58 | pub use storage::*; 59 | 60 | /// Information on which axis is being resized 61 | /// 62 | /// Also conveys the size of the other axis, if fixed. 63 | #[derive(Copy, Clone, Debug)] 64 | pub struct AxisInfo { 65 | vertical: bool, 66 | has_fixed: bool, 67 | other_axis: i32, 68 | } 69 | 70 | impl AxisInfo { 71 | /// Construct with direction and an optional value for the other axis 72 | /// 73 | /// This method is *usually* not required by user code. 74 | #[inline] 75 | pub fn new(vertical: bool, fixed: Option) -> Self { 76 | AxisInfo { 77 | vertical, 78 | has_fixed: fixed.is_some(), 79 | other_axis: fixed.unwrap_or(0), 80 | } 81 | } 82 | 83 | /// True if the current axis is vertical 84 | #[inline] 85 | pub fn is_vertical(self) -> bool { 86 | self.vertical 87 | } 88 | 89 | /// True if the current axis is horizontal 90 | #[inline] 91 | pub fn is_horizontal(self) -> bool { 92 | !self.vertical 93 | } 94 | 95 | /// Size of other axis, if fixed 96 | #[inline] 97 | pub fn other(&self) -> Option { 98 | if self.has_fixed { 99 | Some(self.other_axis) 100 | } else { 101 | None 102 | } 103 | } 104 | 105 | /// Subtract `x` from size of other axis (if applicable) 106 | #[inline] 107 | pub fn sub_other(&mut self, x: i32) { 108 | self.other_axis -= x; 109 | } 110 | } 111 | 112 | impl Directional for AxisInfo { 113 | type Flipped = Self; 114 | type Reversed = Self; 115 | 116 | fn flipped(mut self) -> Self::Flipped { 117 | self.vertical = !self.vertical; 118 | self.has_fixed = false; 119 | self 120 | } 121 | 122 | #[inline] 123 | fn reversed(self) -> Self::Reversed { 124 | self 125 | } 126 | 127 | #[inline] 128 | fn as_direction(self) -> Direction { 129 | match self.vertical { 130 | false => Direction::Right, 131 | true => Direction::Down, 132 | } 133 | } 134 | } 135 | 136 | impl From for Directions { 137 | fn from(axis: AxisInfo) -> Directions { 138 | match axis.vertical { 139 | false => Directions::LEFT | Directions::RIGHT, 140 | true => Directions::UP | Directions::DOWN, 141 | } 142 | } 143 | } 144 | 145 | #[cfg(test)] 146 | #[test] 147 | fn size() { 148 | assert_eq!(std::mem::size_of::(), 8); 149 | } 150 | -------------------------------------------------------------------------------- /crates/kas-core/src/layout/single_solver.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Miscellaneous solvers 7 | 8 | use super::{AlignHints, AxisInfo, RulesSetter, RulesSolver, SizeRules}; 9 | use crate::geom::Rect; 10 | 11 | /// [`RulesSolver`] implementation for a fixed single-child layout 12 | pub struct SingleSolver { 13 | axis: AxisInfo, 14 | rules: SizeRules, 15 | } 16 | 17 | impl SingleSolver { 18 | /// Construct. 19 | /// 20 | /// Argument order is consistent with other [`RulesSolver`]s. 21 | /// 22 | /// - `axis`: `AxisInfo` instance passed into `size_rules` 23 | /// - `_dim`: unused 24 | /// - `_storage`: unused 25 | pub fn new(axis: AxisInfo, _dim: (), _storage: &mut ()) -> Self { 26 | SingleSolver { 27 | axis, 28 | rules: SizeRules::EMPTY, 29 | } 30 | } 31 | } 32 | 33 | impl RulesSolver for SingleSolver { 34 | type Storage = (); 35 | type ChildInfo = (); 36 | 37 | fn for_child SizeRules>( 38 | &mut self, 39 | _storage: &mut Self::Storage, 40 | _child_info: Self::ChildInfo, 41 | child_rules: CR, 42 | ) { 43 | self.rules = child_rules(self.axis); 44 | } 45 | 46 | fn finish(self, _storage: &mut Self::Storage) -> SizeRules { 47 | self.rules 48 | } 49 | } 50 | 51 | /// [`RulesSetter`] implementation for a fixed single-child layout 52 | pub struct SingleSetter { 53 | rect: Rect, 54 | } 55 | 56 | impl SingleSetter { 57 | /// Construct 58 | /// 59 | /// Argument order is consistent with other [`RulesSetter`]s. 60 | /// 61 | /// - `rect`: the [`Rect`] within which to position children 62 | /// - `_dim`: unused 63 | /// - `_align`: unused 64 | /// - `_storage`: unused 65 | pub fn new(rect: Rect, _dim: (), _align: AlignHints, _storage: &mut ()) -> Self { 66 | // NOTE: possibly we should apply alignment here, but we can't without 67 | // storing the ideal size for each dimension in the storage. 68 | // If we do, we should do the same for the other axis of RowSetter. 69 | SingleSetter { rect } 70 | } 71 | } 72 | 73 | impl RulesSetter for SingleSetter { 74 | type Storage = (); 75 | type ChildInfo = (); 76 | 77 | fn child_rect(&mut self, _: &mut Self::Storage, _: Self::ChildInfo) -> Rect { 78 | self.rect 79 | } 80 | 81 | fn maximal_rect_of(&mut self, _: &mut Self::Storage, _: Self::ChildInfo) -> Rect { 82 | self.rect 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /crates/kas-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! KAS GUI core 7 | //! 8 | //! Re-exports: 9 | //! 10 | //! - [`kas::cast`] is a re-export of [`easy-cast`](https://crates.io/crates/easy-cast) 11 | //! - [`impl_scope!`], [`impl_anon!`], [`autoimpl`] and [`impl_default`] are 12 | //! re-implementations of [`impl-tools`](https://crates.io/crates/impl-tools) macros 13 | 14 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 15 | #![cfg_attr(docsrs, feature(doc_cfg))] 16 | #![cfg_attr(feature = "spec", feature(specialization))] 17 | 18 | extern crate self as kas; 19 | 20 | #[macro_use] extern crate bitflags; 21 | 22 | #[doc(inline)] pub extern crate easy_cast as cast; 23 | 24 | // internal modules: 25 | mod action; 26 | mod core; 27 | pub mod decorations; 28 | mod popup; 29 | mod root; 30 | 31 | pub use crate::core::*; 32 | pub use action::Action; 33 | pub use kas_macros::{autoimpl, extends, impl_default}; 34 | pub use kas_macros::{cell_collection, collection, impl_anon, impl_scope}; 35 | pub use kas_macros::{widget, widget_index, widget_set_rect}; 36 | #[doc(inline)] pub use popup::Popup; 37 | #[doc(inline)] pub(crate) use popup::PopupDescriptor; 38 | #[doc(inline)] pub(crate) use root::WindowIdFactory; 39 | #[doc(inline)] 40 | pub use root::{Window, WindowCommand, WindowId}; 41 | 42 | // public implementations: 43 | pub mod config; 44 | pub mod dir; 45 | pub mod draw; 46 | pub mod event; 47 | pub mod geom; 48 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 49 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 50 | pub mod hidden; 51 | pub mod layout; 52 | pub mod messages; 53 | pub mod prelude; 54 | pub mod runner; 55 | pub mod text; 56 | pub mod theme; 57 | pub mod util; 58 | -------------------------------------------------------------------------------- /crates/kas-core/src/prelude.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! KAS core prelude 7 | //! 8 | //! It is recommended to use `kas::prelude` instead, which is an extension of 9 | //! this crate's prelude. 10 | 11 | #[doc(no_inline)] pub use crate::cast::traits::*; 12 | #[doc(no_inline)] 13 | pub use crate::dir::{Direction, Directional}; 14 | #[doc(no_inline)] 15 | pub use crate::event::{ConfigCx, Event, EventCx, EventState, IsUsed, Unused, Used}; 16 | #[doc(no_inline)] 17 | pub use crate::geom::{Coord, Offset, Rect, Size}; 18 | #[doc(no_inline)] 19 | pub use crate::layout::{Align, AlignHints, AlignPair, AxisInfo, SizeRules, Stretch}; 20 | #[doc(no_inline)] pub use crate::text::AccessString; 21 | #[doc(no_inline)] pub use crate::theme::{DrawCx, SizeCx}; 22 | #[doc(no_inline)] pub use crate::Action; 23 | #[doc(no_inline)] 24 | pub use crate::{autoimpl, impl_anon, impl_default, impl_scope}; 25 | #[doc(no_inline)] 26 | pub use crate::{widget, widget_index, widget_set_rect}; 27 | #[doc(no_inline)] 28 | pub use crate::{Events, Layout, Tile, TileExt, Widget, Window, WindowCommand}; 29 | #[doc(no_inline)] pub use crate::{HasId, Id}; 30 | #[doc(no_inline)] pub use crate::{Node, Scrollable}; 31 | -------------------------------------------------------------------------------- /crates/kas-core/src/text/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Text functionality 7 | //! 8 | //! Most of this module is simply a re-export of the [KAS Text] API, hence the 9 | //! lower level of integration than other parts of the library. 10 | //! 11 | //! See also [`crate::theme::Text`] which provides better integration with KAS 12 | //! theming and widget sizing operations. 13 | //! 14 | //! [KAS Text]: https://github.com/kas-gui/kas-text/ 15 | 16 | pub use kas_text::{ 17 | fonts, format, Align, Direction, Effect, EffectFlags, MarkerPos, MarkerPosIter, NotReady, 18 | OwningVecIter, Range, Status, Text, TextDisplay, Vec2, DPU, 19 | }; 20 | 21 | mod selection; 22 | pub use selection::{SelectionAction, SelectionHelper}; 23 | 24 | mod string; 25 | pub use string::AccessString; 26 | -------------------------------------------------------------------------------- /crates/kas-core/src/theme/anim.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Animation helpers 7 | 8 | use crate::draw::DrawImpl; 9 | use crate::Id; 10 | use std::marker::PhantomData; 11 | use std::time::{Duration, Instant}; 12 | 13 | #[derive(Debug)] 14 | struct Config { 15 | cursor_blink_rate: Duration, 16 | fade_dur: Duration, 17 | } 18 | 19 | /// State holding theme animation data 20 | #[derive(Debug)] 21 | pub struct AnimState { 22 | c: Config, 23 | now: Instant, // frame start time 24 | text_cursor: TextCursor, 25 | _d: PhantomData, 26 | } 27 | 28 | impl AnimState { 29 | pub fn new(config: &crate::config::ThemeConfig) -> Self { 30 | let c = Config { 31 | cursor_blink_rate: config.cursor_blink_rate(), 32 | fade_dur: config.transition_fade_duration(), 33 | }; 34 | let now = Instant::now(); 35 | AnimState { 36 | c, 37 | now, 38 | text_cursor: TextCursor { 39 | widget: 0, 40 | byte: 0, 41 | state: false, 42 | time: now, 43 | }, 44 | _d: PhantomData, 45 | } 46 | } 47 | 48 | pub fn update(&mut self) { 49 | self.now = Instant::now(); 50 | } 51 | 52 | fn elapsed(&self, time: Instant) -> Option { 53 | if self.now > time { 54 | Some(self.now - time) 55 | } else { 56 | None 57 | } 58 | } 59 | } 60 | 61 | #[derive(Clone, Copy, Debug)] 62 | struct TextCursor { 63 | widget: u64, 64 | byte: usize, 65 | state: bool, 66 | time: Instant, 67 | } 68 | impl AnimState { 69 | /// Flashing text cursor: return true to draw 70 | /// 71 | /// Assumption: only one widget may draw a text cursor at any time. 72 | pub fn text_cursor(&mut self, draw: &mut D, id: &Id, byte: usize) -> bool { 73 | let entry = &mut self.text_cursor; 74 | if entry.widget == id.as_u64() && entry.byte == byte { 75 | if entry.time < self.now { 76 | entry.state = !entry.state; 77 | entry.time += self.c.cursor_blink_rate; 78 | } 79 | draw.animate_at(entry.time); 80 | entry.state 81 | } else { 82 | entry.widget = id.as_u64(); 83 | entry.byte = byte; 84 | entry.state = true; 85 | entry.time = self.now + self.c.cursor_blink_rate; 86 | draw.animate_at(entry.time); 87 | true 88 | } 89 | } 90 | } 91 | 92 | impl AnimState { 93 | /// Fade over a boolean transition 94 | /// 95 | /// Normally returns `1.0` if `state` else `0.0`, but within a short time 96 | /// after a state change will linearly transition between these values. 97 | pub fn fade_bool(&mut self, draw: &mut D, state: bool, last_change: Option) -> f32 { 98 | if let Some(dur) = last_change.and_then(|inst| self.elapsed(inst)) { 99 | if dur < self.c.fade_dur { 100 | draw.animate(); 101 | let f = dur.as_secs_f32() / self.c.fade_dur.as_secs_f32(); 102 | return if state { f } else { 1.0 - f }; 103 | } 104 | } 105 | state as u8 as f32 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /crates/kas-core/src/theme/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Theme API and sample implementations 7 | //! 8 | //! Widgets expect the theme to provide an implementation of [`SizeCx`] and of 9 | //! [`DrawCx`]. 10 | //! 11 | //! Launching a UI requires a [`Theme`]. Two 12 | //! implementations are provided here: [`SimpleTheme`] and [`FlatTheme`]. 13 | //! An adapter, [`MultiTheme`], is also provided. 14 | 15 | mod anim; 16 | mod colors; 17 | mod draw; 18 | mod flat_theme; 19 | mod multi; 20 | mod simple_theme; 21 | mod size; 22 | mod style; 23 | mod text; 24 | mod theme_dst; 25 | mod traits; 26 | 27 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 28 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 29 | pub mod dimensions; 30 | 31 | pub use colors::{Colors, ColorsLinear, ColorsSrgb, InputState}; 32 | pub use draw::{Background, DrawCx}; 33 | pub use flat_theme::FlatTheme; 34 | pub use multi::{MultiTheme, MultiThemeBuilder}; 35 | pub use simple_theme::SimpleTheme; 36 | pub use size::SizeCx; 37 | pub use style::*; 38 | pub use text::{SizableText, Text}; 39 | pub use theme_dst::ThemeDst; 40 | pub use traits::{Theme, Window}; 41 | 42 | #[cfg_attr(not(feature = "internal_doc"), doc(hidden))] 43 | #[cfg_attr(docsrs, doc(cfg(internal_doc)))] 44 | pub use {draw::ThemeDraw, size::ThemeSize}; 45 | -------------------------------------------------------------------------------- /crates/kas-core/src/theme/multi.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Wrapper around mutliple themes, supporting run-time switching 7 | 8 | use super::{ColorsLinear, Theme, ThemeDst, Window}; 9 | use crate::config::{Config, WindowConfig}; 10 | use crate::draw::{color, DrawIface, DrawSharedImpl}; 11 | use crate::event::EventState; 12 | use crate::theme::ThemeDraw; 13 | use std::cell::RefCell; 14 | use std::collections::HashMap; 15 | 16 | type DynTheme = Box>; 17 | 18 | /// Wrapper around multiple themes, supporting run-time switching 19 | pub struct MultiTheme { 20 | names: HashMap, 21 | themes: Vec>, 22 | active: usize, 23 | } 24 | 25 | /// Builder for [`MultiTheme`] 26 | /// 27 | /// Construct via [`MultiTheme::builder`]. 28 | pub struct MultiThemeBuilder { 29 | names: HashMap, 30 | themes: Vec>, 31 | } 32 | 33 | impl MultiTheme { 34 | /// Construct with builder pattern 35 | pub fn builder() -> MultiThemeBuilder { 36 | MultiThemeBuilder { 37 | names: HashMap::new(), 38 | themes: vec![], 39 | } 40 | } 41 | } 42 | 43 | impl MultiThemeBuilder { 44 | /// Add a theme 45 | #[must_use] 46 | pub fn add(mut self, name: S, theme: T) -> Self 47 | where 48 | DS: DrawSharedImpl, 49 | T: ThemeDst + 'static, 50 | { 51 | let index = self.themes.len(); 52 | self.names.insert(name.to_string(), index); 53 | self.themes.push(Box::new(theme)); 54 | self 55 | } 56 | 57 | /// Build 58 | /// 59 | /// Returns `None` if no themes were added. 60 | pub fn try_build(self) -> Option> { 61 | if self.themes.is_empty() { 62 | return None; 63 | } 64 | Some(MultiTheme { 65 | names: self.names, 66 | themes: self.themes, 67 | active: 0, 68 | }) 69 | } 70 | 71 | /// Build 72 | /// 73 | /// Panics if no themes were added. 74 | pub fn build(self) -> MultiTheme { 75 | self.try_build() 76 | .unwrap_or_else(|| panic!("MultiThemeBuilder: no themes added")) 77 | } 78 | } 79 | 80 | impl Theme for MultiTheme { 81 | type Window = Box; 82 | type Draw<'a> = Box; 83 | 84 | fn init(&mut self, config: &RefCell) { 85 | if config.borrow().theme.active_theme.is_empty() { 86 | for (name, index) in &self.names { 87 | if *index == self.active { 88 | let _ = config.borrow_mut().theme.set_active_theme(name.to_string()); 89 | break; 90 | } 91 | } 92 | } 93 | 94 | for theme in &mut self.themes { 95 | theme.init(config); 96 | } 97 | } 98 | 99 | fn new_window(&mut self, config: &WindowConfig) -> Self::Window { 100 | // We may switch themes here 101 | let theme = &config.theme().active_theme; 102 | if let Some(index) = self.names.get(theme).cloned() { 103 | if index != self.active { 104 | self.active = index; 105 | } 106 | } 107 | 108 | self.themes[self.active].new_window(config) 109 | } 110 | 111 | fn update_window(&mut self, window: &mut Self::Window, config: &WindowConfig) { 112 | self.themes[self.active].update_window(window, config); 113 | } 114 | 115 | fn draw<'a>( 116 | &'a self, 117 | draw: DrawIface<'a, DS>, 118 | ev: &'a mut EventState, 119 | window: &'a mut Self::Window, 120 | ) -> Box { 121 | self.themes[self.active].draw(draw, ev, window) 122 | } 123 | 124 | fn draw_upcast<'a>( 125 | _draw: DrawIface<'a, DS>, 126 | _ev: &'a mut EventState, 127 | _w: &'a mut Self::Window, 128 | _cols: &'a ColorsLinear, 129 | ) -> Self::Draw<'a> { 130 | unimplemented!() 131 | } 132 | 133 | fn clear_color(&self) -> color::Rgba { 134 | self.themes[self.active].clear_color() 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /crates/kas-core/src/theme/theme_dst.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Stack-DST versions of theme traits 7 | 8 | use std::cell::RefCell; 9 | 10 | use super::{Theme, Window}; 11 | use crate::config::{Config, WindowConfig}; 12 | use crate::draw::{color, DrawIface, DrawSharedImpl}; 13 | use crate::event::EventState; 14 | use crate::theme::ThemeDraw; 15 | 16 | /// As [`Theme`], but without associated types 17 | /// 18 | /// This trait is implemented automatically for all implementations of 19 | /// [`Theme`]. It is intended only for use where a less parameterised 20 | /// trait is required. 21 | pub trait ThemeDst { 22 | /// Theme initialisation 23 | /// 24 | /// See also [`Theme::init`]. 25 | fn init(&mut self, config: &RefCell); 26 | 27 | /// Construct per-window storage 28 | /// 29 | /// See also [`Theme::new_window`]. 30 | fn new_window(&mut self, config: &WindowConfig) -> Box; 31 | 32 | /// Update a window created by [`Theme::new_window`] 33 | /// 34 | /// See also [`Theme::update_window`]. 35 | fn update_window(&mut self, window: &mut dyn Window, config: &WindowConfig); 36 | 37 | fn draw<'a>( 38 | &'a self, 39 | draw: DrawIface<'a, DS>, 40 | ev: &'a mut EventState, 41 | window: &'a mut dyn Window, 42 | ) -> Box; 43 | 44 | /// Background colour 45 | /// 46 | /// See also [`Theme::clear_color`]. 47 | fn clear_color(&self) -> color::Rgba; 48 | } 49 | 50 | impl> ThemeDst for T { 51 | fn init(&mut self, config: &RefCell) { 52 | self.init(config); 53 | } 54 | 55 | fn new_window(&mut self, config: &WindowConfig) -> Box { 56 | let window = >::new_window(self, config); 57 | Box::new(window) 58 | } 59 | 60 | fn update_window(&mut self, window: &mut dyn Window, config: &WindowConfig) { 61 | let window = window.as_any_mut().downcast_mut().unwrap(); 62 | self.update_window(window, config); 63 | } 64 | 65 | fn draw<'b>( 66 | &'b self, 67 | draw: DrawIface<'b, DS>, 68 | ev: &'b mut EventState, 69 | window: &'b mut dyn Window, 70 | ) -> Box { 71 | let window = window.as_any_mut().downcast_mut().unwrap(); 72 | Box::new(>::draw(self, draw, ev, window)) 73 | } 74 | 75 | fn clear_color(&self) -> color::Rgba { 76 | self.clear_color() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /crates/kas-core/src/theme/traits.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Theme traits 7 | 8 | use super::{ColorsLinear, ThemeDraw, ThemeSize}; 9 | use crate::autoimpl; 10 | use crate::config::{Config, WindowConfig}; 11 | use crate::draw::{color, DrawIface, DrawSharedImpl}; 12 | use crate::event::EventState; 13 | use std::any::Any; 14 | use std::cell::RefCell; 15 | 16 | #[allow(unused)] use crate::event::EventCx; 17 | 18 | /// A *theme* provides widget sizing and drawing implementations. 19 | /// 20 | /// The theme is generic over some `DrawIface`. 21 | /// 22 | /// Objects of this type are copied within each window's data structure. For 23 | /// large resources (e.g. fonts and icons) consider using external storage. 24 | #[autoimpl(for Box)] 25 | pub trait Theme { 26 | /// The associated [`Window`] implementation. 27 | type Window: Window; 28 | 29 | /// The associated [`ThemeDraw`] implementation. 30 | type Draw<'a>: ThemeDraw 31 | where 32 | DS: 'a, 33 | Self: 'a; 34 | 35 | /// Theme initialisation 36 | /// 37 | /// The toolkit must call this method before [`Theme::new_window`] 38 | /// to allow initialisation specific to the `DrawIface`. 39 | fn init(&mut self, config: &RefCell); 40 | 41 | /// Construct per-window storage 42 | /// 43 | /// Updates theme from configuration and constructs a scaled per-window size 44 | /// cache. 45 | /// 46 | /// On "standard" monitors, the `dpi_factor` is 1. High-DPI screens may 47 | /// have a factor of 2 or higher. The factor may not be an integer; e.g. 48 | /// `9/8 = 1.125` works well with many 1440p screens. It is recommended to 49 | /// round dimensions to the nearest integer, and cache the result: 50 | /// ```notest 51 | /// self.margin = i32::conv_nearest(MARGIN * factor); 52 | /// ``` 53 | /// 54 | /// A reference to the draw backend is provided allowing configuration. 55 | fn new_window(&mut self, config: &WindowConfig) -> Self::Window; 56 | 57 | /// Update a window created by [`Theme::new_window`] 58 | /// 59 | /// This is called when the DPI factor changes or theme config or dimensions change. 60 | fn update_window(&mut self, window: &mut Self::Window, config: &WindowConfig); 61 | 62 | /// Prepare to draw and construct a [`ThemeDraw`] object 63 | /// 64 | /// This is called once per window per frame and should do any necessary 65 | /// preparation such as loading fonts and textures which are loaded on 66 | /// demand. 67 | /// 68 | /// Drawing via this [`ThemeDraw`] object is restricted to the specified `rect`. 69 | /// 70 | /// The `window` is guaranteed to be one created by a call to 71 | /// [`Theme::new_window`] on `self`. 72 | fn draw<'a>( 73 | &'a self, 74 | draw: DrawIface<'a, DS>, 75 | ev: &'a mut EventState, 76 | window: &'a mut Self::Window, 77 | ) -> Self::Draw<'a>; 78 | 79 | /// Construct a draw object from parts 80 | /// 81 | /// This method allows a "derived" theme to construct a draw object for the 82 | /// inherited theme. 83 | fn draw_upcast<'a>( 84 | draw: DrawIface<'a, DS>, 85 | ev: &'a mut EventState, 86 | w: &'a mut Self::Window, 87 | cols: &'a ColorsLinear, 88 | ) -> Self::Draw<'a>; 89 | 90 | /// The window/scene clear color 91 | /// 92 | /// This is not used when the window is transparent. 93 | fn clear_color(&self) -> color::Rgba; 94 | } 95 | 96 | /// Per-window storage for the theme 97 | /// 98 | /// Constructed via [`Theme::new_window`]. 99 | /// 100 | /// The main reason for this separation is to allow proper handling of 101 | /// multi-window applications across screens with differing DPIs. 102 | #[autoimpl(for Box)] 103 | pub trait Window: 'static { 104 | /// Construct a [`ThemeSize`] object 105 | fn size(&self) -> &dyn ThemeSize; 106 | 107 | fn as_any_mut(&mut self) -> &mut dyn Any; 108 | } 109 | -------------------------------------------------------------------------------- /crates/kas-dylib/COPYRIGHT: -------------------------------------------------------------------------------- 1 | This work is copyrighted by the following contributors: 2 | 3 | Diggory Hardy 4 | 5 | This list may be incomplete. 6 | -------------------------------------------------------------------------------- /crates/kas-dylib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kas-dylib" 3 | version = "0.15.0" 4 | authors = ["Diggory Hardy "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description = "KAS GUI / dylib" 8 | readme = "README.md" 9 | documentation = "https://docs.rs/kas-dylib/" 10 | keywords = ["gui"] 11 | categories = ["gui"] 12 | repository = "https://github.com/kas-gui/kas" 13 | 14 | [package.metadata.docs.rs] 15 | features = ["docs_rs"] 16 | 17 | [lib] 18 | crate-type = ["dylib"] 19 | 20 | [features] 21 | resvg = ["dep:kas-resvg"] 22 | 23 | # Non-local features required for doc builds. 24 | # Note: docs.rs does not support direct usage of transitive features. 25 | docs_rs = ["kas-core/winit", "kas-core/wayland"] 26 | 27 | [dependencies] 28 | kas-core = { version = "0.15.0", path = "../kas-core" } 29 | kas-widgets = { version = "0.15.0", path = "../kas-widgets" } 30 | kas-resvg = { version = "0.15.0", path = "../kas-resvg", optional = true } 31 | kas-wgpu = { version = "0.15.0", path = "../kas-wgpu", default-features = false } 32 | -------------------------------------------------------------------------------- /crates/kas-dylib/README.md: -------------------------------------------------------------------------------- 1 | KAS dylib 2 | ====== 3 | 4 | Support crate for dynamic linking. 5 | Recommended usage is to depend on [`kas`](https://crates.io/crates/kas) with the `dynamic` feature. 6 | 7 | 8 | Copyright and Licence 9 | ------- 10 | 11 | The [COPYRIGHT](COPYRIGHT) file includes a list of contributors who claim 12 | copyright on this project. This list may be incomplete; new contributors may 13 | optionally add themselves to this list. 14 | 15 | The KAS library is published under the terms of the Apache License, Version 2.0. 16 | You may obtain a copy of this licence from the [LICENSE](LICENSE) file or on 17 | the following webpage: 18 | -------------------------------------------------------------------------------- /crates/kas-dylib/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! KAS GUI dylib 7 | //! 8 | //! Using this library forces dynamic linking, which can make builds much 9 | //! faster. It may be preferable only to use this in debug builds. 10 | 11 | #![allow(unused_imports)] 12 | #![allow(clippy::single_component_path_imports)] 13 | 14 | use kas_core; 15 | #[cfg(feature = "resvg")] use kas_resvg; 16 | use kas_wgpu; 17 | use kas_widgets; 18 | -------------------------------------------------------------------------------- /crates/kas-macros/COPYRIGHT: -------------------------------------------------------------------------------- 1 | This work is copyrighted by the following contributors: 2 | 3 | Diggory Hardy 4 | 5 | This list may be incomplete. 6 | -------------------------------------------------------------------------------- /crates/kas-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kas-macros" 3 | version = "0.15.0" 4 | authors = ["Diggory Hardy "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description = "KAS GUI / macros" 8 | keywords = ["gui", "proc-macro"] 9 | categories = ["gui"] 10 | repository = "https://github.com/kas-gui/kas" 11 | readme = "README.md" 12 | documentation = "https://docs.rs/kas-macros/" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [features] 18 | # Inject logging into macro-generated code. 19 | # Requires that all crates using these macros depend on the log crate. 20 | log = [] 21 | 22 | # Enable reporting of warnings from proc-macros 23 | nightly = ["proc-macro-error2/nightly"] 24 | 25 | [dependencies] 26 | quote = "1.0" 27 | proc-macro2 = { version = "1.0" } 28 | proc-macro-error2 = { version = "2.0", default-features = false } 29 | bitflags = "2.3.3" 30 | 31 | [dependencies.impl-tools-lib] 32 | version = "0.11.0" # version used in doc links 33 | 34 | [dependencies.syn] 35 | version = "2.0.22" 36 | # We need 'extra-traits' for equality testing 37 | # We need 'full' for parsing macros within macro arguments 38 | features = ["extra-traits", "full", "visit", "visit-mut"] 39 | 40 | [build-dependencies] 41 | version_check = "0.9" 42 | 43 | [lints.clippy] 44 | collapsible_if = "allow" 45 | collapsible_else_if = "allow" 46 | unit_arg = "allow" 47 | -------------------------------------------------------------------------------- /crates/kas-macros/README.md: -------------------------------------------------------------------------------- 1 | KAS Macros 2 | ======== 3 | 4 | This is a sub-library of [KAS] for its procedural macros. 5 | 6 | Users are advised not to depend on this library directly, but instead rely on 7 | the main [KAS] lib, which re-exports these macros in its API. 8 | 9 | [KAS]: https://crates.io/crates/kas 10 | 11 | 12 | Stable vs nightly 13 | ----------------- 14 | 15 | Note that proc macros may emit error messages on stable rust, but currently can 16 | only emit warnings with nightly `rustc`. 17 | 18 | 19 | Copyright and Licence 20 | ------- 21 | 22 | The [COPYRIGHT](COPYRIGHT) file includes a list of contributors who claim 23 | copyright on this project. This list may be incomplete; new contributors may 24 | optionally add themselves to this list. 25 | 26 | The KAS library is published under the terms of the Apache License, Version 2.0. 27 | You may obtain a copy of this licence from the [LICENSE](LICENSE) file or on 28 | the following webpage: 29 | -------------------------------------------------------------------------------- /crates/kas-macros/src/scroll_traits.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | use impl_tools_lib::autoimpl::{Error, ImplArgs, ImplTrait, Result}; 7 | use impl_tools_lib::SimplePath; 8 | use proc_macro2::TokenStream as Toks; 9 | use quote::quote; 10 | use syn::ItemStruct; 11 | 12 | pub struct ImplScrollable; 13 | impl ImplTrait for ImplScrollable { 14 | fn path(&self) -> SimplePath { 15 | SimplePath::new(&["", "kas", "Scrollable"]) 16 | } 17 | 18 | fn support_ignore(&self) -> bool { 19 | false 20 | } 21 | 22 | fn support_using(&self) -> bool { 23 | true 24 | } 25 | 26 | fn struct_items(&self, _: &ItemStruct, args: &ImplArgs) -> Result<(Toks, Toks)> { 27 | if let Some(using) = args.using_member() { 28 | let methods = quote! { 29 | #[inline] 30 | fn scroll_axes(&self, size: ::kas::geom::Size) -> (bool, bool) { 31 | self.#using.scroll_axes(size) 32 | } 33 | #[inline] 34 | fn max_scroll_offset(&self) -> ::kas::geom::Offset { 35 | self.#using.max_scroll_offset() 36 | } 37 | #[inline] 38 | fn scroll_offset(&self) -> ::kas::geom::Offset { 39 | self.#using.scroll_offset() 40 | } 41 | #[inline] 42 | fn set_scroll_offset( 43 | &mut self, 44 | cx: &mut ::kas::event::EventCx, 45 | offset: ::kas::geom::Offset, 46 | ) -> ::kas::geom::Offset { 47 | self.#using.set_scroll_offset(cx, offset) 48 | } 49 | }; 50 | Ok((quote! { ::kas::Scrollable }, methods)) 51 | } else { 52 | Err(Error::RequireUsing) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/kas-resvg/COPYRIGHT: -------------------------------------------------------------------------------- 1 | This work is copyrighted by the following contributors: 2 | 3 | Diggory Hardy 4 | 5 | This list may be incomplete. 6 | -------------------------------------------------------------------------------- /crates/kas-resvg/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kas-resvg" 3 | version = "0.15.0" 4 | authors = ["Diggory Hardy "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description = "KAS GUI / widgets" 8 | readme = "README.md" 9 | documentation = "https://docs.rs/kas-widgets/" 10 | keywords = ["gui"] 11 | categories = ["gui"] 12 | repository = "https://github.com/kas-gui/kas" 13 | exclude = ["/screenshots"] 14 | 15 | [package.metadata.docs.rs] 16 | features = ["docs_rs", "svg"] 17 | rustdoc-args = ["--cfg", "docsrs"] 18 | 19 | [features] 20 | # Non-local features required for doc builds. 21 | # Note: docs.rs does not support direct usage of transitive features. 22 | docs_rs = ["kas/winit", "kas/wayland"] 23 | 24 | # Support SVG images 25 | svg = ["dep:resvg", "dep:usvg"] 26 | 27 | [dependencies] 28 | tiny-skia = { version = "0.11.0" } 29 | resvg = { version = "0.45.0", optional = true } 30 | usvg = { version = "0.45.0", optional = true } 31 | once_cell = "1.17.0" 32 | thiserror = "2.0.3" 33 | 34 | [dependencies.kas] 35 | # We must rename this package since macros expect kas to be in scope: 36 | version = "0.15.0" 37 | package = "kas-core" 38 | path = "../kas-core" 39 | features = ["spawn"] 40 | -------------------------------------------------------------------------------- /crates/kas-resvg/README.md: -------------------------------------------------------------------------------- 1 | KAS resvg 2 | ====== 3 | 4 | This crate provides `Svg` and `Canvas` widgets for [KAS] using the [tiny-skia] and 5 | [resvg] libraries by [Yevhenii Reizner "RazrFalcon"](https://github.com/RazrFalcon/). 6 | 7 | [KAS]: https://crates.io/crates/kas 8 | [tiny-skia]: https://crates.io/crates/tiny-skia 9 | [resvg]: https://crates.io/crates/resvg 10 | 11 | For documentation of feature flags, see [Cargo.toml](Cargo.toml). 12 | 13 | 14 | Copyright and Licence 15 | ------- 16 | 17 | The [COPYRIGHT](COPYRIGHT) file includes a list of contributors who claim 18 | copyright on this project. This list may be incomplete; new contributors may 19 | optionally add themselves to this list. 20 | 21 | The KAS library is published under the terms of the Apache License, Version 2.0. 22 | You may obtain a copy of this licence from the [LICENSE](LICENSE) file or on 23 | the following webpage: 24 | -------------------------------------------------------------------------------- /crates/kas-resvg/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! KAS resvg & tiny-skia integration 7 | //! 8 | //! This crate provides [`Svg`] and [`Canvas`] widgets using the [tiny-skia] and 9 | //! [resvg] libraries by [Yevhenii Reizner "RazrFalcon"](https://github.com/RazrFalcon/). 10 | //! 11 | //! [tiny-skia]: https://crates.io/crates/tiny-skia 12 | //! [resvg]: https://crates.io/crates/resvg 13 | 14 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 15 | 16 | pub extern crate tiny_skia; 17 | 18 | mod canvas; 19 | pub use canvas::{Canvas, CanvasProgram}; 20 | 21 | #[cfg(feature = "svg")] mod svg; 22 | #[cfg(feature = "svg")] pub use svg::Svg; 23 | -------------------------------------------------------------------------------- /crates/kas-view/COPYRIGHT: -------------------------------------------------------------------------------- 1 | This work is copyrighted by the following contributors: 2 | 3 | Diggory Hardy 4 | 5 | This list may be incomplete. 6 | -------------------------------------------------------------------------------- /crates/kas-view/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kas-view" 3 | version = "0.15.0" 4 | authors = ["Diggory Hardy "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description = "KAS GUI / view widgets" 8 | readme = "README.md" 9 | documentation = "https://docs.rs/kas-view/" 10 | keywords = ["gui"] 11 | categories = ["gui"] 12 | repository = "https://github.com/kas-gui/kas" 13 | exclude = ["/screenshots"] 14 | 15 | [package.metadata.docs.rs] 16 | features = ["kas/winit", "kas/wayland"] 17 | rustdoc-args = ["--cfg", "docsrs"] 18 | 19 | [dependencies] 20 | kas-widgets = { version = "0.15.0", path = "../kas-widgets" } 21 | log = "0.4" 22 | linear-map = "1.2.0" 23 | 24 | # We must rename this package since macros expect kas to be in scope: 25 | kas = { version = "0.15.0", package = "kas-core", path = "../kas-core" } 26 | 27 | [lints.clippy] 28 | collapsible_else_if = "allow" 29 | needless_lifetimes = "allow" 30 | unit_arg = "allow" 31 | -------------------------------------------------------------------------------- /crates/kas-view/README.md: -------------------------------------------------------------------------------- 1 | KAS view 2 | ====== 3 | 4 | *View widgets* for [KAS], supporting views over data models. 5 | 6 | For documentation of feature flags, see [Cargo.toml](Cargo.toml). 7 | 8 | [KAS]: https://crates.io/crates/kas 9 | 10 | 11 | Copyright and Licence 12 | ------- 13 | 14 | The [COPYRIGHT](COPYRIGHT) file includes a list of contributors who claim 15 | copyright on this project. This list may be incomplete; new contributors may 16 | optionally add themselves to this list. 17 | 18 | The KAS library is published under the terms of the Apache License, Version 2.0. 19 | You may obtain a copy of this licence from the [LICENSE](LICENSE) file or on 20 | the following webpage: 21 | -------------------------------------------------------------------------------- /crates/kas-view/src/filter/filter_list.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Filter-list adapter 7 | 8 | use super::Filter; 9 | use crate::{DataClerk, Driver, ListView}; 10 | use kas::dir::{Direction, Directional}; 11 | use kas::event::{EventCx, EventState}; 12 | use kas::{autoimpl, impl_scope, Events, Widget}; 13 | use kas_widgets::edit::{EditBox, EditField, EditGuard}; 14 | use kas_widgets::{ScrollBarMode, ScrollBars}; 15 | use std::fmt::Debug; 16 | 17 | #[derive(Debug, Default)] 18 | pub struct SetFilter(pub T); 19 | 20 | /// An [`EditGuard`] which sends a [`SetFilter`] message on every change 21 | /// 22 | /// This may be used for search-as-you-type. 23 | pub struct KeystrokeGuard; 24 | impl EditGuard for KeystrokeGuard { 25 | type Data = (); 26 | 27 | fn edit(edit: &mut EditField, cx: &mut EventCx, _: &Self::Data) { 28 | cx.push(SetFilter(edit.as_str().to_string())); 29 | } 30 | } 31 | 32 | /// An [`EditGuard`] which sends a [`SetFilter`] message on activate and focus loss 33 | /// 34 | /// This may be used for search-as-you-type. 35 | pub struct AflGuard; 36 | impl EditGuard for AflGuard { 37 | type Data = (); 38 | 39 | #[inline] 40 | fn focus_lost(edit: &mut EditField, cx: &mut EventCx, _: &Self::Data) { 41 | cx.push(SetFilter(edit.as_str().to_string())); 42 | } 43 | } 44 | 45 | impl_scope! { 46 | /// An [`EditBox`] above a filtered [`ListView`] 47 | /// 48 | /// This is essentially just two widgets with "glue" to handle a 49 | /// [`SetFilter`] message from the [`EditBox`]. 50 | #[autoimpl(Scrollable using self.list)] 51 | #[widget { 52 | Data = (); 53 | layout = column! [ 54 | self.edit, 55 | self.list, 56 | ]; 57 | }] 58 | pub struct FilterBoxListView 59 | where 60 | F: Filter, 61 | A: DataClerk, 62 | V: Driver, 63 | G: EditGuard, 64 | D: Directional, 65 | { 66 | core: widget_core!(), 67 | filter: F, 68 | #[widget(&())] 69 | edit: EditBox, 70 | #[widget(&self.filter)] 71 | list: ScrollBars>, 72 | } 73 | 74 | impl Self { 75 | /// Construct 76 | /// 77 | /// Parameter `guard` may be [`KeystrokeGuard`], [`AflGuard`] or a 78 | /// custom implementation. 79 | pub fn new(filter: F, list: ListView, guard: G) -> Self { 80 | Self { 81 | core: Default::default(), 82 | filter, 83 | edit: EditBox::new(guard), 84 | list: ScrollBars::new(list), 85 | } 86 | } 87 | 88 | /// Set fixed visibility of scroll bars (inline) 89 | #[inline] 90 | pub fn with_fixed_bars(mut self, horiz: bool, vert: bool) -> Self 91 | where 92 | Self: Sized, 93 | { 94 | self.list = self.list.with_fixed_bars(horiz, vert); 95 | self 96 | } 97 | 98 | /// Set fixed, invisible bars (inline) 99 | /// 100 | /// In this mode scroll bars are either enabled but invisible until 101 | /// hovered by the mouse or disabled completely. 102 | #[inline] 103 | pub fn with_invisible_bars(mut self, horiz: bool, vert: bool) -> Self 104 | where 105 | Self: Sized, 106 | { 107 | self.list = self.list.with_invisible_bars(horiz, vert); 108 | self 109 | } 110 | 111 | /// Get current mode of scroll bars 112 | #[inline] 113 | pub fn scroll_bar_mode(&self) -> ScrollBarMode { 114 | self.list.scroll_bar_mode() 115 | } 116 | 117 | /// Set scroll bar mode 118 | pub fn set_scroll_bar_mode(&mut self, cx: &mut EventState, mode: ScrollBarMode) { 119 | self.list.set_scroll_bar_mode(cx, mode); 120 | } 121 | 122 | /// Access the inner list widget 123 | #[inline] 124 | pub fn list(&self) -> &ListView { 125 | self.list.inner() 126 | } 127 | 128 | /// Access the inner list widget mutably 129 | #[inline] 130 | pub fn list_mut(&mut self) -> &mut ListView { 131 | self.list.inner_mut() 132 | } 133 | } 134 | 135 | impl Events for Self { 136 | fn handle_messages(&mut self, cx: &mut EventCx, data: &()) { 137 | if let Some(SetFilter(value)) = cx.try_pop() { 138 | self.filter.set_filter(value); 139 | cx.update(self.as_node(data)); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /crates/kas-view/src/filter/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Filters over data 7 | 8 | mod filter_list; 9 | pub use filter_list::*; 10 | 11 | /// Ability to set filter 12 | pub trait FilterValue: Default + 'static { 13 | type Value: std::fmt::Debug; 14 | 15 | /// Update the filter 16 | fn set_filter(&mut self, value: Self::Value); 17 | } 18 | 19 | /// Types usable as a filter 20 | pub trait Filter: FilterValue { 21 | /// Returns true if the given item matches this filter 22 | fn matches(&self, item: &T) -> bool; 23 | } 24 | 25 | /// Filter: target contains self (case-sensitive string match) 26 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 27 | pub struct ContainsString(String); 28 | 29 | impl ContainsString { 30 | /// Construct with empty text 31 | pub fn new() -> Self { 32 | ContainsString(String::new()) 33 | } 34 | } 35 | 36 | impl FilterValue for ContainsString { 37 | type Value = String; 38 | fn set_filter(&mut self, value: String) { 39 | self.0 = value; 40 | } 41 | } 42 | 43 | impl Filter for ContainsString { 44 | fn matches(&self, item: &str) -> bool { 45 | item.contains(&self.0) 46 | } 47 | } 48 | impl Filter for ContainsString { 49 | fn matches(&self, item: &String) -> bool { 50 | Filter::::matches(self, item.as_str()) 51 | } 52 | } 53 | 54 | /// Filter: target contains self (case-insensitive string match) 55 | /// 56 | // Note: the implemented method of caseless matching is not unicode compliant, 57 | // however works in most cases (by converting both the source and the target to 58 | // upper case). See [question on StackOverflow]. 59 | // 60 | // [question on StackOverflow]: https://stackoverflow.com/questions/47298336/case-insensitive-string-matching-in-rust 61 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 62 | pub struct ContainsCaseInsensitive(String); 63 | 64 | impl ContainsCaseInsensitive { 65 | /// Construct with empty text 66 | pub fn new() -> Self { 67 | ContainsCaseInsensitive(String::new()) 68 | } 69 | } 70 | 71 | impl FilterValue for ContainsCaseInsensitive { 72 | type Value = String; 73 | fn set_filter(&mut self, value: String) { 74 | self.0 = value.to_uppercase(); 75 | } 76 | } 77 | 78 | impl Filter for ContainsCaseInsensitive { 79 | fn matches(&self, item: &str) -> bool { 80 | item.to_string().to_uppercase().contains(&self.0) 81 | } 82 | } 83 | impl Filter for ContainsCaseInsensitive { 84 | fn matches(&self, item: &String) -> bool { 85 | Filter::::matches(self, item.as_str()) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /crates/kas-view/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! # Views 7 | //! 8 | //! Views allow virtual scrolling and query views over a data set, supporting 9 | //! both sync and async access. 10 | //! 11 | //! Each visible data `Item` is assigned a **view widget**, with dynamic 12 | //! re-assignment as the view changes. 13 | //! 14 | //! ## Data sets and clerks 15 | //! 16 | //! The full data set might be available in local memory, on disk, or on a 17 | //! remote server. 18 | //! 19 | //! A [`DataClerk`] manages all interactions between the view and the data as 20 | //! well as providing a local cache of (at least) the currently visible data. 21 | //! 22 | //! ## View controller 23 | //! 24 | //! This crate provides the following **view controllers**: 25 | //! 26 | //! - [`ListView`] constructs a row or column view over items indexed by type `usize` 27 | //! - [`MatrixView`] constructs a table over items indexed by type `(u32, u32)` 28 | //! 29 | //! ## Driver 30 | //! 31 | //! A view controller uses a **driver** to construct and re-assign view widgets. 32 | //! Simple types (strings and numbers) may use a pre-defined [`driver`], 33 | //! otherwise a custom implementation of [`Driver`] is required. 34 | 35 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 36 | 37 | mod data_traits; 38 | pub use data_traits::*; 39 | 40 | pub mod filter; 41 | 42 | pub mod driver; 43 | pub use driver::Driver; 44 | 45 | mod list_view; 46 | pub use list_view::ListView; 47 | 48 | mod matrix_view; 49 | pub use matrix_view::{MatrixIndex, MatrixView}; 50 | 51 | /// Used to notify selection and deselection of [`ListView`] and [`MatrixView`] children 52 | #[derive(Clone, Debug)] 53 | pub enum SelectionMsg { 54 | /// Selection of item 55 | Select(K), 56 | /// Deselection of item 57 | /// 58 | /// Note: not emitted due to selection of another item in single-item selection mode. 59 | Deselect(K), 60 | } 61 | 62 | /// Selection mode used by [`ListView`] 63 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 64 | pub enum SelectionMode { 65 | /// Disable selection 66 | #[default] 67 | None, 68 | /// Support single-item selection. Selecting another item automatically 69 | /// clears the prior selection (without sending [`SelectionMsg::Deselect`]). 70 | Single, 71 | /// Support multi-item selection. 72 | Multiple, 73 | } 74 | -------------------------------------------------------------------------------- /crates/kas-wgpu/COPYRIGHT: -------------------------------------------------------------------------------- 1 | This work is copyrighted by the following contributors: 2 | 3 | Diggory Hardy 4 | 5 | This list may be incomplete. 6 | -------------------------------------------------------------------------------- /crates/kas-wgpu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kas-wgpu" 3 | version = "0.15.0" 4 | authors = ["Diggory Hardy "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description = "KAS GUI / wgpu front-end" 8 | keywords = ["gui", "wgpu"] 9 | categories = ["gui"] 10 | repository = "https://github.com/kas-gui/kas" 11 | readme = "README.md" 12 | documentation = "https://docs.rs/kas-wgpu/" 13 | 14 | [package.metadata.docs.rs] 15 | features = ["kas/winit", "kas/wayland"] 16 | rustdoc-args = ["--cfg", "docsrs"] 17 | 18 | [features] 19 | default = [] 20 | 21 | # Enables documentation of APIs for graphics library and platform backends. 22 | # This API is not intended for use by end-user applications and 23 | # thus is omitted from built documentation by default. 24 | # This flag does not change the API, only built documentation. 25 | internal_doc = [] 26 | 27 | # WGPU backends 28 | vulkan = ["wgpu/vulkan"] 29 | gles = ["wgpu/gles"] 30 | dx12 = ["wgpu/dx12"] 31 | metal = ["wgpu/metal"] 32 | 33 | shaping = ["kas-text/shaping"] 34 | 35 | # Enable ab_glyph backend (redundant) 36 | ab_glyph = ["kas-text/ab_glyph", "dep:ab_glyph"] 37 | 38 | [dependencies] 39 | bytemuck = "1.7.0" 40 | futures-lite = "2.0" 41 | log = "0.4" 42 | thiserror = "2.0.3" 43 | guillotiere = "0.6.0" 44 | rustc-hash = "2.0" 45 | ab_glyph = { version = "0.2.10", optional = true } 46 | 47 | [dependencies.kas] 48 | # Rename package purely for convenience: 49 | version = "0.15.0" 50 | package = "kas-core" 51 | path = "../kas-core" 52 | 53 | [dependencies.kas-text] 54 | version = "0.8.0" 55 | 56 | [dependencies.swash] 57 | version = "0.2.4" 58 | features = ["scale"] 59 | 60 | [dependencies.wgpu] 61 | version = "25.0.0" 62 | default-features = false 63 | features = ["spirv"] 64 | 65 | [build-dependencies] 66 | glob = "0.3" 67 | 68 | [lints.clippy] 69 | needless_lifetimes = "allow" 70 | unit_arg = "allow" 71 | -------------------------------------------------------------------------------- /crates/kas-wgpu/README.md: -------------------------------------------------------------------------------- 1 | KAS WGPU 2 | ====== 3 | 4 | [KAS] graphics backend over [wgpu]. 5 | 6 | [KAS]: https://crates.io/crates/kas 7 | [wgpu]: https://github.com/gfx-rs/wgpu-rs 8 | 9 | 10 | Compiling shaders 11 | ----------------- 12 | 13 | This library uses GLSL shaders. Pre-compiled SPIR-V modules are distributed so 14 | that users do not need a shader compiler. 15 | 16 | For working on GLSL shaders, a compiler such as `glslc` (part of the [shaderc] 17 | project) is required. `glslc` can be installed from Fedora packages, but on 18 | other platforms manual installation may be required. Alternatively a web-based 19 | tool such as [glslang.js] may be used. 20 | 21 | Automatic re-compilation may be enabled by setting e.g. `SHADERC=glslc`. 22 | See [`build.rs`](build.rs) for details. 23 | 24 | [glslang.js]: https://alexaltea.github.io/glslang.js/ 25 | [shaderc]: https://github.com/google/shaderc 26 | 27 | 28 | Optional features 29 | ------- 30 | 31 | This crate has the following feature flags: 32 | 33 | - `raster` (enabled by default): use [kas-text]'s default backend for glyph 34 | rastering (alternatively, specify `kas-text/ab_glyph` or `kas-text/fontdue`) 35 | - `shaping` (enabled by default): use [kas-text]'s default backend (Rustybuzz) 36 | for text shaping (alternatively, specify `kas-text/harfbuzz` or do not use 37 | shaping) 38 | 39 | Note: at least one of `ab_glyph`, `fontdue` is required. If both are enabled, 40 | the choice of raster engine is controlled at run-time via theme configuration: 41 | 42 | - `mode = 0`: use `ab_glyph` 43 | - `mode = 1`: use `ab_glyph` and align glyphs to side-bearing 44 | - `mode = 2`: use `fontdue` 45 | 46 | [ab_glyph]: https://crates.io/crates/ab_glyph 47 | [fontdue]: https://crates.io/crates/fontdue 48 | 49 | Copyright and Licence 50 | ------- 51 | 52 | The [COPYRIGHT](COPYRIGHT) file includes a list of contributors who claim 53 | copyright on this project. This list may be incomplete; new contributors may 54 | optionally add themselves to this list. 55 | 56 | The KAS library is published under the terms of the Apache License, Version 2.0. 57 | You may obtain a copy of this licence from the [LICENSE](LICENSE) file or on 58 | the following webpage: 59 | -------------------------------------------------------------------------------- /crates/kas-wgpu/build.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Build script — shader compiler 7 | //! 8 | //! This script scans the directory (of the Cargo.toml manifest) for `*.vert` 9 | //! and `*.frag` files, and compiles each to `*.vert.spv` etc., but only if 10 | //! missing or out-of-date. 11 | //! 12 | //! To enable shader compilation, install a compiler such as glslc and set 13 | //! `SHADERC=`. For example, add this to your `~/.bash_profile`: 14 | //! ``` 15 | //! export SHADERC=glslc 16 | //! ``` 17 | //! 18 | //! Warning: change detection is not perfect: this script will not automatically 19 | //! be run when new `.vert` or `.frag` files are created. The easiest way to fix 20 | //! this is to touch (re-save) any existing `.vert`/`.frag` file. 21 | 22 | #![deny(warnings)] 23 | 24 | use glob::glob; 25 | use std::env; 26 | use std::path::PathBuf; 27 | use std::process::{Child, Command}; 28 | 29 | fn main() { 30 | let mut runners = Vec::new(); 31 | 32 | println!("cargo:rerun-if-env-changed=SHADERC"); 33 | let shaderc = match env::var("SHADERC") { 34 | Ok(s) => Some(s), 35 | Err(env::VarError::NotPresent) => None, 36 | Err(e) => panic!("failed to read env var SHADERC: {e}"), 37 | }; 38 | 39 | let mut pat = env::var("CARGO_MANIFEST_DIR").unwrap(); 40 | pat.push_str("/**/*.vert"); 41 | walk(&pat, &shaderc, &mut runners); 42 | pat.replace_range((pat.len() - 4).., "frag"); 43 | walk(&pat, &shaderc, &mut runners); 44 | 45 | for mut r in runners { 46 | let status = r.wait().unwrap(); 47 | if !status.success() { 48 | panic!("Shader compilation failed (exit code {:?})", status.code()); 49 | } 50 | } 51 | } 52 | 53 | fn walk(pat: &str, shaderc: &Option, runners: &mut Vec) { 54 | for path in glob(pat).unwrap().filter_map(Result::ok) { 55 | println!("cargo:rerun-if-changed={}", path.display()); 56 | 57 | let mut path_spv = path.clone().into_os_string(); 58 | path_spv.push(".spv"); 59 | let path_spv = PathBuf::from(path_spv); 60 | let gen = match path_spv.metadata() { 61 | Ok(meta) => { 62 | let orig_meta = path.metadata().unwrap(); 63 | orig_meta.modified().unwrap() > meta.modified().unwrap() 64 | } 65 | Err(_) => true, 66 | }; 67 | if gen { 68 | if let Some(bin) = shaderc.as_ref() { 69 | let mut cmd = Command::new(bin); 70 | cmd.arg(&path).arg("-o").arg(&path_spv); 71 | eprintln!("Launching: {cmd:?}"); 72 | runners.push(cmd.spawn().expect("shader compiler failed to start")); 73 | } else { 74 | eprintln!( 75 | "cargo:warning=Shader compilation required: {}", 76 | path.display() 77 | ); 78 | eprintln!("cargo:warning=No shader found. If you have a shader compiler such as glslc installed, try setting SHADERC=glslc"); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/common.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Common pipeline parts 7 | 8 | use kas::autoimpl; 9 | use kas::cast::Conv; 10 | use std::mem::size_of; 11 | use std::num::NonZeroU64; 12 | use std::ops::Range; 13 | 14 | #[autoimpl(Default)] 15 | #[derive(Clone, Debug)] 16 | struct PassData { 17 | vertices: Vec, 18 | count: u32, 19 | data_range: Range, 20 | } 21 | 22 | /// Per-window state 23 | #[autoimpl(Default)] 24 | pub struct Window { 25 | passes: Vec>, 26 | buffer: Option, 27 | buffer_size: u64, 28 | } 29 | 30 | impl Window { 31 | /// Prepare vertex buffers 32 | pub fn write_buffers( 33 | &mut self, 34 | device: &wgpu::Device, 35 | staging_belt: &mut wgpu::util::StagingBelt, 36 | encoder: &mut wgpu::CommandEncoder, 37 | ) { 38 | let req_len = self 39 | .passes 40 | .iter() 41 | .map(|pd| u64::conv(pd.vertices.len() * size_of::())) 42 | .sum(); 43 | let byte_len = match NonZeroU64::new(req_len) { 44 | Some(nz) => nz, 45 | None => { 46 | for pass in self.passes.iter_mut() { 47 | pass.count = 0; 48 | } 49 | return; 50 | } 51 | }; 52 | 53 | if req_len <= self.buffer_size { 54 | let buffer = self.buffer.as_ref().unwrap(); 55 | let mut slice = staging_belt.write_buffer(encoder, buffer, 0, byte_len, device); 56 | copy_to_slice(&mut self.passes, &mut slice); 57 | } else { 58 | // Size must be a multiple of alignment 59 | let mask = wgpu::COPY_BUFFER_ALIGNMENT - 1; 60 | let buffer_size = (byte_len.get() + mask) & !mask; 61 | let buffer = device.create_buffer(&wgpu::BufferDescriptor { 62 | label: Some("vertex buffer"), 63 | size: buffer_size, 64 | usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, 65 | mapped_at_creation: true, 66 | }); 67 | 68 | let mut slice = buffer.slice(..byte_len.get()).get_mapped_range_mut(); 69 | copy_to_slice(&mut self.passes, &mut slice); 70 | drop(slice); 71 | 72 | buffer.unmap(); 73 | self.buffer = Some(buffer); 74 | self.buffer_size = buffer_size; 75 | } 76 | 77 | fn copy_to_slice(passes: &mut [PassData], slice: &mut [u8]) { 78 | let mut byte_offset = 0; 79 | for pass in passes.iter_mut() { 80 | let len = u32::conv(pass.vertices.len()); 81 | let byte_len = u64::from(len) * u64::conv(size_of::()); 82 | let byte_end = byte_offset + byte_len; 83 | 84 | slice[usize::conv(byte_offset)..usize::conv(byte_end)] 85 | .copy_from_slice(bytemuck::cast_slice(&pass.vertices)); 86 | 87 | pass.vertices.clear(); 88 | pass.count = len; 89 | pass.data_range = byte_offset..byte_end; 90 | byte_offset = byte_end; 91 | } 92 | } 93 | } 94 | 95 | /// Enqueue render commands 96 | pub fn render<'a>( 97 | &'a self, 98 | pass: usize, 99 | rpass: &mut wgpu::RenderPass<'a>, 100 | pipeline: &'a wgpu::RenderPipeline, 101 | bg_common: &'a wgpu::BindGroup, 102 | ) { 103 | if let Some(buffer) = self.buffer.as_ref() { 104 | if let Some(pass) = self.passes.get(pass) { 105 | if pass.data_range.is_empty() { 106 | return; 107 | } 108 | rpass.set_pipeline(pipeline); 109 | rpass.set_bind_group(0, bg_common, &[]); 110 | rpass.set_vertex_buffer(0, buffer.slice(pass.data_range.clone())); 111 | rpass.draw(0..pass.count, 0..1); 112 | } 113 | } 114 | } 115 | 116 | pub fn add_vertices(&mut self, pass: usize, slice: &[V]) { 117 | debug_assert_eq!(slice.len() % 3, 0); 118 | 119 | if self.passes.len() <= pass { 120 | // We only need one more, but no harm in adding extra 121 | self.passes.resize(pass + 8, Default::default()); 122 | } 123 | 124 | self.passes[pass].vertices.extend_from_slice(slice); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Drawing API for `kas_wgpu` 7 | //! 8 | //! Extensions to the API of [`kas::draw`], plus some utility types. 9 | 10 | mod atlases; 11 | mod common; 12 | mod custom; 13 | mod draw_pipe; 14 | mod flat_round; 15 | mod images; 16 | mod round_2col; 17 | mod shaded_round; 18 | mod shaded_square; 19 | mod shaders; 20 | mod text_pipe; 21 | 22 | use kas::draw::WindowCommon; 23 | use kas::geom::{Offset, Rect}; 24 | use shaders::ShaderManager; 25 | use wgpu::TextureFormat; 26 | 27 | pub use custom::{CustomPipe, CustomPipeBuilder, CustomWindow, DrawCustom}; 28 | 29 | /// Output format 30 | /// 31 | /// Required by WGPU to be BGRA, either sRGB or Unorm. Currently we assume sRGB 32 | /// and let the graphics pipeline handle colour conversions. 33 | pub(crate) const RENDER_TEX_FORMAT: TextureFormat = TextureFormat::Bgra8UnormSrgb; 34 | 35 | type Scale = [f32; 4]; 36 | 37 | /// Shared pipeline data 38 | pub struct DrawPipe { 39 | pub(crate) adapter: wgpu::Adapter, 40 | pub(crate) device: wgpu::Device, 41 | queue: wgpu::Queue, 42 | staging_belt: wgpu::util::StagingBelt, 43 | bgl_common: wgpu::BindGroupLayout, 44 | light_norm_buf: wgpu::Buffer, 45 | bg_common: Vec<(wgpu::Buffer, wgpu::BindGroup)>, 46 | images: images::Images, 47 | shaded_square: shaded_square::Pipeline, 48 | shaded_round: shaded_round::Pipeline, 49 | flat_round: flat_round::Pipeline, 50 | round_2col: round_2col::Pipeline, 51 | custom: C, 52 | pub(crate) text: text_pipe::Pipeline, 53 | } 54 | 55 | kas::impl_scope! { 56 | /// Per-window pipeline data 57 | #[impl_default(where CW: Default)] 58 | pub struct DrawWindow { 59 | pub(crate) common: WindowCommon, 60 | scale: Scale, 61 | clip_regions: Vec<(Rect, Offset)> = vec![Default::default()], 62 | images: images::Window, 63 | shaded_square: shaded_square::Window, 64 | shaded_round: shaded_round::Window, 65 | flat_round: flat_round::Window, 66 | round_2col: round_2col::Window, 67 | custom: CW, 68 | pub(crate) text: text_pipe::Window, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Shader management 7 | 8 | use wgpu::{include_spirv, ShaderModule}; 9 | 10 | /// Shader manager 11 | pub struct ShaderManager { 12 | pub vert_flat_round: ShaderModule, 13 | pub vert_round_2col: ShaderModule, 14 | pub vert_shaded_round: ShaderModule, 15 | pub vert_shaded_square: ShaderModule, 16 | pub vert_image: ShaderModule, 17 | pub vert_glyph: ShaderModule, 18 | pub frag_flat_round: ShaderModule, 19 | pub frag_round_2col: ShaderModule, 20 | pub frag_shaded_round: ShaderModule, 21 | pub frag_shaded_square: ShaderModule, 22 | pub frag_image: ShaderModule, 23 | pub frag_glyph: ShaderModule, 24 | } 25 | 26 | macro_rules! create { 27 | ($device:ident, $path:expr) => {{ 28 | $device.create_shader_module(include_spirv!($path)) 29 | }}; 30 | } 31 | 32 | impl ShaderManager { 33 | pub fn new(device: &wgpu::Device) -> Self { 34 | let vert_flat_round = create!(device, "shaders/flat_round.vert.spv"); 35 | let vert_round_2col = create!(device, "shaders/round_2col.vert.spv"); 36 | let vert_shaded_round = create!(device, "shaders/shaded_round.vert.spv"); 37 | let vert_shaded_square = create!(device, "shaders/shaded_square.vert.spv"); 38 | let vert_image = create!(device, "shaders/image.vert.spv"); 39 | let vert_glyph = create!(device, "shaders/glyph.vert.spv"); 40 | 41 | let frag_flat_round = create!(device, "shaders/flat_round.frag.spv"); 42 | let frag_round_2col = create!(device, "shaders/round_2col.frag.spv"); 43 | let frag_shaded_round = create!(device, "shaders/shaded_round.frag.spv"); 44 | let frag_shaded_square = create!(device, "shaders/shaded_square.frag.spv"); 45 | let frag_image = create!(device, "shaders/image.frag.spv"); 46 | let frag_glyph = create!(device, "shaders/glyph.frag.spv"); 47 | 48 | ShaderManager { 49 | vert_image, 50 | vert_glyph, 51 | vert_flat_round, 52 | vert_round_2col, 53 | vert_shaded_round, 54 | vert_shaded_square, 55 | frag_flat_round, 56 | frag_round_2col, 57 | frag_shaded_round, 58 | frag_shaded_square, 59 | frag_image, 60 | frag_glyph, 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/flat_round.frag: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec4 fragColor; 12 | layout(location = 1) in float inner; 13 | layout(location = 2) in vec2 pos; 14 | layout(location = 3) in vec2 off; 15 | 16 | layout(location = 0) out vec4 outColor; 17 | 18 | float sample_a(vec2 pos) { 19 | vec2 pos2 = pos * pos; 20 | float ss = pos2.x + pos2.y; 21 | return (inner <= ss && ss <= 1.0) ? 0.25 : 0.0; 22 | } 23 | 24 | void main() { 25 | // Multi-sample alpha to avoid ugly aliasing. 26 | vec2 off1 = vec2(off.x, 0.0); 27 | vec2 off2 = vec2(0.0, off.y); 28 | float alpha = sample_a(pos + off1) 29 | + sample_a(pos - off1) 30 | + sample_a(pos + off2) 31 | + sample_a(pos - off2); 32 | 33 | outColor = vec4(fragColor.rgb, fragColor.a * alpha); 34 | } 35 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/flat_round.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/flat_round.frag.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/flat_round.vert: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec2 a_pos; 12 | layout(location = 1) in vec4 a_col; 13 | layout(location = 2) in float a1; 14 | layout(location = 3) in vec2 a2; 15 | layout(location = 4) in vec2 a3; 16 | 17 | layout(location = 0) out vec4 b_col; 18 | layout(location = 1) out float b1; 19 | layout(location = 2) out vec2 b2; 20 | layout(location = 3) out vec2 b3; 21 | 22 | layout(set = 0, binding = 0) uniform VertexCommon { 23 | vec2 offset; 24 | vec2 scale; 25 | }; 26 | 27 | void main() { 28 | gl_Position = vec4(scale * (a_pos.xy + offset), 0.0, 1.0); 29 | b_col = a_col; 30 | b1 = a1; 31 | b2 = a2; 32 | b3 = a3; 33 | } 34 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/flat_round.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/flat_round.vert.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/glyph.frag: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec2 tex_coord; 12 | layout(location = 1) in vec4 col; 13 | 14 | layout(location = 0) out vec4 outColor; 15 | 16 | layout(set = 1, binding = 0) uniform texture2D tex; 17 | layout(set = 1, binding = 1) uniform sampler tex_sampler; 18 | 19 | void main() { 20 | float alpha = texture(sampler2D(tex, tex_sampler), tex_coord).r; 21 | outColor = vec4(col.rgb, col.a * alpha); 22 | } 23 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/glyph.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/glyph.frag.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/glyph.vert: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec2 pos_a; 12 | layout(location = 1) in vec2 pos_b; 13 | layout(location = 2) in vec2 tex_a; 14 | layout(location = 3) in vec2 tex_b; 15 | layout(location = 4) in vec4 inColor; 16 | 17 | layout(location = 0) out vec2 tex_pos; 18 | layout(location = 1) out vec4 outColor; 19 | 20 | layout(set = 0, binding = 0) uniform VertexCommon { 21 | vec2 offset; 22 | vec2 scale; 23 | }; 24 | 25 | void main() { 26 | vec2 pos; 27 | switch (gl_VertexIndex) { 28 | case 0: 29 | pos = pos_a; 30 | tex_pos = tex_a; 31 | break; 32 | case 1: 33 | pos = vec2(pos_b.x, pos_a.y); 34 | tex_pos = vec2(tex_b.x, tex_a.y); 35 | break; 36 | case 2: 37 | pos = vec2(pos_a.x, pos_b.y); 38 | tex_pos = vec2(tex_a.x, tex_b.y); 39 | break; 40 | case 3: 41 | pos = pos_b; 42 | tex_pos = tex_b; 43 | break; 44 | } 45 | 46 | outColor = inColor; 47 | 48 | gl_Position = vec4(scale * (pos.xy + offset), 0.0, 1.0); 49 | } 50 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/glyph.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/glyph.vert.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/image.frag: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec2 tex_coord; 12 | 13 | layout(location = 0) out vec4 outColor; 14 | 15 | layout(set = 1, binding = 0) uniform texture2D tex; 16 | layout(set = 1, binding = 1) uniform sampler tex_sampler; 17 | 18 | void main() { 19 | outColor = texture(sampler2D(tex, tex_sampler), tex_coord); 20 | } 21 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/image.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/image.frag.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/image.vert: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec2 pos_a; 12 | layout(location = 1) in vec2 pos_b; 13 | layout(location = 2) in vec2 tex_a; 14 | layout(location = 3) in vec2 tex_b; 15 | 16 | layout(location = 0) out vec2 tex_pos; 17 | 18 | layout(set = 0, binding = 0) uniform VertexCommon { 19 | vec2 offset; 20 | vec2 scale; 21 | }; 22 | 23 | void main() { 24 | vec2 pos; 25 | switch (gl_VertexIndex) { 26 | case 0: 27 | pos = pos_a; 28 | tex_pos = tex_a; 29 | break; 30 | case 1: 31 | pos = vec2(pos_b.x, pos_a.y); 32 | tex_pos = vec2(tex_b.x, tex_a.y); 33 | break; 34 | case 2: 35 | pos = vec2(pos_a.x, pos_b.y); 36 | tex_pos = vec2(tex_a.x, tex_b.y); 37 | break; 38 | case 3: 39 | pos = pos_b; 40 | tex_pos = tex_b; 41 | break; 42 | } 43 | 44 | gl_Position = vec4(scale * (pos.xy + offset), 0.0, 1.0); 45 | } 46 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/image.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/image.vert.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/round_2col.frag: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) flat in vec4 col1; 12 | layout(location = 1) flat in vec4 col2; 13 | layout(location = 2) in vec2 pos; 14 | 15 | layout(location = 0) out vec4 outColor; 16 | 17 | void main() { 18 | vec2 pos2 = pos * pos; 19 | float ss = pos2.x + pos2.y; 20 | if (!(ss <= 1.0)) { 21 | discard; 22 | } 23 | float r = sqrt(ss); 24 | outColor = mix(col1, col2, r); 25 | } 26 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/round_2col.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/round_2col.frag.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/round_2col.vert: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec2 a_pos; 12 | layout(location = 1) in vec4 a_col1; 13 | layout(location = 2) in vec4 a_col2; 14 | layout(location = 3) in vec2 a_v; 15 | 16 | layout(location = 0) flat out vec4 b_col1; 17 | layout(location = 1) flat out vec4 b_col2; 18 | layout(location = 2) out vec2 b_v; 19 | 20 | layout(set = 0, binding = 0) uniform VertexCommon { 21 | vec2 offset; 22 | vec2 scale; 23 | }; 24 | 25 | void main() { 26 | gl_Position = vec4(scale * (a_pos.xy + offset), 0.0, 1.0); 27 | b_col1 = a_col1; 28 | b_col2 = a_col2; 29 | b_v = a_v; 30 | } 31 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/round_2col.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/round_2col.vert.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_round.frag: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec4 fragColor; 12 | layout(location = 1) in vec2 dir; 13 | layout(location = 2) in vec2 adjust; 14 | layout(location = 3) in vec2 off; 15 | 16 | layout(location = 0) out vec4 outColor; 17 | 18 | layout(set = 0, binding = 1) uniform FragCommon { 19 | vec3 lightNorm; 20 | // Note: since WGPU v0.12 this type is interpreted as 16 bytes. 21 | // For compatibility, we explicitly pad to 16 bytes. 22 | float _padding; 23 | }; 24 | 25 | float sample_a(vec2 dir) { 26 | vec2 dir2 = dir * dir; 27 | float ss = dir2.x + dir2.y; 28 | return (ss <= 1.0) ? 0.25 : 0.0; 29 | } 30 | 31 | void main() { 32 | // Multi-sample alpha to avoid ugly aliasing. A single colour sample is adequate. 33 | vec2 off1 = vec2(off.x, 0.0); 34 | vec2 off2 = vec2(0.0, off.y); 35 | float alpha = sample_a(dir + off1) 36 | + sample_a(dir - off1) 37 | + sample_a(dir + off2) 38 | + sample_a(dir - off2); 39 | if (alpha == 0.0) discard; 40 | 41 | vec2 dir2 = dir * dir; 42 | float ss = dir2.x + dir2.y; 43 | 44 | // With multi-sampling we can hit ss>1. Clamp to avoid imaginary roots: 45 | float z = sqrt(max(1.0 - ss, 0)); 46 | float h = sqrt(ss); 47 | float t = adjust.x + adjust.y * atan(h, z); 48 | vec2 normh = vec2(0.0); 49 | if (h > 0.0) { 50 | normh = dir * (sin(t) / h); 51 | z = cos(t); 52 | } 53 | vec3 norm = vec3(normh, z); 54 | 55 | // Simplified version with only scale adjustment; looks okay with convex 56 | // curvature but not with concave: 57 | // float z = sqrt(1.0 - adjust.y * ss); 58 | // vec3 norm = vec3(dir * sqrt(adjust.y), z); 59 | 60 | vec3 c = fragColor.rgb * dot(norm, lightNorm); 61 | outColor = vec4(c, fragColor.a * alpha); 62 | } 63 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_round.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/shaded_round.frag.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_round.vert: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec2 a_pos; 12 | layout(location = 1) in vec4 a_col; 13 | layout(location = 2) in vec2 a1; 14 | layout(location = 3) in vec2 a2; 15 | layout(location = 4) in vec2 a3; 16 | 17 | layout(location = 0) out vec4 b_col; 18 | layout(location = 1) out vec2 b1; 19 | layout(location = 2) out vec2 b2; 20 | layout(location = 3) out vec2 b3; 21 | 22 | layout(set = 0, binding = 0) uniform VertexCommon { 23 | vec2 offset; 24 | vec2 scale; 25 | }; 26 | 27 | void main() { 28 | gl_Position = vec4(scale * (a_pos.xy + offset), 0.0, 1.0); 29 | b_col = a_col; 30 | b1 = a1; 31 | b2 = a2; 32 | b3 = a3; 33 | } 34 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_round.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/shaded_round.vert.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_square.frag: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec4 fragColor; 12 | layout(location = 1) in vec2 norm2; 13 | 14 | layout(location = 0) out vec4 outColor; 15 | 16 | layout(set = 0, binding = 1) uniform FragCommon { 17 | vec3 lightNorm; 18 | // Note: since WGPU v0.12 this type is interpreted as 16 bytes. 19 | // For compatibility, we explicitly pad to 16 bytes. 20 | float _padding; 21 | }; 22 | 23 | void main() { 24 | float n3 = sqrt(1.0 - norm2.x * norm2.x - norm2.y * norm2.y); 25 | vec3 norm = vec3(norm2, n3); 26 | // HACK: hard code the light norm since the value received above is clearly wrong 27 | vec3 lightNorm = vec3(0.1204612, -0.28491753, 1.0); 28 | vec3 c = fragColor.rgb * dot(norm, lightNorm); 29 | outColor = vec4(c, fragColor.a); 30 | } 31 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_square.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/shaded_square.frag.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_square.vert: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision mediump float; 10 | 11 | layout(location = 0) in vec2 a_pos; 12 | layout(location = 1) in vec4 a_col; 13 | layout(location = 2) in vec2 a1; 14 | 15 | layout(location = 0) out vec4 b_col; 16 | layout(location = 1) out vec2 b1; 17 | 18 | layout(set = 0, binding = 0) uniform VertexCommon { 19 | vec2 offset; 20 | vec2 scale; 21 | }; 22 | 23 | void main() { 24 | gl_Position = vec4(scale * (a_pos.xy + offset), 0.0, 1.0); 25 | b_col = a_col; 26 | b1 = a1; 27 | } 28 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_square.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/crates/kas-wgpu/src/draw/shaders/shaded_square.vert.spv -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw/shaders/shaded_square.wgsl: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | struct VertexCommon { 7 | offset: vec2, 8 | scale: vec2, 9 | } 10 | @group(0) @binding(0) 11 | var global: VertexCommon; 12 | 13 | struct FragCommon { 14 | lightNorm: vec3, 15 | _padding: f32, 16 | } 17 | @group(0) @binding(1) 18 | var global2: FragCommon; 19 | 20 | struct VertexOutput { 21 | @location(0) fragColor: vec4, 22 | @location(1) norm2: vec2, 23 | @builtin(position) member: vec4, 24 | } 25 | 26 | struct FragmentOutput { 27 | @location(0) outColor: vec4, 28 | } 29 | 30 | @vertex 31 | fn vert( 32 | @location(0) a_pos: vec2, 33 | @location(1) a_col: vec4, 34 | @location(2) a1: vec2, 35 | ) -> VertexOutput { 36 | let pos = global.scale * (a_pos.xy + global.offset); 37 | let gl_Position = vec4(pos.x, pos.y, 0.0, 1.0); 38 | return VertexOutput(a_col, a1, gl_Position); 39 | } 40 | 41 | @fragment 42 | fn frag( 43 | @location(0) fragColor: vec4, 44 | @location(1) norm2: vec2, 45 | ) -> FragmentOutput { 46 | let n3: f32 = sqrt(1.0 - norm2.x * norm2.x - norm2.y * norm2.y); 47 | let norm = vec3(norm2.x, norm2.y, n3); 48 | let c: vec3 = (fragColor.xyz * dot(norm, global2.lightNorm)); 49 | let outColor = vec4(c.x, c.y, c.z, fragColor.w); 50 | return FragmentOutput(outColor); 51 | } 52 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/draw_shaded.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Drawing APIs — shaded drawing 7 | 8 | use kas::draw::color::Rgba; 9 | use kas::draw::{DrawIface, DrawImpl, DrawSharedImpl, PassId}; 10 | use kas::geom::Quad; 11 | 12 | /// Extension trait providing shaded drawing for [`DrawIface`] 13 | /// 14 | /// All methods draw some feature. 15 | /// 16 | /// Methods are parameterised via a pair of normals, `(inner, outer)`, which 17 | /// specify the surface normal direction at inner and outer edges of the feature 18 | /// respectively (with interpolation between these edges). These have values 19 | /// from the closed range `[-1, 1]`, where -1 points towards the inside of the 20 | /// feature, 1 points away from the feature, and 0 is perpendicular to the 21 | /// screen towards the viewer. 22 | pub trait DrawShaded { 23 | /// Add a shaded square to the draw buffer 24 | /// 25 | /// For shading purposes, the mid-point is considered the inner edge. 26 | fn shaded_square(&mut self, rect: Quad, norm: (f32, f32), col: Rgba); 27 | 28 | /// Add a shaded circle to the draw buffer 29 | /// 30 | /// For shading purposes, the mid-point is considered the inner edge. 31 | fn shaded_circle(&mut self, rect: Quad, norm: (f32, f32), col: Rgba); 32 | 33 | /// Add a shaded frame with square corners to the draw buffer 34 | fn shaded_square_frame( 35 | &mut self, 36 | outer: Quad, 37 | inner: Quad, 38 | norm: (f32, f32), 39 | outer_col: Rgba, 40 | inner_col: Rgba, 41 | ); 42 | 43 | /// Add a shaded frame with rounded corners to the draw buffer 44 | fn shaded_round_frame(&mut self, outer: Quad, inner: Quad, norm: (f32, f32), col: Rgba); 45 | } 46 | 47 | impl<'a, DS: DrawSharedImpl> DrawShaded for DrawIface<'a, DS> 48 | where 49 | DS::Draw: DrawShadedImpl, 50 | { 51 | fn shaded_square(&mut self, rect: Quad, norm: (f32, f32), col: Rgba) { 52 | self.draw.shaded_square(self.pass, rect, norm, col); 53 | } 54 | 55 | fn shaded_circle(&mut self, rect: Quad, norm: (f32, f32), col: Rgba) { 56 | self.draw.shaded_circle(self.pass, rect, norm, col); 57 | } 58 | 59 | fn shaded_square_frame( 60 | &mut self, 61 | outer: Quad, 62 | inner: Quad, 63 | norm: (f32, f32), 64 | outer_col: Rgba, 65 | inner_col: Rgba, 66 | ) { 67 | self.draw 68 | .shaded_square_frame(self.pass, outer, inner, norm, outer_col, inner_col); 69 | } 70 | 71 | fn shaded_round_frame(&mut self, outer: Quad, inner: Quad, norm: (f32, f32), col: Rgba) { 72 | self.draw 73 | .shaded_round_frame(self.pass, outer, inner, norm, col); 74 | } 75 | } 76 | 77 | /// Extended draw interface for [`DrawIface`] providing shaded drawing 78 | /// 79 | /// This trait is an extension over [`DrawImpl`] providing solid shaded shapes. 80 | /// 81 | /// Some drawing primitives (the "round" ones) are partially transparent. 82 | /// If the implementation buffers draw commands, it should draw these 83 | /// primitives after solid primitives. 84 | /// 85 | /// Methods are parameterised via a pair of normals, `(inner, outer)`. These may 86 | /// have values from the closed range `[-1, 1]`, where -1 points inwards, 87 | /// 0 is perpendicular to the screen towards the viewer, and 1 points outwards. 88 | pub trait DrawShadedImpl: DrawImpl { 89 | /// Add a shaded square to the draw buffer 90 | fn shaded_square(&mut self, pass: PassId, rect: Quad, norm: (f32, f32), col: Rgba); 91 | 92 | /// Add a shaded circle to the draw buffer 93 | fn shaded_circle(&mut self, pass: PassId, rect: Quad, norm: (f32, f32), col: Rgba); 94 | 95 | /// Add a square shaded frame to the draw buffer. 96 | fn shaded_square_frame( 97 | &mut self, 98 | pass: PassId, 99 | outer: Quad, 100 | inner: Quad, 101 | norm: (f32, f32), 102 | outer_col: Rgba, 103 | inner_col: Rgba, 104 | ); 105 | 106 | /// Add a rounded shaded frame to the draw buffer. 107 | fn shaded_round_frame( 108 | &mut self, 109 | pass: PassId, 110 | outer: Quad, 111 | inner: Quad, 112 | norm: (f32, f32), 113 | col: Rgba, 114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! KAS graphics backend over [WGPU] 7 | //! 8 | //! This crate implements a KAS's drawing APIs over [WGPU]. 9 | //! 10 | //! This crate supports themes via the [`kas::theme`], and provides one 11 | //! additional theme, [`ShadedTheme`]. 12 | //! 13 | //! Custom GPU-accelerated drawing is supported via [`draw::CustomPipe`] 14 | //! (see the [Mandlebrot example](https://github.com/kas-gui/kas/blob/master/kas-wgpu/examples/mandlebrot.rs)). 15 | //! 16 | //! By default, some environment variables are read for configuration. 17 | //! See [`options::Options::load_from_env`] for documentation. 18 | //! 19 | //! [WGPU]: https://github.com/gfx-rs/wgpu 20 | 21 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 22 | 23 | pub mod draw; 24 | mod draw_shaded; 25 | pub mod options; 26 | mod shaded_theme; 27 | mod surface; 28 | 29 | use crate::draw::{CustomPipeBuilder, DrawPipe}; 30 | use kas::runner::{self, Result}; 31 | use wgpu::rwh; 32 | 33 | pub use draw_shaded::{DrawShaded, DrawShadedImpl}; 34 | pub use options::Options; 35 | pub use shaded_theme::ShadedTheme; 36 | pub extern crate wgpu; 37 | 38 | /// Graphics context 39 | pub struct Instance { 40 | options: Options, 41 | instance: wgpu::Instance, 42 | custom: CB, 43 | } 44 | 45 | impl Instance { 46 | /// Construct a new `Instance` 47 | /// 48 | /// [`Options`] are typically default-constructed then 49 | /// [loaded from enviroment variables](Options::load_from_env). 50 | pub fn new(options: Options, custom: CB) -> Self { 51 | let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { 52 | backends: options.backend(), 53 | ..Default::default() 54 | }); 55 | 56 | Instance { 57 | options, 58 | instance, 59 | custom, 60 | } 61 | } 62 | } 63 | 64 | impl runner::GraphicsInstance for Instance { 65 | type Shared = DrawPipe; 66 | 67 | type Surface<'a> = surface::Surface<'a, CB::Pipe>; 68 | 69 | fn new_shared(&mut self, surface: Option<&Self::Surface<'_>>) -> Result { 70 | DrawPipe::new( 71 | &self.instance, 72 | &mut self.custom, 73 | &self.options, 74 | surface.map(|s| &s.surface), 75 | ) 76 | } 77 | 78 | fn new_surface<'window, W>( 79 | &mut self, 80 | window: W, 81 | transparent: bool, 82 | ) -> Result> 83 | where 84 | W: rwh::HasWindowHandle + rwh::HasDisplayHandle + Send + Sync + 'window, 85 | Self: Sized, 86 | { 87 | surface::Surface::new(&self.instance, window, transparent) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /crates/kas-wgpu/src/options.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Options 7 | 8 | use std::env::var; 9 | use std::path::PathBuf; 10 | pub use wgpu::{Backends, PowerPreference}; 11 | 12 | /// Graphics backend options 13 | #[derive(Clone, PartialEq, Eq, Hash)] 14 | pub struct Options { 15 | /// Adapter power preference. Default value: low power. 16 | pub power_preference: PowerPreference, 17 | /// Adapter backend. Default value: PRIMARY (Vulkan/Metal/DX12). 18 | pub backends: Backends, 19 | /// WGPU's API tracing path 20 | pub wgpu_trace_path: Option, 21 | } 22 | 23 | impl Default for Options { 24 | fn default() -> Self { 25 | Options { 26 | power_preference: PowerPreference::LowPower, 27 | backends: Backends::PRIMARY, 28 | wgpu_trace_path: None, 29 | } 30 | } 31 | } 32 | 33 | impl Options { 34 | /// Read values from environment variables 35 | /// 36 | /// This replaces values in self where specified via env vars. 37 | /// Use e.g. `Options::default().load_from_env()`. 38 | /// 39 | /// The following environment variables are read, in case-insensitive mode. 40 | /// 41 | /// # Graphics options 42 | /// 43 | /// The `KAS_POWER_PREFERENCE` variable supports: 44 | /// 45 | /// - `Default` 46 | /// - `LowPower` 47 | /// - `HighPerformance` 48 | /// 49 | /// The `KAS_BACKENDS` variable supports: 50 | /// 51 | /// - `Vulkan` 52 | /// - `GL` 53 | /// - `Metal` 54 | /// - `DX11` 55 | /// - `DX12` 56 | /// - `BROWSER_WEBGPU`: web target through webassembly 57 | /// - `PRIMARY`: any of Vulkan, Metal or DX12 58 | /// - `SECONDARY`: any of GL or DX11 59 | /// - `FALLBACK`: force use of fallback (CPU) rendering 60 | /// 61 | /// The default backend is `PRIMARY`. Note that secondary backends are less 62 | /// well supported by WGPU, possibly leading to other issues. 63 | /// 64 | /// ~~WGPU has an [API tracing] feature for debugging.~~ 65 | /// NOTE: WGPU has temporarily removed this feature. 66 | /// To use this, ensure the `wgpu/trace` feature is enabled and set the output path: 67 | /// ```sh 68 | /// export KAS_WGPU_TRACE_PATH="api_trace" 69 | /// ``` 70 | /// 71 | /// [API tracing]: https://github.com/gfx-rs/wgpu/wiki/Debugging-wgpu-Applications#tracing-infrastructure 72 | pub fn load_from_env(&mut self) { 73 | if let Ok(mut v) = var("KAS_POWER_PREFERENCE") { 74 | v.make_ascii_uppercase(); 75 | self.power_preference = match v.as_str() { 76 | "DEFAULT" | "LOWPOWER" => PowerPreference::LowPower, 77 | "HIGHPERFORMANCE" => PowerPreference::HighPerformance, 78 | other => { 79 | log::error!("from_env: bad var KAS_POWER_PREFERENCE={other}"); 80 | log::error!( 81 | "from_env: supported power modes: DEFAULT, LOWPOWER, HIGHPERFORMANCE" 82 | ); 83 | self.power_preference 84 | } 85 | } 86 | } 87 | 88 | if let Ok(mut v) = var("KAS_BACKENDS") { 89 | v.make_ascii_uppercase(); 90 | self.backends = match v.as_str() { 91 | "VULKAN" => Backends::VULKAN, 92 | "GL" => Backends::GL, 93 | "METAL" => Backends::METAL, 94 | "DX12" => Backends::DX12, 95 | "BROWSER_WEBGPU" => Backends::BROWSER_WEBGPU, 96 | "PRIMARY" => Backends::PRIMARY, 97 | "SECONDARY" => Backends::SECONDARY, 98 | "FALLBACK" => Backends::empty(), 99 | other => { 100 | log::error!("from_env: bad var KAS_BACKENDS={other}"); 101 | log::error!("from_env: supported backends: VULKAN, GL, METAL, DX12, BROWSER_WEBGPU, PRIMARY, SECONDARY, FALLBACK"); 102 | self.backends 103 | } 104 | } 105 | } 106 | 107 | if let Ok(v) = var("KAS_WGPU_TRACE_PATH") { 108 | self.wgpu_trace_path = Some(v.into()); 109 | } 110 | } 111 | 112 | pub(crate) fn adapter_options(&self) -> wgpu::RequestAdapterOptions { 113 | wgpu::RequestAdapterOptions { 114 | power_preference: self.power_preference, 115 | force_fallback_adapter: self.backends.is_empty(), 116 | compatible_surface: None, 117 | } 118 | } 119 | 120 | pub(crate) fn backend(&self) -> Backends { 121 | if self.backends.is_empty() { 122 | Backends::PRIMARY 123 | } else { 124 | self.backends 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /crates/kas-widgets/COPYRIGHT: -------------------------------------------------------------------------------- 1 | This work is copyrighted by the following contributors: 2 | 3 | Diggory Hardy 4 | 5 | This list may be incomplete. 6 | -------------------------------------------------------------------------------- /crates/kas-widgets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kas-widgets" 3 | version = "0.15.0" 4 | authors = ["Diggory Hardy "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description = "KAS GUI / widgets" 8 | readme = "README.md" 9 | documentation = "https://docs.rs/kas-widgets/" 10 | keywords = ["gui"] 11 | categories = ["gui"] 12 | repository = "https://github.com/kas-gui/kas" 13 | exclude = ["/screenshots"] 14 | 15 | [package.metadata.docs.rs] 16 | features = ["kas/winit", "kas/wayland"] 17 | rustdoc-args = ["--cfg", "docsrs"] 18 | 19 | [dependencies] 20 | log = "0.4" 21 | smallvec = "1.6.1" 22 | unicode-segmentation = "1.7" 23 | thiserror = "2.0.3" 24 | image = { version = "0.25.1", optional = true } 25 | kas-macros = { version = "0.15.0", path = "../kas-macros" } 26 | linear-map = "1.2.0" 27 | 28 | # We must rename this package since macros expect kas to be in scope: 29 | kas = { version = "0.15.0", package = "kas-core", path = "../kas-core" } 30 | 31 | [lints.clippy] 32 | collapsible_else_if = "allow" 33 | collapsible_if = "allow" 34 | comparison_chain = "allow" 35 | module_inception = "allow" 36 | needless_lifetimes = "allow" 37 | redundant_pattern_matching = "allow" 38 | unit_arg = "allow" 39 | -------------------------------------------------------------------------------- /crates/kas-widgets/README.md: -------------------------------------------------------------------------------- 1 | KAS Widgets 2 | ====== 3 | 4 | This is [KAS]'s widget library. 5 | 6 | For documentation of feature flags, see [Cargo.toml](Cargo.toml). 7 | 8 | [KAS]: https://crates.io/crates/kas 9 | 10 | 11 | Copyright and Licence 12 | ------- 13 | 14 | The [COPYRIGHT](COPYRIGHT) file includes a list of contributors who claim 15 | copyright on this project. This list may be incomplete; new contributors may 16 | optionally add themselves to this list. 17 | 18 | The KAS library is published under the terms of the Apache License, Version 2.0. 19 | You may obtain a copy of this licence from the [LICENSE](LICENSE) file or on 20 | the following webpage: 21 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/adapt/mod.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Adapter widgets (wrappers) 7 | 8 | mod adapt; 9 | mod adapt_cx; 10 | mod adapt_events; 11 | mod adapt_widget; 12 | mod reserve; 13 | mod with_label; 14 | 15 | pub use adapt::{Adapt, Map}; 16 | pub use adapt_cx::{AdaptConfigCx, AdaptEventCx}; 17 | pub use adapt_events::AdaptEvents; 18 | pub use adapt_widget::*; 19 | #[doc(inline)] pub use kas::hidden::MapAny; 20 | pub use kas::hidden::{Align, Pack}; 21 | pub use reserve::{Margins, Reserve}; 22 | pub use with_label::WithLabel; 23 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/adapt/reserve.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Size reservation 7 | 8 | use kas::dir::Directions; 9 | use kas::prelude::*; 10 | use kas::theme::MarginStyle; 11 | 12 | impl_scope! { 13 | /// A generic widget for size reservations 14 | /// 15 | /// In a few cases it is desirable to reserve more space for a widget than 16 | /// required for the current content, e.g. if a label's text may change. This 17 | /// widget can be used for this by wrapping the base widget. 18 | /// 19 | /// Usually, this type will be constructed through one of the methods on 20 | /// [`AdaptWidget`](crate::adapt::AdaptWidget). 21 | #[widget{ derive = self.inner; }] 22 | pub struct Reserve { 23 | pub inner: W, 24 | reserve: Box SizeRules + 'static>, 25 | } 26 | 27 | impl Self { 28 | /// Construct a reserve 29 | /// 30 | /// The closure `reserve` should generate `SizeRules` on request, just like 31 | /// [`Layout::size_rules`]. For example: 32 | ///``` 33 | /// use kas_widgets::adapt::Reserve; 34 | /// use kas_widgets::Filler; 35 | /// use kas::prelude::*; 36 | /// 37 | /// let label = Reserve::new(Filler::new(), |sizer: SizeCx<'_>, axis| { 38 | /// let size = i32::conv_ceil(sizer.scale_factor() * 100.0); 39 | /// SizeRules::fixed(size, (0, 0)) 40 | /// }); 41 | ///``` 42 | /// The resulting `SizeRules` will be the max of those for the inner widget 43 | /// and the result of the `reserve` closure. 44 | #[inline] 45 | pub fn new(inner: W, reserve: impl Fn(SizeCx, AxisInfo) -> SizeRules + 'static) -> Self { 46 | let reserve = Box::new(reserve); 47 | Reserve { inner, reserve } 48 | } 49 | } 50 | 51 | impl Layout for Self { 52 | fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { 53 | let inner_rules = self.inner.size_rules(sizer.re(), axis); 54 | let reserve_rules = (self.reserve)(sizer.re(), axis); 55 | inner_rules.max(reserve_rules) 56 | } 57 | } 58 | } 59 | 60 | impl_scope! { 61 | /// Specify margins 62 | /// 63 | /// This replaces a widget's margins. 64 | /// 65 | /// Usually, this type will be constructed through one of the methods on 66 | /// [`AdaptWidget`](crate::adapt::AdaptWidget). 67 | #[widget{ derive = self.inner; }] 68 | pub struct Margins { 69 | pub inner: W, 70 | dirs: Directions, 71 | style: MarginStyle, 72 | } 73 | 74 | impl Self { 75 | /// Construct 76 | #[inline] 77 | pub fn new(inner: W, dirs: Directions, style: MarginStyle) -> Self { 78 | Margins { inner, dirs, style } 79 | } 80 | } 81 | 82 | impl Layout for Self { 83 | fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { 84 | let mut child_rules = self.inner.size_rules(sizer.re(), axis); 85 | if self.dirs.intersects(Directions::from(axis)) { 86 | let mut rule_margins = child_rules.margins(); 87 | let margins = sizer.margins(self.style).extract(axis); 88 | if self.dirs.intersects(Directions::LEFT | Directions::UP) { 89 | rule_margins.0 = margins.0; 90 | } 91 | if self.dirs.intersects(Directions::RIGHT | Directions::DOWN) { 92 | rule_margins.1 = margins.1; 93 | } 94 | child_rules.set_margins(rule_margins); 95 | } 96 | child_rules 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/adapt/with_label.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Wrapper adding a label 7 | 8 | use crate::AccessLabel; 9 | use kas::prelude::*; 10 | 11 | impl_scope! { 12 | /// A wrapper widget with a label 13 | /// 14 | /// The label supports access keys, which activate `self.inner` on 15 | /// usage. 16 | /// 17 | /// Mouse/touch input on the label sends events to the inner widget. 18 | #[derive(Clone, Default)] 19 | #[widget { 20 | Data = W::Data; 21 | layout = list![self.inner, self.label].with_direction(self.dir); 22 | }] 23 | pub struct WithLabel { 24 | core: widget_core!(), 25 | dir: D, 26 | #[widget] 27 | inner: W, 28 | #[widget(&())] 29 | label: AccessLabel, 30 | } 31 | 32 | impl Self { 33 | /// Construct a wrapper around `inner` placing a `label` in the given `direction` 34 | pub fn new>(inner: W, label: T) -> Self where D: Default { 35 | Self::new_dir(inner, D::default(), label) 36 | } 37 | } 38 | impl WithLabel { 39 | /// Construct from `inner` widget and `label` 40 | pub fn left>(inner: W, label: T) -> Self { 41 | Self::new(inner, label) 42 | } 43 | } 44 | impl WithLabel { 45 | /// Construct from `inner` widget and `label` 46 | pub fn right>(inner: W, label: T) -> Self { 47 | Self::new(inner, label) 48 | } 49 | } 50 | 51 | impl Self { 52 | /// Construct a wrapper around `inner` placing a `label` in the given `direction` 53 | #[inline] 54 | pub fn new_dir>(inner: W, direction: D, label: T) -> Self { 55 | WithLabel { 56 | core: Default::default(), 57 | dir: direction, 58 | inner, 59 | label: AccessLabel::new(label.into()), 60 | } 61 | } 62 | 63 | /// Get the direction 64 | #[inline] 65 | pub fn direction(&self) -> Direction { 66 | self.dir.as_direction() 67 | } 68 | 69 | /// Take inner 70 | #[inline] 71 | pub fn take_inner(self) -> W { 72 | self.inner 73 | } 74 | 75 | /// Get whether line-wrapping is enabled 76 | #[inline] 77 | pub fn wrap(&self) -> bool { 78 | self.label.wrap() 79 | } 80 | 81 | /// Enable/disable line wrapping 82 | /// 83 | /// By default this is enabled. 84 | #[inline] 85 | pub fn set_wrap(&mut self, wrap: bool) { 86 | self.label.set_wrap(wrap); 87 | } 88 | 89 | /// Enable/disable line wrapping (inline) 90 | #[inline] 91 | pub fn with_wrap(mut self, wrap: bool) -> Self { 92 | self.label.set_wrap(wrap); 93 | self 94 | } 95 | 96 | /// Set text 97 | /// 98 | /// Note: this must not be called before fonts have been initialised 99 | /// (usually done by the theme when the main loop starts). 100 | pub fn set_text>(&mut self, cx: &mut EventState, text: T) { 101 | self.label.set_text(cx, text.into()); 102 | } 103 | } 104 | 105 | impl Tile for Self { 106 | fn nav_next(&self, _: bool, from: Option) -> Option { 107 | from.xor(Some(widget_index!(self.inner))) 108 | } 109 | 110 | fn probe(&self, _: Coord) -> Id { 111 | self.inner.id() 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/filler.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Filler widget 7 | 8 | use kas::prelude::*; 9 | 10 | impl_scope! { 11 | /// A space filler 12 | /// 13 | /// This widget has zero minimum size but can expand according to the given 14 | /// stretch priority. 15 | #[derive(Clone, Debug, Default)] 16 | #[widget { 17 | Data = (); 18 | }] 19 | pub struct Filler { 20 | core: widget_core!(), 21 | horiz: Stretch, 22 | vert: Stretch, 23 | } 24 | 25 | impl Layout for Filler { 26 | fn size_rules(&mut self, _: SizeCx, axis: AxisInfo) -> SizeRules { 27 | let stretch = if axis.is_horizontal() { self.horiz } else { self.vert }; 28 | SizeRules::empty(stretch) 29 | } 30 | 31 | fn draw(&self, _: DrawCx) {} 32 | } 33 | } 34 | 35 | impl Filler { 36 | /// Construct a filler with priority [`Stretch::Filler`] 37 | pub fn new() -> Self { 38 | Filler::with(Stretch::Filler) 39 | } 40 | 41 | /// Construct a filler with priority [`Stretch::Low`] 42 | pub fn low() -> Self { 43 | Filler::with(Stretch::Low) 44 | } 45 | 46 | /// Construct a filler with priority [`Stretch::High`] 47 | pub fn high() -> Self { 48 | Filler::with(Stretch::High) 49 | } 50 | 51 | /// Construct a filler with priority [`Stretch::Maximize`] 52 | pub fn maximize() -> Self { 53 | Filler::with(Stretch::Maximize) 54 | } 55 | 56 | /// Construct with a custom stretch priority 57 | pub fn with(stretch: Stretch) -> Self { 58 | Filler::with_hv(stretch, stretch) 59 | } 60 | 61 | /// Construct with custom horizontal and vertical priorities 62 | pub fn with_hv(horiz: Stretch, vert: Stretch) -> Self { 63 | let core = Default::default(); 64 | Filler { core, horiz, vert } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/frame.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! A simple frame 7 | 8 | use kas::prelude::*; 9 | use kas::theme::{Background, FrameStyle}; 10 | 11 | /// Make a [`Frame`] widget 12 | /// 13 | /// # Syntax 14 | /// 15 | /// ## Stand-alone usage 16 | /// 17 | /// When called as a stand-alone macro, `frame!(inner)` is just syntactic sugar 18 | /// for `Frame::new(inner)`, and yes, this makes the macro pointless. 19 | /// 20 | /// ## Usage within widget layout syntax 21 | /// 22 | /// When called within [widget layout syntax], `frame!` may be evaluated as a 23 | /// recursive macro and the result does not have a specified type, except that 24 | /// methods [`map_any`], [`align`], [`pack`] and [`with_style`] are supported 25 | /// via emulation. 26 | /// 27 | /// # Example 28 | /// 29 | /// ``` 30 | /// let my_widget = kas_widgets::frame!(kas_widgets::Label::new("content")); 31 | /// ``` 32 | /// 33 | /// [widget layout syntax]: macro@widget#layout-1 34 | /// [`map_any`]: crate::AdaptWidgetAny::map_any 35 | /// [`align`]: crate::AdaptWidget::align 36 | /// [`pack`]: crate::AdaptWidget::pack 37 | /// [`with_style`]: Frame::with_style 38 | #[macro_export] 39 | macro_rules! frame { 40 | ( $e:expr ) => { 41 | $crate::Frame::new($e) 42 | }; 43 | } 44 | 45 | impl_scope! { 46 | /// A frame around content 47 | /// 48 | /// This widget provides a simple abstraction: drawing a frame around its 49 | /// contents. 50 | // 51 | // NOTE: this would use derive mode if that supported custom layout syntax, 52 | // but it does not. This would allow us to implement Deref to self.inner. 53 | #[derive(Clone, Default)] 54 | #[widget{ 55 | Data = W::Data; 56 | layout = frame!(self.inner).with_style(self.style); 57 | }] 58 | pub struct Frame { 59 | core: widget_core!(), 60 | style: FrameStyle, 61 | bg: Background, 62 | /// The inner widget 63 | #[widget] 64 | pub inner: W, 65 | } 66 | 67 | impl Self { 68 | /// Construct a frame 69 | #[inline] 70 | pub fn new(inner: W) -> Self { 71 | Frame { 72 | core: Default::default(), 73 | style: FrameStyle::Frame, 74 | bg: Background::default(), 75 | inner, 76 | } 77 | } 78 | 79 | /// Set the frame style (inline) 80 | /// 81 | /// The default style is [`FrameStyle::Frame`]. 82 | /// 83 | /// Note: using [`FrameStyle::NavFocus`] does not automatically make 84 | /// this widget interactive. Use [`NavFrame`](crate::NavFrame) for that. 85 | #[inline] 86 | #[must_use] 87 | pub fn with_style(mut self, style: FrameStyle) -> Self { 88 | self.style = style; 89 | self 90 | } 91 | 92 | /// Set the frame background color (inline) 93 | /// 94 | /// The default background is [`Background::Default`]. 95 | #[inline] 96 | #[must_use] 97 | pub fn with_background(mut self, bg: Background) -> Self { 98 | self.bg = bg; 99 | self 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! KAS widget library 7 | //! 8 | //! ## Complex widgets 9 | //! 10 | //! - [`EventConfig`] provides an editor for event configuration 11 | //! - [`kas::Window`] is the root of any UI tree used as a window 12 | //! - [`kas::Popup`] is the root of any popup 13 | //! 14 | //! ## Sub-modules 15 | //! 16 | //! - [`adapt`] provides [`Adapt`], [`AdaptWidget`], [`AdaptWidgetAny`] and supporting items 17 | //! (the items mentioned are re-export here). 18 | //! - [`dialog`] provides [`MessageBox`](dialog::MessageBox), ... 19 | //! - [`edit`] provides [`EditBox`], [`EditField`] widgets, [`EditGuard`] trait and some impls 20 | //! - [`menu`] provides a [`MenuBar`](menu::MenuBar), [`SubMenu`](menu::SubMenu), ... 21 | //! 22 | //! ## Container widgets 23 | //! 24 | //! - [`Frame`], [`NavFrame`]: frames around content 25 | //! - [`ScrollRegion`], [`ScrollBarRegion`]: larger on the inside 26 | //! - [`Stack`], [`TabStack`]: a stack of widgets in the same rect 27 | //! - [`List`]: a row / column of children 28 | //! - [`Splitter`]: like [`List`] but with resizing handles 29 | //! - [`Grid`]: a container using matrix layout 30 | //! 31 | //! ## Controls 32 | //! 33 | //! - [`Button`], [`MarkButton`]: button widgets 34 | //! - [`CheckBox`], [`CheckButton`]: checkable boxes 35 | //! - [`RadioBox`], [`RadioButton`]: linked checkable boxes 36 | //! - [`ComboBox`]: a drop-down menu over a list 37 | //! - [`ScrollBar`]: a scroll bar; [`ScrollBars`]: a wrapper adding scroll 38 | //! bars around an inner widget 39 | //! - [`Slider`]: a slider 40 | //! - [`Spinner`]: numeric entry 41 | //! 42 | //! ## Displays 43 | //! 44 | //! - [`Filler`]: an empty widget, sometimes used to fill space 45 | //! - [`Image`]: a pixmap image 46 | //! - [`Label`], [`AccessLabel`]: are static text labels 47 | //! - [`Text`]: a dynamic (input-data derived) text label 48 | //! - [`Mark`]: a small mark 49 | //! - [`ScrollLabel`]: static text label supporting scrolling and selection 50 | //! - [`ScrollText`]: dynamic text label supporting scrolling and selection 51 | //! - [`Separator`]: a visible bar to separate things 52 | //! - [`format_value`] and [`format_data`] are constructors for [`Text`], 53 | //! displaying a text label derived from input data 54 | //! - [`ProgressBar`]: show completion level 55 | //! 56 | //! ## Components 57 | //! 58 | //! - [`AccessLabel`]: a label which parses access keys 59 | //! - [`GripPart`]: a handle (e.g. for a slider, splitter or scroll_bar) 60 | 61 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 62 | 63 | pub mod adapt; 64 | #[doc(no_inline)] 65 | pub use adapt::{Adapt, AdaptWidget, AdaptWidgetAny}; 66 | 67 | mod button; 68 | mod check_box; 69 | mod combobox; 70 | pub mod dialog; 71 | pub mod edit; 72 | mod event_config; 73 | mod filler; 74 | mod float; 75 | mod frame; 76 | mod grid; 77 | mod grip; 78 | mod image; 79 | mod label; 80 | mod list; 81 | mod mark; 82 | pub mod menu; 83 | mod nav_frame; 84 | mod progress; 85 | mod radio_box; 86 | mod scroll; 87 | mod scroll_bar; 88 | mod scroll_label; 89 | mod scroll_text; 90 | mod separator; 91 | mod slider; 92 | mod spinner; 93 | mod splitter; 94 | mod stack; 95 | mod tab_stack; 96 | mod text; 97 | 98 | pub use crate::image::Image; 99 | #[cfg(feature = "image")] pub use crate::image::ImageError; 100 | pub use button::Button; 101 | pub use check_box::{CheckBox, CheckButton}; 102 | pub use combobox::ComboBox; 103 | pub use edit::{EditBox, EditField, EditGuard}; 104 | pub use event_config::EventConfig; 105 | pub use filler::Filler; 106 | pub use float::Float; 107 | pub use frame::Frame; 108 | pub use grid::Grid; 109 | pub use grip::{GripMsg, GripPart}; 110 | pub use label::{AccessLabel, Label}; 111 | pub use list::*; 112 | pub use mark::{Mark, MarkButton}; 113 | pub use nav_frame::NavFrame; 114 | pub use progress::ProgressBar; 115 | pub use radio_box::{RadioBox, RadioButton}; 116 | pub use scroll::ScrollRegion; 117 | pub use scroll_bar::{ScrollBar, ScrollBarMode, ScrollBarRegion, ScrollBars, ScrollMsg}; 118 | pub use scroll_label::ScrollLabel; 119 | pub use scroll_text::ScrollText; 120 | pub use separator::Separator; 121 | pub use slider::{Slider, SliderValue}; 122 | pub use spinner::{Spinner, SpinnerValue}; 123 | pub use splitter::Splitter; 124 | pub use stack::{BoxStack, Stack}; 125 | pub use tab_stack::{BoxTabStack, Tab, TabStack}; 126 | pub use text::Text; 127 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/mark.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Mark widget 7 | 8 | use kas::prelude::*; 9 | use kas::theme::MarkStyle; 10 | use std::fmt::Debug; 11 | 12 | impl_scope! { 13 | /// A mark 14 | /// 15 | /// These are small theme-defined "glyphs"; see [`MarkStyle`]. They may be 16 | /// used as icons or visual connectors. See also [`MarkButton`]. 17 | /// 18 | /// TODO: expand or replace. 19 | #[derive(Clone, Debug)] 20 | #[widget { 21 | Data = (); 22 | }] 23 | pub struct Mark { 24 | core: widget_core!(), 25 | style: MarkStyle, 26 | } 27 | impl Self { 28 | /// Construct 29 | pub fn new(style: MarkStyle) -> Self { 30 | Mark { 31 | core: Default::default(), 32 | style, 33 | } 34 | } 35 | 36 | /// Get mark style 37 | #[inline] 38 | pub fn mark(&self) -> MarkStyle { 39 | self.style 40 | } 41 | 42 | /// Set mark style 43 | #[inline] 44 | pub fn set_mark(&mut self, mark: MarkStyle) { 45 | self.style = mark; 46 | } 47 | } 48 | impl Layout for Self { 49 | fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { 50 | sizer.feature(self.style.into(), axis) 51 | } 52 | 53 | fn draw(&self, mut draw: DrawCx) { 54 | draw.mark(self.rect(), self.style); 55 | } 56 | } 57 | } 58 | 59 | impl_scope! { 60 | /// A mark which is also a button 61 | /// 62 | /// A clickable button over a [`Mark`]. 63 | /// This button is not keyboard navigable; only mouse/touch interactive. 64 | /// 65 | /// Uses stretch policy [`Stretch::Low`]. 66 | #[derive(Clone, Debug)] 67 | #[widget { 68 | hover_highlight = true; 69 | }] 70 | pub struct MarkButton { 71 | core: widget_core!(), 72 | style: MarkStyle, 73 | msg: M, 74 | } 75 | 76 | impl Self { 77 | /// Construct 78 | /// 79 | /// A clone of `msg` is sent as a message on click. 80 | pub fn new_msg(style: MarkStyle, msg: M) -> Self { 81 | MarkButton { 82 | core: Default::default(), 83 | style, 84 | msg, 85 | } 86 | } 87 | } 88 | 89 | impl Layout for Self { 90 | fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { 91 | sizer.feature(self.style.into(), axis) 92 | } 93 | 94 | fn draw(&self, mut draw: DrawCx) { 95 | draw.mark(self.rect(), self.style); 96 | } 97 | } 98 | 99 | impl Events for Self { 100 | type Data = (); 101 | 102 | fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed { 103 | event.on_activate(cx, self.id(), |cx| { 104 | cx.push(self.msg.clone()); 105 | Used 106 | }) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/nav_frame.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! A "navigable" wrapper 7 | 8 | use kas::prelude::*; 9 | 10 | impl_scope! { 11 | /// Navigation Frame wrapper 12 | /// 13 | /// This widget is a wrapper that can be used to make a static widget such as a 14 | /// `Label` navigable with the keyboard. 15 | /// 16 | /// # Messages 17 | /// 18 | /// When activated, this widget pushes [`Select`] to the message stack. 19 | /// 20 | /// [`Select`]: kas::messages::Select 21 | #[derive(Clone, Default)] 22 | #[widget{ 23 | Data = W::Data; 24 | navigable = true; 25 | layout = frame!(self.inner).with_style(kas::theme::FrameStyle::NavFocus); 26 | }] 27 | pub struct NavFrame { 28 | core: widget_core!(), 29 | /// The inner widget 30 | #[widget] 31 | pub inner: W, 32 | } 33 | 34 | impl Self { 35 | /// Construct a frame 36 | #[inline] 37 | pub fn new(inner: W) -> Self { 38 | NavFrame { 39 | core: Default::default(), 40 | inner, 41 | } 42 | } 43 | } 44 | 45 | impl Events for Self { 46 | fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed { 47 | match event { 48 | Event::Command(cmd, code) if cmd.is_activate() => { 49 | cx.depress_with_key(self.id(), code); 50 | cx.push(kas::messages::Select); 51 | Used 52 | } 53 | _ => Unused, 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/progress.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Progress bar 7 | 8 | use kas::prelude::*; 9 | use kas::theme::Feature; 10 | 11 | impl_scope! { 12 | /// A progress bar 13 | /// 14 | /// The "progress" value may range from 0.0 to 1.0. 15 | #[autoimpl(Debug ignore self.value_fn)] 16 | #[widget] 17 | pub struct ProgressBar { 18 | core: widget_core!(), 19 | direction: D, 20 | value: f32, 21 | value_fn: Box f32>, 22 | } 23 | 24 | impl Self 25 | where 26 | D: Default, 27 | { 28 | /// Construct a slider 29 | /// 30 | /// Closure `value_fn` returns the current progress as a value between 31 | /// 0.0 and 1.0. 32 | #[inline] 33 | pub fn new(value_fn: impl Fn(&ConfigCx, &A) -> f32 + 'static) -> Self { 34 | Self::new_dir(value_fn, D::default()) 35 | } 36 | } 37 | impl ProgressBar { 38 | /// Construct a progress bar (horizontal) 39 | /// 40 | /// Closure `value_fn` returns the current progress as a value between 41 | /// 0.0 and 1.0. 42 | #[inline] 43 | pub fn right(value_fn: impl Fn(&ConfigCx, &A) -> f32 + 'static) -> Self { 44 | ProgressBar::new(value_fn) 45 | } 46 | } 47 | 48 | impl Self { 49 | /// Construct a slider with the given `direction` 50 | /// 51 | /// Closure `value_fn` returns the current progress as a value between 52 | /// 0.0 and 1.0. 53 | #[inline] 54 | pub fn new_dir(value_fn: impl Fn(&ConfigCx, &A) -> f32 + 'static, direction: D) -> Self { 55 | ProgressBar { 56 | core: Default::default(), 57 | direction, 58 | value: 0.0, 59 | value_fn: Box::new(value_fn), 60 | } 61 | } 62 | 63 | /// Get the progress bar's direction 64 | #[inline] 65 | pub fn direction(&self) -> Direction { 66 | self.direction.as_direction() 67 | } 68 | } 69 | 70 | impl Layout for Self { 71 | fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { 72 | sizer.feature(Feature::ProgressBar(self.direction()), axis) 73 | } 74 | 75 | fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { 76 | let align = match self.direction.is_vertical() { 77 | false => AlignPair::new(Align::Stretch, hints.vert.unwrap_or(Align::Center)), 78 | true => AlignPair::new(hints.horiz.unwrap_or(Align::Center), Align::Stretch), 79 | }; 80 | let rect = cx.align_feature(Feature::ProgressBar(self.direction()), rect, align); 81 | widget_set_rect!(rect); 82 | } 83 | 84 | fn draw(&self, mut draw: DrawCx) { 85 | let dir = self.direction.as_direction(); 86 | draw.progress_bar(self.rect(), dir, self.value); 87 | } 88 | } 89 | 90 | impl Events for Self { 91 | type Data = A; 92 | 93 | fn update(&mut self, cx: &mut ConfigCx, data: &A) { 94 | let value = (self.value_fn)(cx, data); 95 | self.value = value.clamp(0.0, 1.0); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /crates/kas-widgets/src/separator.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! A separator 7 | 8 | use crate::menu::Menu; 9 | use kas::prelude::*; 10 | use std::marker::PhantomData; 11 | 12 | impl_scope! { 13 | /// A separator 14 | /// 15 | /// This widget draws a bar when in a list. 16 | #[autoimpl(Clone, Debug, Default)] 17 | #[widget { 18 | Data = A; 19 | }] 20 | pub struct Separator { 21 | core: widget_core!(), 22 | _pd: PhantomData, 23 | } 24 | 25 | impl Self { 26 | /// Construct a frame, with void message type 27 | #[inline] 28 | pub fn new() -> Self { 29 | Separator { 30 | core: Default::default(), 31 | _pd: PhantomData, 32 | } 33 | } 34 | } 35 | 36 | impl Layout for Self { 37 | fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules { 38 | sizer.feature(kas::theme::Feature::Separator, axis) 39 | } 40 | 41 | fn draw(&self, mut draw: DrawCx) { 42 | draw.separator(self.rect()); 43 | } 44 | } 45 | 46 | /// A separator is a valid menu widget 47 | impl Menu for Self {} 48 | } 49 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | KAS Examples 2 | ========== 3 | 4 | Examples are listed below in alphabetical order. 5 | 6 | If learning KAS, the Hello, Counter, Calculator, and perhaps Filter-list 7 | examples may be the most accessible. Also see 8 | [the tutorials](https://kas-gui.github.io/tutorials/). 9 | 10 | ### Async event 11 | 12 | Demonstrates updating the UI in response to events from a background thread. 13 | 14 | ![Async-event](https://github.com/kas-gui/data-dump/blob/master/kas_0_9/image/async-event.png) 15 | 16 | ### Calculator 17 | 18 | A simple calculator show-casing the grid layout and keyboard support. 19 | 20 | ![Calculator](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/calculator.png) 21 | 22 | ### Clock 23 | 24 | A simple clock. An example of a custom widget using mid-level draw routines and 25 | timer updates. 26 | 27 | ![Clock](https://github.com/kas-gui/data-dump/blob/master/kas_0_9/image/clock.png) 28 | 29 | ### Counter 30 | 31 | (Almost) the simplest interactive example possible: a counter with push-buttons. 32 | 33 | **Variant:** `sync-counter` opens two windows with a synchronised counter. 34 | 35 | ![Counter](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/counter.png) 36 | 37 | ### Cursors 38 | 39 | Curious what each mouse cursor available on your desktop (via winit) looks like? 40 | 41 | ![Cursors](https://github.com/kas-gui/data-dump/blob/master/kas_0_9/image/cursors.png) 42 | 43 | ### Data list 44 | 45 | This example demonstrates an interface over a list data structure of 46 | user-defined length. It has two implementations, both with (approximately) the 47 | same UI, but different internals: 48 | 49 | - `data-list` directly allocates a widget for each data entry and stores data 50 | within the widgets; it can scale to hundreds of entries or potentially tens 51 | of thousands when using release optimisations and tolerating some delays 52 | - `data-list-view` uses a dynamic view over a lazily-allocated data structure; 53 | performance is thus independent of the number of entries (though length is 54 | still limited by the maximum possible scroll offset; see issue #222) 55 | 56 | ![Data list](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/data-list.png) 57 | 58 | ### Gallery 59 | 60 | A testbed demoing most widgets, animations, data models, canvas and configuration. 61 | 62 | ![Gallery](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/gallery.png) 63 | ![Gallery](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/video/gallery.apng) 64 | 65 | ### Hello 66 | 67 | A message box. 68 | 69 | ![Hello](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/hello.png) 70 | 71 | ### Layout 72 | 73 | Demonstration of complex layout and multi-paragraph text. 74 | 75 | ![Layout](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/layout.png) 76 | 77 | ### Mandlebrot 78 | 79 | GPU-accelerated fractals via a custom embedded WGPU graphics pipeline. 80 | 81 | ![Mandlebrot](https://github.com/kas-gui/data-dump/blob/master/kas_0_9/image/mandlebrot.png) 82 | 83 | ### Splitter 84 | 85 | Demonstrates resizable panes. 86 | 87 | ![Splitter](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/splitter.png) 88 | 89 | ### Stopwatch 90 | 91 | Ready? Set! Go! 92 | 93 | ![Stopwatch](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/stopwatch.png) 94 | 95 | ### Sync-counter 96 | 97 | A variant of [Counter](#Counter), demonstrating multiple windows and the 98 | `SingleView` widget (the simplest shared data widget). 99 | 100 | ### Times-tables 101 | 102 | A simple demonstration of the `MatrixView` widget. 103 | 104 | ![Times-tables](https://github.com/kas-gui/data-dump/blob/master/kas_0_11/image/times-tables.png) 105 | 106 | 107 | Copyright and Licence 108 | ------- 109 | 110 | The file includes a list of contributors who claim copyright on this 111 | project. This list may be incomplete; new contributors may optionally add 112 | themselves to this list. 113 | 114 | The KAS library is published under the terms of the Apache License, Version 2.0. 115 | You may obtain a copy of this licence from the file or on 116 | the following webpage: 117 | -------------------------------------------------------------------------------- /examples/counter.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Counter example (simple button) 7 | 8 | use kas::prelude::*; 9 | use kas::widgets::{column, format_value, row, Button}; 10 | 11 | #[derive(Clone, Debug)] 12 | struct Increment(i32); 13 | 14 | fn counter() -> impl Widget { 15 | let tree = column![ 16 | format_value!("{}").align(AlignHints::CENTER), 17 | row![ 18 | Button::label_msg("−", Increment(-1)), 19 | Button::label_msg("+", Increment(1)), 20 | ] 21 | .map_any(), 22 | ]; 23 | 24 | tree.with_state(0) 25 | .on_message(|_, count, Increment(add)| *count += add) 26 | } 27 | 28 | fn main() -> kas::runner::Result<()> { 29 | env_logger::init(); 30 | 31 | let theme = kas::theme::SimpleTheme::new(); 32 | let mut app = kas::runner::Runner::with_theme(theme).build(())?; 33 | let _ = app.config_mut().font.set_size(24.0); 34 | app.with(Window::new(counter(), "Counter")).run() 35 | } 36 | -------------------------------------------------------------------------------- /examples/cursors.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Cursor gallery 7 | 8 | use kas::event::CursorIcon; 9 | use kas::prelude::*; 10 | use kas::widgets::{Column, Label}; 11 | 12 | impl_scope! { 13 | #[widget{ 14 | Data = (); 15 | layout = self.label; 16 | cursor_icon = self.cursor; 17 | }] 18 | struct CursorWidget { 19 | core: widget_core!(), 20 | #[widget] 21 | label: Label<&'static str>, 22 | cursor: CursorIcon, 23 | } 24 | impl Tile for Self { 25 | fn probe(&self, _: Coord) -> Id { 26 | // Steal mouse focus: hover points to self, not self.label 27 | self.id() 28 | } 29 | } 30 | } 31 | 32 | // Using a macro lets us stringify! the type name 33 | macro_rules! cursor { 34 | ($name: tt) => { 35 | CursorWidget { 36 | core: Default::default(), 37 | label: Label::new(stringify!($name)), 38 | cursor: CursorIcon::$name, 39 | } 40 | }; 41 | } 42 | 43 | fn main() -> kas::runner::Result<()> { 44 | env_logger::init(); 45 | 46 | // These are winit::window::CursorIcon enum variants 47 | let column = Column::new([ 48 | cursor!(Default), 49 | cursor!(ContextMenu), 50 | cursor!(Help), 51 | cursor!(Pointer), 52 | cursor!(Progress), 53 | cursor!(Wait), 54 | cursor!(Cell), 55 | cursor!(Crosshair), 56 | cursor!(Text), 57 | cursor!(VerticalText), 58 | cursor!(Alias), 59 | cursor!(Copy), 60 | cursor!(Move), 61 | cursor!(NoDrop), 62 | cursor!(NotAllowed), 63 | cursor!(Grab), 64 | cursor!(Grabbing), 65 | cursor!(EResize), 66 | cursor!(NResize), 67 | cursor!(NeResize), 68 | cursor!(NwResize), 69 | cursor!(SResize), 70 | cursor!(SeResize), 71 | cursor!(SwResize), 72 | cursor!(WResize), 73 | cursor!(EwResize), 74 | cursor!(NsResize), 75 | cursor!(NeswResize), 76 | cursor!(NwseResize), 77 | cursor!(ColResize), 78 | cursor!(RowResize), 79 | cursor!(AllScroll), 80 | cursor!(ZoomIn), 81 | cursor!(ZoomOut), 82 | ]); 83 | 84 | let window = Window::new(column, "Cursor gallery"); 85 | kas::runner::Runner::new(())?.with(window).run() 86 | } 87 | -------------------------------------------------------------------------------- /examples/hello.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Hello world example 7 | 8 | use kas::widgets::dialog::MessageBox; 9 | 10 | fn main() -> kas::runner::Result<()> { 11 | let window = MessageBox::new("Message").into_window("Hello world"); 12 | 13 | kas::runner::Runner::new(())?.with(window).run() 14 | } 15 | -------------------------------------------------------------------------------- /examples/layout.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Demonstration of widget and text layouts 7 | 8 | use kas::layout::AlignHints; 9 | use kas::widgets::{grid, AdaptWidget, CheckBox, EditBox, ScrollLabel}; 10 | use kas::Window; 11 | 12 | const LIPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nunc mi, consequat eget urna ut, auctor luctus mi. Sed molestie mi est. Sed non ligula ante. Curabitur ac molestie ante, nec sodales eros. In non arcu at turpis euismod bibendum ut tincidunt eros. Suspendisse blandit maximus nisi, viverra hendrerit elit efficitur et. Morbi ut facilisis eros. Vivamus dignissim, sapien sed mattis consectetur, libero leo imperdiet turpis, ac pulvinar libero purus eu lorem. Etiam quis sollicitudin urna. Integer vitae erat vel neque gravida blandit ac non quam."; 13 | const CRASIT: &str = "Cras sit amet justo ipsum. Aliquam in nunc posuere leo egestas laoreet convallis eu libero. Nullam ut massa ante. Cras vitae velit pharetra, euismod nisl suscipit, feugiat nulla. Aenean consectetur, diam non tristique iaculis, nisl lectus hendrerit sapien, nec rhoncus mi sem non odio. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nulla a lorem eu ipsum faucibus placerat ac quis quam. Curabitur justo ligula, laoreet nec ultrices eu, scelerisque non metus. Mauris sit amet est enim. Mauris risus eros, accumsan ut iaculis sit amet, sagittis facilisis neque. Nunc venenatis risus nec purus malesuada, a tristique arcu efficitur. Nulla suscipit arcu nibh. Cras facilisis nibh a gravida aliquet. Praesent fringilla felis a tristique luctus."; 14 | 15 | fn main() -> kas::runner::Result<()> { 16 | env_logger::init(); 17 | 18 | let ui = grid! { 19 | (1, 0) => "Layout demo", 20 | (2, 0) => CheckBox::new(|_, _| true), 21 | (0..3, 1) => ScrollLabel::new(LIPSUM), 22 | (0, 2) => "abc אבג def".align(AlignHints::CENTER), 23 | (1..3, 3) => ScrollLabel::new(CRASIT).align(AlignHints::STRETCH), 24 | (0, 3) => EditBox::text("A small\nsample\nof text").with_multi_line(true), 25 | }; 26 | let window = Window::new(ui, "Layout demo"); 27 | 28 | kas::runner::Runner::new(())?.with(window).run() 29 | } 30 | -------------------------------------------------------------------------------- /examples/mandlebrot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kas-mandlebrot" 3 | version = "0.15.0" 4 | authors = ["Diggory Hardy "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description = "KAS GUI / Mandlebrot example" 8 | publish = false 9 | 10 | [dependencies] 11 | kas = { version = "0.15.0", features = ["wgpu"], path = "../.." } 12 | kas-wgpu = { version = "0.15.0", path = "../../crates/kas-wgpu" } 13 | chrono = "0.4" 14 | env_logger = "0.11" 15 | log = "0.4" 16 | bytemuck = "1.7.0" 17 | 18 | [features] 19 | # Use 64-bit shaders 20 | # This allows much greater zoom levels, but has compatibility issues. 21 | shader64 = [] 22 | 23 | [[bin]] 24 | name = "mandlebrot" 25 | path = "mandlebrot.rs" 26 | 27 | [build-dependencies] 28 | glob = "0.3" 29 | -------------------------------------------------------------------------------- /examples/mandlebrot/README.md: -------------------------------------------------------------------------------- 1 | Mandlebrot example 2 | ========== 3 | 4 | This example demonstrates an embedded WGPU graphics pipeline and two-finger 5 | scroll/zoom/rotation via `Event::Pan`. 6 | 7 | ![Mandlebrot](https://github.com/kas-gui/data-dump/blob/master/kas_0_9/image/mandlebrot.png) 8 | 9 | 10 | Copyright and Licence 11 | ------- 12 | 13 | The file includes a list of contributors who claim copyright on this 14 | project. This list may be incomplete; new contributors may optionally add 15 | themselves to this list. 16 | 17 | The KAS library is published under the terms of the Apache License, Version 2.0. 18 | You may obtain a copy of this licence from the file or on 19 | the following webpage: 20 | -------------------------------------------------------------------------------- /examples/mandlebrot/build.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Build script — shader compiler 7 | //! 8 | //! This script scans the directory (of the Cargo.toml manifest) for `*.vert` 9 | //! and `*.frag` files, and compiles each to `*.vert.spv` etc., but only if 10 | //! missing or out-of-date. 11 | //! 12 | //! To enable shader compilation, install a compiler such as glslc and set 13 | //! `SHADERC=`. For example, add this to your `~/.bash_profile`: 14 | //! ``` 15 | //! export SHADERC=glslc 16 | //! ``` 17 | //! 18 | //! Warning: change detection is not perfect: this script will not automatically 19 | //! be run when new `.vert` or `.frag` files are created. The easiest way to fix 20 | //! this is to touch (re-save) any existing `.vert`/`.frag` file. 21 | 22 | #![deny(warnings)] 23 | 24 | use glob::glob; 25 | use std::env; 26 | use std::path::PathBuf; 27 | use std::process::{Child, Command}; 28 | 29 | fn main() { 30 | let mut runners = Vec::new(); 31 | 32 | println!("cargo:rerun-if-env-changed=SHADERC"); 33 | let shaderc = match env::var("SHADERC") { 34 | Ok(s) => Some(s), 35 | Err(env::VarError::NotPresent) => None, 36 | Err(e) => panic!("failed to read env var SHADERC: {e}"), 37 | }; 38 | 39 | let mut pat = env::var("CARGO_MANIFEST_DIR").unwrap(); 40 | pat.push_str("/**/*.vert"); 41 | walk(&pat, &shaderc, &mut runners); 42 | pat.replace_range((pat.len() - 4).., "frag"); 43 | walk(&pat, &shaderc, &mut runners); 44 | 45 | for mut r in runners { 46 | let status = r.wait().unwrap(); 47 | if !status.success() { 48 | panic!("Shader compilation failed (exit code {:?})", status.code()); 49 | } 50 | } 51 | } 52 | 53 | fn walk(pat: &str, shaderc: &Option, runners: &mut Vec) { 54 | for path in glob(pat).unwrap().filter_map(Result::ok) { 55 | println!("cargo:rerun-if-changed={}", path.display()); 56 | 57 | let mut path_spv = path.clone().into_os_string(); 58 | path_spv.push(".spv"); 59 | let path_spv = PathBuf::from(path_spv); 60 | let gen = match path_spv.metadata() { 61 | Ok(meta) => { 62 | let orig_meta = path.metadata().unwrap(); 63 | orig_meta.modified().unwrap() > meta.modified().unwrap() 64 | } 65 | Err(_) => true, 66 | }; 67 | if gen { 68 | if let Some(bin) = shaderc.as_ref() { 69 | let mut cmd = Command::new(bin); 70 | cmd.arg(&path).arg("-o").arg(&path_spv); 71 | eprintln!("Launching: {cmd:?}"); 72 | runners.push(cmd.spawn().expect("shader compiler failed to start")); 73 | } else { 74 | eprintln!( 75 | "cargo:warning=Shader compilation required: {}", 76 | path.display() 77 | ); 78 | eprintln!("cargo:warning=No shader found. If you have a shader compiler such as glslc installed, try setting SHADERC=glslc"); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/mandlebrot/shader.vert: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision highp float; 10 | 11 | layout(location = 0) in vec3 a_pos; 12 | layout(location = 1) in vec2 a1; 13 | 14 | layout(location = 0) out vec2 b1; 15 | 16 | layout(set = 0, binding = 0) uniform VertexCommon { 17 | vec2 offset; 18 | vec2 scale; 19 | }; 20 | 21 | void main() { 22 | gl_Position = vec4(scale * (a_pos.xy + offset), 0.0, 1.0); 23 | b1 = a1; 24 | } 25 | -------------------------------------------------------------------------------- /examples/mandlebrot/shader.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/examples/mandlebrot/shader.vert.spv -------------------------------------------------------------------------------- /examples/mandlebrot/shader32.frag: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision highp float; 10 | 11 | layout(location = 0) in vec2 cf; 12 | 13 | layout(location = 0) out vec4 outColor; 14 | 15 | layout(push_constant) uniform Locals { 16 | vec2 alpha; 17 | vec2 delta; 18 | int iter; 19 | }; 20 | 21 | void main() { 22 | vec2 cd = cf; 23 | vec2 c = vec2(alpha.x * cd.x - alpha.y * cd.y, alpha.x * cd.y + alpha.y * cd.x) + delta; 24 | 25 | vec2 z = c; 26 | int i; 27 | for(i=0; i 4.0) break; 32 | z.x = x; 33 | z.y = y; 34 | } 35 | 36 | float r = (i == iter) ? 0.0 : float(i) / iter; 37 | float g = r * r; 38 | float b = g * g; 39 | outColor = vec4(r, g, b, 1.0); 40 | } 41 | -------------------------------------------------------------------------------- /examples/mandlebrot/shader32.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/examples/mandlebrot/shader32.frag.spv -------------------------------------------------------------------------------- /examples/mandlebrot/shader64.frag: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | #version 450 7 | #extension GL_ARB_separate_shader_objects : enable 8 | 9 | precision highp float; 10 | 11 | layout(location = 0) in vec2 cf; 12 | 13 | layout(location = 0) out vec4 outColor; 14 | 15 | layout(push_constant) uniform Locals { 16 | dvec2 alpha; 17 | dvec2 delta; 18 | int iter; 19 | }; 20 | 21 | void main() { 22 | dvec2 cd = cf; 23 | dvec2 c = dvec2(alpha.x * cd.x - alpha.y * cd.y, alpha.x * cd.y + alpha.y * cd.x) + delta; 24 | 25 | dvec2 z = c; 26 | int i; 27 | for(i=0; i 4.0) break; 35 | 36 | z.x = x; 37 | z.y = y; 38 | } 39 | 40 | float r = (i == iter) ? 0.0 : float(i) / iter; 41 | float g = r * r; 42 | float b = g * g; 43 | outColor = vec4(r, g, b, 1.0); 44 | } 45 | -------------------------------------------------------------------------------- /examples/mandlebrot/shader64.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kas-gui/kas/f211f79bb9b506d419aa266f62cfbf39ce57a1c7/examples/mandlebrot/shader64.frag.spv -------------------------------------------------------------------------------- /examples/proxy.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Asynchronous events using a proxy 7 | //! 8 | //! This is a copy-cat of Druid's async event example, demonstrating usage of 9 | //! `Runner::create_proxy()`. For a more integrated approach to async, see 10 | //! `EventState::send_async()` and `send_spawn()`. 11 | 12 | use std::thread; 13 | use std::time::{Duration, Instant}; 14 | 15 | use kas::draw::color::Rgba; 16 | use kas::prelude::*; 17 | use kas::theme::{Text, TextClass}; 18 | 19 | #[derive(Debug)] 20 | struct SetColor(Rgba); 21 | 22 | struct AppData { 23 | color: Option, 24 | } 25 | 26 | impl kas::runner::AppData for AppData { 27 | fn handle_messages(&mut self, messages: &mut kas::messages::MessageStack) { 28 | if let Some(SetColor(color)) = messages.try_pop() { 29 | self.color = Some(color); 30 | } 31 | } 32 | } 33 | 34 | fn main() -> kas::runner::Result<()> { 35 | env_logger::init(); 36 | 37 | let data = AppData { color: None }; 38 | let app = kas::runner::Runner::new(data)?; 39 | 40 | // We construct a proxy from the app to enable cross-thread communication. 41 | let proxy = app.create_proxy(); 42 | thread::spawn(move || generate_colors(proxy)); 43 | 44 | let widget = ColourSquare::new(); 45 | let window = Window::new(widget, "Async event demo"); 46 | 47 | app.with(window).run() 48 | } 49 | 50 | impl_scope! { 51 | // A custom widget incorporating "Loading..." text, drawing and layout. 52 | #[widget] 53 | struct ColourSquare { 54 | core: widget_core!(), 55 | color: Option, 56 | loading_text: Text<&'static str>, 57 | } 58 | impl Self { 59 | fn new() -> Self { 60 | ColourSquare { 61 | core: Default::default(), 62 | color: None, 63 | loading_text: Text::new("Loading...", TextClass::Label(false)), 64 | } 65 | } 66 | } 67 | impl Layout for ColourSquare { 68 | fn size_rules(&mut self, sizer: SizeCx, _axis: AxisInfo) -> SizeRules { 69 | SizeRules::fixed_scaled(100.0, 10.0, sizer.scale_factor()) 70 | } 71 | 72 | fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) { 73 | widget_set_rect!(rect); 74 | self.loading_text.set_rect(cx, rect, hints.combine(AlignHints::CENTER)); 75 | } 76 | 77 | fn draw(&self, mut draw: DrawCx) { 78 | if let Some(color) = self.color { 79 | let draw = draw.draw_device(); 80 | draw.rect((self.rect()).cast(), color); 81 | } else { 82 | draw.text(self.rect(), &self.loading_text); 83 | } 84 | } 85 | } 86 | impl Events for ColourSquare { 87 | type Data = AppData; 88 | 89 | fn configure(&mut self, cx: &mut ConfigCx) { 90 | self.loading_text.set_align((Align::Center, Align::Center)); 91 | cx.text_configure(&mut self.loading_text); 92 | } 93 | 94 | fn update(&mut self, cx: &mut ConfigCx, data: &AppData) { 95 | self.color = data.color; 96 | cx.redraw(self); 97 | } 98 | } 99 | } 100 | 101 | fn generate_colors(mut proxy: kas::runner::Proxy) { 102 | // Loading takes time: 103 | thread::sleep(Duration::from_secs(1)); 104 | 105 | // This function is called in a separate thread, and runs until the program ends. 106 | let start_time = Instant::now(); 107 | 108 | loop { 109 | let hue = (Instant::now() - start_time).as_secs_f32() / 5.0; 110 | 111 | // convert from HSV, using S=V=1 (see Wikipedia): 112 | let f = |n| { 113 | let k: f32 = (n + hue * 6.0) % 6.0; 114 | 1.0 - k.min(4.0 - k).clamp(0.0, 1.0) 115 | }; 116 | let c = Rgba::rgb(f(5.0), f(3.0), f(1.0)); 117 | 118 | if proxy.push(SetColor(c)).is_err() { 119 | // Sending failed; we should quit 120 | break; 121 | } 122 | 123 | thread::sleep(Duration::from_millis(20)); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/splitter.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Counter example (simple button) 7 | 8 | use kas::prelude::*; 9 | use kas::widgets::{column, row, Adapt, Button, EditField, Splitter}; 10 | 11 | #[derive(Clone, Debug)] 12 | enum Message { 13 | Decr, 14 | Incr, 15 | } 16 | 17 | fn main() -> kas::runner::Result<()> { 18 | env_logger::init(); 19 | 20 | let ui = column![ 21 | row![ 22 | Button::label_msg("−", Message::Decr), 23 | Button::label_msg("+", Message::Incr), 24 | ] 25 | .map_any(), 26 | Splitter::right(vec![]).on_update(|cx, panes, len| panes.resize_with(len, cx, *len, |n| { 27 | EditField::text(format!("Pane {}", n + 1)).with_multi_line(true) 28 | })), 29 | ]; 30 | 31 | let adapt = Adapt::new(ui, 3).on_message(|_, len, msg| { 32 | *len = match msg { 33 | Message::Decr => len.saturating_sub(1), 34 | Message::Incr => len.saturating_add(1), 35 | } 36 | }); 37 | 38 | let window = Window::new(adapt, "Slitter panes"); 39 | 40 | let theme = kas_wgpu::ShadedTheme::new(); 41 | kas::runner::Runner::with_theme(theme) 42 | .build(())? 43 | .with(window) 44 | .run() 45 | } 46 | -------------------------------------------------------------------------------- /examples/stopwatch.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! Counter example (simple button) 7 | 8 | use std::time::{Duration, Instant}; 9 | 10 | use kas::decorations::Decorations; 11 | use kas::event::TimerHandle; 12 | use kas::prelude::*; 13 | use kas::widgets::{format_data, row, Button}; 14 | 15 | #[derive(Clone, Debug)] 16 | struct MsgReset; 17 | #[derive(Clone, Debug)] 18 | struct MsgStart; 19 | 20 | #[derive(Debug, Default)] 21 | struct Timer { 22 | elapsed: Duration, 23 | last: Option, 24 | } 25 | 26 | const TIMER: TimerHandle = TimerHandle::new(0, true); 27 | 28 | fn make_window() -> impl Widget { 29 | let ui = row![ 30 | format_data!(timer: &Timer, "{}.{:03}", timer.elapsed.as_secs(), timer.elapsed.subsec_millis()), 31 | Button::label_msg("&reset", MsgReset).map_any(), 32 | Button::label_msg("&start / &stop", MsgStart).map_any(), 33 | ]; 34 | 35 | ui.with_state(Timer::default()) 36 | .on_configure(|cx, _| cx.enable_alt_bypass(true)) 37 | .on_message(|_, timer, MsgReset| *timer = Timer::default()) 38 | .on_message(|cx, timer, MsgStart| { 39 | let now = Instant::now(); 40 | if let Some(last) = timer.last.take() { 41 | timer.elapsed += now - last; 42 | } else { 43 | timer.last = Some(now); 44 | cx.request_frame_timer(TIMER); 45 | } 46 | }) 47 | .on_timer(TIMER, |cx, timer, _| { 48 | if let Some(last) = timer.last { 49 | let now = Instant::now(); 50 | timer.elapsed += now - last; 51 | timer.last = Some(now); 52 | cx.request_frame_timer(TIMER); 53 | } 54 | }) 55 | } 56 | 57 | fn main() -> kas::runner::Result<()> { 58 | env_logger::init(); 59 | 60 | let window = Window::new(make_window(), "Stopwatch") 61 | .with_decorations(Decorations::Border) 62 | .with_transparent(true) 63 | .with_restrictions(true, true); 64 | 65 | let theme = kas_wgpu::ShadedTheme::new(); 66 | let mut app = kas::runner::Runner::with_theme(theme).build(())?; 67 | let _ = app.config_mut().font.set_size(24.0); 68 | let _ = app.config_mut().theme.set_active_scheme("dark"); 69 | app.with(window).run() 70 | } 71 | -------------------------------------------------------------------------------- /examples/sync-counter.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! A counter synchronised between multiple windows 7 | //! 8 | //! Each window shares the counter, but has its own increment step. 9 | 10 | use kas::widgets::{column, format_data, row, AdaptWidget, Button, Label, Slider}; 11 | use kas::{messages::MessageStack, Window}; 12 | 13 | #[derive(Clone, Debug)] 14 | struct Increment(i32); 15 | 16 | #[derive(Clone, Copy, Debug)] 17 | struct Count(i32); 18 | impl kas::runner::AppData for Count { 19 | fn handle_messages(&mut self, messages: &mut MessageStack) { 20 | if let Some(Increment(add)) = messages.try_pop() { 21 | self.0 += add; 22 | } 23 | } 24 | } 25 | 26 | fn counter(title: &str) -> Window { 27 | // Per window state: (count, increment). 28 | // We must store a local copy of the count in order to have a Data instance 29 | // to pass by reference. 30 | // (Eventually we may be able to support Adapt forwarding data by reference, 31 | // but this would require Rust to support object-safe GATs.) 32 | type Data = (Count, i32); 33 | // Initial count is replaced during configure, but initial increment is used. 34 | let initial: Data = (Count(0), 1); 35 | 36 | #[derive(Clone, Debug)] 37 | struct SetValue(i32); 38 | 39 | let slider = Slider::right(1..=10, |_, data: &Data| data.1).with_msg(SetValue); 40 | let ui = column![ 41 | format_data!(data: &Data, "Count: {}", data.0.0), 42 | row![slider, format_data!(data: &Data, "{}", data.1)], 43 | row![ 44 | Button::new(Label::new_any("Sub")).with(|cx, data: &Data| cx.push(Increment(-data.1))), 45 | Button::new(Label::new_any("Add")).with(|cx, data: &Data| cx.push(Increment(data.1))), 46 | ], 47 | ]; 48 | 49 | let ui = ui 50 | .with_state(initial) 51 | .on_update(|_, state, count| state.0 = *count) 52 | .on_message(|_, state, SetValue(v)| state.1 = v); 53 | Window::new(ui, title) 54 | } 55 | 56 | fn main() -> kas::runner::Result<()> { 57 | env_logger::init(); 58 | 59 | let count = Count(0); 60 | let theme = kas_wgpu::ShadedTheme::new(); 61 | 62 | let mut runner = kas::runner::Runner::with_theme(theme).build(count)?; 63 | let _ = runner.config_mut().font.set_size(24.0); 64 | runner 65 | .with(counter("Counter 1")) 66 | .with(counter("Counter 2")) 67 | .run() 68 | } 69 | -------------------------------------------------------------------------------- /examples/times-tables.rs: -------------------------------------------------------------------------------- 1 | //! Do you know your times tables? 2 | 3 | use kas::prelude::*; 4 | use kas::view::{driver, DataClerk, MatrixIndex, MatrixView, SelectionMode, SelectionMsg}; 5 | use kas::widgets::{column, row, EditBox, ScrollBars}; 6 | use std::ops::Range; 7 | 8 | /// A cache of the visible part of our table 9 | #[derive(Debug, Default)] 10 | struct TableCache { 11 | dim: u32, 12 | col_len: usize, 13 | col_start: u32, 14 | row_start: u32, 15 | contents: Vec, 16 | } 17 | 18 | fn product(x: u32, y: u32) -> u64 { 19 | let x = u64::conv(x + 1); 20 | let y = u64::conv(y + 1); 21 | x * y 22 | } 23 | 24 | impl DataClerk for TableCache { 25 | /// Our table is square; it's size is input. 26 | type Data = u32; 27 | 28 | /// We re-usize the index as our key. 29 | type Key = MatrixIndex; 30 | 31 | /// Data items are `u64` since e.g. 65536² is not representable by `u32`. 32 | type Item = u64; 33 | 34 | fn update(&mut self, _: &mut ConfigCx, _: Id, dim: &Self::Data) { 35 | self.dim = *dim; 36 | } 37 | 38 | fn len(&self, _: &Self::Data) -> MatrixIndex { 39 | MatrixIndex::splat(self.dim) 40 | } 41 | 42 | fn prepare_range( 43 | &mut self, 44 | _: &mut ConfigCx, 45 | _: Id, 46 | _: &Self::Data, 47 | range: Range, 48 | ) { 49 | // This is a simple hack to cache contents for the given range for usage by item() 50 | let x_len = usize::conv(range.end.col - range.start.col); 51 | let y_len = usize::conv(range.end.row - range.start.row); 52 | if x_len != self.col_len 53 | || x_len * y_len != self.contents.len() 54 | || self.col_start != range.start.col 55 | || self.row_start != range.start.row 56 | { 57 | self.col_len = x_len; 58 | self.col_start = range.start.col; 59 | self.row_start = range.start.row; 60 | self.contents.clear(); 61 | self.contents.reserve(x_len * y_len); 62 | 63 | for y in range.start.row..range.end.row { 64 | for x in range.start.col..range.end.col { 65 | self.contents.push(product(x, y)); 66 | } 67 | } 68 | } 69 | } 70 | 71 | fn key(&self, _: &Self::Data, index: MatrixIndex) -> Option { 72 | Some(index) 73 | } 74 | 75 | fn item(&self, _: &Self::Data, key: &Self::Key) -> Option<&Self::Item> { 76 | // We are required to return a reference, otherwise we would simply 77 | // calculate the value here! 78 | let MatrixIndex { col, row } = *key; 79 | let xrel = usize::conv(col - self.col_start); 80 | let yrel = usize::conv(row - self.row_start); 81 | let i = xrel + yrel * self.col_len; 82 | self.contents.get(i) 83 | } 84 | } 85 | 86 | fn main() -> kas::runner::Result<()> { 87 | env_logger::init(); 88 | 89 | let table = MatrixView::new(TableCache::default(), driver::NavView) 90 | .with_num_visible(12, 12) 91 | .with_selection_mode(SelectionMode::Single); 92 | let table = ScrollBars::new(table); 93 | 94 | #[derive(Debug)] 95 | struct SetLen(u32); 96 | 97 | let ui = column![ 98 | row!["From 1 to", EditBox::parser(|dim: &u32| *dim, SetLen)], 99 | table.align(AlignHints::RIGHT), 100 | ]; 101 | let ui = ui 102 | .with_state(12) 103 | .on_message(|_, dim, SetLen(len)| *dim = len) 104 | .on_message(|_, _, selection| match selection { 105 | SelectionMsg::::Select(MatrixIndex { col, row }) => { 106 | println!("{} × {} = {}", col + 1, row + 1, product(col, row)); 107 | } 108 | _ => (), 109 | }); 110 | let window = Window::new(ui, "Times-Tables"); 111 | 112 | let theme = kas::theme::SimpleTheme::new(); 113 | kas::runner::Runner::with_theme(theme) 114 | .build(())? 115 | .with(window) 116 | .run() 117 | } 118 | -------------------------------------------------------------------------------- /res/README.md: -------------------------------------------------------------------------------- 1 | Resources 2 | ========= 3 | 4 | The Ferris image is from . 5 | 6 | Other icons are from . 7 | -------------------------------------------------------------------------------- /res/contrast-2-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/contrast-2-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/error-warning-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/gallery-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Licensed under the Apache License, Version 2.0 (the "License"); 2 | // you may not use this file except in compliance with the License. 3 | // You may obtain a copy of the License in the LICENSE-APACHE file or at: 4 | // https://www.apache.org/licenses/LICENSE-2.0 5 | 6 | //! KAS GUI Toolkit 7 | //! 8 | //! This, the main KAS crate, is a wrapper over other crates designed to make 9 | //! content easily available while remaining configurable. The following crates 10 | //! (some optional, dependant on a feature flag) are re-exported by this crate: 11 | //! 12 | //! - [`kas_core`] is re-export at the top-level 13 | //! - [`easy-cast`](https://crates.io/crates/easy-cast) is re-export as [`cast`] 14 | //! - `kas_macros` is an extended version of [`impl-tools`](https://crates.io/crates/impl-tools), 15 | //! re-export at the top-level 16 | //! - [`kas_widgets`](https://crates.io/crates/kas-widgets) is re-export as [`widgets`](mod@widgets) 17 | //! - [`kas_resvg`](https://crates.io/crates/kas-resvg) is re-export as [`resvg`] (`resvg` or `tiny-skia` feature) 18 | //! - [`kas_view`](https://crates.io/crates/kas-view) is re-export as [`view`] (`view` feature) 19 | //! 20 | //! Also refer to: 21 | //! 22 | //! - [KAS Tutorials](https://kas-gui.github.io/tutorials/) 23 | //! - [Examples](https://github.com/kas-gui/kas/tree/master/examples) 24 | //! - [Discuss](https://github.com/kas-gui/kas/discussions) 25 | 26 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 27 | 28 | /// KAS prelude 29 | /// 30 | /// This module allows convenient importation of common unabiguous items: 31 | /// ``` 32 | /// use kas::prelude::*; 33 | /// ``` 34 | /// 35 | /// This prelude may be more useful when implementing widgets than when simply 36 | /// using widgets in a GUI. 37 | pub mod prelude { 38 | #[doc(no_inline)] pub use kas_core::prelude::*; 39 | #[doc(no_inline)] 40 | pub use kas_widgets::adapt::{AdaptWidget, AdaptWidgetAny}; 41 | } 42 | 43 | pub use kas_core::*; 44 | 45 | #[doc(inline)] pub extern crate kas_widgets as widgets; 46 | 47 | #[cfg(feature = "view")] 48 | #[doc(inline)] 49 | pub extern crate kas_view as view; 50 | 51 | /// `Canvas` and `Svg` widgets over [`tiny-skia`](https://crates.io/crates/tiny-skia) 52 | /// and [`resvg`](https://crates.io/crates/resvg) 53 | /// 54 | /// This crate provides widgets using 55 | /// libraries by [Yevhenii Reizner "RazrFalcon"](https://github.com/RazrFalcon/). 56 | /// 57 | /// This module is gated behind the `resvg` feature. Alternatively, the 58 | /// `tiny-skia` feature may be used to enable only the `Canvas` widget 59 | /// plus support (i.e. everything but `Svg`), saving approx 200 KiB. 60 | #[cfg(any(feature = "resvg", feature = "tiny-skia"))] 61 | pub mod resvg { 62 | pub use kas_resvg::*; 63 | } 64 | 65 | pub mod runner; 66 | 67 | #[cfg(feature = "dynamic")] 68 | #[allow(unused_imports)] 69 | use kas_dylib; 70 | -------------------------------------------------------------------------------- /tests/layout_macros.rs: -------------------------------------------------------------------------------- 1 | use kas::layout::AlignHints; 2 | use kas::widgets::{aligned_column, aligned_row, column, float, grid, list, row}; 3 | use kas::Widget; 4 | 5 | fn use_widget>(_: W) {} 6 | 7 | #[test] 8 | fn column() { 9 | use_widget(column!["one", "two",]) 10 | } 11 | 12 | #[test] 13 | fn row() { 14 | use_widget(row!["one", "two"]); 15 | } 16 | 17 | #[test] 18 | fn list() { 19 | use_widget(list!["one", "two"].with_direction(kas::dir::Left)); 20 | } 21 | 22 | #[test] 23 | fn float() { 24 | use_widget(float![ 25 | "one".pack(AlignHints::TOP_LEFT), 26 | "two".pack(AlignHints::BOTTOM_RIGHT), 27 | "some text\nin the\nbackground", 28 | ]); 29 | } 30 | 31 | #[test] 32 | fn grid() { 33 | use_widget(grid! { 34 | (0, 0) => "top left", 35 | (1, 0) => "top right", 36 | (0..2, 1) => "bottom row (merged)", 37 | }); 38 | } 39 | 40 | #[test] 41 | fn aligned_column() { 42 | #[rustfmt::skip] 43 | use_widget(aligned_column![ 44 | row!["one", "two"], 45 | row!["three", "four"], 46 | ]); 47 | } 48 | 49 | #[test] 50 | fn aligned_row() { 51 | use_widget(aligned_row![column!["one", "two"], column![ 52 | "three", "four" 53 | ],]); 54 | } 55 | --------------------------------------------------------------------------------