From 294a4102b4e463d9485ab979c13f6e21fa147972 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sat, 14 Oct 2017 21:01:07 -0700 Subject: [PATCH] Fix mouse support for ListView --- src/view/scroll.rs | 5 ++ src/views/list_view.rs | 136 +++++++++++++++++++++++++++------------ src/views/select_view.rs | 87 +++++++++++++------------ 3 files changed, 145 insertions(+), 83 deletions(-) diff --git a/src/view/scroll.rs b/src/view/scroll.rs index 5755fcc..d5de695 100644 --- a/src/view/scroll.rs +++ b/src/view/scroll.rs @@ -207,6 +207,11 @@ impl ScrollBase { } } + /// Returns `true` if we are in the process of dragging the scroll thumb. + pub fn is_dragging(&self) -> bool { + self.thumb_grab.is_some() + } + /// Stops grabbing the scrollbar. pub fn release_grab(&mut self) { self.thumb_grab = None; diff --git a/src/views/list_view.rs b/src/views/list_view.rs index 42a5aba..1ed4877 100644 --- a/src/views/list_view.rs +++ b/src/views/list_view.rs @@ -2,7 +2,7 @@ use Cursive; use Printer; use With; use direction; -use event::{Callback, Event, EventResult, Key}; +use event::{Callback, Event, EventResult, Key, MouseEvent, MouseButton}; use std::any::Any; use std::rc::Rc; use unicode_width::UnicodeWidthStr; @@ -44,6 +44,7 @@ pub struct ListView { focus: usize, // This callback is called when the selection is changed. on_select: Option>, + last_size: Vec2, } new_default!(ListView); @@ -56,6 +57,7 @@ impl ListView { scrollbase: ScrollBase::new(), focus: 0, on_select: None, + last_size: Vec2::zero(), } } @@ -94,8 +96,10 @@ impl ListView { /// Adds a view to the end of the list. pub fn add_child(&mut self, label: &str, mut view: V) { view.take_focus(direction::Direction::none()); - self.children - .push(ListChild::Row(label.to_string(), Box::new(view))); + self.children.push(ListChild::Row( + label.to_string(), + Box::new(view), + )); } /// Removes all children from this view. @@ -148,9 +152,8 @@ impl ListView { self.focus } - fn iter_mut<'a>( - &'a mut self, from_focus: bool, source: direction::Relative - ) -> Box + 'a> { + fn iter_mut<'a>(&'a mut self, from_focus: bool, source: direction::Relative) + -> Box + 'a> { match source { direction::Relative::Front => { let start = if from_focus { self.focus } else { 0 }; @@ -168,9 +171,8 @@ impl ListView { } } - fn move_focus( - &mut self, n: usize, source: direction::Direction - ) -> EventResult { + fn move_focus(&mut self, n: usize, source: direction::Direction) + -> EventResult { let i = if let Some(i) = source .relative(direction::Orientation::Vertical) .and_then(|rel| { @@ -181,7 +183,8 @@ impl ListView { .filter_map(|p| try_focus(p, source)) .take(n) .last() - }) { + }) + { i } else { return EventResult::Ignored; @@ -238,16 +241,17 @@ impl ListView { } } -fn try_focus( - (i, child): (usize, &mut ListChild), source: direction::Direction -) -> Option { +fn try_focus((i, child): (usize, &mut ListChild), source: direction::Direction) + -> Option { match *child { ListChild::Delimiter => None, - ListChild::Row(_, ref mut view) => if view.take_focus(source) { - Some(i) - } else { - None - }, + ListChild::Row(_, ref mut view) => { + if view.take_focus(source) { + Some(i) + } else { + None + } + } } } @@ -260,14 +264,16 @@ impl View for ListView { let offset = self.labels_width() + 1; debug!("Offset: {}", offset); - self.scrollbase - .draw(printer, |printer, i| match self.children[i] { + self.scrollbase.draw( + printer, + |printer, i| match self.children[i] { ListChild::Row(ref label, ref view) => { printer.print((0, 0), label); view.draw(&printer.offset((offset, 0), i == self.focus)); } ListChild::Delimiter => (), - }); + }, + ); } fn required_size(&mut self, req: Vec2) -> Vec2 { @@ -294,6 +300,7 @@ impl View for ListView { } fn layout(&mut self, size: Vec2) { + self.last_size = size; self.scrollbase.set_heights(size.y, self.children.len()); // We'll show 2 columns: the labels, and the views. @@ -307,8 +314,9 @@ impl View for ListView { let spacing = 1; let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 }; - let available = size.x - .saturating_sub(label_width + spacing + scrollbar_width); + let available = size.x.saturating_sub( + label_width + spacing + scrollbar_width, + ); debug!("Available: {}", available); @@ -322,7 +330,43 @@ impl View for ListView { return EventResult::Ignored; } - // First: some events can move the focus around. + // First: some events can directly affect the ListView + match event { + Event::Mouse { + event: MouseEvent::Press(MouseButton::Left), + position, + offset, + } + if position + .checked_sub(offset) + .map(|position| { + self.scrollbase.start_drag(position, self.last_size.x) + }) + .unwrap_or(false) => { + return EventResult::Consumed(None); + } + Event::Mouse { + event: MouseEvent::Hold(MouseButton::Left), + position, + offset, + } if self.scrollbase.is_dragging() => { + position.checked_sub(offset).map(|position| { + self.scrollbase.drag(position) + }); + return EventResult::Consumed(None); + } + Event::Mouse { + event: MouseEvent::Release(MouseButton::Left), .. + } if self.scrollbase.is_dragging() => { + self.scrollbase.release_grab(); + return EventResult::Consumed(None); + } + _ => (), + + } + + + // Then: some events can move the focus around. self.check_focus_grab(&event); // Send the event to the focused child. @@ -350,13 +394,15 @@ impl View for ListView { Event::Key(Key::PageDown) => { self.move_focus(10, direction::Direction::up()) } - Event::Key(Key::Home) | Event::Ctrl(Key::Home) => { + Event::Key(Key::Home) | + Event::Ctrl(Key::Home) => { self.move_focus( usize::max_value(), direction::Direction::back(), ) } - Event::Key(Key::End) | Event::Ctrl(Key::End) => { + Event::Key(Key::End) | + Event::Ctrl(Key::End) => { self.move_focus( usize::max_value(), direction::Direction::front(), @@ -368,32 +414,40 @@ impl View for ListView { Event::Shift(Key::Tab) => { self.move_focus(1, direction::Direction::back()) } + Event::Mouse { event: MouseEvent::WheelDown, .. } + if self.scrollbase.can_scroll_down() => { + self.scrollbase.scroll_down(5); + EventResult::Consumed(None) + } + Event::Mouse { event: MouseEvent::WheelUp, .. } + if self.scrollbase.can_scroll_up() => { + self.scrollbase.scroll_up(5); + EventResult::Consumed(None) + } _ => EventResult::Ignored, } } fn take_focus(&mut self, source: direction::Direction) -> bool { let rel = source.relative(direction::Orientation::Vertical); - let i = if let Some(i) = self.iter_mut( - rel.is_none(), - rel.unwrap_or(direction::Relative::Front), - ).filter_map(|p| try_focus(p, source)) - .next() - { - i - } else { - // No one wants to be in focus - return false; - }; + let i = + if let Some(i) = self.iter_mut( + rel.is_none(), + rel.unwrap_or(direction::Relative::Front), + ).filter_map(|p| try_focus(p, source)) + .next() + { + i + } else { + // No one wants to be in focus + return false; + }; self.focus = i; self.scrollbase.scroll_to(self.focus); true } - fn call_on_any<'a>( - &mut self, selector: &Selector, - mut callback: Box, - ) { + fn call_on_any<'a>(&mut self, selector: &Selector, mut callback: Box) { for view in self.children.iter_mut().filter_map(ListChild::view) { view.call_on_any(selector, Box::new(|any| callback(any))); } diff --git a/src/views/select_view.rs b/src/views/select_view.rs index 98abe3d..d7d918b 100644 --- a/src/views/select_view.rs +++ b/src/views/select_view.rs @@ -339,19 +339,13 @@ impl SelectView { Event::Key(Key::End) => { self.focus.set(self.items.len().saturating_sub(1)) } - Event::Mouse { - event: MouseEvent::WheelDown, - .. - } if self.scrollbase.can_scroll_down() => - { + Event::Mouse { event: MouseEvent::WheelDown, .. } + if self.scrollbase.can_scroll_down() => { fix_scroll = false; self.scrollbase.scroll_down(5); } - Event::Mouse { - event: MouseEvent::WheelUp, - .. - } if self.scrollbase.can_scroll_up() => - { + Event::Mouse { event: MouseEvent::WheelUp, .. } + if self.scrollbase.can_scroll_up() => { fix_scroll = false; self.scrollbase.scroll_up(5); } @@ -359,13 +353,13 @@ impl SelectView { event: MouseEvent::Press(MouseButton::Left), position, offset, - } if position - .checked_sub(offset) - .map(|position| { + } + if position + .checked_sub(offset) + .map(|position| { self.scrollbase.start_drag(position, self.last_size.x) }) - .unwrap_or(false) => - { + .unwrap_or(false) => { fix_scroll = false; } Event::Mouse { @@ -375,26 +369,31 @@ impl SelectView { } => { // If the mouse is dragged, we always consume the event. fix_scroll = false; - position - .checked_sub(offset) - .map(|position| self.scrollbase.drag(position)); + position.checked_sub(offset).map(|position| { + self.scrollbase.drag(position) + }); } Event::Mouse { event: MouseEvent::Press(_), position, offset, - } => if let Some(position) = position.checked_sub(offset) { - let scrollbar_size = if self.scrollbase.scrollable() { - (2, 0) - } else { - (0, 0) - }; - let clickable_size = self.last_size.saturating_sub(scrollbar_size); - if position < clickable_size { - fix_scroll = false; - self.focus.set(position.y + self.scrollbase.start_line); + } => { + if let Some(position) = position.checked_sub(offset) { + let scrollbar_size = if self.scrollbase.scrollable() { + (2, 0) + } else { + (0, 0) + }; + let clickable_size = + self.last_size.saturating_sub(scrollbar_size); + if position < clickable_size { + fix_scroll = false; + self.focus.set( + position.y + self.scrollbase.start_line, + ); + } } - }, + } Event::Mouse { event: MouseEvent::Release(MouseButton::Left), position, @@ -409,10 +408,11 @@ impl SelectView { } else { (0, 0) }; - let clickable_size = self.last_size.saturating_sub(scrollbar_size); - if position < clickable_size - && (position.y + self.scrollbase.start_line) - == self.focus() + let clickable_size = + self.last_size.saturating_sub(scrollbar_size); + if position < clickable_size && + (position.y + self.scrollbase.start_line) == + self.focus() { return self.submit(); } @@ -429,9 +429,12 @@ impl SelectView { // the list when we reach the end. // This is achieved by chaining twice the iterator let iter = self.items.iter().chain(self.items.iter()); - if let Some((i, _)) = iter.enumerate() - .skip(self.focus() + 1) - .find(|&(_, item)| item.label.starts_with(c)) + if let Some((i, _)) = + iter.enumerate().skip(self.focus() + 1).find( + |&(_, item)| { + item.label.starts_with(c) + }, + ) { // Apply modulo in case we have a hit // from the chained iterator @@ -513,8 +516,7 @@ impl SelectView { event: MouseEvent::Release(MouseButton::Left), position, offset, - } if position.fits_in_rect(offset, self.last_size) => - { + } if position.fits_in_rect(offset, self.last_size) => { self.open_popup() } _ => EventResult::Ignored, @@ -603,15 +605,16 @@ impl View for SelectView { &printer.sub_printer(Vec2::new(0, offset), printer.size, true); self.scrollbase.draw(printer, |printer, i| { - printer.with_selection(i == self.focus(), |printer| { - if i != self.focus() && !self.enabled { + printer.with_selection( + i == self.focus(), + |printer| if i != self.focus() && !self.enabled { printer.with_color(ColorStyle::Secondary, |printer| { self.draw_item(printer, i) }); } else { self.draw_item(printer, i); - } - }); + }, + ); }); } }