Implement shift+click selection

This commit is contained in:
Jason Rodney Hansen 2024-05-27 10:23:48 -06:00 • committed by Jeremy Soller
parent 80aa2c31e9
commit d8e258dfb1

View file

@ -710,7 +710,7 @@ pub struct Tab {
pub dnd_hovered: Option<(Location, Instant)>, pub dnd_hovered: Option<(Location, Instant)>,
scrollable_id: widget::Id, scrollable_id: widget::Id,
select_focus: Option<usize>, select_focus: Option<usize>,
select_shift: Option<usize>, select_range: Option<(usize, usize)>,
cached_selected: RefCell<Option<bool>>, cached_selected: RefCell<Option<bool>>,
clicked: Option<usize>, clicked: Option<usize>,
selected_clicked: bool, selected_clicked: bool,
@ -734,7 +734,7 @@ impl Tab {
items_opt: None, items_opt: None,
scrollable_id: widget::Id::unique(), scrollable_id: widget::Id::unique(),
select_focus: None, select_focus: None,
select_shift: None, select_range: None,
cached_selected: RefCell::new(None), cached_selected: RefCell::new(None),
clicked: None, clicked: None,
dnd_hovered: None, dnd_hovered: None,
@ -808,21 +808,19 @@ impl Tab {
let mut start = (row, col); let mut start = (row, col);
let mut end = (row, col); let mut end = (row, col);
if mod_shift { if mod_shift {
if self.select_focus.is_none() || self.select_shift.is_none() { if self.select_focus.is_none() || self.select_range.is_none() {
// Set select shift to initial state if necessary // Set select range to initial state if necessary
self.select_shift = self.select_focus; self.select_range = self.select_focus.map(|i| (i, i));
} }
if let Some(pos) = self.select_shift_pos_opt() {
if let Some(pos) = self.select_range_start_pos_opt() {
if pos.0 < row || (pos.0 == row && pos.1 < col) { if pos.0 < row || (pos.0 == row && pos.1 < col) {
start = pos; start = pos;
} else { } else {
end = pos; end = pos;
} }
} }
} else { }
// Clear select shift if the modifier is not set
self.select_shift = None;
};
let mut found = false; let mut found = false;
if let Some(ref mut items) = self.items_opt { if let Some(ref mut items) = self.items_opt {
@ -843,6 +841,11 @@ impl Tab {
if pos == (row, col) { if pos == (row, col) {
// Update focus if this is what we wanted to select // Update focus if this is what we wanted to select
self.select_focus = Some(i); self.select_focus = Some(i);
self.select_range = if mod_shift {
self.select_range.map(|r| (r.0, i))
} else {
Some((i, i))
};
found = true; found = true;
} }
item.selected = true; item.selected = true;
@ -919,9 +922,9 @@ impl Tab {
} }
} }
fn select_shift_pos_opt(&self) -> Option<(usize, usize)> { fn select_range_start_pos_opt(&self) -> Option<(usize, usize)> {
let items = self.items_opt.as_ref()?; let items = self.items_opt.as_ref()?;
let item = items.get(self.select_shift?)?; let item = items.get(self.select_range.map(|r| r.0)?)?;
item.pos_opt.get() item.pos_opt.get()
} }
@ -1004,6 +1007,7 @@ impl Tab {
if mod_ctrl { if mod_ctrl {
if Some(i) == click_i_opt && item.selected { if Some(i) == click_i_opt && item.selected {
item.selected = false; item.selected = false;
self.select_range = None;
} }
} else if Some(i) != click_i_opt { } else if Some(i) != click_i_opt {
item.selected = false; item.selected = false;
@ -1044,43 +1048,64 @@ impl Tab {
if click_i_opt.is_none() { if click_i_opt.is_none() {
self.clicked = click_i_opt; self.clicked = click_i_opt;
} }
let dont_unset = mod_ctrl
|| mod_shift if mod_shift {
|| self.column_sort().is_some_and(|l| { if let Some(click_i) = click_i_opt {
l.iter() self.select_range = self
.any(|(e_i, e)| Some(e_i) == click_i_opt.as_ref() && e.selected) .select_range
}); .map_or(Some((click_i, click_i)), |r| Some((r.0, click_i)));
*self.cached_selected.borrow_mut() = None; if let Some(range) = self.select_range {
if let Some(ref mut items) = self.items_opt { let min = range.0.min(range.1);
for (i, item) in items.iter_mut().enumerate() { let max = range.0.max(range.1);
if Some(i) == click_i_opt { if let Some(ref mut items) = self.items_opt {
// Filter out selection if it does not match dialog kind for (i, item) in items.iter_mut().enumerate() {
if let Some(dialog) = &self.dialog { item.selected = i >= min && i <= max;
let item_is_dir = item.metadata.is_dir();
if item_is_dir != dialog.is_dir() {
// Allow selecting folder if dialog is for files to make it
// possible to double click
//TODO: clear any other selection when selecting a folder
if !item_is_dir {
continue;
}
} }
} }
if !item.selected { }
self.clicked = click_i_opt; self.clicked = click_i_opt;
item.selected = true; self.select_focus = click_i_opt;
} self.selected_clicked = true;
}
} else {
let dont_unset = mod_ctrl
|| self.column_sort().is_some_and(|l| {
l.iter()
.any(|(e_i, e)| Some(e_i) == click_i_opt.as_ref() && e.selected)
});
*self.cached_selected.borrow_mut() = None;
if let Some(ref mut items) = self.items_opt {
for (i, item) in items.iter_mut().enumerate() {
if Some(i) == click_i_opt {
// Filter out selection if it does not match dialog kind
if let Some(dialog) = &self.dialog {
let item_is_dir = item.metadata.is_dir();
if item_is_dir != dialog.is_dir() {
// Allow selecting folder if dialog is for files to make it
// possible to double click
//TODO: clear any other selection when selecting a folder
if !item_is_dir {
continue;
}
}
}
if !item.selected {
self.clicked = click_i_opt;
item.selected = true;
self.select_range = Some((i, i));
}
self.selected_clicked = true; self.selected_clicked = true;
} else if !dont_unset && item.selected { } else if !dont_unset && item.selected {
self.clicked = click_i_opt; self.clicked = click_i_opt;
item.selected = false; item.selected = false;
}
} }
} }
} if self.select_focus.take().is_some() {
if self.select_focus.take().is_some() { // Unfocus currently focused button
// Unfocus currently focused button commands.push(Command::FocusButton(widget::Id::unique()));
commands.push(Command::FocusButton(widget::Id::unique())); }
} }
} }
Message::Config(config) => { Message::Config(config) => {