pub struct StackNavigationBar<P: NavigationProvider + 'static> {
id: Id,
pub rect: Rectangle,
children: Vec<Box<dyn View>>,
selected: P::LevelKey,
pub vertical_limit: i32,
max_levels: usize,
provider: P,
enable_resize: bool,
}Expand description
A vertically-stacked navigation bar with dynamic height and level management.
StackNavigationBar displays a stack of navigation levels (e.g., directory hierarchy)
with separators between them. It supports interactive resizing via swipe gestures,
automatic level management based on available space, and reuse of existing bars
when navigating to related items.
§Architecture
The navigation bar uses a generic NavigationProvider trait to abstract domain-specific
logic (e.g., file system navigation, category hierarchies). This separation allows the
same container to work with different hierarchical data structures.
§Layout Structure
Children are stored in alternating order:
- Even indices (0, 2, 4…): Navigation bars for each level
- Odd indices (1, 3, 5…): Separator fillers between bars
The container’s rect is dynamically adjusted to match the total height of all children.
§ASCII illustration (top = smaller y, bottom = larger y):
container.rect.min.y
+--------------------------------------+
| Bar (index 0) | <-- even indices are bars (level 0)
+--------------------------------------+
| Separator (index 1) | <-- odd indices are separators
+--------------------------------------+
| Bar (index 2) | <-- even indices are bars (level 1)
+--------------------------------------+
| Separator (index 3) |
+--------------------------------------+
| Bar (index 4) | <-- deeper level / leaf
+--------------------------------------+
container.rect.max.yThe diagram shows the alternating bar/separator pattern and how the container’s min.y and max.y encompass the stacked children.
§Interactive Resize
Users can resize individual bars via vertical (up/down) swipe gestures. The container:
- Calculates the desired size based on grid-snapped line counts
- Delegates actual resize to the provider via
resize_bar_by() - Updates the container rect to match the last child’s position
Minimum height constraints are enforced by the provider to prevent 1px collapse bugs.
§Level Management
When set_selected() is called:
- Existing bars are reused when navigating to ancestors/descendants
- New bars are created only when needed
- Excess bars (beyond
max_levels) are trimmed - Empty levels are skipped unless they’re the selected level
§Type Parameters
P- The navigation provider that implements domain-specific traversal logic
§Why P: 'static?
The view tree stores views as owned trait objects (Box<dyn View>) inside containers.
Those boxed trait objects are used without borrowing from caller stack frames or
tied lifetimes, so the concrete view types placed in the boxes must not contain
non-’static references. StackNavigationBar owns its provider: P field directly,
therefore to safely store StackNavigationBar<P> as a boxed view the provider type
must be 'static. This keeps the view-tree API simple and avoids needing to
propagate lifetimes through the entire view hierarchy.
Fields§
§id: IdUnique view identifier
rect: RectangleContainer rectangle (dynamically adjusted to fit children)
children: Vec<Box<dyn View>>Child views: bars at even indices, separators at odd indices
selected: P::LevelKeyCurrently selected level key
vertical_limit: i32Maximum Y coordinate for the navigation bar’s bottom edge
max_levels: usizeMaximum number of levels to display simultaneously
provider: PDomain-specific navigation logic provider
enable_resize: boolIf this bar type should allow resizing via gesture
Implementations§
Sourcepub fn new(
rect: Rectangle,
vertical_limit: i32,
max_levels: usize,
provider: P,
selected: P::LevelKey,
) -> Self
pub fn new( rect: Rectangle, vertical_limit: i32, max_levels: usize, provider: P, selected: P::LevelKey, ) -> Self
Creates a new navigation bar.
The bar starts empty and must be populated via set_selected().
§Arguments
rect- Initial container rectanglevertical_limit- Maximum Y coordinate for the bar’s bottom edgemax_levels- Maximum number of hierarchy levels to displayprovider- Domain-specific navigation providerselected- Initial selected level (bar remains empty untilset_selected()is called)
pub fn disable_resize(self) -> Self
Sourcepub fn set_selected(
&mut self,
selected: P::LevelKey,
rq: &mut RenderQueue,
context: &mut Context,
)
pub fn set_selected( &mut self, selected: P::LevelKey, rq: &mut RenderQueue, context: &mut Context, )
Updates the selected level and rebuilds the navigation bar hierarchy.
This method reuses existing bars when navigating to related levels (ancestors or descendants) to minimize rendering work. New bars are created only when necessary, and excess bars are trimmed.
§Algorithm
- Trim trailing bars that are no longer ancestors of the selected level
- Prefetch data for all levels from selected up to root (or max_levels)
- Build bar hierarchy bottom-up from leaf to root
- Reuse existing bars when they’re still valid
- Position all bars starting from container’s min.y
- Update container rect to match total children height
§Arguments
selected- The new selected level keyrq- Render queue for scheduling redrawscontext- Application context with fonts and other resources
fn first_bar_key(&self) -> Option<P::LevelKey>
fn last_bar_key(&self) -> Option<P::LevelKey>
Sourcefn trim_trailing_children(
&mut self,
selected: &P::LevelKey,
last_key: &mut Option<P::LevelKey>,
)
fn trim_trailing_children( &mut self, selected: &P::LevelKey, last_key: &mut Option<P::LevelKey>, )
Trim trailing children that are no longer ancestors of selected.
children stores views in alternating order: bar views at even indices and
separator filler views at odd indices. That means one logical navigation
“level” corresponds to two entries in children (bar + separator).
leftovers is the number of logical levels that should be removed from the
end, so we drain 2 * leftovers entries.
saturating_sub ensures we never underflow if 2 * leftovers > children.len()
(in that case we simply drain from 0.. and clear the vector).
fn prefetch_needed_levels( &self, selected: &P::LevelKey, context: &mut Context, ) -> BTreeMap<P::LevelKey, P::LevelData>
Sourcefn can_reuse_existing(
&self,
first: &Option<P::LevelKey>,
last: &Option<P::LevelKey>,
current: &P::LevelKey,
) -> bool
fn can_reuse_existing( &self, first: &Option<P::LevelKey>, last: &Option<P::LevelKey>, current: &P::LevelKey, ) -> bool
Returns true if an existing contiguous range of bars can be reused for
the given current level when rebuilding the navigation stack.
Reuse is possible only when both first and last boundaries are
present and current lies between them in the ancestry chain. Concretely,
this method returns true when:
provider.is_ancestor(current, last)(i.e.currentis an ancestor oflast)provider.is_ancestor(first, current)(i.e.firstis an ancestor ofcurrent)
If either first or last is None, reuse is not possible and the
function returns false.
fn reuse_existing_bar_and_separator( &mut self, index: usize, y_max: i32, thickness: i32, ) -> (usize, i32)
Sourcefn should_insert_bar(
&self,
selected: &P::LevelKey,
current: &P::LevelKey,
data_by_level: &BTreeMap<P::LevelKey, P::LevelData>,
) -> bool
fn should_insert_bar( &self, selected: &P::LevelKey, current: &P::LevelKey, data_by_level: &BTreeMap<P::LevelKey, P::LevelData>, ) -> bool
Decide whether a bar for current should be created while rebuilding the stack.
Rules:
- If
currentis not theselectedlevel, we always insert a bar for it. This ensures ancestor/ancestor-sibling levels remain visible when traversing. - If
currentis theselectedlevel, we only insert a bar when there is content to show. That is determined by consultingdata_by_leveland calling the provider’sestimate_line_count; an estimate > 0 indicates the selected level is non-empty and should be represented by a bar.
If data_by_level does not contain an entry for selected, the function
conservatively returns false (do not insert).
Sourcefn compute_bar_height(
&self,
layout: &Layout,
key: &P::LevelKey,
data: &P::LevelData,
y_max: i32,
) -> (i32, bool)
fn compute_bar_height( &self, layout: &Layout, key: &P::LevelKey, data: &P::LevelData, y_max: i32, ) -> (i32, bool)
Compute the visual height for a bar representing key with data, and
indicate whether that bar can be placed without overlapping the container’s
top edge.
Calculation details:
- The provider’s
estimate_line_countis used to determine how many lines the bar should display. The count is clamped to a minimum of 1. - The height formula is: height = count * layout.x_height + (count + 1) * layout.padding / 2 which accounts for per-line x-height and vertical padding between/around lines.
- The returned boolean is
truewhen the bar fits betweenself.rect.min.yandy_maxafter reserving space for a separator (layout.thickness). If placing the bar would push it aboveself.rect.min.ythe function returns(height, false)to signal that the bar cannot be created at the requested position.
Parameters:
layout: Precomputed layout metrics (x_height, padding, thickness).key/data: Provider-specific level identifier and data used to estimate lines.y_max: The candidate bottom y coordinate (inclusive) where the bar would end.
Returns:
(height, ok)whereheightis the computed pixel height for the bar andokindicates whether the bar can be placed without exceeding the top bound.
Sourcefn insert_bar_and_separator(
&mut self,
layout: &Layout,
key: &P::LevelKey,
height: i32,
index: &mut usize,
y_max: &mut i32,
)
fn insert_bar_and_separator( &mut self, layout: &Layout, key: &P::LevelKey, height: i32, index: &mut usize, y_max: &mut i32, )
Insert a bar and its separator into the children vector at the given insertion
index, updating the available bottom coordinate (y_max) accordingly.
The function ensures the visual ordering is correct (bar immediately above its separator) and handles two insertion scenarios:
- If the current element at
*indexis absent or already aFiller, the bar is inserted first followed by the separator so the resulting sequence is: [bar, separator, …]. - Otherwise the separator is inserted first and the bar after it so that the
separator sits directly at
y_maxand the bar sits immediately above it.
After inserting each element the function subtracts its height from y_max
so the caller can continue inserting further elements above the ones just
added. Note that index itself is not modified to account for the inserted
children; callers should update it if they need a different insertion anchor.
layout when a bar and separator are added:
+----------------------+ <- top (smaller y)
| BAR (newly inserted) |
+----------------------+ <- separator immediately below the bar
| SEPARATOR (filler) |
+----------------------+ <- bottom (larger y)Vector ordering note:
- Conceptually the visual stack is Bar above Separator (top -> bottom).
- Depending on insertion order and index arithmetic, the vector indices may be impacted by the insert() semantics (inserting at the same index shifts previously-inserted items to the right). The implementation below follows the established convention used by this container to maintain the alternating bar/filler pattern.
fn ensure_minimum_bar(&mut self, layout: &Layout, selected: &P::LevelKey)
fn remove_extra_leading_separator(&mut self)
fn position_and_populate_children( &mut self, selected: &P::LevelKey, leaf: &P::LevelKey, data_by_level: &BTreeMap<P::LevelKey, P::LevelData>, first: &Option<P::LevelKey>, last: &Option<P::LevelKey>, rq: &mut RenderQueue, fonts: &mut Fonts, )
Sourcepub fn shift(&mut self, delta: Point)
pub fn shift(&mut self, delta: Point)
Shifts the entire navigation bar and all its children by a delta.
This is typically used when repositioning the bar within the parent view.
Sourcepub fn shrink(&mut self, delta_y: i32, fonts: &mut Fonts) -> i32
pub fn shrink(&mut self, delta_y: i32, fonts: &mut Fonts) -> i32
Shrinks the navigation bar by distributing resize across all bars.
This method proportionally shrinks all bars based on their available space (height minus minimum height). Bars that cannot shrink further are left at minimum height. If needed, entire bar+separator pairs are removed from the top of the stack.
§Arguments
delta_y- Target shrink amount (negative number)fonts- Font registry for resize calculations
§Returns
Actual shrink amount achieved (maybe less than requested if minimum heights prevent further shrinking)
fn resize_child( &mut self, child_index: usize, delta_y: i32, fonts: &mut Fonts, ) -> i32
Trait Implementations§
fn handle_event( &mut self, evt: &Event, _hub: &Hub, bus: &mut Bus, _rq: &mut RenderQueue, context: &mut Context, ) -> bool
fn render( &self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts, )
fn rect(&self) -> &Rectangle
fn rect_mut(&mut self) -> &mut Rectangle
fn children(&self) -> &Vec<Box<dyn View>>
fn children_mut(&mut self) -> &mut Vec<Box<dyn View>>
fn id(&self) -> Id
fn render_rect(&self, _rect: &Rectangle) -> Rectangle
fn resize( &mut self, rect: Rectangle, _hub: &Hub, _rq: &mut RenderQueue, _context: &mut Context, )
fn child(&self, index: usize) -> &dyn View
fn child_mut(&mut self, index: usize) -> &mut dyn View
fn len(&self) -> usize
fn might_skip(&self, _evt: &Event) -> bool
fn might_rotate(&self) -> bool
fn is_background(&self) -> bool
fn view_id(&self) -> Option<ViewId>
Auto Trait Implementations§
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.