Remember last selection when navigating back

Pushed the selected index onto a stack on every drill-down (library →
series → season → episode) and pop it on back so the cursor lands on
the row the user just opened, instead of snapping to row 0. Clamps the
restored index to the current list length to survive a shrunk list.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
janisslsmandClaude Opus 4.7 committed 2026-05-16 22:01:02 +03:00
1 parent af43b21f5d
commit 79f3a2e13b
1 file changed
+23 -7
+23 -7
View File
@@ -35,6 +35,10 @@ pub struct App {
pub list_state: ListState,
pub current_title: Option<String>,
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<usize>,
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<usize>, len: usize) -> Option<usize> {
if len == 0 {
return None;
}
Some(saved.unwrap_or(0).min(len - 1))
}
fn build_home_entries(libraries: &[Item], resume: &[Item]) -> Vec<Item> {
let mut entries: Vec<Item> = Vec::with_capacity(libraries.len() + 1);
if !resume.is_empty() {