Restore tab scroll position, fixes #1115

This commit is contained in:
Jeremy Soller 2025-08-15 11:01:08 -06:00
parent 5e0645d1c2
commit fb74be9a70
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA
4 changed files with 43 additions and 17 deletions

View file

@ -26,6 +26,7 @@ use cosmic::{
futures::{self, SinkExt}, futures::{self, SinkExt},
keyboard::{Event as KeyEvent, Key, Modifiers}, keyboard::{Event as KeyEvent, Key, Modifiers},
stream, stream,
widget::scrollable,
window::{self, Event as WindowEvent, Id as WindowId}, window::{self, Event as WindowEvent, Id as WindowId},
Alignment, Event, Length, Point, Rectangle, Size, Subscription, Alignment, Event, Length, Point, Rectangle, Size, Subscription,
}, },
@ -664,6 +665,7 @@ pub struct App {
progress_operations: BTreeSet<u64>, progress_operations: BTreeSet<u64>,
complete_operations: BTreeMap<u64, Operation>, complete_operations: BTreeMap<u64, Operation>,
failed_operations: BTreeMap<u64, (Operation, Controller, String)>, failed_operations: BTreeMap<u64, (Operation, Controller, String)>,
scrollable_id: widget::Id,
search_id: widget::Id, search_id: widget::Id,
size: Option<Size>, size: Option<Size>,
#[cfg(all(feature = "wayland", feature = "desktop-applet"))] #[cfg(all(feature = "wayland", feature = "desktop-applet"))]
@ -959,6 +961,7 @@ impl App {
location: Location, location: Location,
activate: bool, activate: bool,
selection_paths: Option<Vec<PathBuf>>, selection_paths: Option<Vec<PathBuf>>,
scrollable_id: widget::Id,
window_id: Option<window::Id>, window_id: Option<window::Id>,
) -> (Entity, Task<Message>) { ) -> (Entity, Task<Message>) {
#[cfg(feature = "gvfs")] #[cfg(feature = "gvfs")]
@ -1002,6 +1005,7 @@ impl App {
self.config.tab, self.config.tab,
self.config.thumb_cfg, self.config.thumb_cfg,
Some(&self.state.sort_names), Some(&self.state.sort_names),
scrollable_id,
window_id, window_id,
); );
tab.mode = match self.mode { tab.mode = match self.mode {
@ -1041,8 +1045,14 @@ impl App {
activate: bool, activate: bool,
selection_paths: Option<Vec<PathBuf>>, selection_paths: Option<Vec<PathBuf>>,
) -> Task<Message> { ) -> Task<Message> {
self.open_tab_entity(location, activate, selection_paths, None) self.open_tab_entity(
.1 location,
activate,
selection_paths,
self.scrollable_id.clone(),
None,
)
.1
} }
// This wrapper ensures that local folders use trash and remote folders permanently delete with a dialog // This wrapper ensures that local folders use trash and remote folders permanently delete with a dialog
@ -2144,6 +2154,7 @@ impl Application for App {
progress_operations: BTreeSet::new(), progress_operations: BTreeSet::new(),
complete_operations: BTreeMap::new(), complete_operations: BTreeMap::new(),
failed_operations: BTreeMap::new(), failed_operations: BTreeMap::new(),
scrollable_id: widget::Id::unique(),
search_id: widget::Id::unique(), search_id: widget::Id::unique(),
size: None, size: None,
#[cfg(all(feature = "wayland", feature = "desktop-applet"))] #[cfg(all(feature = "wayland", feature = "desktop-applet"))]
@ -3742,6 +3753,12 @@ impl Application for App {
// Activate new tab // Activate new tab
self.tab_model.activate(entity); self.tab_model.activate(entity);
if let Some(tab) = self.tab_model.data::<Tab>(entity) { if let Some(tab) = self.tab_model.data::<Tab>(entity) {
{
//Restore scroll
//TODO: why do scrollers with different IDs get the same scroll position?
let scroll = tab.scroll_opt.unwrap_or_default();
tasks.push(scrollable::scroll_to(tab.scrollable_id.clone(), scroll));
}
self.activate_nav_model_location(&tab.location.clone()); self.activate_nav_model_location(&tab.location.clone());
} }
tasks.push(self.update_title()); tasks.push(self.update_title());
@ -3781,6 +3798,8 @@ impl Application for App {
} }
} }
Message::TabClose(entity_opt) => { Message::TabClose(entity_opt) => {
let mut tasks = Vec::with_capacity(3);
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active()); let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
// Activate closest item // Activate closest item
@ -3791,12 +3810,8 @@ impl Application for App {
position + 1 position + 1
}; };
if self.tab_model.activate_position(new_position) { if let Some(new_entity) = self.tab_model.entity_at(new_position) {
if let Some(new_entity) = self.tab_model.entity_at(new_position) { tasks.push(self.update(Message::TabActivate(new_entity)));
if let Some(tab) = self.tab_model.data::<Tab>(new_entity) {
self.activate_nav_model_location(&tab.location.clone());
}
}
} }
} }
@ -3806,11 +3821,13 @@ impl Application for App {
// If that was the last tab, close window // If that was the last tab, close window
if self.tab_model.iter().next().is_none() { if self.tab_model.iter().next().is_none() {
if let Some(window_id) = self.core.main_window_id() { if let Some(window_id) = self.core.main_window_id() {
return window::close(window_id); tasks.push(window::close(window_id));
} }
} }
return Task::batch([self.update_title(), self.update_watcher()]); tasks.push(self.update_watcher());
return Task::batch(tasks);
} }
Message::TabConfig(config) => { Message::TabConfig(config) => {
if config != self.config.tab { if config != self.config.tab {
@ -4567,6 +4584,7 @@ impl Application for App {
Location::Desktop(crate::desktop_dir(), display, self.config.desktop), Location::Desktop(crate::desktop_dir(), display, self.config.desktop),
false, false,
None, None,
widget::Id::unique(),
Some(surface_id), Some(surface_id),
); );
self.windows.insert(surface_id, WindowKind::Desktop(entity)); self.windows.insert(surface_id, WindowKind::Desktop(entity));
@ -6378,7 +6396,13 @@ pub(crate) mod test_utils {
// New tab with items // New tab with items
let location = Location::Path(path.to_owned()); let location = Location::Path(path.to_owned());
let (parent_item_opt, items) = location.scan(IconSizes::default()); let (parent_item_opt, items) = location.scan(IconSizes::default());
let mut tab = Tab::new(location, TabConfig::default(), ThumbCfg::default(), None); let mut tab = Tab::new(
location,
TabConfig::default(),
ThumbCfg::default(),
widget::Id::unique(),
None,
);
tab.parent_item_opt = parent_item_opt; tab.parent_item_opt = parent_item_opt;
tab.set_items(items); tab.set_items(items);

View file

@ -36,7 +36,7 @@ use std::{
use crate::{ use crate::{
app::{Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind}, app::{Action, ContextPage, Message as AppMessage, PreviewItem, PreviewKind},
config::{Config, DialogConfig, Favorite, TabConfig, ThumbCfg, TimeConfig, TIME_CONFIG_ID}, config::{Config, DialogConfig, Favorite, ThumbCfg, TimeConfig, TIME_CONFIG_ID},
fl, home_dir, fl, home_dir,
key_bind::key_binds, key_bind::key_binds,
localize::LANGUAGE_SORTER, localize::LANGUAGE_SORTER,
@ -950,6 +950,7 @@ impl Application for App {
flags.config.dialog_tab(), flags.config.dialog_tab(),
ThumbCfg::default(), ThumbCfg::default(),
None, None,
widget::Id::unique(),
None, None,
); );
tab.mode = tab::Mode::Dialog(flags.kind.clone()); tab.mode = tab::Mode::Dialog(flags.kind.clone());

View file

@ -524,7 +524,7 @@ fn update<Message: Clone>(
viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) -> event::Status {
let offset = layout.virtual_offset(); let offset = layout.virtual_offset();
let mut layout_bounds = layout.bounds(); let layout_bounds = layout.bounds();
if let Some(message) = widget.on_resize.as_ref() { if let Some(message) = widget.on_resize.as_ref() {
if state.viewport != Some(*viewport) { if state.viewport != Some(*viewport) {

View file

@ -2455,7 +2455,7 @@ pub struct Tab {
pub(crate) parent_item_opt: Option<Item>, pub(crate) parent_item_opt: Option<Item>,
pub(crate) items_opt: Option<Vec<Item>>, pub(crate) items_opt: Option<Vec<Item>>,
pub dnd_hovered: Option<(Location, Instant)>, pub dnd_hovered: Option<(Location, Instant)>,
scrollable_id: widget::Id, pub(crate) scrollable_id: widget::Id,
select_focus: Option<usize>, select_focus: Option<usize>,
select_range: Option<(usize, usize)>, select_range: Option<(usize, usize)>,
clicked: Option<usize>, clicked: Option<usize>,
@ -2535,6 +2535,7 @@ impl Tab {
config: TabConfig, config: TabConfig,
thumb_config: ThumbCfg, thumb_config: ThumbCfg,
sorting_options: Option<&OrderMap<String, (HeadingOptions, bool)>>, sorting_options: Option<&OrderMap<String, (HeadingOptions, bool)>>,
scrollable_id: widget::Id,
window_id: Option<window::Id>, window_id: Option<window::Id>,
) -> Self { ) -> Self {
let location_str = location.to_string(); let location_str = location.to_string();
@ -2570,7 +2571,7 @@ impl Tab {
gallery: false, gallery: false,
parent_item_opt: None, parent_item_opt: None,
items_opt: None, items_opt: None,
scrollable_id: widget::Id::unique(), scrollable_id,
select_focus: None, select_focus: None,
select_range: None, select_range: None,
clicked: None, clicked: None,
@ -3589,7 +3590,7 @@ impl Tab {
Some(selected_paths), Some(selected_paths),
)); ));
} }
Message::RightClick(p, click_i_opt) => { Message::RightClick(_point_opt, click_i_opt) => {
if mod_ctrl || mod_shift { if mod_ctrl || mod_shift {
self.update(Message::Click(click_i_opt), modifiers); self.update(Message::Click(click_i_opt), modifiers);
} }
@ -5529,7 +5530,7 @@ impl Tab {
} }
} }
} }
Location::Network(uri, _display_name, path) if uri == "network:///" => { Location::Network(uri, _display_name, _path) if uri == "network:///" => {
tab_column = tab_column.push( tab_column = tab_column.push(
widget::layer_container(widget::row::with_children(vec![ widget::layer_container(widget::row::with_children(vec![
widget::horizontal_space().into(), widget::horizontal_space().into(),