From e483ec0edce031244f7e3961a616353cc49d58a5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 12 Dec 2023 12:14:29 +0000 Subject: [PATCH 1/4] Fix docs.rs builds --- crates/kas-dylib/Cargo.toml | 5 ++++- crates/kas-resvg/Cargo.toml | 3 ++- crates/kas-view/Cargo.toml | 4 ++-- crates/kas-widgets/Cargo.toml | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/kas-dylib/Cargo.toml b/crates/kas-dylib/Cargo.toml index f058721bd..973644e9e 100644 --- a/crates/kas-dylib/Cargo.toml +++ b/crates/kas-dylib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-dylib" -version = "0.14.1" +version = "0.14.2" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -11,6 +11,9 @@ keywords = ["gui"] categories = ["gui"] repository = "https://github.com/kas-gui/kas" +[package.metadata.docs.rs] +features = ["kas-core/winit", "kas-core/wayland"] + [lib] crate-type = ["dylib"] diff --git a/crates/kas-resvg/Cargo.toml b/crates/kas-resvg/Cargo.toml index 72160ccf8..1d3a6c3cd 100644 --- a/crates/kas-resvg/Cargo.toml +++ b/crates/kas-resvg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-resvg" -version = "0.14.1" +version = "0.14.2" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -13,6 +13,7 @@ repository = "https://github.com/kas-gui/kas" exclude = ["/screenshots"] [package.metadata.docs.rs] +features = ["svg", "kas/winit", "kas/wayland"] all-features = true rustdoc-args = ["--cfg", "doc_cfg"] # To build locally: diff --git a/crates/kas-view/Cargo.toml b/crates/kas-view/Cargo.toml index 4f72d55e0..248a2f133 100644 --- a/crates/kas-view/Cargo.toml +++ b/crates/kas-view/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-view" -version = "0.14.1" +version = "0.14.2" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -13,7 +13,7 @@ repository = "https://github.com/kas-gui/kas" exclude = ["/screenshots"] [package.metadata.docs.rs] -features = [] +features = ["kas/winit", "kas/wayland"] rustdoc-args = ["--cfg", "doc_cfg"] # To build locally: # RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --no-deps --open diff --git a/crates/kas-widgets/Cargo.toml b/crates/kas-widgets/Cargo.toml index ed0d9402e..5e7edd444 100644 --- a/crates/kas-widgets/Cargo.toml +++ b/crates/kas-widgets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas-widgets" -version = "0.14.1" +version = "0.14.2" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -13,7 +13,7 @@ repository = "https://github.com/kas-gui/kas" exclude = ["/screenshots"] [package.metadata.docs.rs] -features = ["min_spec"] +features = ["min_spec", "kas/winit", "kas/wayland"] rustdoc-args = ["--cfg", "doc_cfg"] # To build locally: # RUSTDOCFLAGS="--cfg doc_cfg" cargo +nightly doc --no-deps --open From 0b1e9fd6407f2f1a884441ba734f634f802edf84 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 12 Dec 2023 12:48:38 +0000 Subject: [PATCH 2/4] Add kas-widgets::edit::InstantParseGuard --- crates/kas-widgets/src/edit.rs | 142 ++++++++++++++++++++++++++++++--- 1 file changed, 131 insertions(+), 11 deletions(-) diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 17a1dd17d..852b33a63 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -124,7 +124,11 @@ impl EditGuard for DefaultGuard { } impl_scope! { - /// An [`EditGuard`] impl for string input + /// An [`EditGuard`] for read-only strings + /// + /// This may be used with read-only edit fields, essentially resulting in a + /// fancier version of [`Text`](crate::Text) or + /// [`ScrollText`](crate::ScrollText). #[autoimpl(Debug ignore self.value_fn, self.on_afl)] pub struct StringGuard { value_fn: Box String>, @@ -199,7 +203,11 @@ impl_scope! { } impl_scope! { - /// An [`EditGuard`] impl for simple parsable types (e.g. numbers) + /// An [`EditGuard`] for parsable types + /// + /// This guard displays a value formatted from input data, updates the error + /// state according to parse success on each keystroke, and sends a message + /// on focus loss (where successful parsing occurred). #[autoimpl(Debug ignore self.value_fn, self.on_afl)] pub struct ParseGuard { parsed: Option, @@ -265,6 +273,68 @@ impl_scope! { } } +impl_scope! { + /// An as-you-type [`EditGuard`] for parsable types + /// + /// This guard displays a value formatted from input data, updates the error + /// state according to parse success on each keystroke, and sends a message + /// immediately (where successful parsing occurred). + #[autoimpl(Debug ignore self.value_fn, self.on_afl)] + pub struct InstantParseGuard { + value_fn: Box T>, + on_afl: Box, + } + + impl Self { + /// Construct + /// + /// On update, `value_fn` is used to extract a value from input data + /// which is then formatted as a string via [`Display`]. + /// If, however, the input field has focus, the update is ignored. + /// + /// On every edit, the guard attempts to parse the field's input as type + /// `T` via [`FromStr`]. On success, the result is converted to a + /// message via `on_afl` then emitted via [`EventCx::push`]. + pub fn new( + value_fn: impl Fn(&A) -> T + 'static, + on_afl: impl Fn(T) -> M + 'static, + ) -> Self { + InstantParseGuard { + value_fn: Box::new(value_fn), + on_afl: Box::new(move |cx, value| cx.push(on_afl(value))), + } + } + } + + impl EditGuard for Self { + type Data = A; + + fn focus_lost(edit: &mut EditField, cx: &mut EventCx, data: &A) { + // Always reset data on focus loss + let value = (edit.guard.value_fn)(data); + let action = edit.set_string(format!("{}", value)); + cx.action(edit, action); + } + + fn edit(edit: &mut EditField, cx: &mut EventCx, _: &A) { + let result = edit.get_str().parse(); + let action = edit.set_error_state(result.is_err()); + cx.action(edit.id(), action); + if let Ok(value) = result { + (edit.guard.on_afl)(cx, value); + } + } + + fn update(edit: &mut EditField, cx: &mut ConfigCx, data: &A) { + if !edit.has_edit_focus() { + let value = (edit.guard.value_fn)(data); + let action = edit.set_string(format!("{}", value)); + cx.action(&edit, action); + } + } + } +} + impl_scope! { /// A text-edit box /// @@ -418,16 +488,25 @@ impl EditBox> { } } - /// Construct an `EditField` displaying some `String` value - /// - /// The field is read-only. To make it read-write call [`Self::with_msg`] - /// or [`Self::with_editable`]. + /// Construct a read-only `EditBox` displaying some `String` value #[inline] pub fn string(value_fn: impl Fn(&A) -> String + 'static) -> EditBox> { EditBox::new(StringGuard::new(value_fn)).with_editable(false) } /// Construct an `EditBox` for a parsable value (e.g. a number) + /// + /// On update, `value_fn` is used to extract a value from input data + /// which is then formatted as a string via [`Display`]. + /// If, however, the input field has focus, the update is ignored. + /// + /// On every edit, the guard attempts to parse the field's input as type + /// `T` via [`FromStr`], caching the result and setting the error state. + /// + /// On field activation and focus loss when a `T` value is cached (see + /// previous paragraph), `on_afl` is used to construct a message to be + /// emitted via [`EventCx::push`]. The cached value is then cleared to + /// avoid sending duplicate messages. #[inline] pub fn parser( value_fn: impl Fn(&A) -> T + 'static, @@ -435,6 +514,22 @@ impl EditBox> { ) -> EditBox> { EditBox::new(ParseGuard::new(value_fn, msg_fn)) } + + /// Construct an `EditBox` for a parsable value (e.g. a number) + /// + /// On update, `value_fn` is used to extract a value from input data + /// which is then formatted as a string via [`Display`]. + /// If, however, the input field has focus, the update is ignored. + /// + /// On every edit, the guard attempts to parse the field's input as type + /// `T` via [`FromStr`]. On success, the result is converted to a + /// message via `on_afl` then emitted via [`EventCx::push`]. + pub fn instant_parser( + value_fn: impl Fn(&A) -> T + 'static, + msg_fn: impl Fn(T) -> M + 'static, + ) -> EditBox> { + EditBox::new(InstantParseGuard::new(value_fn, msg_fn)) + } } impl EditBox> { @@ -882,16 +977,25 @@ impl EditField> { } } - /// Construct an `EditField` displaying some `String` value - /// - /// The field is read-only. To make it read-write call [`Self::with_msg`] - /// or [`Self::with_editable`]. + /// Construct a read-only `EditField` displaying some `String` value #[inline] pub fn string(value_fn: impl Fn(&A) -> String + 'static) -> EditField> { EditField::new(StringGuard::new(value_fn)).with_editable(false) } /// Construct an `EditField` for a parsable value (e.g. a number) + /// + /// On update, `value_fn` is used to extract a value from input data + /// which is then formatted as a string via [`Display`]. + /// If, however, the input field has focus, the update is ignored. + /// + /// On every edit, the guard attempts to parse the field's input as type + /// `T` via [`FromStr`], caching the result and setting the error state. + /// + /// On field activation and focus loss when a `T` value is cached (see + /// previous paragraph), `on_afl` is used to construct a message to be + /// emitted via [`EventCx::push`]. The cached value is then cleared to + /// avoid sending duplicate messages. #[inline] pub fn parser( value_fn: impl Fn(&A) -> T + 'static, @@ -899,6 +1003,22 @@ impl EditField> { ) -> EditField> { EditField::new(ParseGuard::new(value_fn, msg_fn)) } + + /// Construct an `EditField` for a parsable value (e.g. a number) + /// + /// On update, `value_fn` is used to extract a value from input data + /// which is then formatted as a string via [`Display`]. + /// If, however, the input field has focus, the update is ignored. + /// + /// On every edit, the guard attempts to parse the field's input as type + /// `T` via [`FromStr`]. On success, the result is converted to a + /// message via `on_afl` then emitted via [`EventCx::push`]. + pub fn instant_parser( + value_fn: impl Fn(&A) -> T + 'static, + msg_fn: impl Fn(T) -> M + 'static, + ) -> EditField> { + EditField::new(InstantParseGuard::new(value_fn, msg_fn)) + } } impl EditField> { @@ -939,7 +1059,7 @@ impl EditField { /// Set the initial text (inline) /// - /// This method should only be used on a new `EditBox`. + /// This method should only be used on a new `EditField`. #[inline] #[must_use] pub fn with_text(mut self, text: impl ToString) -> Self { From 8ae08beb3618d18c1cb083d190784c7796dbc45b Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 12 Dec 2023 12:49:56 +0000 Subject: [PATCH 3/4] Update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e267cd09..0e06ea521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ 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). +## [0.14.2] — 2023-12-12 + +- Add `kas-widgets::edit::InstantParseGuard` (#427) +- Fix doc builds for kas-widgets, kas-view, kas-resvg, kas-dylib (#427) + ## [0.14.1] — 2023-12-12 The focus of this version is *input data*: widgets now have a `Data` associated type, passed by reference From 266f46166c7822452393e986a4ad3e65943d0942 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 12 Dec 2023 12:53:29 +0000 Subject: [PATCH 4/4] Bump kas to 0.14.2 This only affects dependants of crates already bumped to 0.14.2 --- Cargo.toml | 10 +++++----- crates/kas-dylib/Cargo.toml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ffa436b2e..2d181dd99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kas" -version = "0.14.1" +version = "0.14.2" authors = ["Diggory Hardy "] edition = "2021" license = "Apache-2.0" @@ -119,10 +119,10 @@ unsafe_node = ["kas-core/unsafe_node"] [dependencies] kas-core = { version = "0.14.1", path = "crates/kas-core" } -kas-dylib = { version = "0.14.1", path = "crates/kas-dylib", optional = true } -kas-widgets = { version = "0.14.1", path = "crates/kas-widgets" } -kas-view = { version = "0.14.1", path = "crates/kas-view", optional = true } -kas-resvg = { version = "0.14.1", path = "crates/kas-resvg", optional = true } +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 } [dependencies.kas-wgpu] version = "0.14.1" diff --git a/crates/kas-dylib/Cargo.toml b/crates/kas-dylib/Cargo.toml index 973644e9e..f951e020a 100644 --- a/crates/kas-dylib/Cargo.toml +++ b/crates/kas-dylib/Cargo.toml @@ -24,6 +24,6 @@ resvg = ["dep:kas-resvg"] [dependencies] kas-core = { version = "0.14.1", path = "../kas-core" } -kas-widgets = { version = "0.14.1", path = "../kas-widgets" } -kas-resvg = { version = "0.14.1", path = "../kas-resvg", optional = true } +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 }