From ecd2383a9dbc30310edb92fd20381e5baca81437 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 2 Feb 2025 09:26:23 +0000 Subject: [PATCH 01/12] Do not return Action from {List, Splitter, TabStack}::set_direction --- crates/kas-view/src/list_view.rs | 10 ++++------ crates/kas-widgets/src/list.rs | 6 +++--- crates/kas-widgets/src/splitter.rs | 6 +++--- crates/kas-widgets/src/tab_stack.rs | 6 +++--- examples/data-list-view.rs | 3 +-- examples/data-list.rs | 3 +-- examples/gallery.rs | 3 +-- 7 files changed, 16 insertions(+), 21 deletions(-) diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 477814c7e..16f7705d1 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -112,13 +112,11 @@ impl_scope! { } impl> ListView { /// Set the direction of contents - pub fn set_direction(&mut self, direction: Direction) -> Action { - if direction == self.direction { - return Action::empty(); + pub fn set_direction(&mut self, cx: &mut EventState, direction: Direction) { + if direction != self.direction { + self.direction = direction; + cx.action(self, Action::SET_RECT); } - - self.direction = direction; - Action::SET_RECT } } diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 3a11d43f8..ee510feb4 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -263,14 +263,14 @@ impl_scope! { impl List { /// Set the direction of contents - pub fn set_direction(&mut self, direction: Direction) -> Action { + pub fn set_direction(&mut self, cx: &mut EventState, direction: Direction) { if direction == self.direction { - return Action::empty(); + return; } self.direction = direction; // Note: most of the time SET_RECT would be enough, but margins can be different - Action::RESIZE + cx.action(self, Action::RESIZE); } } diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index f5d2350ee..f629e351b 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -71,14 +71,14 @@ impl_scope! { impl Splitter { /// Set the direction of contents - pub fn set_direction(&mut self, direction: Direction) -> Action { + pub fn set_direction(&mut self, cx: &mut EventState, direction: Direction) { if direction == self.direction { - return Action::empty(); + return; } self.direction = direction; // Note: most of the time SET_RECT would be enough, but margins can be different - Action::RESIZE + cx.action(self, Action::RESIZE); } } diff --git a/crates/kas-widgets/src/tab_stack.rs b/crates/kas-widgets/src/tab_stack.rs index 32c6d8cb8..b667810ea 100644 --- a/crates/kas-widgets/src/tab_stack.rs +++ b/crates/kas-widgets/src/tab_stack.rs @@ -131,14 +131,14 @@ impl_scope! { /// Set the position of tabs relative to content /// /// Default value: [`Direction::Up`] - pub fn set_direction(&mut self, direction: Direction) -> Action { + pub fn set_direction(&mut self, cx: &mut EventState, direction: Direction) { if direction == self.direction { - return Action::empty(); + return; } self.direction = direction; // Note: most of the time SET_RECT would be enough, but margins can be different - Action::RESIZE + cx.action(self, Action::RESIZE); } /// Call the handler `f` on page change diff --git a/examples/data-list-view.rs b/examples/data-list-view.rs index 9423bc2f2..67f2247fc 100644 --- a/examples/data-list-view.rs +++ b/examples/data-list-view.rs @@ -201,8 +201,7 @@ fn main() -> kas::runner::Result<()> { let data = Data::new(5); let list = ListView::new(MyDriver).on_update(|cx, list, data| { - let act = list.set_direction(data.dir); - cx.action(list, act); + list.set_direction(cx, data.dir); }); let tree = column![ "Demonstration of dynamic widget creation / deletion", diff --git a/examples/data-list.rs b/examples/data-list.rs index 1a35f431b..1ef20f9b8 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -163,12 +163,11 @@ fn main() -> kas::runner::Result<()> { }; let list = List::new(vec![]).on_update(|cx, list, data: &Data| { - let act = list.set_direction(data.dir); + list.set_direction(cx, data.dir); let len = data.len; if len != list.len() { list.resize_with(cx, data, len, ListEntry::new); } - cx.action(list, act); }); let tree = column![ "Demonstration of dynamic widget creation / deletion", diff --git a/examples/gallery.rs b/examples/gallery.rs index 01e0fb9e7..f234615c2 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -339,8 +339,7 @@ Demonstration of *as-you-type* formatting from **Markdown**. .map_any() ]) .on_update(|cx, list, data: &Data| { - let act = list.set_direction(data.dir); - cx.action(list, act); + list.set_direction(cx, data.dir); }), ]; From c06dd475eda0bf460127b0f43009f07f9c7921d3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 2 Feb 2025 09:33:47 +0000 Subject: [PATCH 02/12] Do not return Action from fn set_text For widgets Label, AccessLabel, ScrollLabel, WithLabel --- crates/kas-widgets/src/adapt/with_label.rs | 4 ++-- crates/kas-widgets/src/label.rs | 10 ++++++---- crates/kas-widgets/src/scroll_label.rs | 6 +++--- examples/gallery.rs | 3 +-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/kas-widgets/src/adapt/with_label.rs b/crates/kas-widgets/src/adapt/with_label.rs index 34ca5ce90..da6b1c938 100644 --- a/crates/kas-widgets/src/adapt/with_label.rs +++ b/crates/kas-widgets/src/adapt/with_label.rs @@ -106,8 +106,8 @@ impl_scope! { /// /// Note: this must not be called before fonts have been initialised /// (usually done by the theme when the main loop starts). - pub fn set_text>(&mut self, text: T) -> Action { - self.label.set_text(text.into()) + pub fn set_text>(&mut self, cx: &mut EventState, text: T) { + self.label.set_text(cx, text.into()); } } diff --git a/crates/kas-widgets/src/label.rs b/crates/kas-widgets/src/label.rs index 6f00b41cc..1fd860b97 100644 --- a/crates/kas-widgets/src/label.rs +++ b/crates/kas-widgets/src/label.rs @@ -105,9 +105,10 @@ impl_scope! { /// /// Note: this must not be called before fonts have been initialised /// (usually done by the theme when the main loop starts). - pub fn set_text(&mut self, text: T) -> Action { + pub fn set_text(&mut self, cx: &mut EventState, text: T) { self.text.set_text(text); - self.text.reprepare_action() + let act = self.text.reprepare_action(); + cx.action(self, act); } /// Get text contents @@ -254,9 +255,10 @@ impl_scope! { /// /// Note: this must not be called before fonts have been initialised /// (usually done by the theme when the main loop starts). - pub fn set_text(&mut self, text: AccessString) -> Action { + pub fn set_text(&mut self, cx: &mut EventState, text: AccessString) { self.text.set_text(text); - self.text.reprepare_action() + let act = self.text.reprepare_action(); + cx.action(self, act); } } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 768974037..f0cd596c3 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -105,10 +105,10 @@ impl_scope! { /// /// Note: this must not be called before fonts have been initialised /// (usually done by the theme when the main loop starts). - pub fn set_text(&mut self, text: T) -> Action { + pub fn set_text(&mut self, cx: &mut EventState, text: T) { self.text.set_text(text); if self.text.prepare() != Ok(true) { - return Action::empty(); + return; } self.text_size = Vec2::from(self.text.bounding_box().unwrap().1).cast_ceil(); @@ -118,7 +118,7 @@ impl_scope! { self.selection.set_max_len(self.text.str_len()); - Action::REDRAW + cx.action(self, Action::REDRAW); } fn set_edit_pos_from_coord(&mut self, cx: &mut EventCx, coord: Coord) { diff --git a/examples/gallery.rs b/examples/gallery.rs index f234615c2..c6aed6089 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -333,8 +333,7 @@ Demonstration of *as-you-type* formatting from **Markdown**. cx.send(label.id(), SetLabelId(label.id())); }) .on_message(|cx, label, text| { - let act = label.set_text(text); - cx.action(label, act); + label.set_text(cx, text); }) .map_any() ]) From 8c17cab71f059219342ced5bd1b182fa9eee6998 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 2 Feb 2025 09:37:39 +0000 Subject: [PATCH 03/12] Do not return Action from Slider::set_value --- crates/kas-widgets/src/slider.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index 2ccd8e2cd..c0794d609 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -238,8 +238,11 @@ impl_scope! { } /// Set value and update grip + /// + /// Returns `true` if, after clamping to the supported range, `value` + /// differs from the existing value. #[allow(clippy::neg_cmp_op_on_partial_ord)] - fn set_value(&mut self, value: T) -> Action { + fn set_value(&mut self, cx: &mut EventState, value: T) -> bool { let value = if !(value >= self.range.0) { self.range.0 } else if !(value <= self.range.1) { @@ -249,11 +252,13 @@ impl_scope! { }; if value == self.value { - return Action::empty(); + return false; } self.value = value; - self.grip.set_offset(self.offset()).1 + let act = self.grip.set_offset(self.offset()).1; + cx.action(self, act); + true } // translate value to offset in local coordinates @@ -282,9 +287,7 @@ impl_scope! { if self.direction.is_reversed() { a = b - a; } - let action = self.set_value(a + self.range.0); - if !action.is_empty() { - cx.action(&self, action); + if self.set_value(cx, a + self.range.0) { if let Some(ref f) = self.on_move { f(cx, data, self.value); } @@ -336,8 +339,8 @@ impl_scope! { type Data = A; fn update(&mut self, cx: &mut ConfigCx, data: &A) { - let action = self.set_value((self.state_fn)(cx, data)); - cx.action(self, action); + let v = (self.state_fn)(cx, data); + self.set_value(cx, v); } fn handle_event(&mut self, cx: &mut EventCx, data: &A, event: Event) -> IsUsed { @@ -375,9 +378,7 @@ impl_scope! { cx.depress_with_key(self.id(), code); - let action = self.set_value(value); - if !action.is_empty() { - cx.action(&self, action); + if self.set_value(cx, value) { if let Some(ref f) = self.on_move { f(cx, data, self.value); } From b5dc1c3722d66965932b06954de16ec777b61313 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 2 Feb 2025 09:46:18 +0000 Subject: [PATCH 04/12] Do not return Action from Grip::set_offset and ScrollBar::set_limits --- crates/kas-widgets/src/edit.rs | 3 +-- crates/kas-widgets/src/grip.rs | 10 ++++------ crates/kas-widgets/src/scroll_bar.rs | 26 ++++++++++++++------------ crates/kas-widgets/src/scroll_label.rs | 4 ++-- crates/kas-widgets/src/scroll_text.rs | 2 +- crates/kas-widgets/src/slider.rs | 5 ++--- crates/kas-widgets/src/splitter.rs | 3 +-- 7 files changed, 25 insertions(+), 28 deletions(-) diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 03091cd15..3d77755cf 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -429,8 +429,7 @@ impl_scope! { impl Self { fn update_scroll_bar(&mut self, cx: &mut EventState) { let max_offset = self.inner.max_scroll_offset().1; - let action = self.bar.set_limits(max_offset, self.inner.rect().size.1); - cx.action(&self, action); + self.bar.set_limits(cx, max_offset, self.inner.rect().size.1); self.bar.set_value(cx, self.inner.view_offset.1); } diff --git a/crates/kas-widgets/src/grip.rs b/crates/kas-widgets/src/grip.rs index fb692af7f..461361189 100644 --- a/crates/kas-widgets/src/grip.rs +++ b/crates/kas-widgets/src/grip.rs @@ -179,20 +179,18 @@ impl_scope! { /// The input `offset` is clamped between [`Offset::ZERO`] and /// [`Self::max_offset`]. /// - /// The return value is a tuple of the new offest and an action: - /// [`Action::REDRAW`] if the grip has moved, otherwise an empty action. + /// The return value is a tuple of the new offest. /// /// It is expected that [`Self::set_track`] and [`Self::set_size`] are /// called before this method. - pub fn set_offset(&mut self, offset: Offset) -> (Offset, Action) { + pub fn set_offset(&mut self, cx: &mut EventState, offset: Offset) -> Offset { let offset = offset.min(self.max_offset()).max(Offset::ZERO); let grip_pos = self.track.pos + offset; if grip_pos != self.rect().pos { widget_set_rect!(Rect { pos: grip_pos, size: self.rect().size }); - (offset, Action::REDRAW) - } else { - (offset, Action::empty()) + cx.redraw(self); } + offset } /// Handle an event on the track itself diff --git a/crates/kas-widgets/src/scroll_bar.rs b/crates/kas-widgets/src/scroll_bar.rs index 9a4e40920..391ce1a53 100644 --- a/crates/kas-widgets/src/scroll_bar.rs +++ b/crates/kas-widgets/src/scroll_bar.rs @@ -124,7 +124,11 @@ impl_scope! { #[inline] #[must_use] pub fn with_limits(mut self, max_value: i32, grip_size: i32) -> Self { - let _ = self.set_limits(max_value, grip_size); + // We should gracefully handle zero, though appearance may be wrong. + self.grip_size = grip_size.max(1); + + self.max_value = max_value.max(0); + self.value = self.value.clamp(0, self.max_value); self } @@ -152,13 +156,13 @@ impl_scope! { /// so long as both parameters use the same units. /// /// Returns [`Action::REDRAW`] if a redraw is required. - pub fn set_limits(&mut self, max_value: i32, grip_size: i32) -> Action { + pub fn set_limits(&mut self, cx: &mut EventState, max_value: i32, grip_size: i32) { // We should gracefully handle zero, though appearance may be wrong. self.grip_size = grip_size.max(1); self.max_value = max_value.max(0); self.value = self.value.clamp(0, self.max_value); - self.update_widgets() + self.update_widgets(cx); } /// Read the current max value @@ -191,8 +195,7 @@ impl_scope! { let changed = value != self.value; if changed { self.value = value; - let action = self.grip.set_offset(self.offset()).1; - cx.action(&self, action); + self.grip.set_offset(cx, self.offset()); } self.force_visible(cx); changed @@ -212,7 +215,7 @@ impl_scope! { } } - fn update_widgets(&mut self) -> Action { + fn update_widgets(&mut self, cx: &mut EventState) { let len = self.bar_len(); let total = 1i64.max(i64::from(self.max_value) + i64::from(self.grip_size)); let grip_len = i64::from(self.grip_size) * i64::conv(len) / total; @@ -220,7 +223,7 @@ impl_scope! { let mut size = self.rect().size; size.set_component(self.direction, self.grip_len); self.grip.set_size(size); - self.grip.set_offset(self.offset()).1 + self.grip.set_offset(cx, self.offset()); } // translate value to offset in local coordinates @@ -244,8 +247,7 @@ impl_scope! { // true if not equal to old value fn apply_grip_offset(&mut self, cx: &mut EventCx, offset: Offset) { - let (offset, action) = self.grip.set_offset(offset); - cx.action(&self, action); + let offset = self.grip.set_offset(cx, offset); let len = self.bar_len() - self.grip_len; let mut offset = match self.direction.is_vertical() { @@ -288,7 +290,7 @@ impl_scope! { self.grip.set_rect(cx, Rect::ZERO, AlignHints::NONE); self.min_grip_len = cx.size_cx().grip_len(); - let _ = self.update_widgets(); + self.update_widgets(cx); } fn draw(&mut self, mut draw: DrawCx) { @@ -483,7 +485,7 @@ impl_scope! { let pos = Coord(pos.0, rect.pos2().1 - bar_width); let size = Size::new(child_size.0, bar_width); self.horiz_bar.set_rect(cx, Rect { pos, size }, AlignHints::NONE); - let _ = self.horiz_bar.set_limits(max_scroll_offset.0, rect.size.0); + self.horiz_bar.set_limits(cx, max_scroll_offset.0, rect.size.0); } else { self.horiz_bar.set_rect(cx, Rect::ZERO, AlignHints::NONE); } @@ -492,7 +494,7 @@ impl_scope! { let pos = Coord(rect.pos2().0 - bar_width, pos.1); let size = Size::new(bar_width, self.rect().size.1); self.vert_bar.set_rect(cx, Rect { pos, size }, AlignHints::NONE); - let _ = self.vert_bar.set_limits(max_scroll_offset.1, rect.size.1); + self.vert_bar.set_limits(cx, max_scroll_offset.1, rect.size.1); } else { self.vert_bar.set_rect(cx, Rect::ZERO, AlignHints::NONE); } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index f0cd596c3..0f8a0c3f4 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -57,7 +57,7 @@ impl_scope! { rect.pos.0 += rect.size.0 - w; rect.size.0 = w; self.bar.set_rect(cx, rect, AlignHints::NONE); - let _ = self.bar.set_limits(max_offset.1, rect.size.1); + self.bar.set_limits(cx, max_offset.1, rect.size.1); self.bar.set_value(cx, self.view_offset.1); } @@ -113,7 +113,7 @@ impl_scope! { self.text_size = Vec2::from(self.text.bounding_box().unwrap().1).cast_ceil(); let max_offset = self.max_scroll_offset(); - let _ = self.bar.set_limits(max_offset.1, self.rect().size.1); + self.bar.set_limits(cx, max_offset.1, self.rect().size.1); self.view_offset = self.view_offset.min(max_offset); self.selection.set_max_len(self.text.str_len()); diff --git a/crates/kas-widgets/src/scroll_text.rs b/crates/kas-widgets/src/scroll_text.rs index 77a652e3d..357afcbac 100644 --- a/crates/kas-widgets/src/scroll_text.rs +++ b/crates/kas-widgets/src/scroll_text.rs @@ -57,7 +57,7 @@ impl_scope! { rect.pos.0 += rect.size.0 - w; rect.size.0 = w; self.bar.set_rect(cx, rect, AlignHints::NONE); - let _ = self.bar.set_limits(max_offset.1, rect.size.1); + self.bar.set_limits(cx, max_offset.1, rect.size.1); self.bar.set_value(cx, self.view_offset.1); } diff --git a/crates/kas-widgets/src/slider.rs b/crates/kas-widgets/src/slider.rs index c0794d609..44127a6f4 100644 --- a/crates/kas-widgets/src/slider.rs +++ b/crates/kas-widgets/src/slider.rs @@ -256,8 +256,7 @@ impl_scope! { } self.value = value; - let act = self.grip.set_offset(self.offset()).1; - cx.action(self, act); + self.grip.set_offset(cx, self.offset()); true } @@ -315,7 +314,7 @@ impl_scope! { rect.size.set_component(self.direction, cx.size_cx().grip_len()); self.grip.set_rect(cx, rect, AlignHints::NONE); // Correct the position: - let _ = self.grip.set_offset(self.offset()); + self.grip.set_offset(cx, self.offset()); } fn draw(&mut self, mut draw: DrawCx) { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index f629e351b..365164fe5 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -279,8 +279,7 @@ impl_scope! { if self.direction.is_reversed() { offset = Offset::conv(grip.track().size) - offset; } - let action = grip.set_offset(offset).1; - cx.action(&self, action); + grip.set_offset(cx, offset); } self.adjust_size(&mut cx.config_cx(), n); } From 3fed351ce20a2a98ea498c9a9de133d828c451c9 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 2 Feb 2025 09:54:05 +0000 Subject: [PATCH 05/12] Do not return Action from {EditBox, EditField}::set_error_state --- crates/kas-widgets/src/edit.rs | 21 +++++++++------------ crates/kas-widgets/src/spinner.rs | 3 +-- examples/gallery.rs | 6 ++---- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/crates/kas-widgets/src/edit.rs b/crates/kas-widgets/src/edit.rs index 3d77755cf..30dc69b01 100644 --- a/crates/kas-widgets/src/edit.rs +++ b/crates/kas-widgets/src/edit.rs @@ -255,8 +255,7 @@ impl_scope! { fn edit(edit: &mut EditField, cx: &mut EventCx, _: &A) { edit.guard.parsed = edit.as_str().parse().ok(); - let action = edit.set_error_state(edit.guard.parsed.is_none()); - cx.action(edit, action); + edit.set_error_state(cx, edit.guard.parsed.is_none()); } fn update(edit: &mut EditField, cx: &mut ConfigCx, data: &A) { @@ -313,8 +312,7 @@ impl_scope! { fn edit(edit: &mut EditField, cx: &mut EventCx, _: &A) { let result = edit.as_str().parse(); - let action = edit.set_error_state(result.is_err()); - cx.action(edit.id(), action); + edit.set_error_state(cx, result.is_err()); if let Ok(value) = result { (edit.guard.on_afl)(cx, value); } @@ -653,8 +651,8 @@ impl EditBox { /// /// When true, the input field's background is drawn red. /// This state is cleared by [`Self::set_string`]. - pub fn set_error_state(&mut self, error_state: bool) -> Action { - self.inner.set_error_state(error_state) + pub fn set_error_state(&mut self, cx: &mut EventState, error_state: bool) { + self.inner.set_error_state(cx, error_state); } } @@ -982,15 +980,14 @@ impl_scope! { } self.selection.set_max_len(self.text.str_len()); - let mut action = Action::REDRAW; + cx.redraw(&self); self.text_size = Vec2::from(self.text.bounding_box().unwrap().1).cast_ceil(); let view_offset = self.view_offset.min(self.max_scroll_offset()); if view_offset != self.view_offset { - action |= Action::SCROLLED; + cx.action(&self, Action::SCROLLED); self.view_offset = view_offset; } - action |= self.set_error_state(false); - cx.action(self, action); + self.set_error_state(cx, false); } } } @@ -1232,9 +1229,9 @@ impl EditField { /// When true, the input field's background is drawn red. /// This state is cleared by [`Self::set_string`]. // TODO: possibly change type to Option and display the error - pub fn set_error_state(&mut self, error_state: bool) -> Action { + pub fn set_error_state(&mut self, cx: &mut EventState, error_state: bool) { self.error_state = error_state; - Action::REDRAW + cx.redraw(self); } fn prepare_text(&mut self, cx: &mut EventCx) { diff --git a/crates/kas-widgets/src/spinner.rs b/crates/kas-widgets/src/spinner.rs index c2eebb34e..65158c8f0 100644 --- a/crates/kas-widgets/src/spinner.rs +++ b/crates/kas-widgets/src/spinner.rs @@ -147,8 +147,7 @@ impl EditGuard for SpinnerGuard { } else { is_err = true; }; - let action = edit.set_error_state(is_err); - cx.action(edit, action); + edit.set_error_state(cx, is_err); } } diff --git a/examples/gallery.rs b/examples/gallery.rs index c6aed6089..c589b55bd 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -97,8 +97,7 @@ fn widgets() -> Box> { fn edit(edit: &mut EditField, cx: &mut EventCx, _: &Data) { // 7a is the colour of *magic*! - let act = edit.set_error_state(edit.as_str().len() % (7 + 1) == 0); - cx.action(edit, act); + edit.set_error_state(cx, edit.as_str().len() % (7 + 1) == 0); } } @@ -294,8 +293,7 @@ fn editor() -> Box> { fn edit(edit: &mut EditField, cx: &mut EventCx, data: &Data) { let result = Markdown::new(edit.as_str()); - let act = edit.set_error_state(result.is_err()); - cx.action(edit, act); + edit.set_error_state(cx, result.is_err()); let text = result.unwrap_or_else(|err| Markdown::new(&format!("{err}")).unwrap()); cx.send(data.label_id.clone(), text); } From 757c420c74f65aff9e8fccc72000e6ac4a96f963 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 2 Feb 2025 09:55:59 +0000 Subject: [PATCH 06/12] Do not return Action from fn set_scaling For widgets Canvas, Svg, Image --- crates/kas-resvg/src/canvas.rs | 4 ++-- crates/kas-resvg/src/svg.rs | 4 ++-- crates/kas-widgets/src/image.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/kas-resvg/src/canvas.rs b/crates/kas-resvg/src/canvas.rs index 388783796..4fd8ec2a5 100644 --- a/crates/kas-resvg/src/canvas.rs +++ b/crates/kas-resvg/src/canvas.rs @@ -151,10 +151,10 @@ impl_scope! { /// Default size is 128 × 128; default stretch is [`Stretch::High`]. /// Other fields use [`PixmapScaling`]'s default values. #[inline] - pub fn set_scaling(&mut self, f: impl FnOnce(&mut PixmapScaling)) -> Action { + pub fn set_scaling(&mut self, cx: &mut EventState, f: impl FnOnce(&mut PixmapScaling)) { f(&mut self.scaling); // NOTE: if only `aspect` is changed, REDRAW is enough - Action::RESIZE + cx.resize(self); } } diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index b10d7e907..8e8466546 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -231,10 +231,10 @@ impl_scope! { /// [`PixmapScaling::size`] is set from the SVG on loading (it may also be set here). /// Other scaling parameters take their default values from [`PixmapScaling`]. #[inline] - pub fn set_scaling(&mut self, f: impl FnOnce(&mut PixmapScaling)) -> Action { + pub fn set_scaling(&mut self, cx: &mut EventState, f: impl FnOnce(&mut PixmapScaling)) { f(&mut self.scaling); // NOTE: if only `aspect` is changed, REDRAW is enough - Action::RESIZE + cx.resize(self); } } diff --git a/crates/kas-widgets/src/image.rs b/crates/kas-widgets/src/image.rs index 565d65864..104b400e4 100644 --- a/crates/kas-widgets/src/image.rs +++ b/crates/kas-widgets/src/image.rs @@ -139,10 +139,10 @@ impl_scope! { /// By default, this is [`PixmapScaling::default`] except with /// `fix_aspect: true`. #[inline] - pub fn set_scaling(&mut self, f: impl FnOnce(&mut PixmapScaling)) -> Action { + pub fn set_scaling(&mut self, cx: &mut EventState, f: impl FnOnce(&mut PixmapScaling)) { f(&mut self.scaling); // NOTE: if only `aspect` is changed, REDRAW is enough - Action::RESIZE + cx.resize(self); } } From 449ac4503c6c075cad5eb75626aa412386ccd222 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Sun, 2 Feb 2025 10:06:06 +0000 Subject: [PATCH 07/12] Do not return Action from {ListView, MatrixView} selection methods --- crates/kas-view/src/list_view.rs | 54 ++++++++++++++---------------- crates/kas-view/src/matrix_view.rs | 54 ++++++++++++++---------------- examples/gallery.rs | 3 +- 3 files changed, 53 insertions(+), 58 deletions(-) diff --git a/crates/kas-view/src/list_view.rs b/crates/kas-view/src/list_view.rs index 16f7705d1..9bafa92f2 100644 --- a/crates/kas-view/src/list_view.rs +++ b/crates/kas-view/src/list_view.rs @@ -166,28 +166,29 @@ impl_scope! { /// except via [`Select`] from view widgets. (TODO: reconsider this.) /// /// [`Select`]: kas::messages::Select - pub fn set_selection_mode(&mut self, mode: SelectionMode) -> Action { + pub fn set_selection_mode(&mut self, cx: &mut EventState, mode: SelectionMode) { self.sel_mode = mode; match mode { SelectionMode::None if !self.selection.is_empty() => { self.selection.clear(); - Action::REDRAW + cx.redraw(self); } SelectionMode::Single if self.selection.len() > 1 => { if let Some(first) = self.selection.iter().next().cloned() { self.selection.retain(|item| *item == first); } - Action::REDRAW + cx.redraw(self); } - _ => Action::empty(), + _ => (), } } - /// Set the selection mode (inline) + /// Set the initial selection mode (inline) /// /// See [`Self::set_selection_mode`] documentation. #[must_use] pub fn with_selection_mode(mut self, mode: SelectionMode) -> Self { - let _ = self.set_selection_mode(mode); + debug_assert!(self.selection.is_empty()); + self.sel_mode = mode; self } @@ -199,14 +200,11 @@ impl_scope! { /// /// By default, [`SelectionStyle::Highlight`] is used. Other modes may /// add margin between elements. - pub fn set_selection_style(&mut self, style: SelectionStyle) -> Action { - let action = if style.is_external() != self.sel_style.is_external() { - Action::RESIZE - } else { - Action::empty() + pub fn set_selection_style(&mut self, cx: &mut EventState, style: SelectionStyle) { + if style.is_external() != self.sel_style.is_external() { + cx.resize(&self); }; self.sel_style = style; - action } /// Set the selection style (inline) /// @@ -231,12 +229,10 @@ impl_scope! { } /// Clear all selected items - pub fn clear_selected(&mut self) -> Action { - if self.selection.is_empty() { - Action::empty() - } else { + pub fn clear_selected(&mut self, cx: &mut EventState) { + if !self.selection.is_empty() { self.selection.clear(); - Action::REDRAW + cx.redraw(self); } } @@ -246,30 +242,32 @@ impl_scope! { /// Does not verify the validity of `key`. /// Does not send [`SelectionMsg`] messages. /// - /// Returns `Action::REDRAW` if newly selected, `Action::empty()` if + /// Returns `true` if newly selected, `false` if /// already selected. Fails if selection mode does not permit selection /// or if the key is invalid. - pub fn select(&mut self, key: A::Key) -> Action { + pub fn select(&mut self, cx: &mut EventState, key: A::Key) -> bool { match self.sel_mode { - SelectionMode::None => return Action::empty(), + SelectionMode::None => return false, SelectionMode::Single => self.selection.clear(), _ => (), } - match self.selection.insert(key) { - true => Action::REDRAW, - false => Action::empty(), + let r = self.selection.insert(key); + if r { + cx.redraw(self); } + r } /// Directly deselect an item /// - /// Returns `Action::REDRAW` if deselected, `Action::empty()` if not + /// Returns `true` if deselected, `false` if not /// previously selected or if the key is invalid. - pub fn deselect(&mut self, key: &A::Key) -> Action { - match self.selection.remove(key) { - true => Action::REDRAW, - false => Action::empty(), + pub fn deselect(&mut self, cx: &mut EventState, key: &A::Key) -> bool { + let r = self.selection.remove(key); + if r { + cx.redraw(self); } + r } /// Get the direction of contents diff --git a/crates/kas-view/src/matrix_view.rs b/crates/kas-view/src/matrix_view.rs index c481c8436..aef5fec52 100644 --- a/crates/kas-view/src/matrix_view.rs +++ b/crates/kas-view/src/matrix_view.rs @@ -121,28 +121,29 @@ impl_scope! { /// except via [`Select`] from view widgets. (TODO: reconsider this.) /// /// [`Select`]: kas::messages::Select - pub fn set_selection_mode(&mut self, mode: SelectionMode) -> Action { + pub fn set_selection_mode(&mut self, cx: &mut EventState, mode: SelectionMode) { self.sel_mode = mode; match mode { SelectionMode::None if !self.selection.is_empty() => { self.selection.clear(); - Action::REDRAW + cx.redraw(self); } SelectionMode::Single if self.selection.len() > 1 => { if let Some(first) = self.selection.iter().next().cloned() { self.selection.retain(|item| *item == first); } - Action::REDRAW + cx.redraw(self); } - _ => Action::empty(), + _ => (), } } - /// Set the selection mode (inline) + /// Set the initial selection mode (inline) /// /// See [`Self::set_selection_mode`] documentation. #[must_use] pub fn with_selection_mode(mut self, mode: SelectionMode) -> Self { - let _ = self.set_selection_mode(mode); + debug_assert!(self.selection.is_empty()); + self.sel_mode = mode; self } @@ -154,14 +155,11 @@ impl_scope! { /// /// By default, [`SelectionStyle::Highlight`] is used. Other modes may /// add margin between elements. - pub fn set_selection_style(&mut self, style: SelectionStyle) -> Action { - let action = if style.is_external() != self.sel_style.is_external() { - Action::RESIZE - } else { - Action::empty() + pub fn set_selection_style(&mut self, cx: &mut EventState, style: SelectionStyle) { + if style.is_external() != self.sel_style.is_external() { + cx.resize(&self); }; self.sel_style = style; - action } /// Set the selection style (inline) /// @@ -186,12 +184,10 @@ impl_scope! { } /// Clear all selected items - pub fn clear_selected(&mut self) -> Action { - if self.selection.is_empty() { - Action::empty() - } else { + pub fn clear_selected(&mut self, cx: &mut EventState) { + if !self.selection.is_empty() { self.selection.clear(); - Action::REDRAW + cx.redraw(self); } } @@ -201,30 +197,32 @@ impl_scope! { /// Does not verify the validity of `key`. /// Does not send [`SelectionMsg`] messages. /// - /// Returns `Action::REDRAW` if newly selected, `Action::empty()` if + /// Returns `true` if newly selected, `false` if /// already selected. Fails if selection mode does not permit selection /// or if the key is invalid. - pub fn select(&mut self, key: A::Key) -> Action { + pub fn select(&mut self, cx: &mut EventState, key: A::Key) -> bool { match self.sel_mode { - SelectionMode::None => return Action::empty(), + SelectionMode::None => return false, SelectionMode::Single => self.selection.clear(), _ => (), } - match self.selection.insert(key) { - true => Action::REDRAW, - false => Action::empty(), + let r = self.selection.insert(key); + if r { + cx.redraw(self); } + r } /// Directly deselect an item /// - /// Returns `Action::REDRAW` if deselected, `Action::empty()` if not + /// Returns `true` if deselected, `false` if not /// previously selected or if the key is invalid. - pub fn deselect(&mut self, key: &A::Key) -> Action { - match self.selection.remove(key) { - true => Action::REDRAW, - false => Action::empty(), + pub fn deselect(&mut self, cx: &mut EventState, key: &A::Key) -> bool { + let r = self.selection.remove(key); + if r { + cx.redraw(self); } + r } /// Set the preferred number of items visible (inline) diff --git a/examples/gallery.rs b/examples/gallery.rs index c589b55bd..d01403b8e 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -396,8 +396,7 @@ fn filter_list() -> Box> { let list_view = filter::FilterBoxList::new(ListView::down(ListGuard), filter, guard) .map(|data: &Data| &data.list) .on_update(|cx, list, data| { - let act = list.list_mut().set_selection_mode(data.mode); - cx.action(list, act); + list.list_mut().set_selection_mode(cx, data.mode); }); let sel_buttons = row![ From 66c19ae8c4c609e84420f099a92d3f7c6008ddd3 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 7 Feb 2025 10:36:37 +0000 Subject: [PATCH 08/12] Remove HasScrollBars trait, merging methods into implementing types This was only used by widgets ScrollBars and ScrollBarRegion. Interface is revised. --- crates/kas-core/src/core/scroll_traits.rs | 73 +----------- crates/kas-core/src/prelude.rs | 3 +- crates/kas-macros/src/lib.rs | 3 +- crates/kas-macros/src/scroll_traits.rs | 41 ------- crates/kas-widgets/src/lib.rs | 2 +- crates/kas-widgets/src/scroll_bar.rs | 139 ++++++++++++++++++---- 6 files changed, 117 insertions(+), 144 deletions(-) diff --git a/crates/kas-core/src/core/scroll_traits.rs b/crates/kas-core/src/core/scroll_traits.rs index b02c019b9..d6ad6731a 100644 --- a/crates/kas-core/src/core/scroll_traits.rs +++ b/crates/kas-core/src/core/scroll_traits.rs @@ -7,7 +7,7 @@ use crate::event::EventCx; use crate::geom::{Offset, Size}; -use crate::{Action, Widget}; +use crate::Widget; #[allow(unused)] use crate::{Events, Layout}; /// Additional functionality on scrollable widgets @@ -50,74 +50,3 @@ pub trait Scrollable: Widget { /// resulting offset is returned. fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset; } - -/// Scroll bar mode -/// -/// Note that in addition to this mode, bars may be disabled on each axis. -#[kas_macros::impl_default(ScrollBarMode::Auto)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum ScrollBarMode { - /// Scroll bars are always shown if enabled. - Fixed, - /// Automatically enable/disable scroll bars as required when resized. - /// - /// This has the side-effect of reserving enough space for scroll bars even - /// when not required. - Auto, - /// Scroll bars float over content and are only drawn when hovered over by - /// the mouse. - Invisible, -} - -/// Scroll bar control -pub trait HasScrollBars { - /// Get mode - fn get_mode(&self) -> ScrollBarMode; - - /// Set mode - fn set_mode(&mut self, mode: ScrollBarMode) -> Action; - - /// Get currently visible bars - /// - /// Returns `(horiz, vert)` tuple. - fn get_visible_bars(&self) -> (bool, bool); - - /// Set enabled bars without adjusting mode - /// - /// Note: if mode is `Auto` this has no effect. - /// - /// This requires a [`Action::RESIZE`]. - fn set_visible_bars(&mut self, bars: (bool, bool)) -> Action; - - /// Set auto mode (inline) - #[inline] - fn with_auto_bars(mut self) -> Self - where - Self: Sized, - { - let _ = self.set_mode(ScrollBarMode::Auto); - self - } - - /// Set fixed bars (inline) - #[inline] - fn with_fixed_bars(mut self, horiz: bool, vert: bool) -> Self - where - Self: Sized, - { - let _ = self.set_mode(ScrollBarMode::Fixed); - let _ = self.set_visible_bars((horiz, vert)); - self - } - - /// Set invisible bars (inline) - #[inline] - fn with_invisible_bars(mut self, horiz: bool, vert: bool) -> Self - where - Self: Sized, - { - let _ = self.set_mode(ScrollBarMode::Invisible); - let _ = self.set_visible_bars((horiz, vert)); - self - } -} diff --git a/crates/kas-core/src/prelude.rs b/crates/kas-core/src/prelude.rs index 38bd9c95a..5f4025c29 100644 --- a/crates/kas-core/src/prelude.rs +++ b/crates/kas-core/src/prelude.rs @@ -29,5 +29,4 @@ pub use crate::{widget, widget_index, widget_set_rect}; #[doc(no_inline)] pub use crate::{Events, Layout, Tile, TileExt, Widget, Window, WindowCommand}; #[doc(no_inline)] pub use crate::{HasId, Id}; -#[doc(no_inline)] -pub use crate::{HasScrollBars, Node, ScrollBarMode, Scrollable}; +#[doc(no_inline)] pub use crate::{Node, Scrollable}; diff --git a/crates/kas-macros/src/lib.rs b/crates/kas-macros/src/lib.rs index 483415f49..68fd30882 100644 --- a/crates/kas-macros/src/lib.rs +++ b/crates/kas-macros/src/lib.rs @@ -71,7 +71,7 @@ pub fn impl_default(attr: TokenStream, item: TokenStream) -> TokenStream { #[proc_macro_error] pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { use autoimpl::ImplTrait; - use scroll_traits::{ImplHasScrollBars, ImplScrollable}; + use scroll_traits::ImplScrollable; use std::iter::once; let mut toks = item.clone(); @@ -84,7 +84,6 @@ pub fn autoimpl(attr: TokenStream, item: TokenStream) -> TokenStream { autoimpl::STD_IMPLS .iter() .cloned() - .chain(once(&ImplHasScrollBars as &dyn ImplTrait)) .chain(once(&ImplScrollable as &dyn ImplTrait)) .find(|impl_| impl_.path().matches_ident_or_path(path)) }; diff --git a/crates/kas-macros/src/scroll_traits.rs b/crates/kas-macros/src/scroll_traits.rs index c0bb9fec9..54da052c8 100644 --- a/crates/kas-macros/src/scroll_traits.rs +++ b/crates/kas-macros/src/scroll_traits.rs @@ -53,44 +53,3 @@ impl ImplTrait for ImplScrollable { } } } - -pub struct ImplHasScrollBars; -impl ImplTrait for ImplHasScrollBars { - fn path(&self) -> SimplePath { - SimplePath::new(&["", "kas", "HasScrollBars"]) - } - - fn support_ignore(&self) -> bool { - false - } - - fn support_using(&self) -> bool { - true - } - - fn struct_items(&self, _: &ItemStruct, args: &ImplArgs) -> Result<(Toks, Toks)> { - if let Some(using) = args.using_member() { - let methods = quote! { - #[inline] - fn get_mode(&self) -> ::kas::ScrollBarMode { - self.#using.get_mode() - } - #[inline] - fn set_mode(&mut self, mode: ::kas::ScrollBarMode) -> ::kas::Action { - self.#using.set_mode(mode) - } - #[inline] - fn get_visible_bars(&self) -> (bool, bool) { - self.#using.get_visible_bars() - } - #[inline] - fn set_visible_bars(&mut self, bars: (bool, bool)) -> ::kas::Action { - self.#using.set_visible_bars(bars) - } - }; - Ok((quote! { ::kas::HasScrollBars }, methods)) - } else { - Err(Error::RequireUsing) - } - } -} diff --git a/crates/kas-widgets/src/lib.rs b/crates/kas-widgets/src/lib.rs index 2a80006fc..7d7b9899f 100644 --- a/crates/kas-widgets/src/lib.rs +++ b/crates/kas-widgets/src/lib.rs @@ -114,7 +114,7 @@ pub use nav_frame::NavFrame; pub use progress::ProgressBar; pub use radio_box::{RadioBox, RadioButton}; pub use scroll::ScrollRegion; -pub use scroll_bar::{ScrollBar, ScrollBarRegion, ScrollBars, ScrollMsg}; +pub use scroll_bar::{ScrollBar, ScrollBarMode, ScrollBarRegion, ScrollBars, ScrollMsg}; pub use scroll_label::ScrollLabel; pub use scroll_text::ScrollText; pub use separator::Separator; diff --git a/crates/kas-widgets/src/scroll_bar.rs b/crates/kas-widgets/src/scroll_bar.rs index 391ce1a53..07f0ffba6 100644 --- a/crates/kas-widgets/src/scroll_bar.rs +++ b/crates/kas-widgets/src/scroll_bar.rs @@ -11,6 +11,28 @@ use kas::prelude::*; use kas::theme::Feature; use std::fmt::Debug; +/// Scroll bar mode +/// +/// The default value is [`ScrollBarMode::Auto`]. +#[kas_macros::impl_default(ScrollBarMode::Auto)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ScrollBarMode { + /// Automatically enable/disable scroll bars as required when resized. + /// + /// This has the side-effect of reserving enough space for scroll bars even + /// when not required. + Auto, + /// Each scroll bar has fixed visibility. + /// + /// Parameters: `(horiz_is_visible, vert_is_visible)`. + Fixed(bool, bool), + /// Enabled scroll bars float over content and are only drawn when hovered + /// over by the mouse. Disabled scroll bars are fully hidden. + /// + /// Parameters: `(horiz_is_enabled, vert_is_enabled)`. + Invisible(bool, bool), +} + /// Message from a [`ScrollBar`] #[derive(Copy, Clone, Debug)] pub struct ScrollMsg(pub i32); @@ -375,7 +397,6 @@ impl_scope! { /// Construct /// /// By default scroll bars are automatically enabled based on requirements. - /// Use the [`HasScrollBars`] trait to adjust this behaviour. #[inline] pub fn new(inner: W) -> Self { ScrollBars { @@ -388,37 +409,72 @@ impl_scope! { } } - /// Access inner widget directly + /// Set fixed visibility of scroll bars (inline) #[inline] - pub fn inner(&self) -> &W { - &self.inner + pub fn with_fixed_bars(mut self, horiz: bool, vert: bool) -> Self + where + Self: Sized, + { + self.mode = ScrollBarMode::Fixed(horiz, vert); + self.horiz_bar.set_invisible(false); + self.vert_bar.set_invisible(false); + self.show_bars = (horiz, vert); + self } - /// Access inner widget directly + /// Set fixed, invisible bars (inline) + /// + /// In this mode scroll bars are either enabled but invisible until + /// hovered by the mouse or disabled completely. #[inline] - pub fn inner_mut(&mut self) -> &mut W { - &mut self.inner + pub fn with_invisible_bars(mut self, horiz: bool, vert: bool) -> Self + where + Self: Sized, + { + self.mode = ScrollBarMode::Invisible(horiz, vert); + self.horiz_bar.set_invisible(true); + self.vert_bar.set_invisible(true); + self.show_bars = (horiz, vert); + self } - } - impl HasScrollBars for Self { - fn get_mode(&self) -> ScrollBarMode { + /// Get current mode of scroll bars + #[inline] + pub fn scroll_bar_mode(&self) -> ScrollBarMode { self.mode } - fn set_mode(&mut self, mode: ScrollBarMode) -> Action { - self.mode = mode; - let invisible = mode == ScrollBarMode::Invisible; - self.horiz_bar.set_invisible(invisible); - self.vert_bar.set_invisible(invisible); - Action::RESIZE + + /// Set scroll bar mode + pub fn set_scroll_bar_mode(&mut self, cx: &mut EventState, mode: ScrollBarMode) { + if mode != self.mode { + self.mode = mode; + let (invis_horiz, invis_vert) = match mode { + ScrollBarMode::Auto => (false, false), + ScrollBarMode::Fixed(horiz, vert) => { + self.show_bars = (horiz, vert); + (false, false) + } + ScrollBarMode::Invisible(horiz, vert) => { + self.show_bars = (horiz, vert); + (horiz, vert) + } + }; + self.horiz_bar.set_invisible(invis_horiz); + self.vert_bar.set_invisible(invis_vert); + cx.resize(self); + } } - fn get_visible_bars(&self) -> (bool, bool) { - self.show_bars + /// Access inner widget directly + #[inline] + pub fn inner(&self) -> &W { + &self.inner } - fn set_visible_bars(&mut self, bars: (bool, bool)) -> Action { - self.show_bars = bars; - Action::RESIZE + + /// Access inner widget directly + #[inline] + pub fn inner_mut(&mut self) -> &mut W { + &mut self.inner } } @@ -449,9 +505,9 @@ impl_scope! { let vert_rules = self.vert_bar.size_rules(sizer.re(), axis); let horiz_rules = self.horiz_bar.size_rules(sizer.re(), axis); let (use_horiz, use_vert) = match self.mode { - ScrollBarMode::Fixed => self.show_bars, + ScrollBarMode::Fixed(horiz, vert) => (horiz, vert), ScrollBarMode::Auto => (true, true), - ScrollBarMode::Invisible => (false, false), + ScrollBarMode::Invisible(_, _) => (false, false), }; if axis.is_horizontal() && use_horiz { rules.append(vert_rules); @@ -553,9 +609,7 @@ impl_scope! { /// This is essentially a `ScrollBars>`: /// [`ScrollRegion`] handles the actual scrolling and wheel/touch events, /// while [`ScrollBars`] adds scroll bar controls. - /// - /// Use the [`HasScrollBars`] trait to adjust scroll bar behaviour. - #[autoimpl(Deref, DerefMut, HasScrollBars, Scrollable using self.0)] + #[autoimpl(Deref, DerefMut, Scrollable using self.0)] #[derive(Clone, Debug, Default)] #[widget{ derive = self.0; @@ -569,6 +623,39 @@ impl_scope! { ScrollBarRegion(ScrollBars::new(ScrollRegion::new(inner))) } + /// Set fixed visibility of scroll bars (inline) + #[inline] + pub fn with_fixed_bars(self, horiz: bool, vert: bool) -> Self + where + Self: Sized, + { + ScrollBarRegion(self.0.with_fixed_bars(horiz, vert)) + } + + /// Set fixed, invisible bars (inline) + /// + /// In this mode scroll bars are either enabled but invisible until + /// hovered by the mouse or disabled completely. + #[inline] + pub fn with_invisible_bars(self, horiz: bool, vert: bool) -> Self + where + Self: Sized, + { + ScrollBarRegion(self.0.with_invisible_bars(horiz, vert)) + } + + /// Get current mode of scroll bars + #[inline] + pub fn scroll_bar_mode(&self) -> ScrollBarMode { + self.0.scroll_bar_mode() + } + + /// Set scroll bar mode + #[inline] + pub fn set_scroll_bar_mode(&mut self, cx: &mut EventState, mode: ScrollBarMode) { + self.0.set_scroll_bar_mode(cx, mode); + } + /// Access inner widget directly #[inline] pub fn inner(&self) -> &W { From bbc305d39159b2a3bd67679a1cb4c603b5528565 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 7 Feb 2025 10:44:19 +0000 Subject: [PATCH 09/12] Do not return Action from Svg::load, load_path --- crates/kas-resvg/src/svg.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 8e8466546..9c22327b9 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -169,11 +169,14 @@ impl_scope! { /// happens (hence returning [`Action::RESIZE`]). /// /// This sets [`PixmapScaling::size`] from the SVG. - pub fn load(&mut self, data: &'static [u8], resources_dir: Option<&Path>) - -> Result - { + pub fn load( + &mut self, + cx: &mut EventState, + data: &'static [u8], + resources_dir: Option<&Path>, + ) -> Result<(), impl std::error::Error> { let source = Source::Static(data, resources_dir.map(|p| p.to_owned())); - self.load_source(source) + self.load_source(source).map(|act| cx.action(self, act)) } fn load_source(&mut self, source: Source) -> Result { @@ -191,10 +194,12 @@ impl_scope! { /// Load from a path /// /// This is a wrapper around [`Self::load`]. - pub fn load_path>(&mut self, path: P) - -> Result - { - self.load_path_(path.as_ref()) + pub fn load_path>( + &mut self, + cx: &mut EventState, + path: P, + ) -> Result<(), impl std::error::Error> { + self.load_path_(path.as_ref()).map(|act| cx.action(self, act)) } fn load_path_(&mut self, path: &Path) -> Result { From 7032c395b1adac9003132c7cff2c7d2d63ad98db Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 7 Feb 2025 11:34:56 +0000 Subject: [PATCH 10/12] Do not return Action from Image::set, load_path, clear --- crates/kas-widgets/src/image.rs | 48 +++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/crates/kas-widgets/src/image.rs b/crates/kas-widgets/src/image.rs index 104b400e4..3c1253dfc 100644 --- a/crates/kas-widgets/src/image.rs +++ b/crates/kas-widgets/src/image.rs @@ -52,8 +52,12 @@ impl_scope! { /// The image may be allocated through the [`DrawShared`] interface. #[inline] pub fn new(handle: ImageHandle, draw: &mut dyn DrawShared) -> Option { - let mut sprite = Self::default(); - sprite.set(handle, draw).map(|_| sprite) + draw.image_size(&handle).map(|size| { + let mut sprite = Self::default(); + sprite.scaling.size = size.cast(); + sprite.handle = Some(handle); + sprite + }) } /// Construct from a path @@ -64,32 +68,50 @@ impl_scope! { draw: &mut dyn DrawShared, ) -> Result { let mut sprite = Self::default(); - let _ = sprite.load_path(path, draw)?; + sprite._load_path(path, draw)?; Ok(sprite) } /// Assign a pre-allocated image /// - /// Returns `Action::RESIZE` on success. On error, `self` is unchanged. - pub fn set(&mut self, handle: ImageHandle, draw: &mut dyn DrawShared) -> Option { + /// Returns `true` on success. On error, `self` is unchanged. + pub fn set( + &mut self, + cx: &mut EventState, + handle: ImageHandle, + draw: &mut dyn DrawShared, + ) -> bool { if let Some(size) = draw.image_size(&handle) { self.scaling.size = size.cast(); self.handle = Some(handle); - Some(Action::RESIZE) + cx.resize(self); + true } else { - None + false } } /// Load from a path /// - /// Returns `Action::RESIZE` on success. On error, `self` is unchanged. + /// On error, `self` is unchanged. #[cfg(feature = "image")] pub fn load_path>( + &mut self, + cx: &mut EventState, + path: P, + draw: &mut dyn DrawShared, + ) -> Result<()> { + self._load_path(path, draw).map(|_| { + cx.resize(self); + }) + } + + #[cfg(feature = "image")] + fn _load_path>( &mut self, path: P, draw: &mut dyn DrawShared, - ) -> Result { + ) -> Result<()> { let image = image::ImageReader::open(path)? .with_guessed_format()? .decode()?; @@ -110,16 +132,14 @@ impl_scope! { self.scaling.size = size.cast(); self.handle = Some(handle); - Ok(Action::RESIZE) + Ok(()) } /// Remove image (set empty) - pub fn clear(&mut self, draw: &mut dyn DrawShared) -> Action { + pub fn clear(&mut self, cx: &mut EventState, draw: &mut dyn DrawShared) { if let Some(handle) = self.handle.take() { draw.image_free(handle); - Action::RESIZE - } else { - Action::empty() + cx.resize(self); } } From 02c90471ebd499776a8411e1581e131250106373 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 7 Feb 2025 11:43:03 +0000 Subject: [PATCH 11/12] Tweaks to Svg --- crates/kas-resvg/src/svg.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/kas-resvg/src/svg.rs b/crates/kas-resvg/src/svg.rs index 9c22327b9..e87449b36 100644 --- a/crates/kas-resvg/src/svg.rs +++ b/crates/kas-resvg/src/svg.rs @@ -153,22 +153,20 @@ impl_scope! { pub fn new(data: &'static [u8]) -> Result { let mut svg = Svg::default(); let source = Source::Static(data, None); - svg.load_source(source).map(|_action| svg) + svg.load_source(source).map(|_| svg) } /// Construct from a path pub fn new_path>(path: P) -> Result { let mut svg = Svg::default(); - let _action = svg.load_path_(path.as_ref())?; + svg._load_path(path.as_ref())?; Result::::Ok(svg) } /// Load from `data` /// - /// Replaces existing data, but does not re-render until a resize - /// happens (hence returning [`Action::RESIZE`]). - /// - /// This sets [`PixmapScaling::size`] from the SVG. + /// Replaces existing data and request a resize. The sizing policy is + /// set to [`PixmapScaling::size`] using dimensions from the SVG. pub fn load( &mut self, cx: &mut EventState, @@ -176,10 +174,10 @@ impl_scope! { resources_dir: Option<&Path>, ) -> Result<(), impl std::error::Error> { let source = Source::Static(data, resources_dir.map(|p| p.to_owned())); - self.load_source(source).map(|act| cx.action(self, act)) + self.load_source(source).map(|_| cx.resize(self)) } - fn load_source(&mut self, source: Source) -> Result { + fn load_source(&mut self, source: Source) -> Result<(), usvg::Error> { // Set scaling size. TODO: this is useless if Self::with_size is called after. let size = source.tree()?.size(); self.scaling.size = LogicalSize(size.width(), size.height()); @@ -188,7 +186,7 @@ impl_scope! { State::Ready(_, px) => State::Ready(source, px), _ => State::Initial(source), }; - Ok(Action::RESIZE) + Ok(()) } /// Load from a path @@ -199,10 +197,10 @@ impl_scope! { cx: &mut EventState, path: P, ) -> Result<(), impl std::error::Error> { - self.load_path_(path.as_ref()).map(|act| cx.action(self, act)) + self._load_path(path.as_ref()).map(|_| cx.resize(self)) } - fn load_path_(&mut self, path: &Path) -> Result { + fn _load_path(&mut self, path: &Path) -> Result<(), LoadError> { let buf = std::fs::read(path)?; let rd = path.parent().map(|path| path.to_owned()); let source = Source::Heap(buf.into(), rd); From 9f6c62a5a91246424284fc49ec6eafa398397ff7 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Fri, 7 Feb 2025 11:49:00 +0000 Subject: [PATCH 12/12] Tweak: use EventState::resize / redraw over action --- crates/kas-widgets/src/list.rs | 4 ++-- crates/kas-widgets/src/scroll_label.rs | 2 +- crates/kas-widgets/src/splitter.rs | 4 ++-- crates/kas-widgets/src/tab_stack.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index ee510feb4..acbd25aab 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -269,8 +269,8 @@ impl_scope! { } self.direction = direction; - // Note: most of the time SET_RECT would be enough, but margins can be different - cx.action(self, Action::RESIZE); + // Note: most of the time Action::SET_RECT would be enough, but margins can be different + cx.resize(self); } } diff --git a/crates/kas-widgets/src/scroll_label.rs b/crates/kas-widgets/src/scroll_label.rs index 0f8a0c3f4..896524e4d 100644 --- a/crates/kas-widgets/src/scroll_label.rs +++ b/crates/kas-widgets/src/scroll_label.rs @@ -118,7 +118,7 @@ impl_scope! { self.selection.set_max_len(self.text.str_len()); - cx.action(self, Action::REDRAW); + cx.redraw(self); } fn set_edit_pos_from_coord(&mut self, cx: &mut EventCx, coord: Coord) { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 365164fe5..e188eccd9 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -77,8 +77,8 @@ impl_scope! { } self.direction = direction; - // Note: most of the time SET_RECT would be enough, but margins can be different - cx.action(self, Action::RESIZE); + // Note: most of the time Action::SET_RECT would be enough, but margins can be different + cx.resize(self); } } diff --git a/crates/kas-widgets/src/tab_stack.rs b/crates/kas-widgets/src/tab_stack.rs index b667810ea..4be72db1c 100644 --- a/crates/kas-widgets/src/tab_stack.rs +++ b/crates/kas-widgets/src/tab_stack.rs @@ -137,8 +137,8 @@ impl_scope! { } self.direction = direction; - // Note: most of the time SET_RECT would be enough, but margins can be different - cx.action(self, Action::RESIZE); + // Note: most of the time Action::SET_RECT would be enough, but margins can be different + cx.resize(self); } /// Call the handler `f` on page change