Emulate double click to play items
Change the behavior of `ListView` to treat a click on an item that is already selected as a double click. It allows the user to play songs (not episodes since currently it's not possible to determine if a `ListItem` is an episode) by "double clicking" them. It is a bit of a hack, but it works pretty well. A possible downside is that when people that don't like mouse integration in a TUI click to focus the terminal window, it could jump to the song they clicked if that happened to be the selected one.
This commit is contained in:
@@ -125,6 +125,11 @@ impl<I: ListItem + Clone> ListView<I> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return whether this [ListView] has visible scrollbars.
|
||||
pub fn has_visible_scrollbars(&self) -> bool {
|
||||
self.scroller.get_show_scrollbars()
|
||||
}
|
||||
|
||||
pub fn get_selected_index(&self) -> usize {
|
||||
self.selected
|
||||
}
|
||||
@@ -154,6 +159,9 @@ impl<I: ListItem + Clone> ListView<I> {
|
||||
self.move_focus_to(max(new, 0) as usize);
|
||||
}
|
||||
|
||||
/// Append the currently selected item and all the following ones to the queue after the
|
||||
/// currently playing track and start playing them. Returns true if adding and playing the
|
||||
/// tracks succeeded, false otherwhise.
|
||||
fn attempt_play_all_tracks(&self) -> bool {
|
||||
let content = self.content.read().unwrap();
|
||||
let any = &(*content) as &dyn std::any::Any;
|
||||
@@ -172,6 +180,14 @@ impl<I: ListItem + Clone> ListView<I> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends the currently focused item after the currently playing item and starts playing it.
|
||||
fn play_current_item(&mut self) {
|
||||
let mut content = self.content.write().unwrap();
|
||||
if let Some(listitem) = content.get_mut(self.selected) {
|
||||
listitem.play(&self.queue);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&self, index: usize) {
|
||||
let mut c = self.content.write().unwrap();
|
||||
c.remove(index);
|
||||
@@ -328,31 +344,60 @@ impl<I: ListItem + Clone> View for ListView<I> {
|
||||
position,
|
||||
offset,
|
||||
} => {
|
||||
if self.scroller.get_show_scrollbars()
|
||||
&& position
|
||||
.checked_sub(offset)
|
||||
.map(|p| self.scroller.start_drag(p))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
// This is safe as a mouse event is only propagated to a view when it is inside the
|
||||
// view. Therefore underflow shouldn't occur.
|
||||
let view_coordinates_click_position = position - offset;
|
||||
|
||||
let drag_started = if self.has_visible_scrollbars() {
|
||||
self.scroller.start_drag(view_coordinates_click_position)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if drag_started {
|
||||
log::debug!("grabbing scroller");
|
||||
} else {
|
||||
let viewport = self.scroller.content_viewport().top_left();
|
||||
let selected_row = position.checked_sub(offset).map(|p| p.y + viewport.y);
|
||||
if let Some(y) = selected_row.filter(|row| row < &self.content_len(false)) {
|
||||
self.move_focus_to(y);
|
||||
if let Some(clicked_row_index) =
|
||||
selected_row.filter(|row| *row < self.content_len(false))
|
||||
{
|
||||
let currently_selected_listitem = self
|
||||
.content
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(clicked_row_index)
|
||||
.map(ListItem::as_listitem);
|
||||
let currently_selected_is_individual = currently_selected_listitem
|
||||
.filter(|item| item.track().is_some())
|
||||
.is_some();
|
||||
if self.selected == clicked_row_index && currently_selected_is_individual {
|
||||
// The selected position was already focused. Play the item at the
|
||||
// position as if Enter was pressed. This sort of emulates double
|
||||
// clicking, which isn't supported by Cursive.
|
||||
self.queue.clear();
|
||||
|
||||
let queue = self.queue.clone();
|
||||
let library = self.library.clone();
|
||||
if let Some(target) = {
|
||||
if !self.attempt_play_all_tracks() {
|
||||
self.play_current_item();
|
||||
}
|
||||
} else {
|
||||
// The clicked position wasn't focused yet or the item is a collection
|
||||
// that can be opened.
|
||||
self.move_focus_to(clicked_row_index);
|
||||
let content = self.content.read().unwrap();
|
||||
content.get(self.selected).map(|t| t.as_listitem())
|
||||
} {
|
||||
if let Some(view) = target.open(queue, library) {
|
||||
return EventResult::Consumed(Some(Callback::from_fn_once(
|
||||
move |s| {
|
||||
s.on_layout(|_, mut l| l.push_view(view));
|
||||
},
|
||||
)));
|
||||
let clicked_list_item =
|
||||
content.get(self.selected).map(ListItem::as_listitem);
|
||||
|
||||
if let Some(target) = clicked_list_item {
|
||||
if let Some(view) =
|
||||
target.open(self.queue.clone(), self.library.clone())
|
||||
{
|
||||
return EventResult::Consumed(Some(Callback::from_fn_once(
|
||||
move |s| {
|
||||
s.on_layout(|_, mut l| l.push_view(view));
|
||||
},
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,7 +431,7 @@ impl<I: ListItem + Clone> View for ListView<I> {
|
||||
position,
|
||||
offset,
|
||||
} => {
|
||||
if self.scroller.get_show_scrollbars() {
|
||||
if self.has_visible_scrollbars() {
|
||||
self.scroller.drag(position.saturating_sub(offset));
|
||||
}
|
||||
}
|
||||
@@ -425,10 +470,7 @@ impl<I: ListItem + Clone> ViewExt for ListView<I> {
|
||||
self.queue.clear();
|
||||
|
||||
if !self.attempt_play_all_tracks() {
|
||||
let mut content = self.content.write().unwrap();
|
||||
if let Some(item) = content.get_mut(self.selected) {
|
||||
item.play(&self.queue);
|
||||
}
|
||||
self.play_current_item();
|
||||
}
|
||||
|
||||
return Ok(CommandResult::Consumed(None));
|
||||
|
||||
Reference in New Issue
Block a user