Significantly optimize drag select performance

This commit is contained in:
Jeremy Soller 2025-07-10 13:57:41 -06:00
parent b7c9ed8dff
commit 7524487ba7
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA
3 changed files with 89 additions and 88 deletions

View file

@ -30,7 +30,7 @@ use cosmic::{
Alignment, Event, Length, Point, Rectangle, Size, Subscription, Alignment, Event, Length, Point, Rectangle, Size, Subscription,
}, },
iced_runtime::clipboard, iced_runtime::clipboard,
style, theme, style, surface, theme,
widget::{ widget::{
self, self,
dnd_destination::DragId, dnd_destination::DragId,
@ -41,7 +41,6 @@ use cosmic::{
}, },
Application, ApplicationExt, Element, Application, ApplicationExt, Element,
}; };
use cosmic::{iced::mouse::Event::CursorMoved, surface};
use mime_guess::Mime; use mime_guess::Mime;
use notify_debouncer_full::{ use notify_debouncer_full::{
new_debouncer, new_debouncer,

View file

@ -8,7 +8,7 @@ use cosmic::{
self, event, self, event,
futures::{self, SinkExt}, futures::{self, SinkExt},
keyboard::{Event as KeyEvent, Key, Modifiers}, keyboard::{Event as KeyEvent, Key, Modifiers},
mouse, stream, window, Alignment, Event, Length, Point, Size, Subscription, stream, window, Alignment, Event, Length, Size, Subscription,
}, },
theme, theme,
widget::{ widget::{

View file

@ -2402,6 +2402,7 @@ pub struct Tab {
search_context: Option<SearchContext>, search_context: Option<SearchContext>,
date_time_formatter: DateTimeFormatter, date_time_formatter: DateTimeFormatter,
time_formatter: DateTimeFormatter, time_formatter: DateTimeFormatter,
watch_drag: bool,
} }
async fn calculate_dir_size(path: &Path, controller: Controller) -> Result<u64, String> { async fn calculate_dir_size(path: &Path, controller: Controller) -> Result<u64, String> {
@ -2512,6 +2513,7 @@ impl Tab {
search_context: None, search_context: None,
date_time_formatter: date_time_formatter(config.military_time), date_time_formatter: date_time_formatter(config.military_time),
time_formatter: time_formatter(config.military_time), time_formatter: time_formatter(config.military_time),
watch_drag: true,
} }
} }
@ -2881,6 +2883,7 @@ impl Tab {
} }
Message::DragEnd => { Message::DragEnd => {
self.clicked = None; self.clicked = None;
self.watch_drag = true;
} }
Message::DoubleClick(click_i_opt) => { Message::DoubleClick(click_i_opt) => {
if let Some(clicked_item) = self if let Some(clicked_item) = self
@ -3141,6 +3144,7 @@ impl Tab {
} }
} }
Message::Drag(rect_opt) => { Message::Drag(rect_opt) => {
self.watch_drag = false;
if let Some(rect) = rect_opt { if let Some(rect) = rect_opt {
self.context_menu = None; self.context_menu = None;
self.location_context_menu_index = None; self.location_context_menu_index = None;
@ -3557,11 +3561,13 @@ impl Tab {
} }
} }
Message::HighlightDeactivate(i) => { Message::HighlightDeactivate(i) => {
self.watch_drag = true;
if let Some(item) = self.items_opt.as_mut().and_then(|f| f.get_mut(i)) { if let Some(item) = self.items_opt.as_mut().and_then(|f| f.get_mut(i)) {
item.highlighted = false; item.highlighted = false;
} }
} }
Message::HighlightActivate(i) => { Message::HighlightActivate(i) => {
self.watch_drag = true;
if let Some(item) = self.items_opt.as_mut().and_then(|f| f.get_mut(i)) { if let Some(item) = self.items_opt.as_mut().and_then(|f| f.get_mut(i)) {
item.highlighted = true; item.highlighted = true;
} }
@ -3571,6 +3577,7 @@ impl Tab {
self.scroll_opt = Some(viewport.absolute_offset()); self.scroll_opt = Some(viewport.absolute_offset());
} }
Message::ScrollTab(scroll_speed) => { Message::ScrollTab(scroll_speed) => {
self.watch_drag = true;
commands.push(Command::Iced( commands.push(Command::Iced(
scrollable::scroll_by( scrollable::scroll_by(
self.scrollable_id.clone(), self.scrollable_id.clone(),
@ -4579,7 +4586,6 @@ impl Tab {
pub fn grid_view(&self) -> (Option<Element<'static, Message>>, Element<Message>, bool) { pub fn grid_view(&self) -> (Option<Element<'static, Message>>, Element<Message>, bool) {
let cosmic_theme::Spacing { let cosmic_theme::Spacing {
space_m,
space_xxs, space_xxs,
space_xxxs, space_xxxs,
.. ..
@ -4605,7 +4611,7 @@ impl Tab {
let (width, height) = match self.size_opt.get() { let (width, height) = match self.size_opt.get() {
Some(size) => ( Some(size) => (
(size.width.floor() as usize) (size.width.floor() as usize)
.saturating_sub(2 * (space_m as usize)) .saturating_sub(2 * (space_xxs as usize))
.max(item_width), .max(item_width),
(size.height.floor() as usize).max(item_height), (size.height.floor() as usize).max(item_height),
), ),
@ -4650,7 +4656,6 @@ impl Tab {
let mut drag_s_i = 0; let mut drag_s_i = 0;
let mut children = Vec::new(); let mut children = Vec::new();
if let Some(items) = self.column_sort() { if let Some(items) = self.column_sort() {
let mut count = 0; let mut count = 0;
let mut col = 0; let mut col = 0;
@ -4668,8 +4673,8 @@ impl Tab {
item.pos_opt.set(Some((row, col))); item.pos_opt.set(Some((row, col)));
let item_rect = Rectangle::new( let item_rect = Rectangle::new(
Point::new( Point::new(
(col * (item_width + column_spacing as usize) + space_m as usize) as f32, (col * (item_width + column_spacing as usize) + space_xxs as usize) as f32,
(row * (item_height + grid_spacing as usize)) as f32, (row * (item_height + grid_spacing as usize) + space_xxs as usize) as f32,
), ),
Size::new(item_width as f32, item_height as f32), Size::new(item_width as f32, item_height as f32),
); );
@ -4831,40 +4836,37 @@ impl Tab {
} }
} }
( let drag_list = (!dnd_items.is_empty()).then(|| {
(!dnd_items.is_empty()).then(|| { let mut dnd_grid = widget::grid()
let mut dnd_grid = widget::grid() .column_spacing(column_spacing)
.column_spacing(column_spacing) .row_spacing(grid_spacing)
.row_spacing(grid_spacing) .padding(space_xxs.into());
.padding(space_xxs.into());
let mut dnd_item_i = 0; let mut dnd_item_i = 0;
for r in drag_n_i..=drag_s_i { for r in drag_n_i..=drag_s_i {
dnd_grid = dnd_grid.insert_row(); dnd_grid = dnd_grid.insert_row();
for c in drag_w_i..=drag_e_i { for c in drag_w_i..=drag_e_i {
let Some((i, (row, col), item)) = dnd_items.get(dnd_item_i) else { let Some((i, (row, col), item)) = dnd_items.get(dnd_item_i) else {
break; break;
}; };
if *row == r && *col == c { if *row == r && *col == c {
let buttons = vec![ let buttons = vec![
widget::button::custom( widget::button::custom(
widget::icon::icon(item.icon_handle_grid.clone()) widget::icon::icon(item.icon_handle_grid.clone())
.content_fit(ContentFit::Contain) .content_fit(ContentFit::Contain)
.size(icon_sizes.grid()), .size(icon_sizes.grid()),
) )
.on_press(Message::Click(Some(*i))) .on_press(Message::Click(Some(*i)))
.padding(space_xxxs) .padding(space_xxxs)
.class(button_style( .class(button_style(
item.selected, item.selected,
item.highlighted, item.highlighted,
item.cut, item.cut,
false, false,
false, false,
false, false,
)), )),
widget::button::custom(widget::text::body( widget::button::custom(widget::text::body(item.display_name.clone()))
item.display_name.clone(),
))
.id(item.button_id.clone()) .id(item.button_id.clone())
.on_press(Message::Click(Some(*i))) .on_press(Message::Click(Some(*i)))
.padding([0, space_xxxs]) .padding([0, space_xxxs])
@ -4876,46 +4878,46 @@ impl Tab {
true, true,
false, false,
)), )),
]; ];
let mut column = widget::column::with_capacity(buttons.len()) let mut column = widget::column::with_capacity(buttons.len())
.align_x(Alignment::Center) .align_x(Alignment::Center)
.height(Length::Fixed(item_height as f32)) .height(Length::Fixed(item_height as f32))
.width(Length::Fixed(item_width as f32)); .width(Length::Fixed(item_width as f32));
for button in buttons { for button in buttons {
column = column.push(button) column = column.push(button)
}
dnd_grid = dnd_grid.push(column);
dnd_item_i += 1;
} else {
dnd_grid = dnd_grid.push(
widget::container(Space::with_height(item_width as f32))
.height(Length::Fixed(item_height as f32)),
);
} }
dnd_grid = dnd_grid.push(column);
dnd_item_i += 1;
} else {
dnd_grid = dnd_grid.push(
widget::container(Space::with_height(item_width as f32))
.height(Length::Fixed(item_height as f32)),
);
} }
} }
Element::from(dnd_grid) }
}), Element::from(dnd_grid)
});
let mut mouse_area =
mouse_area::MouseArea::new(widget::column::with_children(children).width(Length::Fill)) mouse_area::MouseArea::new(widget::column::with_children(children).width(Length::Fill))
.on_press(|_| Message::Click(None)) .on_press(|_| Message::Click(None))
.on_auto_scroll(Message::AutoScroll) .on_auto_scroll(Message::AutoScroll)
.on_drag(Message::Drag)
.on_drag_end(|_| Message::DragEnd) .on_drag_end(|_| Message::DragEnd)
.show_drag_rect(self.mode.multiple()) .show_drag_rect(self.mode.multiple())
.on_release(|_| Message::ClickRelease(None)) .on_release(|_| Message::ClickRelease(None));
.into(), if self.watch_drag {
true, mouse_area = mouse_area.on_drag(Message::Drag);
) }
(drag_list, mouse_area.into(), true)
} }
pub fn list_view(&self) -> (Option<Element<'static, Message>>, Element<Message>, bool) { pub fn list_view(&self) -> (Option<Element<'static, Message>>, Element<Message>, bool) {
let cosmic_theme::Spacing { let cosmic_theme::Spacing {
space_m, space_s, space_xxs, ..
space_s,
space_xxs,
..
} = theme::active().cosmic().spacing; } = theme::active().cosmic().spacing;
let TabConfig { let TabConfig {
@ -4964,12 +4966,6 @@ impl Tab {
hidden += 1; hidden += 1;
continue; continue;
} }
item.pos_opt.set(Some((count, 0)));
let item_rect = Rectangle::new(
Point::new(space_m as f32, y),
Size::new(size.width - (2 * space_m) as f32, row_height as f32),
);
item.rect_opt.set(Some(item_rect));
if count > 0 { if count > 0 {
children.push( children.push(
@ -4980,6 +4976,13 @@ impl Tab {
y += 1.0; y += 1.0;
} }
item.pos_opt.set(Some((count, 0)));
let item_rect = Rectangle::new(
Point::new(space_s as f32, y),
Size::new(size.width - (2 * space_s) as f32, row_height as f32),
);
item.rect_opt.set(Some(item_rect));
// Only build elements if visible (for performance) // Only build elements if visible (for performance)
let button_row = if item_rect.intersects(&visible_rect) { let button_row = if item_rect.intersects(&visible_rect) {
let modified_text = match &item.metadata { let modified_text = match &item.metadata {
@ -5279,21 +5282,20 @@ impl Tab {
let drag_col = (!drag_items.is_empty()) let drag_col = (!drag_items.is_empty())
.then(|| Element::from(widget::column::with_children(drag_items))); .then(|| Element::from(widget::column::with_children(drag_items)));
( let mut mouse_area = mouse_area::MouseArea::new(
drag_col, widget::column::with_children(children).padding([0, space_s]),
mouse_area::MouseArea::new(
widget::column::with_children(children).padding([0, space_s]),
)
.with_id(Id::new("list-view"))
.on_press(|_| Message::Click(None))
.on_auto_scroll(Message::AutoScroll)
.on_drag(Message::Drag)
.on_drag_end(|_| Message::DragEnd)
.show_drag_rect(self.mode.multiple())
.on_release(|_| Message::ClickRelease(None))
.into(),
true,
) )
.with_id(Id::new("list-view"))
.on_press(|_| Message::Click(None))
.on_auto_scroll(Message::AutoScroll)
.on_drag_end(|_| Message::DragEnd)
.show_drag_rect(self.mode.multiple())
.on_release(|_| Message::ClickRelease(None));
if self.watch_drag {
mouse_area = mouse_area.on_drag(Message::Drag);
}
(drag_col, mouse_area.into(), true)
} }
pub fn view_responsive( pub fn view_responsive(