add iced_nav_scrollable widget crate
[?]
Jun 27, 2025, 6:19 PM
WW36JYLR4AILV7RHQEDJWMX74P74B7G7DRBHH3O2V5TCHRTZJWZQCDependencies
- [2]
6YZAVBWUInitial commit - [3]
KLR5FRIBadd fs state read/write of repos - [4]
CALXOZXAflatten crates dir - [5]
3SYSJKYLadd app icon - [6]
I76WLGCNupdate iced - [7]
IQDCHWCPload a pijul repo - [8]
23SFYK4Qbig view refactor into a new crate - [9]
I3HPDVKLRevert "start inflorescence_diff"
Change contents
- file addition: iced_nav_scrollable[2.2]
- file addition: src[0.45]
- file addition: lib.rs[0.78]
//! Vertically scrollable widget that allows scrolling to the positions of//! its contents.use iced::widget::{self, container, scrollable};use iced::{advanced, window, Element, Rectangle, Subscription, Task};use indexmap::IndexMap;#[derive(Debug)]pub struct NavScrollable {pub offsets_ready: bool,pub offsets: Vec<f32>,pub y_offset: f32,pub id: scrollable::Id,pub sections: IndexMap<container::Id, f32>,}#[derive(Debug, Clone)]pub enum Msg {RefreshOffsets,GotHeight { id: container::Id, height: f32 },Scrolled(scrollable::Viewport),ScrollToPrev,ScrollToNext,}pub fn init(contents_count: usize) -> (NavScrollable, Task<Msg>) {let (sections, tasks): (IndexMap<_, _>, Vec<_>) = (0..contents_count).map(|_i| {let id = container::Id::unique();let section = (id.clone(), 0.0_f32);let id_clone = id.clone();let task =height_task(id.clone()).map(move |height| Msg::GotHeight {id: id_clone.clone(),height,});(section, task)}).collect();let id = scrollable::Id::unique();(NavScrollable {offsets_ready: false,// To be computed from sizes once tasks completeoffsets: vec![0.0; sections.len()],y_offset: 0.0,id,sections,},Task::batch(tasks),)}pub fn subs() -> Subscription<Msg> {window::resize_events().map(|_| Msg::RefreshOffsets)}pub fn update(nav: &mut NavScrollable, msg: Msg) -> Task<Msg> {match msg {Msg::RefreshOffsets => {reset_offsets(nav);Task::batch(nav.sections.keys().map(|id| {let id_clone = id.clone();height_task(id.clone()).map(move |height| Msg::GotHeight {id: id_clone.clone(),height,})}))}Msg::GotHeight { id, height } => {reset_offsets(nav);nav.sections.insert(id, height);Task::none()}Msg::Scrolled(viewport) => {nav.y_offset = viewport.absolute_offset().y;Task::none()}Msg::ScrollToNext => {update_offsets(nav);if nav.offsets_ready {if let Some(y) =nav.offsets.iter().find(|offset| *offset > &nav.y_offset){return scrollable::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset { x: 0.0, y: *y },);}}Task::none()}Msg::ScrollToPrev => {update_offsets(nav);if nav.offsets_ready {if let Some(y) = nav.offsets.iter().rev().find(|offset| *offset < &nav.y_offset){return scrollable::scroll_to(nav.id.clone(),scrollable::AbsoluteOffset { x: 0.0, y: *y },);}}Task::none()}}}/// NOTE: call `into_iter()` on the children parameter to make it type-check/// with `ExactSizeIterator`.pub fn view<'a, Message, Theme, Renderer, F>(nav: &NavScrollable,children: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>+ ExactSizeIterator,map_msg: F,) -> Element<'a, Message, Theme, Renderer>whereMessage: 'a,Theme: container::Catalog + scrollable::Catalog + 'a,Renderer: iced::advanced::Renderer + 'a,F: Fn(Msg) -> Message + 'a,{debug_assert_eq!(nav.sections.len(), children.len(), "The `NavScrollable` was most likely initialized with a count different from the number of actual children given to the the view function. Count is {}, but got {} children", nav.sections.len(), children.len());let children = children.into_iter().zip(nav.sections.keys()).map(|(child, id)| Element::from(container(child).id(id.clone())));Element::from(widget::scrollable(widget::column(children)).id(nav.id.clone()).on_scroll(move |viewport| map_msg(Msg::Scrolled(viewport))),)}fn reset_offsets(nav: &mut NavScrollable) {nav.offsets_ready = false;}fn update_offsets(nav: &mut NavScrollable) {if !nav.offsets_ready {debug_assert_eq!(nav.sections.len(), nav.offsets.len());let mut acc = 0.0_f32;nav.sections.values().enumerate().for_each(|(i, height)| {// The current offset is the value of the accnav.offsets[i] = acc;// Increment the acc with the height of this sectionacc += *height;});nav.offsets_ready = true;}}/// Produces a [`Task`] that queries the height of the [`Container`] with the/// given [`Id`].fn height_task(id: impl Into<container::Id>) -> Task<f32> {let id = id.into();struct SizeOp {target: advanced::widget::Id,height: Option<f32>,}impl advanced::widget::Operation<f32> for SizeOp {fn container(&mut self,id: Option<&advanced::widget::Id>,bounds: Rectangle,operate_on_children: &mut dyn FnMut(&mut dyn advanced::widget::Operation<f32>,),) {if id == Some(&self.target) {self.height = Some(bounds.height);return;}operate_on_children(self);}fn finish(&self) -> advanced::widget::operation::Outcome<f32> {advanced::widget::operation::Outcome::Some(self.height.expect("Must be able to determine the size. If this even panics, make the height optional and re-try the task when None"),)}}advanced::widget::operate(SizeOp {target: id.into(),height: None,})} - file addition: Cargo.toml[0.45]
[package]name = "iced-nav-scrollable"description = "Iced vertically scrollable widget that allows navigation to positions of its contents"version.workspace = trueedition.workspace = truelicense.workspace = trueauthors.workspace = true[dependencies]# External dependencies[dependencies.iced]workspace = true[dependencies.indexmap]workspace = true - edit in Cargo.toml at line 6
"iced_nav_scrollable", - edit in Cargo.toml at line 40
# path = "../iced" - edit in Cargo.toml at line 42
[workspace.dependencies.iced_runtime]git = "https://github.com/iced-rs/iced"rev = "c952ea8485b00e58bdff153989f708553272e131" - edit in Cargo.toml at line 45
# path = "../iced/test" - edit in Cargo.lock at line 2095
][[package]]name = "iced-nav-scrollable"version = "0.1.0"dependencies = ["iced","indexmap", - edit in Cargo.lock at line 4520
[[package]]name = "scrollable"version = "0.1.0"dependencies = ["iced","iced_runtime",]