Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.14.2: Add InstantParseGuard, fix docs.rs builds #427

Merged
merged 4 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "kas"
version = "0.14.1"
version = "0.14.2"
authors = ["Diggory Hardy <git@dhardy.name>"]
edition = "2021"
license = "Apache-2.0"
Expand Down Expand Up @@ -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"
Expand Down
9 changes: 6 additions & 3 deletions crates/kas-dylib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "kas-dylib"
version = "0.14.1"
version = "0.14.2"
authors = ["Diggory Hardy <git@dhardy.name>"]
edition = "2021"
license = "Apache-2.0"
Expand All @@ -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"]

Expand All @@ -21,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 }
3 changes: 2 additions & 1 deletion crates/kas-resvg/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "kas-resvg"
version = "0.14.1"
version = "0.14.2"
authors = ["Diggory Hardy <git@dhardy.name>"]
edition = "2021"
license = "Apache-2.0"
Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions crates/kas-view/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "kas-view"
version = "0.14.1"
version = "0.14.2"
authors = ["Diggory Hardy <git@dhardy.name>"]
edition = "2021"
license = "Apache-2.0"
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions crates/kas-widgets/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "kas-widgets"
version = "0.14.1"
version = "0.14.2"
authors = ["Diggory Hardy <git@dhardy.name>"]
edition = "2021"
license = "Apache-2.0"
Expand All @@ -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
Expand Down
142 changes: 131 additions & 11 deletions crates/kas-widgets/src/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ impl<A: 'static> EditGuard for DefaultGuard<A> {
}

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<A> {
value_fn: Box<dyn Fn(&A) -> String>,
Expand Down Expand Up @@ -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<A, T: Debug + Display + FromStr> {
parsed: Option<T>,
Expand Down Expand Up @@ -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<A, T: Debug + Display + FromStr> {
value_fn: Box<dyn Fn(&A) -> T>,
on_afl: Box<dyn Fn(&mut EventCx, T)>,
}

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<M: Debug + 'static>(
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<Self>, 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<Self>, 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<Self>, 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
///
Expand Down Expand Up @@ -418,23 +488,48 @@ impl<A: 'static> EditBox<DefaultGuard<A>> {
}
}

/// 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<StringGuard<A>> {
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<T: Debug + Display + FromStr, M: Debug + 'static>(
value_fn: impl Fn(&A) -> T + 'static,
msg_fn: impl Fn(T) -> M + 'static,
) -> EditBox<ParseGuard<A, T>> {
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<T: Debug + Display + FromStr, M: Debug + 'static>(
value_fn: impl Fn(&A) -> T + 'static,
msg_fn: impl Fn(T) -> M + 'static,
) -> EditBox<InstantParseGuard<A, T>> {
EditBox::new(InstantParseGuard::new(value_fn, msg_fn))
}
}

impl<A: 'static> EditBox<StringGuard<A>> {
Expand Down Expand Up @@ -882,23 +977,48 @@ impl<A: 'static> EditField<DefaultGuard<A>> {
}
}

/// 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<StringGuard<A>> {
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<T: Debug + Display + FromStr, M: Debug + 'static>(
value_fn: impl Fn(&A) -> T + 'static,
msg_fn: impl Fn(T) -> M + 'static,
) -> EditField<ParseGuard<A, T>> {
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<T: Debug + Display + FromStr, M: Debug + 'static>(
value_fn: impl Fn(&A) -> T + 'static,
msg_fn: impl Fn(T) -> M + 'static,
) -> EditField<InstantParseGuard<A, T>> {
EditField::new(InstantParseGuard::new(value_fn, msg_fn))
}
}

impl<A: 'static> EditField<StringGuard<A>> {
Expand Down Expand Up @@ -939,7 +1059,7 @@ impl<G: EditGuard> EditField<G> {

/// 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 {
Expand Down