diff --git a/src/app.rs b/src/app.rs index ec08c8d..727cbab 100644 --- a/src/app.rs +++ b/src/app.rs @@ -35,6 +35,10 @@ pub struct App { pub list_state: ListState, pub current_title: Option, pub breadcrumbs: Vec<(String, String)>, // (id, name) + /// Selected index at each level we navigated away from, so `back` can restore + /// the cursor where the user left it (e.g. Series โ†’ Season โ†’ Episode โ†’ back + /// brings you to the same season/series row, not row 0). + pub selection_stack: Vec, pub status: String, } @@ -66,6 +70,7 @@ impl App { list_state, current_title: None, breadcrumbs: vec![], + selection_stack: vec![], status, }) } @@ -122,6 +127,9 @@ impl App { let Some(entry) = self.home_entries.get(i).cloned() else { return Ok(None); }; + // Remember which library/virtual entry was selected for restore on back. + self.selection_stack.clear(); + self.selection_stack.push(i); if entry.id == RESUME_VIRTUAL_ID { self.items = self.resume.clone(); self.view = View::Items; @@ -151,6 +159,8 @@ impl App { return Ok(None); }; if item.is_folder { + // Remember position in this folder before drilling down. + self.selection_stack.push(i); self.status = format!("Loading {}...", item.name); self.items = self.client.items(&item.id).await?; self.breadcrumbs.push((item.id.clone(), item.name.clone())); @@ -182,6 +192,7 @@ impl App { } // Pop the current breadcrumb (current folder) self.breadcrumbs.pop(); + let restore = self.selection_stack.pop(); if let Some((parent_id, parent_name)) = self.breadcrumbs.last().cloned() { if parent_id == RESUME_VIRTUAL_ID { self.items = self.resume.clone(); @@ -189,18 +200,14 @@ impl App { self.items = self.client.items(&parent_id).await?; } self.current_title = Some(parent_name.clone()); - self.list_state - .select(if self.items.is_empty() { None } else { Some(0) }); + self.list_state.select(restore_selection(restore, self.items.len())); self.status = format!("{}: {} items", parent_name, self.items.len()); } else { self.view = View::Libraries; self.current_title = None; self.items.clear(); - self.list_state.select(if self.home_entries.is_empty() { - None - } else { - Some(0) - }); + self.list_state + .select(restore_selection(restore, self.home_entries.len())); self.status = format!( "{} libraries ยท {} in progress", self.libraries.len(), @@ -245,6 +252,15 @@ impl App { } } +/// Clamp the remembered selection to the current list size. Returns None if +/// the list is empty (so no row is highlighted). +fn restore_selection(saved: Option, len: usize) -> Option { + if len == 0 { + return None; + } + Some(saved.unwrap_or(0).min(len - 1)) +} + fn build_home_entries(libraries: &[Item], resume: &[Item]) -> Vec { let mut entries: Vec = Vec::with_capacity(libraries.len() + 1); if !resume.is_empty() {