diff --git a/crates/kas-core/src/core/widget.rs b/crates/kas-core/src/core/widget.rs index 9808b0e92..c4c842ea4 100644 --- a/crates/kas-core/src/core/widget.rs +++ b/crates/kas-core/src/core/widget.rs @@ -110,26 +110,14 @@ pub trait WidgetChildren: WidgetCore { /// This method may be removed in the future. fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig>; - /// Get the [`WidgetId`] for this child - /// - /// Note: the result should be generated relative to `self.id`. - /// Most widgets may use the default implementation. - /// - /// This must return `Some(..)` when `index` is valid; in other cases the - /// result does not matter. - /// - /// If a custom implementation is used, then [`Self::find_child_index`] - /// must be implemented to do the inverse of `make_child_id`, and - /// probably a custom implementation of [`Layout::spatial_nav`] is needed. - #[inline] - fn make_child_id(&self, index: usize) -> Option { - Some(self.id_ref().make_child(index)) - } - /// Find the child which is an ancestor of this `id`, if any /// /// If `Some(index)` is returned, this is *probably* but not guaranteed /// to be a valid child index. + /// + /// The default implementation simply uses [`WidgetId::next_key_after`]. + /// Widgets may choose to assign children custom keys by overriding this + /// method and [`WidgetConfig::configure_recurse`]. #[inline] fn find_child_index(&self, id: &WidgetId) -> Option { id.next_key_after(self.id_ref()) @@ -142,51 +130,43 @@ pub trait WidgetChildren: WidgetCore { /// [`derive(Widget)`] unless explicitly implemented. /// /// Widgets are *configured* on window creation or dynamically via the -/// parent calling [`SetRectMgr::configure`]. Configuration may be repeated. -/// -/// Configuration is required to happen before [`Layout::size_rules`] is first -/// called, thus [`Self::configure`] may be used to load assets required by -/// [`Layout::size_rules`]. -/// -/// Configuration happens in this order: +/// parent calling [`SetRectMgr::configure`]. Parent widgets are responsible +/// for ensuring that children are configured before calling +/// [`Layout::size_rules`]. Configuration may be repeated and may be used as a +/// mechanism to change a child's [`WidgetId`], but this may be expensive. /// -/// 1. [`Self::pre_configure`] is used to assign a [`WidgetId`] and may -/// influence configuration of children. -/// 2. Configuration of children accessible through [`WidgetChildren`]. -/// 3. [`Self::configure`] allows user-configuration of event handling and -/// may be used for resource loading. -/// -/// Note that if parent widgets add child widgets dynamically, that parent is -/// responsible for ensuring that the new child widgets gets configured. This -/// may be done (1) by adding the children during [`Self::pre_configure`], (2) -/// by requesting [`TkAction::RECONFIGURE`] or (3) by calling -/// [`SetRectMgr::configure`]. The latter may also be used to change a child's -/// [`WidgetId`]. +/// Configuration invokes [`Self::configure_recurse`] which then calls +/// [`Self::configure`]. The latter may be used to load assets before sizing. /// /// [`derive(Widget)`]: https://docs.rs/kas/latest/kas/macros/index.html#the-derivewidget-macro -// -// TODO(specialization): provide a blanket implementation, so that users only -// need implement manually when they have something to configure. #[autoimpl(for Box)] pub trait WidgetConfig: Layout { - /// Pre-configure widget + /// Configure widget and children /// - /// This method is part of configuration (see trait documentation). + /// This method: /// - /// This method assigns the widget's [`WidgetId`] and may be used to - /// affect the manager in ways which influence the child, for example - /// [`EventState::new_accel_layer`]. Custom implementations should be aware - /// that children may not have been configured and thus may have invalid - /// identifiers at the time of calling. + /// 1. Assigns `id` to self + /// 2. Constructs an identifier for and call `configure_recurse` on each child + /// 3. Calls [`Self::configure`] /// - /// The window's scale factor (and thus any sizes available through - /// [`SetRectMgr::size_mgr`]) may not be correct initially (some platforms - /// construct all windows using scale factor 1) and/or may change in the - /// future. Changes to the scale factor result in recalculation of - /// [`Layout::size_rules`] but not repeated configuration. - fn pre_configure(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { - let _ = mgr; + /// Normally the default implementation is used. A custom implementation + /// may be used to influence configuration of children, for example by + /// calling [`EventState::new_accel_layer`] or by constructing children's + /// [`WidgetId`] values in a non-standard manner (in this case ensure that + /// [`WidgetChildren::find_child_index`] has a correct implementation). + /// + /// To directly configure a child, call [`SetRectMgr::configure`] instead. + fn configure_recurse(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { self.core_data_mut().id = id; + + for index in 0..self.num_children() { + let id = self.id_ref().make_child(index); + if let Some(widget) = self.get_child_mut(index) { + widget.configure_recurse(mgr, id); + } + } + + self.configure(mgr); } /// Configure widget @@ -194,7 +174,7 @@ pub trait WidgetConfig: Layout { /// This method is part of configuration (see trait documentation). /// /// This method may be used to configure event handling and to load - /// resources. + /// resources, including resources affecting [`Layout::size_rules`]. /// /// The window's scale factor (and thus any sizes available through /// [`SetRectMgr::size_mgr`]) may not be correct initially (some platforms @@ -240,9 +220,14 @@ pub trait WidgetConfig: Layout { /// This trait is part of the [`Widget`] family. It may be derived by /// the [`crate::macros::widget`] macro, but is not by default. /// -/// Implementations of this trait should *either* define [`Self::layout`] -/// (optionally with other methods as required) *or* define at least -/// [`Self::size_rules`] and [`Self::draw`]. +/// There are two methods of implementing this trait: +/// +/// - Implement [`Self::layout`]. This alone suffices in many cases; other +/// methods may be overridden if necessary. +/// - Ignore [`Self::layout`] and implement [`Self::size_rules`] (to give the +/// widget size) and [`Self::draw`] (to make it show something). Other +/// methods may be required (e.g. [`Self::set_rect`] to position child +/// elements). /// /// Layout solving happens in two steps: /// @@ -262,8 +247,9 @@ pub trait Layout: WidgetChildren { /// /// The default implementation is for an empty layout (zero size required, /// no child elements, no graphics). + #[inline] fn layout(&mut self) -> layout::Layout<'_> { - Default::default() // TODO: remove default impl + Default::default() } /// Get size rules for the given axis @@ -446,7 +432,7 @@ pub trait WidgetExt: WidgetChildren { /// /// Note that the default-constructed [`WidgetId`] is *invalid*: any /// operations on this value will cause a panic. Valid identifiers are - /// assigned by [`WidgetConfig::pre_configure`]. + /// assigned by [`WidgetConfig::configure_recurse`]. #[inline] fn id(&self) -> WidgetId { self.core_data().id.clone() @@ -456,7 +442,7 @@ pub trait WidgetExt: WidgetChildren { /// /// Note that the default-constructed [`WidgetId`] is *invalid*: any /// operations on this value will cause a panic. Valid identifiers are - /// assigned by [`WidgetConfig::pre_configure`]. + /// assigned by [`WidgetConfig::configure_recurse`]. #[inline] fn id_ref(&self) -> &WidgetId { &self.core_data().id diff --git a/crates/kas-core/src/event/manager.rs b/crates/kas-core/src/event/manager.rs index ee8252787..abf56815a 100644 --- a/crates/kas-core/src/event/manager.rs +++ b/crates/kas-core/src/event/manager.rs @@ -147,8 +147,6 @@ type AccelLayer = (bool, HashMap); pub struct EventState { config: WindowConfig, disabled: Vec, - configure_active: bool, - configure_count: usize, window_has_focus: bool, modifiers: ModifiersState, /// char focus is on same widget as sel_focus; otherwise its value is ignored diff --git a/crates/kas-core/src/event/manager/mgr_shell.rs b/crates/kas-core/src/event/manager/mgr_shell.rs index 1c4241e99..48c9b75db 100644 --- a/crates/kas-core/src/event/manager/mgr_shell.rs +++ b/crates/kas-core/src/event/manager/mgr_shell.rs @@ -33,8 +33,6 @@ impl EventState { EventState { config: WindowConfig::new(config, scale_factor), disabled: vec![], - configure_active: false, - configure_count: 0, window_has_focus: false, modifiers: ModifiersState::empty(), char_focus: false, @@ -66,52 +64,6 @@ impl EventState { self.config.set_scale_factor(scale_factor); } - /// Configure a widget - /// - /// All widgets must be configured after construction (see - /// [`WidgetConfig::configure`]). This method may be used to configure a new - /// child widget without requiring the whole window to be reconfigured. - /// - /// Pass the `id` to assign to the widget: this should be constructed from - /// the parent's id via [`WidgetId::make_child`]. - pub fn configure(mgr: &mut SetRectMgr, id: WidgetId, widget: &mut dyn WidgetConfig) { - assert!(!mgr.ev.configure_active, "configure recursion"); - if mgr.ev.action.contains(TkAction::RECONFIGURE) { - return; // whole window will be reconfigured - } - mgr.ev.configure_active = true; - - fn recurse( - mgr: &mut SetRectMgr, - id: WidgetId, - widget: &mut dyn WidgetConfig, - count: &mut usize, - ) { - widget.pre_configure(mgr, id); - *count += 1; - for i in 0..widget.num_children() { - if let Some(id) = widget.make_child_id(i) { - if let Some(w) = widget.get_child_mut(i) { - recurse(mgr, id, w, count); - } - } - } - widget.configure(mgr); - } - - let mut count = 0; - recurse(mgr, id, widget, &mut count); - - if mgr.ev.action.contains(TkAction::RECONFIGURE) { - log::warn!("Detected TkAction::RECONFIGURE during configure. This may cause a reconfigure-loop."); - if count == mgr.ev.configure_count { - panic!("Reconfigure occurred with the same number of widgets — we are probably stuck in a reconfigure-loop."); - } - } - mgr.ev.configure_count = count; - mgr.ev.configure_active = false; - } - /// Configure event manager for a widget tree. /// /// This should be called by the toolkit on the widget tree when the window @@ -136,7 +88,7 @@ impl EventState { shell.size_and_draw_shared(&mut |size_handle, draw_shared| { let mut mgr = SetRectMgr::new(size_handle, draw_shared, self); - Self::configure(&mut mgr, WidgetId::ROOT, widget.as_widget_mut()); + mgr.configure(WidgetId::ROOT, widget.as_widget_mut()); }); let hover = widget.find_id(self.last_mouse_coord); diff --git a/crates/kas-core/src/layout/mod.rs b/crates/kas-core/src/layout/mod.rs index 37e693ed1..78d46ad45 100644 --- a/crates/kas-core/src/layout/mod.rs +++ b/crates/kas-core/src/layout/mod.rs @@ -198,7 +198,9 @@ impl<'a> SetRectMgr<'a> { /// the parent's id via [`WidgetId::make_child`]. #[inline] pub fn configure(&mut self, id: WidgetId, widget: &mut dyn WidgetConfig) { - EventState::configure(self, id, widget); + // Yes, this method is just a shim! We reserve the option to add other code here in the + // future, hence do not advise calling `configure_recurse` directly. + widget.configure_recurse(self, id); } } diff --git a/crates/kas-core/src/toolkit.rs b/crates/kas-core/src/toolkit.rs index 4f9e82a3f..9017beebf 100644 --- a/crates/kas-core/src/toolkit.rs +++ b/crates/kas-core/src/toolkit.rs @@ -46,8 +46,9 @@ bitflags! { /// assignments are supported by both `TkAction` and [`event::EventMgr`]. /// /// Users receiving a value of this type from a widget update method should - /// generally call `*mgr |= action;` during event handling. Prior to - /// starting the event loop (`toolkit.run()`), these values can be ignored. + /// usually handle with `*mgr |= action;`. Before the event loop starts + /// (`toolkit.run()`) or if the widget in question is not part of a UI these + /// values can be ignored. #[must_use] #[derive(Default)] pub struct TkAction: u32 { @@ -70,7 +71,7 @@ bitflags! { */ /// Reset size of all widgets without recalculating requirements const SET_SIZE = 1 << 8; - /// Resize all widgets + /// Resize all widgets in the window const RESIZE = 1 << 9; /// Update theme memory const THEME_UPDATE = 1 << 10; diff --git a/crates/kas-core/src/updatable/data_traits.rs b/crates/kas-core/src/updatable/data_traits.rs index ec3895a09..3e3532982 100644 --- a/crates/kas-core/src/updatable/data_traits.rs +++ b/crates/kas-core/src/updatable/data_traits.rs @@ -105,6 +105,11 @@ pub trait ListData: Debug { /// increase the version number (allowing change detection). fn version(&self) -> u64; + /// No data is available + fn is_empty(&self) -> bool { + self.len() == 0 + } + /// Number of data items available /// /// Note: users may assume this is `O(1)`. diff --git a/crates/kas-widgets/src/combobox.rs b/crates/kas-widgets/src/combobox.rs index c4cdbbfcd..73ce013f8 100644 --- a/crates/kas-widgets/src/combobox.rs +++ b/crates/kas-widgets/src/combobox.rs @@ -190,15 +190,18 @@ impl ComboBox { /// types, and the chosen `active` entry. For example: /// ``` /// # use kas_widgets::ComboBox; - /// let combobox = ComboBox::new(&["zero", "one", "two"], 0); + /// let combobox = ComboBox::new_from_iter(&["zero", "one", "two"], 0); /// ``` #[inline] - pub fn new, I: IntoIterator>(iter: I, active: usize) -> Self { + pub fn new_from_iter, I: IntoIterator>( + iter: I, + active: usize, + ) -> Self { let entries = iter .into_iter() .map(|label| MenuEntry::new(label, ())) .collect(); - Self::new_entries(entries, active) + Self::new(entries, active) } /// Construct a combobox with the given menu entries @@ -206,7 +209,7 @@ impl ComboBox { /// A combobox presents a menu with a fixed set of choices when clicked, /// with the `active` choice selected (0-based index). #[inline] - pub fn new_entries(entries: Vec>, active: usize) -> Self { + pub fn new(entries: Vec>, active: usize) -> Self { let label = entries.get(active).map(|entry| entry.get_string()); let label = Text::new_single(label.unwrap_or("".to_string())); ComboBox { @@ -290,54 +293,46 @@ impl ComboBox { } /// Remove all choices - /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn clear(&mut self) -> TkAction { + pub fn clear(&mut self) { self.popup.inner.clear() } /// Add a choice to the combobox, in last position /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn push>(&mut self, label: T) -> TkAction { + /// Returns the index of the new choice + // + // TODO(opt): these methods cause full-window resize. They don't need to + // resize at all if the menu is closed! + pub fn push>(&mut self, mgr: &mut SetRectMgr, label: T) -> usize { let column = &mut self.popup.inner; - column.push(MenuEntry::new(label, ())) - // TODO: localised reconfigure + column.push(mgr, MenuEntry::new(label, ())) } /// Pops the last choice from the combobox - /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn pop(&mut self) -> (Option<()>, TkAction) { - let r = self.popup.inner.pop(); - (r.0.map(|_| ()), r.1) + pub fn pop(&mut self, mgr: &mut SetRectMgr) -> Option<()> { + self.popup.inner.pop(mgr).map(|_| ()) } /// Add a choice at position `index` /// /// Panics if `index > len`. - /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn insert>(&mut self, index: usize, label: T) -> TkAction { + pub fn insert>(&mut self, mgr: &mut SetRectMgr, index: usize, label: T) { let column = &mut self.popup.inner; - column.insert(index, MenuEntry::new(label, ())) - // TODO: localised reconfigure + column.insert(mgr, index, MenuEntry::new(label, ())); } /// Removes the choice at position `index` /// /// Panics if `index` is out of bounds. - /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn remove(&mut self, index: usize) -> TkAction { - self.popup.inner.remove(index).1 + pub fn remove(&mut self, mgr: &mut SetRectMgr, index: usize) { + self.popup.inner.remove(mgr, index); } /// Replace the choice at `index` /// /// Panics if `index` is out of bounds. - pub fn replace>(&mut self, index: usize, label: T) -> TkAction { - self.popup.inner[index].set_accel(label) + pub fn replace>(&mut self, mgr: &mut SetRectMgr, index: usize, label: T) { + *mgr |= self.popup.inner[index].set_accel(label); } } diff --git a/crates/kas-widgets/src/grid.rs b/crates/kas-widgets/src/grid.rs index ecc05018f..e4aa7ffec 100644 --- a/crates/kas-widgets/src/grid.rs +++ b/crates/kas-widgets/src/grid.rs @@ -139,10 +139,15 @@ impl Grid { } /// Edit an existing grid via a builder + /// + /// This may be used to edit children before window construction. It may + /// also be used from a running UI, but in this case a full reconfigure + /// of the window's widgets is required (triggered by the the return + /// value, [`TkAction::RECONFIGURE`]). pub fn edit)>(&mut self, f: F) -> TkAction { f(GridBuilder(&mut self.widgets)); self.calc_dim(); - TkAction::RECONFIGURE // just assume this is requried + TkAction::RECONFIGURE } /// True if there are no child widgets diff --git a/crates/kas-widgets/src/list.rs b/crates/kas-widgets/src/list.rs index 9623bae4d..67bf10738 100644 --- a/crates/kas-widgets/src/list.rs +++ b/crates/kas-widgets/src/list.rs @@ -7,6 +7,7 @@ use kas::dir::{Down, Right}; use kas::{event, layout, prelude::*}; +use std::collections::hash_map::{Entry, HashMap}; use std::ops::{Index, IndexMut}; /// Support for optionally-indexed messages @@ -174,9 +175,12 @@ widget! { > { #[widget_core] core: CoreData, + layout_store: layout::DynRowStorage, widgets: Vec, - data: layout::DynRowStorage, direction: D, + size_solved: bool, + next: usize, + id_map: HashMap, // map key of WidgetId to index _pd: std::marker::PhantomData, } @@ -193,11 +197,42 @@ widget! { fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { self.widgets.get_mut(index).map(|w| w.as_widget_mut()) } + + fn find_child_index(&self, id: &WidgetId) -> Option { + id.next_key_after(self.id_ref()).and_then(|k| self.id_map.get(&k).cloned()) + } + } + + impl WidgetConfig for Self { + fn configure_recurse(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { + self.core_data_mut().id = id; + self.id_map.clear(); + + for index in 0..self.widgets.len() { + let id = self.make_next_id(index); + self.widgets[index].configure_recurse(mgr, id); + } + + self.configure(mgr); + } } impl Layout for Self { fn layout(&mut self) -> layout::Layout<'_> { - make_layout!(self.core; slice(self.direction): self.widgets) + if self.size_solved { + layout::Layout::slice(&mut self.widgets, self.direction, &mut self.layout_store) + } else { + // Draw without sizing all elements may cause a panic, so don't. + Default::default() + } + } + + fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules { + // Assumption: if size_rules is called, then set_rect will be too. + self.size_solved = true; + + layout::Layout::slice(&mut self.widgets, self.direction, &mut self.layout_store) + .size_rules(size_mgr, axis) } } @@ -247,18 +282,55 @@ widget! { } impl Self { + // Assumption: index is a valid entry of self.widgets + fn make_next_id(&mut self, index: usize) -> WidgetId { + if let Some(child) = self.widgets.get(index) { + // Use the widget's existing identifier, if any + if child.id_ref().is_valid() { + if let Some(key) = child.id_ref().next_key_after(self.id_ref()) { + self.id_map.insert(key, index); + return child.id(); + } + } + } + + loop { + let key = self.next; + self.next += 1; + if let Entry::Vacant(entry) = self.id_map.entry(key) { + entry.insert(index); + return self.id_ref().make_child(key); + } + } + } + /// Construct a new instance with explicit direction #[inline] pub fn new_with_direction(direction: D, widgets: Vec) -> Self { GenericList { core: Default::default(), + layout_store: Default::default(), widgets, - data: Default::default(), direction, + size_solved: false, + next: 0, + id_map: Default::default(), _pd: Default::default(), } } + /// Edit the list of children directly + /// + /// This may be used to edit children before window construction. It may + /// also be used from a running UI, but in this case a full reconfigure + /// of the window's widgets is required (triggered by the the return + /// value, [`TkAction::RECONFIGURE`]). + #[inline] + pub fn edit)>(&mut self, f: F) -> TkAction { + f(&mut self.widgets); + TkAction::RECONFIGURE + } + /// Get the direction of contents pub fn direction(&self) -> Direction { self.direction.as_direction() @@ -274,129 +346,155 @@ widget! { self.widgets.len() } - /// Returns the number of elements the vector can hold without reallocating. - pub fn capacity(&self) -> usize { - self.widgets.capacity() - } - - /// Reserves capacity for at least `additional` more elements to be inserted - /// into the list. See documentation of [`Vec::reserve`]. - pub fn reserve(&mut self, additional: usize) { - self.widgets.reserve(additional); - } - /// Remove all child widgets - /// - /// Triggers a [reconfigure action](EventState::send_action) if any widget is - /// removed. - pub fn clear(&mut self) -> TkAction { - let action = match self.widgets.is_empty() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, - }; + pub fn clear(&mut self) { self.widgets.clear(); - action + self.size_solved = false; } /// Append a child widget /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn push(&mut self, widget: W) -> TkAction { + /// The new child is configured immediately. [`TkAction::RESIZE`] is + /// triggered. + /// + /// Returns the new element's index. + pub fn push(&mut self, mgr: &mut SetRectMgr, widget: W) -> usize { + let index = self.widgets.len(); self.widgets.push(widget); - TkAction::RECONFIGURE + let id = self.make_next_id(index); + mgr.configure(id, &mut self.widgets[index]); + self.size_solved = false; + *mgr |= TkAction::RESIZE; + index } - /// Remove the last child widget - /// - /// Returns `None` if there are no children. Otherwise, this - /// triggers a reconfigure before the next draw operation. + /// Remove the last child widget (if any) and return /// - /// Triggers a [reconfigure action](EventState::send_action) if any widget is - /// removed. - pub fn pop(&mut self) -> (Option, TkAction) { - let action = match self.widgets.is_empty() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, - }; - (self.widgets.pop(), action) + /// Triggers [`TkAction::RESIZE`]. + pub fn pop(&mut self, mgr: &mut SetRectMgr) -> Option { + let result = self.widgets.pop(); + if let Some(w) = result.as_ref() { + *mgr |= TkAction::RESIZE; + + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + } + result } /// Inserts a child widget position `index` /// /// Panics if `index > len`. /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn insert(&mut self, index: usize, widget: W) -> TkAction { + /// The new child is configured immediately. Triggers [`TkAction::RESIZE`]. + pub fn insert(&mut self, mgr: &mut SetRectMgr, index: usize, widget: W) { + for v in self.id_map.values_mut() { + if *v >= index { + *v += 1; + } + } self.widgets.insert(index, widget); - TkAction::RECONFIGURE + let id = self.make_next_id(index); + mgr.configure(id, &mut self.widgets[index]); + self.size_solved = false; + *mgr |= TkAction::RESIZE; } /// Removes the child widget at position `index` /// /// Panics if `index` is out of bounds. /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn remove(&mut self, index: usize) -> (W, TkAction) { - let r = self.widgets.remove(index); - (r, TkAction::RECONFIGURE) + /// Triggers [`TkAction::RESIZE`]. + pub fn remove(&mut self, mgr: &mut SetRectMgr, index: usize) -> W { + let w = self.widgets.remove(index); + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + + *mgr |= TkAction::RESIZE; + + for v in self.id_map.values_mut() { + if *v > index { + *v -= 1; + } + } + w } /// Replace the child at `index` /// /// Panics if `index` is out of bounds. /// - /// Triggers a [reconfigure action](EventState::send_action). - // TODO: in theory it is possible to avoid a reconfigure where both widgets - // have no children and have compatible size. Is this a good idea and can - // we somehow test "has compatible size"? - pub fn replace(&mut self, index: usize, mut widget: W) -> (W, TkAction) { - std::mem::swap(&mut widget, &mut self.widgets[index]); - (widget, TkAction::RECONFIGURE) + /// The new child is configured immediately. Triggers [`TkAction::RESIZE`]. + pub fn replace(&mut self, mgr: &mut SetRectMgr, index: usize, mut w: W) -> W { + std::mem::swap(&mut w, &mut self.widgets[index]); + + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + + let id = self.make_next_id(index); + mgr.configure(id, &mut self.widgets[index]); + + self.size_solved = false; + *mgr |= TkAction::RESIZE; + + w } /// Append child widgets from an iterator /// - /// Triggers a [reconfigure action](EventState::send_action) if any widgets - /// are added. - pub fn extend>(&mut self, iter: T) -> TkAction { - let len = self.widgets.len(); + /// New children are configured immediately. Triggers [`TkAction::RESIZE`]. + pub fn extend>(&mut self, mgr: &mut SetRectMgr, iter: T) { + let old_len = self.widgets.len(); self.widgets.extend(iter); - match len == self.widgets.len() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, + for index in old_len..self.widgets.len() { + let id = self.make_next_id(index); + mgr.configure(id, &mut self.widgets[index]); } + + self.size_solved = false; + *mgr |= TkAction::RESIZE; } /// Resize, using the given closure to construct new widgets /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn resize_with W>(&mut self, len: usize, f: F) -> TkAction { - let l0 = self.widgets.len(); - if l0 == len { - return TkAction::empty(); - } else if l0 > len { - self.widgets.truncate(len); - } else { - self.widgets.reserve(len); - for i in l0..len { - self.widgets.push(f(i)); + /// New children are configured immediately. Triggers [`TkAction::RESIZE`]. + pub fn resize_with W>(&mut self, mgr: &mut SetRectMgr, len: usize, f: F) { + let old_len = self.widgets.len(); + + if len < old_len { + *mgr |= TkAction::RESIZE; + loop { + let w = self.widgets.pop().unwrap(); + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + if len == self.widgets.len() { + return; + } } } - TkAction::RECONFIGURE - } - /// Retain only widgets satisfying predicate `f` - /// - /// See documentation of [`Vec::retain`]. - /// - /// Triggers a [reconfigure action](EventState::send_action) if any widgets - /// are removed. - pub fn retain bool>(&mut self, f: F) -> TkAction { - let len = self.widgets.len(); - self.widgets.retain(f); - match len == self.widgets.len() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, + if len > old_len { + self.widgets.reserve(len - old_len); + for index in old_len..len { + let id = self.make_next_id(index); + let mut widget = f(index); + mgr.configure(id, &mut widget); + self.widgets.push(widget); + } + self.size_solved = false; + *mgr |= TkAction::RESIZE; } } diff --git a/crates/kas-widgets/src/menu/submenu.rs b/crates/kas-widgets/src/menu/submenu.rs index d4b71ad18..1224f596f 100644 --- a/crates/kas-widgets/src/menu/submenu.rs +++ b/crates/kas-widgets/src/menu/submenu.rs @@ -120,10 +120,15 @@ widget! { } impl WidgetConfig for Self { - fn pre_configure(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { + fn configure_recurse(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { self.core_data_mut().id = id; mgr.add_accel_keys(self.id_ref(), self.label.text().keys()); mgr.new_accel_layer(self.id(), true); + + let id = self.id_ref().make_child(widget_index![self.list]); + self.list.configure_recurse(mgr, id); + + self.configure(mgr); } fn key_nav(&self) -> bool { diff --git a/crates/kas-widgets/src/splitter.rs b/crates/kas-widgets/src/splitter.rs index 8af90db4e..dd561388b 100644 --- a/crates/kas-widgets/src/splitter.rs +++ b/crates/kas-widgets/src/splitter.rs @@ -6,6 +6,7 @@ //! A row or column with sizes adjustable via dividing handles use log::warn; +use std::collections::hash_map::{Entry, HashMap}; use std::ops::{Index, IndexMut}; use super::DragHandle; @@ -80,6 +81,36 @@ widget! { handles: Vec, data: layout::DynRowStorage, direction: D, + size_solved: bool, + next: usize, + id_map: HashMap, // map key of WidgetId to index + } + + impl Self { + // Assumption: index is a valid entry of self.widgets + fn make_next_id(&mut self, is_handle: bool, index: usize) -> WidgetId { + let child_index = (2 * index) + (is_handle as usize); + if !is_handle { + if let Some(child) = self.widgets.get(index) { + // Use the widget's existing identifier, if any + if child.id_ref().is_valid() { + if let Some(key) = child.id_ref().next_key_after(self.id_ref()) { + self.id_map.insert(key, child_index); + return child.id(); + } + } + } + } + + loop { + let key = self.next; + self.next += 1; + if let Entry::Vacant(entry) = self.id_map.entry(key) { + entry.insert(index); + return self.id_ref().make_child(key); + } + } + } } impl WidgetChildren for Self { @@ -103,10 +134,36 @@ widget! { self.widgets.get_mut(index >> 1).map(|w| w.as_widget_mut()) } } + + fn find_child_index(&self, id: &WidgetId) -> Option { + id.next_key_after(self.id_ref()).and_then(|k| self.id_map.get(&k).cloned()) + } + } + + impl WidgetConfig for Self { + fn configure_recurse(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { + self.core_data_mut().id = id; + self.id_map.clear(); + + // It does not matter what order we choose widget/child ids: + for index in 0..self.widgets.len() { + let id = self.make_next_id(false, index); + self.widgets[index].configure_recurse(mgr, id); + } + for index in 0..self.handles.len() { + let id = self.make_next_id(true, index); + self.handles[index].configure_recurse(mgr, id); + } + + self.configure(mgr); + } } impl Layout for Self { fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules { + // Assumption: if size_rules is called, then set_rect will be too. + self.size_solved = true; + if self.widgets.is_empty() { return SizeRules::EMPTY; } @@ -173,7 +230,7 @@ widget! { } fn find_id(&mut self, coord: Coord) -> Option { - if !self.rect().contains(coord) { + if !self.rect().contains(coord) || !self.size_solved { return None; } @@ -195,6 +252,9 @@ widget! { } fn draw(&mut self, mut draw: DrawMgr) { + if !self.size_solved { + return; + } // as with find_id, there's not much harm in invoking the solver twice let solver = layout::RowPositionSolver::new(self.direction); @@ -274,9 +334,26 @@ impl Splitter { handles, data: Default::default(), direction, + size_solved: false, + next: 0, + id_map: Default::default(), } } + /// Edit the list of children directly + /// + /// This may be used to edit children before window construction. It may + /// also be used from a running UI, but in this case a full reconfigure + /// of the window's widgets is required (triggered by the the return + /// value, [`TkAction::RECONFIGURE`]). + #[inline] + pub fn edit)>(&mut self, f: F) -> TkAction { + f(&mut self.widgets); + let len = self.widgets.len().saturating_sub(1); + self.handles.resize_with(len, DragHandle::new); + TkAction::RECONFIGURE + } + fn adjust_size(&mut self, mgr: &mut SetRectMgr, n: usize) { assert!(n < self.handles.len()); assert_eq!(self.widgets.len(), self.handles.len() + 1); @@ -323,145 +400,138 @@ impl Splitter { self.widgets.len() } - /// Returns the number of elements the vector can hold without reallocating. - pub fn capacity(&self) -> usize { - self.widgets.capacity() - } - - /// Reserves capacity for at least `additional` more elements to be inserted - /// into the list. See documentation of [`Vec::reserve`]. - pub fn reserve(&mut self, additional: usize) { - self.widgets.reserve(additional); - self.handles.reserve(additional); - } - /// Remove all child widgets - /// - /// Triggers a [reconfigure action](EventState::send_action) if any widget is - /// removed. - pub fn clear(&mut self) -> TkAction { - let action = match self.widgets.is_empty() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, - }; + pub fn clear(&mut self) { self.widgets.clear(); self.handles.clear(); - action + self.size_solved = false; } /// Append a child widget /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn push(&mut self, widget: W) -> TkAction { - if !self.widgets.is_empty() { + /// The new child is configured immediately. [`TkAction::RESIZE`] is + /// triggered. + /// + /// Returns the new element's index. + pub fn push(&mut self, mgr: &mut SetRectMgr, widget: W) -> usize { + let index = self.widgets.len(); + if index > 0 { + let len = self.handles.len(); self.handles.push(DragHandle::new()); + let id = self.make_next_id(true, len); + mgr.configure(id, &mut self.handles[len]); } self.widgets.push(widget); - TkAction::RECONFIGURE + let id = self.make_next_id(false, index); + mgr.configure(id, &mut self.widgets[index]); + self.size_solved = false; + *mgr |= TkAction::RESIZE; + index } - /// Remove the last child widget - /// - /// Returns `None` if there are no children. Otherwise, this - /// triggers a reconfigure before the next draw operation. + /// Remove the last child widget (if any) and return /// - /// Triggers a [reconfigure action](EventState::send_action) if any widget is - /// removed. - pub fn pop(&mut self) -> (Option, TkAction) { - let action = match self.widgets.is_empty() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, - }; - let _ = self.handles.pop(); - (self.widgets.pop(), action) + /// Triggers [`TkAction::RESIZE`]. + pub fn pop(&mut self, mgr: &mut SetRectMgr) -> Option { + let result = self.widgets.pop(); + if let Some(w) = result.as_ref() { + *mgr |= TkAction::RESIZE; + + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + + if let Some(w) = self.handles.pop() { + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + } + } + result } /// Inserts a child widget position `index` /// /// Panics if `index > len`. /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn insert(&mut self, index: usize, widget: W) -> TkAction { + /// The new child is configured immediately. Triggers [`TkAction::RESIZE`]. + pub fn insert(&mut self, mgr: &mut SetRectMgr, index: usize, widget: W) { + for v in self.id_map.values_mut() { + if *v >= index { + *v += 2; + } + } + if !self.widgets.is_empty() { - self.handles.push(DragHandle::new()); + let index = index.min(self.handles.len()); + self.handles.insert(index, DragHandle::new()); + let id = self.make_next_id(true, index); + mgr.configure(id, &mut self.handles[index]); } + self.widgets.insert(index, widget); - TkAction::RECONFIGURE - } + let id = self.make_next_id(false, index); + mgr.configure(id, &mut self.widgets[index]); - /// Removes the child widget at position `index` - /// - /// Panics if `index` is out of bounds. - /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn remove(&mut self, index: usize) -> (W, TkAction) { - let _ = self.handles.pop(); - let r = self.widgets.remove(index); - (r, TkAction::RECONFIGURE) + self.size_solved = false; + *mgr |= TkAction::RESIZE; } - /// Replace the child at `index` + /// Removes the child widget at position `index` /// /// Panics if `index` is out of bounds. /// - /// Triggers a [reconfigure action](EventState::send_action). - // TODO: in theory it is possible to avoid a reconfigure where both widgets - // have no children and have compatible size. Is this a good idea and can - // we somehow test "has compatible size"? - pub fn replace(&mut self, index: usize, mut widget: W) -> (W, TkAction) { - std::mem::swap(&mut widget, &mut self.widgets[index]); - (widget, TkAction::RECONFIGURE) - } + /// Triggers [`TkAction::RESIZE`]. + pub fn remove(&mut self, mgr: &mut SetRectMgr, index: usize) -> W { + if !self.handles.is_empty() { + let index = index.min(self.handles.len()); + let w = self.handles.remove(index); + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } - /// Append child widgets from an iterator - /// - /// Triggers a [reconfigure action](EventState::send_action) if any widgets - /// are added. - pub fn extend>(&mut self, iter: T) -> TkAction { - let len = self.widgets.len(); - self.widgets.extend(iter); - self.handles - .resize_with(self.widgets.len().saturating_sub(1), DragHandle::new); - match len == self.widgets.len() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, + let w = self.widgets.remove(index); + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } } - } - /// Resize, using the given closure to construct new widgets - /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn resize_with W>(&mut self, len: usize, f: F) -> TkAction { - let l0 = self.widgets.len(); - if l0 == len { - return TkAction::empty(); - } else if l0 > len { - self.widgets.truncate(len); - } else { - self.widgets.reserve(len); - for i in l0..len { - self.widgets.push(f(i)); + *mgr |= TkAction::RESIZE; + + for v in self.id_map.values_mut() { + if *v > index { + *v -= 2; } } - self.handles - .resize_with(self.widgets.len().saturating_sub(1), DragHandle::new); - TkAction::RECONFIGURE + w } - /// Retain only widgets satisfying predicate `f` + /// Replace the child at `index` /// - /// See documentation of [`Vec::retain`]. + /// Panics if `index` is out of bounds. /// - /// Triggers a [reconfigure action](EventState::send_action) if any widgets - /// are removed. - pub fn retain bool>(&mut self, f: F) -> TkAction { - let len = self.widgets.len(); - self.widgets.retain(f); - self.handles - .resize_with(self.widgets.len().saturating_sub(1), DragHandle::new); - match len == self.widgets.len() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, + /// The new child is configured immediately. Triggers [`TkAction::RESIZE`]. + pub fn replace(&mut self, mgr: &mut SetRectMgr, index: usize, mut w: W) -> W { + std::mem::swap(&mut w, &mut self.widgets[index]); + + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } } + + let id = self.make_next_id(false, index); + mgr.configure(id, &mut self.widgets[index]); + + self.size_solved = false; + *mgr |= TkAction::RESIZE; + + w } } diff --git a/crates/kas-widgets/src/stack.rs b/crates/kas-widgets/src/stack.rs index d20b110d2..a4d0e2910 100644 --- a/crates/kas-widgets/src/stack.rs +++ b/crates/kas-widgets/src/stack.rs @@ -5,10 +5,10 @@ //! A stack -use std::fmt::Debug; -use std::ops::{Index, IndexMut}; - use kas::{event, prelude::*}; +use std::collections::hash_map::{Entry, HashMap}; +use std::fmt::Debug; +use std::ops::{Index, IndexMut, Range}; /// A stack of boxed widgets /// @@ -23,21 +23,52 @@ pub type RefStack<'a, M> = Stack<&'a mut dyn Widget>; widget! { /// A stack of widgets /// - /// A stack consists a set of child widgets, all of equal size. - /// Only a single member is visible at a time. + /// A stack consists a set of child widgets, "pages", all of equal size. + /// Only a single page is visible at a time. The page is "turned" by calling + /// [`Self::set_active`]. /// - /// This may only be parametrised with a single widget type; [`BoxStack`] is - /// a parametrisation allowing run-time polymorphism of child widgets. + /// This may only be parametrised with a single widget type, thus usually + /// it will be necessary to box children (this is what [`BoxStack`] is). /// - /// Configuring and resizing elements is O(n) in the number of children. - /// Drawing and event handling is O(1). + /// Configuring is `O(n)` in the number of pages `n`. Resizing may be `O(n)` + /// or may be limited: see [`Self::set_size_limit`]. Drawing is `O(1)`, and + /// so is event handling in the expected case. #[derive(Clone, Default, Debug)] #[handler(msg=::Msg)] pub struct Stack { #[widget_core] core: CoreData, + align_hints: AlignHints, widgets: Vec, + sized_range: Range, active: usize, + size_limit: usize, + next: usize, + id_map: HashMap, // map key of WidgetId to index + } + + impl Self { + // Assumption: index is a valid entry of self.widgets + fn make_next_id(&mut self, index: usize) -> WidgetId { + if let Some(child) = self.widgets.get(index) { + // Use the widget's existing identifier, if any + if child.id_ref().is_valid() { + if let Some(key) = child.id_ref().next_key_after(self.id_ref()) { + self.id_map.insert(key, index); + return child.id(); + } + } + } + + loop { + let key = self.next; + self.next += 1; + if let Entry::Vacant(entry) = self.id_map.entry(key) { + entry.insert(index); + return self.id_ref().make_child(key); + } + } + } } impl WidgetChildren for Self { @@ -53,33 +84,57 @@ widget! { fn get_child_mut(&mut self, index: usize) -> Option<&mut dyn WidgetConfig> { self.widgets.get_mut(index).map(|w| w.as_widget_mut()) } + + fn find_child_index(&self, id: &WidgetId) -> Option { + id.next_key_after(self.id_ref()).and_then(|k| self.id_map.get(&k).cloned()) + } + } + + impl WidgetConfig for Self { + fn configure_recurse(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { + self.core_data_mut().id = id; + self.id_map.clear(); + + for index in 0..self.widgets.len() { + let id = self.make_next_id(index); + self.widgets[index].configure_recurse(mgr, id); + } + + self.configure(mgr); + } } impl Layout for Self { fn size_rules(&mut self, size_mgr: SizeMgr, axis: AxisInfo) -> SizeRules { let mut rules = SizeRules::EMPTY; - for child in &mut self.widgets { - rules = rules.max(child.size_rules(size_mgr.re(), axis)); + let end = self.active.saturating_add(self.size_limit).min(self.widgets.len()); + let start = end.saturating_sub(self.size_limit); + self.sized_range = start..end; + debug_assert!(self.sized_range.contains(&self.active)); + for index in start..end { + rules = rules.max(self.widgets[index].size_rules(size_mgr.re(), axis)); } rules } fn set_rect(&mut self, mgr: &mut SetRectMgr, rect: Rect, align: AlignHints) { self.core.rect = rect; - for child in &mut self.widgets { + self.align_hints = align; + if let Some(child) = self.widgets.get_mut(self.active) { child.set_rect(mgr, rect, align); } } fn find_id(&mut self, coord: Coord) -> Option { - if self.active < self.widgets.len() { + // Latter condition is implied, but compiler doesn't know this: + if self.sized_range.contains(&self.active) && self.active < self.widgets.len() { return self.widgets[self.active].find_id(coord); } None } fn draw(&mut self, mut draw: DrawMgr) { - if self.active < self.widgets.len() { + if self.sized_range.contains(&self.active) && self.active < self.widgets.len() { self.widgets[self.active].draw(draw.re()); } } @@ -91,7 +146,7 @@ widget! { if let Some(child) = self.widgets.get_mut(index) { return match child.send(mgr, id, event) { Response::Focus(rect) => { - *mgr |= self.set_active(index); + mgr.set_rect_mgr(|mgr| self.set_active(mgr, index)); Response::Focus(rect) } r => r, @@ -122,185 +177,294 @@ impl Stack { /// Construct a new instance /// /// If `active < widgets.len()`, then `widgets[active]` will initially be - /// visible; otherwise, no widget will be visible. + /// shown; otherwise, no page will be visible or receive press events. pub fn new(widgets: Vec, active: usize) -> Self { Stack { core: Default::default(), + align_hints: Default::default(), widgets, + sized_range: 0..0, active, + size_limit: usize::MAX, + next: 0, + id_map: Default::default(), } } + /// Edit the list of children directly + /// + /// This may be used to edit pages before window construction. It may + /// also be used from a running UI, but in this case a full reconfigure + /// of the window's widgets is required (triggered by the the return + /// value, [`TkAction::RECONFIGURE`]). + #[inline] + pub fn edit)>(&mut self, f: F) -> TkAction { + f(&mut self.widgets); + TkAction::RECONFIGURE + } + + /// Limit the number of pages considered by [`Layout::size_rules`] + /// + /// By default, this is `usize::MAX`: all pages affect the result. If + /// this is set to 1 then only the active page will affect the result. If + /// this is `n > 1`, then `min(n, num_pages)` pages (including active) + /// will be used. (If this is set to 0 it is silently replaced with 1.) + /// + /// Using a limit lower than the number of pages has two effects: + /// (1) resizing is faster and (2) calling [`Self::set_active`] may cause a + /// full-window resize. + pub fn set_size_limit(&mut self, limit: usize) { + self.size_limit = limit.max(1); + } + /// Get the index of the active widget pub fn active_index(&self) -> usize { self.active } - /// Change the active widget via index + /// Set the active page /// - /// It is not required that `active < self.len()`; if not, no widget will be - /// drawn or respond to events, but the stack will still size as required by - /// child widgets. - pub fn set_active(&mut self, active: usize) -> TkAction { - if self.active == active { - TkAction::empty() - } else { - self.active = active; - TkAction::REGION_MOVED + /// Behaviour depends on whether [`SizeRules`] were already solved for + /// `index` (see [`Self::set_size_limit`] and note that methods like + /// [`Self::push`] do not solve rules for new pages). Case: + /// + /// - `index >= num_pages`: no page displayed + /// - `index == active` and `SizeRules` were solved: nothing happens + /// - `SizeRules` were solved: set layout ([`Layout::set_rect`]) and + /// update mouse-cursor target ([`TkAction::REGION_MOVED`]) + /// - Otherwise: resize the whole window ([`TkAction::RESIZE`]) + pub fn set_active(&mut self, mgr: &mut SetRectMgr, index: usize) { + let old_index = self.active; + self.active = index; + if index >= self.widgets.len() { + if old_index < self.widgets.len() { + *mgr |= TkAction::REGION_MOVED; + } + return; } - } - /// Get a direct reference to the active widget, if any - pub fn active(&self) -> Option<&W> { - if self.active < self.widgets.len() { - Some(&self.widgets[self.active]) + if self.sized_range.contains(&index) { + if old_index != index { + self.widgets[index].set_rect(mgr, self.core.rect, self.align_hints); + *mgr |= TkAction::REGION_MOVED; + } } else { - None + *mgr |= TkAction::RESIZE; } } - /// Get a direct mutable reference to the active widget, if any - pub fn active_mut(&mut self) -> Option<&mut W> { + /// Get a direct reference to the active child widget, if any + pub fn active(&self) -> Option<&W> { if self.active < self.widgets.len() { - Some(&mut self.widgets[self.active]) + Some(&self.widgets[self.active]) } else { None } } - /// True if there are no child widgets + /// True if there are no pages pub fn is_empty(&self) -> bool { self.widgets.is_empty() } - /// Returns the number of child widgets + /// Returns the number of pages pub fn len(&self) -> usize { self.widgets.len() } - /// Returns the number of elements the vector can hold without reallocating. - pub fn capacity(&self) -> usize { - self.widgets.capacity() - } - - /// Reserves capacity for at least `additional` more elements to be inserted - /// into the list. See documentation of [`Vec::reserve`]. - pub fn reserve(&mut self, additional: usize) { - self.widgets.reserve(additional); - } - - /// Remove all child widgets + /// Remove all pages /// - /// Triggers a [reconfigure action](EventState::send_action) if any widget is - /// removed. - pub fn clear(&mut self) -> TkAction { - let action = match self.widgets.is_empty() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, - }; + /// This does not change the active page index. + pub fn clear(&mut self) { self.widgets.clear(); - action + self.sized_range = 0..0; } - /// Append a child widget + /// Append a page /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn push(&mut self, widget: W) -> TkAction { + /// The new page is configured immediately. If it becomes the active page + /// and then [`TkAction::RESIZE`] will be triggered. + /// + /// Returns the new page's index. + pub fn push(&mut self, mgr: &mut SetRectMgr, widget: W) -> usize { + let index = self.widgets.len(); self.widgets.push(widget); - TkAction::RECONFIGURE + let id = self.make_next_id(index); + mgr.configure(id, &mut self.widgets[index]); + if index == self.active { + *mgr |= TkAction::RESIZE; + } + self.sized_range.end = self.sized_range.end.min(index); + index } - /// Remove the last child widget - /// - /// Returns `None` if there are no children. Otherwise, this - /// triggers a reconfigure before the next draw operation. + /// Remove the last child widget (if any) and return /// - /// Triggers a [reconfigure action](EventState::send_action) if any widget is - /// removed. - pub fn pop(&mut self) -> (Option, TkAction) { - let action = match self.widgets.is_empty() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, - }; - (self.widgets.pop(), action) + /// If this page was active then the previous page becomes active. + pub fn pop(&mut self, mgr: &mut SetRectMgr) -> Option { + let result = self.widgets.pop(); + if let Some(w) = result.as_ref() { + if self.active > 0 && self.active == self.widgets.len() { + self.active -= 1; + if self.sized_range.contains(&self.active) { + self.widgets[self.active].set_rect(mgr, self.core.rect, self.align_hints); + } else { + *mgr |= TkAction::RESIZE; + } + } + + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + } + result } /// Inserts a child widget position `index` /// /// Panics if `index > len`. /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn insert(&mut self, index: usize, widget: W) -> TkAction { + /// The new child is configured immediately. The active page does not + /// change. + pub fn insert(&mut self, mgr: &mut SetRectMgr, index: usize, widget: W) { + if self.active < index { + self.sized_range.end = self.sized_range.end.min(index); + } else { + self.sized_range.start = (self.sized_range.start + 1).max(index + 1); + self.sized_range.end += 1; + self.active = self.active.saturating_add(1); + } + for v in self.id_map.values_mut() { + if *v >= index { + *v += 1; + } + } self.widgets.insert(index, widget); - TkAction::RECONFIGURE + let id = self.make_next_id(index); + mgr.configure(id, &mut self.widgets[index]); } /// Removes the child widget at position `index` /// /// Panics if `index` is out of bounds. /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn remove(&mut self, index: usize) -> (W, TkAction) { - let r = self.widgets.remove(index); - (r, TkAction::RECONFIGURE) + /// If the active page is removed then the previous page (if any) becomes + /// active. + pub fn remove(&mut self, mgr: &mut SetRectMgr, index: usize) -> W { + let w = self.widgets.remove(index); + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + + if self.active == index { + self.active = self.active.saturating_sub(1); + if self.sized_range.contains(&self.active) { + self.widgets[self.active].set_rect(mgr, self.core.rect, self.align_hints); + } else { + *mgr |= TkAction::RESIZE; + } + } + if index < self.sized_range.end { + self.sized_range.end -= 1; + if index < self.sized_range.start { + self.sized_range.start -= 1; + } + } + + for v in self.id_map.values_mut() { + if *v > index { + *v -= 1; + } + } + w } /// Replace the child at `index` /// /// Panics if `index` is out of bounds. /// - /// Triggers a [reconfigure action](EventState::send_action). - // TODO: in theory it is possible to avoid a reconfigure where both widgets - // have no children and have compatible size. Is this a good idea and can - // we somehow test "has compatible size"? - pub fn replace(&mut self, index: usize, mut widget: W) -> (W, TkAction) { - std::mem::swap(&mut widget, &mut self.widgets[index]); - (widget, TkAction::RECONFIGURE) + /// The new child is configured immediately. If it replaces the active page, + /// then [`TkAction::RESIZE`] is triggered. + pub fn replace(&mut self, mgr: &mut SetRectMgr, index: usize, mut w: W) -> W { + std::mem::swap(&mut w, &mut self.widgets[index]); + + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + + let id = self.make_next_id(index); + mgr.configure(id, &mut self.widgets[index]); + + if self.active < index { + self.sized_range.end = self.sized_range.end.min(index); + } else { + self.sized_range.start = (self.sized_range.start + 1).max(index + 1); + self.sized_range.end += 1; + if index == self.active { + *mgr |= TkAction::RESIZE; + } + } + + w } /// Append child widgets from an iterator /// - /// Triggers a [reconfigure action](EventState::send_action) if any widgets - /// are added. - pub fn extend>(&mut self, iter: T) -> TkAction { - let len = self.widgets.len(); + /// New children are configured immediately. If a new page becomes active, + /// then [`TkAction::RESIZE`] is triggered. + pub fn extend>(&mut self, mgr: &mut SetRectMgr, iter: T) { + let old_len = self.widgets.len(); self.widgets.extend(iter); - match len == self.widgets.len() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, + for index in old_len..self.widgets.len() { + let id = self.make_next_id(index); + mgr.configure(id, &mut self.widgets[index]); + } + + if (old_len..self.widgets.len()).contains(&self.active) { + *mgr |= TkAction::RESIZE; } } /// Resize, using the given closure to construct new widgets /// - /// Triggers a [reconfigure action](EventState::send_action). - pub fn resize_with W>(&mut self, len: usize, f: F) -> TkAction { - let l0 = self.widgets.len(); - if l0 == len { - return TkAction::empty(); - } else if l0 > len { - self.widgets.truncate(len); - } else { - self.widgets.reserve(len); - for i in l0..len { - self.widgets.push(f(i)); + /// New children are configured immediately. If a new page becomes active, + /// then [`TkAction::RESIZE`] is triggered. + pub fn resize_with W>(&mut self, mgr: &mut SetRectMgr, len: usize, f: F) { + let old_len = self.widgets.len(); + + if len < old_len { + self.sized_range.end = self.sized_range.end.min(len); + loop { + let w = self.widgets.pop().unwrap(); + if w.id_ref().is_valid() { + if let Some(key) = w.id_ref().next_key_after(self.id_ref()) { + self.id_map.remove(&key); + } + } + if len == self.widgets.len() { + return; + } } } - TkAction::RECONFIGURE - } - /// Retain only widgets satisfying predicate `f` - /// - /// See documentation of [`Vec::retain`]. - /// - /// Triggers a [reconfigure action](EventState::send_action) if any widgets - /// are removed. - pub fn retain bool>(&mut self, f: F) -> TkAction { - let len = self.widgets.len(); - self.widgets.retain(f); - match len == self.widgets.len() { - true => TkAction::empty(), - false => TkAction::RECONFIGURE, + if len > old_len { + self.widgets.reserve(len - old_len); + for index in old_len..len { + let id = self.make_next_id(index); + let mut widget = f(index); + mgr.configure(id, &mut widget); + self.widgets.push(widget); + } + + if (old_len..len).contains(&self.active) { + *mgr |= TkAction::RESIZE; + } } } } diff --git a/crates/kas-widgets/src/view/driver.rs b/crates/kas-widgets/src/view/driver.rs index ee0b3a039..3d8be02c9 100644 --- a/crates/kas-widgets/src/view/driver.rs +++ b/crates/kas-widgets/src/view/driver.rs @@ -41,6 +41,10 @@ pub trait Driver: Debug + 'static { /// The controller may later call [`Driver::set`] on the widget then show it. fn make(&self) -> Self::Widget; /// Set the viewed data + /// + /// The widget may expect `configure` to be called at least once before data + /// is set and to have `size_rules` and `set_rect` called after each time + /// data is set. fn set(&self, widget: &mut Self::Widget, data: T) -> TkAction; /// Get data from the view /// diff --git a/crates/kas-widgets/src/view/list_view.rs b/crates/kas-widgets/src/view/list_view.rs index 78371b727..b5bc01f30 100644 --- a/crates/kas-widgets/src/view/list_view.rs +++ b/crates/kas-widgets/src/view/list_view.rs @@ -396,11 +396,6 @@ widget! { fn num_children(&self) -> usize { self.widgets.len() } - fn make_child_id(&self, index: usize) -> Option { - self.widgets.get(index) - .and_then(|w| w.key.as_ref()) - .map(|key| self.data.make_id(self.id_ref(), key)) - } #[inline] fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { self.widgets.get(index).map(|w| w.widget.as_widget()) @@ -426,6 +421,33 @@ widget! { } impl WidgetConfig for Self { + fn configure_recurse(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { + self.core_data_mut().id = id; + + // If data is available but not loaded yet, make some widgets for + // use by size_rules (this allows better sizing). Configure the new + // widgets (this allows resource loading which may affect size.) + self.data_ver = self.data.version(); + if self.widgets.len() == 0 && !self.data.is_empty() { + let items = self.data.iter_vec(self.ideal_visible.cast()); + let len = items.len(); + debug!("allocating {} widgets", len); + self.widgets.reserve(len); + for key in items.into_iter() { + let id = self.data.make_id(self.id_ref(), &key); + let mut widget = self.view.make(); + mgr.configure(id, &mut widget); + if let Some(item) = self.data.get_cloned(&key) { + *mgr |= self.view.set(&mut widget, item); + } + let key = Some(key); + self.widgets.push(WidgetData { key, widget }); + } + } + + self.configure(mgr); + } + fn configure(&mut self, mgr: &mut SetRectMgr) { for handle in self.data.update_handles().into_iter() { mgr.update_on_handle(handle, self.id()); @@ -446,24 +468,6 @@ widget! { self.child_size_min = rules.min_size(); } - // If data is already available, create some widgets and ensure - // that the ideal size meets all expectations of these children. - self.data_ver = self.data.version(); - if self.widgets.len() == 0 && self.data.len() > 0 { - let items = self.data.iter_vec(self.ideal_visible.cast()); - debug!("allocating widgets (reserve = {})", items.len()); - self.widgets.reserve(items.len()); - for key in items.into_iter() { - if let Some(item) = self.data.get_cloned(&key) { - let mut widget = self.view.make(); - // Note: we cannot call configure here, but it needs - // to happen! Therefore we set key=None and do not - // care about order of data within self.widgets. - let _ = self.view.set(&mut widget, item); - self.widgets.push(WidgetData { key: None, widget }); - } - } - } if self.widgets.len() > 0 { let other = axis.other().map(|mut size| { // Use same logic as in set_rect to find per-child size: diff --git a/crates/kas-widgets/src/view/matrix_view.rs b/crates/kas-widgets/src/view/matrix_view.rs index 2287b6e1e..bb92ca784 100644 --- a/crates/kas-widgets/src/view/matrix_view.rs +++ b/crates/kas-widgets/src/view/matrix_view.rs @@ -301,10 +301,10 @@ widget! { .row_iter_vec_from(solver.first_row, solver.row_len); let mut action = TkAction::empty(); - for (cn, col) in cols.iter().enumerate() { - let ci = solver.first_col + cn; - for (rn, row) in rows.iter().enumerate() { - let ri = solver.first_row + rn; + for (rn, row) in rows.iter().enumerate() { + let ri = solver.first_row + rn; + for (cn, col) in cols.iter().enumerate() { + let ci = solver.first_col + cn; let i = solver.data_to_child(ci, ri); let key = T::make_key(col, row); let id = self.data.make_id(self.id_ref(), &key); @@ -369,11 +369,6 @@ widget! { fn num_children(&self) -> usize { self.widgets.len() } - fn make_child_id(&self, index: usize) -> Option { - self.widgets.get(index) - .and_then(|w| w.key.as_ref()) - .map(|key| self.data.make_id(self.id_ref(), key)) - } #[inline] fn get_child(&self, index: usize) -> Option<&dyn WidgetConfig> { self.widgets.get(index).map(|w| w.widget.as_widget()) @@ -399,6 +394,37 @@ widget! { } impl WidgetConfig for Self { + fn configure_recurse(&mut self, mgr: &mut SetRectMgr, id: WidgetId) { + self.core_data_mut().id = id; + + // If data is available but not loaded yet, make some widgets for + // use by size_rules (this allows better sizing). Configure the new + // widgets (this allows resource loading which may affect size.) + self.data_ver = self.data.version(); + if self.widgets.len() == 0 && !self.data.is_empty() { + let cols = self.data.col_iter_vec(self.ideal_len.cols.cast()); + let rows = self.data.row_iter_vec(self.ideal_len.rows.cast()); + let len = cols.len() * rows.len(); + debug!("allocating {} widgets", len); + self.widgets.reserve(len); + for row in rows.iter(){ + for col in cols.iter() { + let key = T::make_key(col, row); + let id = self.data.make_id(self.id_ref(), &key); + let mut widget = self.view.make(); + mgr.configure(id, &mut widget); + if let Some(item) = self.data.get_cloned(&key) { + *mgr |= self.view.set(&mut widget, item); + } + let key = Some(key); + self.widgets.push(WidgetData { key, widget }); + } + } + } + + self.configure(mgr); + } + fn configure(&mut self, mgr: &mut SetRectMgr) { for handle in self.data.update_handles().into_iter() { mgr.update_on_handle(handle, self.id()); @@ -417,29 +443,6 @@ widget! { let mut rules = self.view.make().size_rules(size_mgr.re(), axis); self.child_size_min.set_component(axis, rules.min_size()); - // If data is already available, create some widgets and ensure - // that the ideal size meets all expectations of these children. - self.data_ver = self.data.version(); - if self.widgets.len() == 0 && !self.data.is_empty() { - let cols = self.data.col_iter_vec(self.ideal_len.cols.cast()); - let rows = self.data.row_iter_vec(self.ideal_len.rows.cast()); - let len = cols.len() * rows.len(); - debug!("allocating widgets (reserve = {})", len); - self.widgets.reserve(len); - for col in cols.iter() { - for row in rows.iter(){ - let key = T::make_key(col, row); - let mut widget = self.view.make(); - if let Some(item) = self.data.get_cloned(&key) { - // Note: we cannot call configure here, but it needs - // to happen! Therefore we set key=None and do not - // care about order of data within self.widgets. - let _ = self.view.set(&mut widget, item); - self.widgets.push(WidgetData { key: None, widget }); - } - } - } - } if self.widgets.len() > 0 { let other = axis.other().map(|mut size| { // Use same logic as in set_rect to find per-child size: diff --git a/crates/kas-widgets/src/view/single_view.rs b/crates/kas-widgets/src/view/single_view.rs index fc0cfb87d..613eea53b 100644 --- a/crates/kas-widgets/src/view/single_view.rs +++ b/crates/kas-widgets/src/view/single_view.rs @@ -58,9 +58,8 @@ widget! { impl Self { /// Construct a new instance with explicit view pub fn new_with_driver(view: V, data: T) -> Self { - let mut child = view.make(); + let child = view.make(); let data_ver = data.version(); - let _ = view.set(&mut child, data.get_cloned()); SingleView { core: Default::default(), view, @@ -110,6 +109,9 @@ widget! { for handle in self.data.update_handles().into_iter() { mgr.update_on_handle(handle, self.id()); } + + // We set data now, after child is configured + *mgr |= self.view.set(&mut self.child, self.data.get_cloned()); } } diff --git a/examples/data-list.rs b/examples/data-list.rs index e2d0a87fb..f610f62ad 100644 --- a/examples/data-list.rs +++ b/examples/data-list.rs @@ -162,7 +162,10 @@ fn main() -> kas::shell::Result<()> { Control::Set(len) => { let active = self.active; let old_len = self.list.len(); - *mgr |= self.list.inner_mut().resize_with(len, |n| ListEntry::new(n, n == active)); + mgr.set_rect_mgr(|mgr| { + self.list.inner_mut() + .resize_with(mgr, len, |n| ListEntry::new(n, n == active)) + }); if active >= old_len && active < len { let _ = self.set_radio(mgr, (active, EntryMsg::Select)); } diff --git a/examples/gallery.rs b/examples/gallery.rs index b17d58277..f67274c26 100644 --- a/examples/gallery.rs +++ b/examples/gallery.rs @@ -256,7 +256,7 @@ fn main() -> kas::shell::Result<()> { .with_state(true) .on_select(|_| Some(Item::Radio(2))), #[widget] cbbl = Label::new("ComboBox"), - #[widget] cbb = ComboBox::new(&["&One", "T&wo", "Th&ree"], 0) + #[widget] cbb = ComboBox::new_from_iter(&["&One", "T&wo", "Th&ree"], 0) .on_select(|_, index| Some(Item::Combo((index + 1).cast()))), #[widget] sdl = Label::new("Slider"), #[widget(map_msg = handle_slider)] sd = diff --git a/examples/splitter.rs b/examples/splitter.rs index a4b5d7206..4716b72d0 100644 --- a/examples/splitter.rs +++ b/examples/splitter.rs @@ -28,10 +28,8 @@ fn main() -> kas::shell::Result<()> { #[widget] _ = TextButton::new_msg("+", Message::Incr), } }; - let mut panes = RowSplitter::::default(); - let _ = panes.resize_with(2, |n| { - EditField::new(format!("Pane {}", n + 1)).multi_line(true) - }); + let panes = (0..2).map(|n| EditField::new(format!("Pane {}", n + 1)).multi_line(true)); + let panes = RowSplitter::::new(panes.collect()); let window = Window::new( "Slitter panes", @@ -49,11 +47,14 @@ fn main() -> kas::shell::Result<()> { fn handle_button(&mut self, mgr: &mut EventMgr, msg: Message) { match msg { Message::Decr => { - *mgr |= self.panes.pop().1; + mgr.set_rect_mgr(|mgr| self.panes.pop(mgr)); } Message::Incr => { let n = self.panes.len() + 1; - *mgr |= self.panes.push(EditField::new(format!("Pane {}", n)).multi_line(true)); + mgr.set_rect_mgr(|mgr| self.panes.push( + mgr, + EditField::new(format!("Pane {}", n)).multi_line(true) + )); } }; }