StackNavigationBar

Struct StackNavigationBar 

Source
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.y

The 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:

  1. Calculates the desired size based on grid-snapped line counts
  2. Delegates actual resize to the provider via resize_bar_by()
  3. 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:

  1. Existing bars are reused when navigating to ancestors/descendants
  2. New bars are created only when needed
  3. Excess bars (beyond max_levels) are trimmed
  4. 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: Id

Unique view identifier

§rect: Rectangle

Container rectangle (dynamically adjusted to fit children)

§children: Vec<Box<dyn View>>

Child views: bars at even indices, separators at odd indices

§selected: P::LevelKey

Currently selected level key

§vertical_limit: i32

Maximum Y coordinate for the navigation bar’s bottom edge

§max_levels: usize

Maximum number of levels to display simultaneously

§provider: P

Domain-specific navigation logic provider

§enable_resize: bool

If this bar type should allow resizing via gesture

Implementations§

Source§

impl<P: NavigationProvider + 'static> StackNavigationBar<P>

Source

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 rectangle
  • vertical_limit - Maximum Y coordinate for the bar’s bottom edge
  • max_levels - Maximum number of hierarchy levels to display
  • provider - Domain-specific navigation provider
  • selected - Initial selected level (bar remains empty until set_selected() is called)
Source

pub fn disable_resize(self) -> Self

Source

pub fn clear(&mut self)

Removes all child bars and separators.

Source

pub fn selected(&self) -> &P::LevelKey

Returns the currently selected level key.

Source

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
  1. Trim trailing bars that are no longer ancestors of the selected level
  2. Prefetch data for all levels from selected up to root (or max_levels)
  3. Build bar hierarchy bottom-up from leaf to root
  4. Reuse existing bars when they’re still valid
  5. Position all bars starting from container’s min.y
  6. Update container rect to match total children height
§Arguments
  • selected - The new selected level key
  • rq - Render queue for scheduling redraws
  • context - Application context with fonts and other resources
Source

fn first_bar_key(&self) -> Option<P::LevelKey>

Source

fn last_bar_key(&self) -> Option<P::LevelKey>

Source

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).

Source

fn prefetch_needed_levels( &self, selected: &P::LevelKey, context: &mut Context, ) -> BTreeMap<P::LevelKey, P::LevelData>

Source

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. current is an ancestor of last)
  • provider.is_ancestor(first, current) (i.e. first is an ancestor of current)

If either first or last is None, reuse is not possible and the function returns false.

Source

fn reuse_existing_bar_and_separator( &mut self, index: usize, y_max: i32, thickness: i32, ) -> (usize, i32)

Source

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 current is not the selected level, we always insert a bar for it. This ensures ancestor/ancestor-sibling levels remain visible when traversing.
  • If current is the selected level, we only insert a bar when there is content to show. That is determined by consulting data_by_level and calling the provider’s estimate_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).

Source

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_count is 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 true when the bar fits between self.rect.min.y and y_max after reserving space for a separator (layout.thickness). If placing the bar would push it above self.rect.min.y the 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) where height is the computed pixel height for the bar and ok indicates whether the bar can be placed without exceeding the top bound.
Source

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 *index is absent or already a Filler, 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_max and 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.
Source

fn ensure_minimum_bar(&mut self, layout: &Layout, selected: &P::LevelKey)

Source

fn remove_extra_leading_separator(&mut self)

Source

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, )

Source

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.

Source

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)

Source

fn resize_child( &mut self, child_index: usize, delta_y: i32, fonts: &mut Fonts, ) -> i32

Trait Implementations§

Source§

impl<P: Debug + NavigationProvider + 'static> Debug for StackNavigationBar<P>
where P::LevelKey: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<P: NavigationProvider + 'static> View for StackNavigationBar<P>

Source§

fn handle_event( &mut self, evt: &Event, _hub: &Hub, bus: &mut Bus, _rq: &mut RenderQueue, context: &mut Context, ) -> bool

Source§

fn render( &self, _fb: &mut dyn Framebuffer, _rect: Rectangle, _fonts: &mut Fonts, )

Source§

fn rect(&self) -> &Rectangle

Source§

fn rect_mut(&mut self) -> &mut Rectangle

Source§

fn children(&self) -> &Vec<Box<dyn View>>

Source§

fn children_mut(&mut self) -> &mut Vec<Box<dyn View>>

Source§

fn id(&self) -> Id

Source§

fn render_rect(&self, _rect: &Rectangle) -> Rectangle

Source§

fn resize( &mut self, rect: Rectangle, _hub: &Hub, _rq: &mut RenderQueue, _context: &mut Context, )

Source§

fn child(&self, index: usize) -> &dyn View

Source§

fn child_mut(&mut self, index: usize) -> &mut dyn View

Source§

fn len(&self) -> usize

Source§

fn might_skip(&self, _evt: &Event) -> bool

Source§

fn might_rotate(&self) -> bool

Source§

fn is_background(&self) -> bool

Source§

fn view_id(&self) -> Option<ViewId>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts 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>

Converts 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)

Converts &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)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more