diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a9e08a9f..efeb0e81e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,7 @@ jobs: toolchain: [stable] include: - os: ubuntu-latest - toolchain: "1.79.0" + toolchain: "1.80.0" - os: ubuntu-latest toolchain: beta diff --git a/CHANGELOG.md b/CHANGELOG.md index d4a8af6b9..488f657b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,34 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.15.0] — 2024-12-02 -- Rename `Application` → `Runner` +Most significant is the addition of the `Collection` trait representing a list or tuple of widgets. + +### Uncategorized +- Bump MSRV to 1.80 (#457) +- Add struct `kas::config::Config` to house event/theme/font config (#449) +- Make `PRIMARY` backend default and fix transparency (#452) +- Add `kas::decorations` module (#452) +- Rename `Application` → `Runner` and `kas::app` → `kas::runner` (#456) + +### Event handling +- Add `fn EventState::send` and `send_erased` to allow *sending* a message to any widget (#429) +- Assume `Action::UPDATE` from some handlers instead of requiring this explicitly (#432) +- Remove event stealing and revise handling of disabled events (#453) + +### Layout +- Add trait `Collection` and macro `kas::collection!` (#431) +- Add trait `CellCollection` and macro `kas::cell_collection!` (#443) +- Replace trait `AutoLayout` with `LayoutVisitor` using RPITIT (#434) +- Replace `align!(spec DSL, child)`, `pack!(spec DSL, child)` and `margins!(spec DSL, child)` with `.align(hints)`, `.pack(hints)`, `.margins(dirs, style)` widget/layout adapters; more uniform support for adapters within and without layout macros (#438) +- Pass `AlignHints` to `fn set_rect` not `size_rules` (#447) + +### Widgets +- Make `kas::widgets::ImageError` public (#428) +- Make `AdaptEvents` a thin wrapper without `Id` (#430) +- Use RPITIT in traits `ListData`, `MatrixData` (#433) +- Remove widget aliases `StrLabel`, `StringLabel`, `StrText`, `StringText` (#438) ## [0.14.2] — 2023-12-12 diff --git a/Cargo.toml b/Cargo.toml index 608da5a38..81a254428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas" -version = "0.14.3" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -11,7 +11,7 @@ keywords = ["gui"] categories = ["gui"] repository = "https://github.com/kas-gui/kas" exclude = ["/examples"] -rust-version = "1.79.0" +rust-version = "1.80.0" [package.metadata.docs.rs] features = ["stable"] @@ -132,14 +132,14 @@ recursive-layout-widgets = ["kas-core/recursive-layout-widgets"] unsafe_node = ["kas-core/unsafe_node"] [dependencies] -kas-core = { version = "0.14.1", path = "crates/kas-core" } -kas-dylib = { version = "0.14.2", path = "crates/kas-dylib", optional = true } -kas-widgets = { version = "0.14.2", path = "crates/kas-widgets" } -kas-view = { version = "0.14.2", path = "crates/kas-view", optional = true } -kas-resvg = { version = "0.14.2", path = "crates/kas-resvg", optional = true } +kas-core = { version = "0.15.0", path = "crates/kas-core" } +kas-dylib = { version = "0.15.0", path = "crates/kas-dylib", optional = true } +kas-widgets = { version = "0.15.0", path = "crates/kas-widgets" } +kas-view = { version = "0.15.0", path = "crates/kas-view", optional = true } +kas-resvg = { version = "0.15.0", path = "crates/kas-resvg", optional = true } [dependencies.kas-wgpu] -version = "0.14.2" +version = "0.15.0" path = "crates/kas-wgpu" optional = true default-features = false @@ -161,7 +161,3 @@ members = [ "crates/kas-view", "examples/mandlebrot", ] - -[patch.crates-io.kas-text] -git = "https://github.com/kas-gui/kas-text.git" -rev = "8828bb3df7c34559e18429a18a38974ddc3e905f" diff --git a/README.md b/README.md index e7c9328ec..e086c7606 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ KAS GUI [![Crates.io](https://img.shields.io/crates/v/kas.svg)](https://crates.io/crates/kas) [![kas-text](https://img.shields.io/badge/GitHub-kas--text-blueviolet)](https://github.com/kas-gui/kas-text/) [![Docs](https://docs.rs/kas/badge.svg)](https://docs.rs/kas) -![Minimum rustc version](https://img.shields.io/badge/rustc-1.79+-lightgray.svg) +![Minimum rustc version](https://img.shields.io/badge/rustc-1.80+-lightgray.svg) KAS is a stateful, pure-Rust GUI toolkit supporting: diff --git a/ROADMAP.md b/ROADMAP.md index 9a12e3dc3..41f7b0fdb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -170,6 +170,11 @@ to event handlers and to a new `update` method. The key advantages of this chang For more on *input data*, read the [design document](https://github.com/kas-gui/design/blob/input-data/widget/input-data.md). +### 0.15.0 — December 2024 + +Add traits `Collection` and `CellCollection` representing a list/tuple of widgets. +Revise layout macros with `.align` / `.pack` / `.margins` adapters. + Future work ----------- diff --git a/crates/kas-core/Cargo.toml b/crates/kas-core/Cargo.toml index 87337304e..6ae3dae71 100644 --- a/crates/kas-core/Cargo.toml +++ b/crates/kas-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-core" -version = "0.14.1" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -119,11 +119,11 @@ arboard = { version = "3.2.0", optional = true, default-features = false } [dependencies.kas-macros] -version = "0.14.1" +version = "0.15.0" path = "../kas-macros" [dependencies.kas-text] -version = "0.6.0" +version = "0.7.0" [dependencies.easy-cast] version = "0.5.0" # used in doc links diff --git a/crates/kas-core/src/config/font.rs b/crates/kas-core/src/config/font.rs index 34f8faf7d..a28bce6bd 100644 --- a/crates/kas-core/src/config/font.rs +++ b/crates/kas-core/src/config/font.rs @@ -5,7 +5,7 @@ //! Font configuration -use crate::text::fonts::{self, AddMode, FontSelector}; +use crate::text::fonts::{library, AddMode, FontSelector}; use crate::theme::TextClass; use crate::Action; use std::collections::BTreeMap; @@ -166,9 +166,9 @@ impl FontConfig { /// Apply config effects which only happen on startup pub(super) fn init(&self) { if !self.aliases.is_empty() { - fonts::library().update_db(|db| { + library().adjust_resolver(|resolver| { for (family, aliases) in self.aliases.iter() { - db.add_aliases( + resolver.add_aliases( family.to_string().into(), aliases.list.iter().map(|s| s.to_string().into()), aliases.mode, diff --git a/crates/kas-core/src/event/cx/config.rs b/crates/kas-core/src/event/cx/config.rs index a172bc4bf..222946bb7 100644 --- a/crates/kas-core/src/event/cx/config.rs +++ b/crates/kas-core/src/event/cx/config.rs @@ -9,12 +9,10 @@ use super::PendingNavFocus; use crate::event::{EventState, FocusSource}; use crate::geom::{Rect, Size}; use crate::layout::AlignPair; -use crate::messages::Erased; use crate::text::format::FormattableText; use crate::theme::{Feature, SizeCx, Text, ThemeSize}; use crate::{Id, Node}; use cast::Cast; -use std::fmt::Debug; use std::ops::{Deref, DerefMut}; #[allow(unused)] use crate::event::{Event, EventCx}; @@ -92,13 +90,12 @@ impl<'a> ConfigCx<'a> { widget._update(self); } + /* TODO: can we support these (i.e. track `id`)? /// Push a message (replay) /// /// Unlike [`EventCx::push`], this is not handled while unwinding /// from event sending, but via a fresh traversal of the widget tree. - /// - /// TODO: `id` should not be part of the function signature? - pub fn push(&mut self, id: Id, msg: M) { + pub fn push(&mut self, msg: M) { self.send(id, msg); } @@ -110,9 +107,10 @@ impl<'a> ConfigCx<'a> { /// The message may be [popped](EventCx::try_pop) or /// [observed](EventCx::try_observe) from [`Events::handle_messages`] /// by the widget itself, its parent, or any ancestor. - pub fn push_erased(&mut self, id: Id, msg: Erased) { + pub fn push_erased(&mut self, msg: Erased) { self.send_erased(id, msg); } + */ /// Align a feature's rect /// diff --git a/crates/kas-core/src/event/cx/cx_pub.rs b/crates/kas-core/src/event/cx/cx_pub.rs index 36af21e76..5a355748e 100644 --- a/crates/kas-core/src/event/cx/cx_pub.rs +++ b/crates/kas-core/src/event/cx/cx_pub.rs @@ -596,11 +596,19 @@ impl EventState { } /// Send a message to `id` + /// + /// Similar to [`EventCx::push`], the message is sent as part of a widget + /// tree traversal. + /// The message may be [popped](EventCx::try_pop) or + /// [observed](EventCx::try_observe) from [`Events::handle_messages`] + /// by the widget itself, its parent, or any ancestor. pub fn send(&mut self, id: Id, msg: M) { self.send_erased(id, Erased::new(msg)); } - /// Send an erased message to `id` + /// Push a type-erased message to the stack + /// + /// This is a lower-level variant of [`Self::send`]. pub fn send_erased(&mut self, id: Id, msg: Erased) { self.send_queue.push_back((id, msg)); } diff --git a/crates/kas-core/src/theme/simple_theme.rs b/crates/kas-core/src/theme/simple_theme.rs index 94fb4edf6..435559762 100644 --- a/crates/kas-core/src/theme/simple_theme.rs +++ b/crates/kas-core/src/theme/simple_theme.rs @@ -72,8 +72,8 @@ where fn init(&mut self, config: &RefCell) { let fonts = fonts::library(); - if let Err(e) = fonts.select_default() { - panic!("Error loading font: {e}"); + if let Err(e) = fonts.init() { + panic!("Error initializing fonts: {e}"); } self.fonts = Some(Rc::new( config diff --git a/crates/kas-dylib/Cargo.toml b/crates/kas-dylib/Cargo.toml index e89119388..e876e3881 100644 --- a/crates/kas-dylib/Cargo.toml +++ b/crates/kas-dylib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-dylib" -version = "0.14.2" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -27,7 +27,7 @@ resvg = ["dep:kas-resvg"] docs_rs = ["kas-core/winit", "kas-core/wayland"] [dependencies] -kas-core = { version = "0.14.1", path = "../kas-core" } -kas-widgets = { version = "0.14.2", path = "../kas-widgets" } -kas-resvg = { version = "0.14.2", path = "../kas-resvg", optional = true } -kas-wgpu = { version = "0.14.1", path = "../kas-wgpu", default-features = false } +kas-core = { version = "0.15.0", path = "../kas-core" } +kas-widgets = { version = "0.15.0", path = "../kas-widgets" } +kas-resvg = { version = "0.15.0", path = "../kas-resvg", optional = true } +kas-wgpu = { version = "0.15.0", path = "../kas-wgpu", default-features = false } diff --git a/crates/kas-macros/Cargo.toml b/crates/kas-macros/Cargo.toml index 1a4314e47..210edfed4 100644 --- a/crates/kas-macros/Cargo.toml +++ b/crates/kas-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-macros" -version = "0.14.1" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" diff --git a/crates/kas-resvg/Cargo.toml b/crates/kas-resvg/Cargo.toml index 816cf6381..9820cb142 100644 --- a/crates/kas-resvg/Cargo.toml +++ b/crates/kas-resvg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-resvg" -version = "0.14.2" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -33,7 +33,7 @@ thiserror = "2.0.3" [dependencies.kas] # We must rename this package since macros expect kas to be in scope: -version = "0.14.1" +version = "0.15.0" package = "kas-core" path = "../kas-core" features = ["spawn"] diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index c65f89b5e..6acdfe135 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -26,8 +26,11 @@ enum LoadError { fn load(data: &[u8], resources_dir: Option<&Path>) -> Result { use once_cell::sync::Lazy; static FONT_FAMILY: Lazy = Lazy::new(|| { - let fonts_db = kas::text::fonts::library().read_db(); - fonts_db.font_family_from_alias("SERIF").unwrap_or_default() + let resolver = kas::text::fonts::library().resolver(); + let db = kas::text::fonts::db().unwrap(); + resolver + .font_family_from_alias(db, "SERIF") + .unwrap_or_default() }); // Defaults are taken from usvg::Options::default(). Notes: diff --git a/crates/kas-view/Cargo.toml b/crates/kas-view/Cargo.toml index 785b70f7a..d2c79b8bc 100644 --- a/crates/kas-view/Cargo.toml +++ b/crates/kas-view/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-view" -version = "0.14.2" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -17,12 +17,12 @@ features = ["kas/winit", "kas/wayland"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -kas-widgets = { version = "0.14.1", path = "../kas-widgets" } +kas-widgets = { version = "0.15.0", path = "../kas-widgets" } log = "0.4" linear-map = "1.2.0" # We must rename this package since macros expect kas to be in scope: -kas = { version = "0.14.1", package = "kas-core", path = "../kas-core" } +kas = { version = "0.15.0", package = "kas-core", path = "../kas-core" } [lints.clippy] collapsible_else_if = "allow" diff --git a/crates/kas-wgpu/Cargo.toml b/crates/kas-wgpu/Cargo.toml index b61084a4d..a8394e641 100644 --- a/crates/kas-wgpu/Cargo.toml +++ b/crates/kas-wgpu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-wgpu" -version = "0.14.2" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -39,12 +39,12 @@ rustc-hash = "2.0" [dependencies.kas] # Rename package purely for convenience: -version = "0.14.1" +version = "0.15.0" package = "kas-core" path = "../kas-core" [dependencies.kas-text] -version = "0.6.0" +version = "0.7.0" [dependencies.wgpu] version = "23.0.1" diff --git a/crates/kas-widgets/Cargo.toml b/crates/kas-widgets/Cargo.toml index 3844b7943..c900af591 100644 --- a/crates/kas-widgets/Cargo.toml +++ b/crates/kas-widgets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-widgets" -version = "0.14.2" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -26,11 +26,11 @@ smallvec = "1.6.1" unicode-segmentation = "1.7" thiserror = "2.0.3" image = { version = "0.25.1", optional = true } -kas-macros = { version = "0.14.1", path = "../kas-macros" } +kas-macros = { version = "0.15.0", path = "../kas-macros" } linear-map = "1.2.0" # We must rename this package since macros expect kas to be in scope: -kas = { version = "0.14.1", package = "kas-core", path = "../kas-core" } +kas = { version = "0.15.0", package = "kas-core", path = "../kas-core" } [lints.clippy] collapsible_else_if = "allow" diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 2c4425cb1..15825c1ff 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -10,20 +10,6 @@ use kas::prelude::*; use kas::text::format::{EditableText, FormattableText}; use kas::theme::{Text, TextClass}; -/// Construct a [`Label`] -#[inline] -pub fn label(text: T) -> Label { - Label::new(text) -} - -/// Construct a [`Label`] which accepts any data -/// -/// This is just a shortcut for `Label::new(text).map_any()`. -#[inline] -pub fn label_any(text: T) -> MapAny> { - MapAny::new(Label::new(text)) -} - impl_scope! { /// A text label /// @@ -56,6 +42,12 @@ impl_scope! { } } + /// Construct from `text`, mapping to support any data type + #[inline] + pub fn new_any(text: T) -> MapAny { + MapAny::new(Label::new(text)) + } + /// Get text class #[inline] pub fn class(&self) -> TextClass { diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index 58f6552f2..954b98ebd 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -108,7 +108,7 @@ pub use filler::Filler; pub use frame::Frame; pub use grid::Grid; pub use grip::{GripMsg, GripPart}; -pub use label::{label, label_any, AccessLabel, Label}; +pub use label::{AccessLabel, Label}; pub use list::*; pub use mark::{Mark, MarkButton}; pub use nav_frame::NavFrame; diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 90b8a7c0c..35871f350 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -189,8 +189,8 @@ impl_scope! { /// Where widgets have the same type and the length is fixed, an array /// may be used: /// ``` - /// use kas_widgets::{label, Row}; - /// let _ = Row::new([label("left"), label("right")]); + /// use kas_widgets::{Label, Row}; + /// let _ = Row::new([Label::new("left"), Label::new("right")]); /// ``` /// /// To support run-time insertion/deletion, use [`Vec`]: diff --git a/examples/gallery.rs b/examples/gallery.rs index 86565134f..17a42eecf 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -331,7 +331,7 @@ Demonstration of *as-you-type* formatting from **Markdown**. .with_text(DOC), ScrollLabel::new(Markdown::new(DOC).unwrap()) .on_configure(|cx, label| { - cx.push(label.id(), SetLabelId(label.id())); + cx.send(label.id(), SetLabelId(label.id())); }) .on_message(|cx, label, text| { let act = label.set_text(text); diff --git a/examples/mandlebrot/Cargo.toml b/examples/mandlebrot/Cargo.toml index a54fc3ca9..d5ef408af 100644 --- a/examples/mandlebrot/Cargo.toml +++ b/examples/mandlebrot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-mandlebrot" -version = "0.14.1" +version = "0.15.0" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -8,8 +8,8 @@ description = "KAS GUI / Mandlebrot example" publish = false [dependencies] -kas = { version = "0.14.1", features = ["wgpu"], path = "../.." } -kas-wgpu = { version = "0.14.1", path = "../../crates/kas-wgpu" } +kas = { version = "0.15.0", features = ["wgpu"], path = "../.." } +kas-wgpu = { version = "0.15.0", path = "../../crates/kas-wgpu" } chrono = "0.4" env_logger = "0.11" log = "0.4" diff --git a/examples/sync-counter.rs b/examples/sync-counter.rs index bd0b2d4c8..7e787254e 100644 --- a/examples/sync-counter.rs +++ b/examples/sync-counter.rs @@ -7,7 +7,7 @@ //! //! Each window shares the counter, but has its own increment step. -use kas::widgets::{column, format_data, label_any, row, Adapt, Button, Slider}; +use kas::widgets::{column, format_data, row, Adapt, Button, Label, Slider}; use kas::{messages::MessageStack, Window}; #[derive(Clone, Debug)] @@ -41,8 +41,8 @@ fn counter(title: &str) -> Window { format_data!(data: &Data, "Count: {}", data.0.0), row![slider, format_data!(data: &Data, "{}", data.1)], row![ - Button::new(label_any("Sub")).with(|cx, data: &Data| cx.push(Increment(-data.1))), - Button::new(label_any("Add")).with(|cx, data: &Data| cx.push(Increment(data.1))), + Button::new(Label::new_any("Sub")).with(|cx, data: &Data| cx.push(Increment(-data.1))), + Button::new(Label::new_any("Add")).with(|cx, data: &Data| cx.push(Increment(data.1))), ], ];