Fix mouse support for ListView

This commit is contained in:
Alexandre Bury 2017-10-14 21:01:07 -07:00
parent 850e0b2cd1
commit 294a4102b4
3 changed files with 145 additions and 83 deletions

View File

@ -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. /// Stops grabbing the scrollbar.
pub fn release_grab(&mut self) { pub fn release_grab(&mut self) {
self.thumb_grab = None; self.thumb_grab = None;

View File

@ -2,7 +2,7 @@ use Cursive;
use Printer; use Printer;
use With; use With;
use direction; use direction;
use event::{Callback, Event, EventResult, Key}; use event::{Callback, Event, EventResult, Key, MouseEvent, MouseButton};
use std::any::Any; use std::any::Any;
use std::rc::Rc; use std::rc::Rc;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -44,6 +44,7 @@ pub struct ListView {
focus: usize, focus: usize,
// This callback is called when the selection is changed. // This callback is called when the selection is changed.
on_select: Option<Rc<Fn(&mut Cursive, &String)>>, on_select: Option<Rc<Fn(&mut Cursive, &String)>>,
last_size: Vec2,
} }
new_default!(ListView); new_default!(ListView);
@ -56,6 +57,7 @@ impl ListView {
scrollbase: ScrollBase::new(), scrollbase: ScrollBase::new(),
focus: 0, focus: 0,
on_select: None, on_select: None,
last_size: Vec2::zero(),
} }
} }
@ -94,8 +96,10 @@ impl ListView {
/// Adds a view to the end of the list. /// Adds a view to the end of the list.
pub fn add_child<V: View + 'static>(&mut self, label: &str, mut view: V) { pub fn add_child<V: View + 'static>(&mut self, label: &str, mut view: V) {
view.take_focus(direction::Direction::none()); view.take_focus(direction::Direction::none());
self.children self.children.push(ListChild::Row(
.push(ListChild::Row(label.to_string(), Box::new(view))); label.to_string(),
Box::new(view),
));
} }
/// Removes all children from this view. /// Removes all children from this view.
@ -148,9 +152,8 @@ impl ListView {
self.focus self.focus
} }
fn iter_mut<'a>( fn iter_mut<'a>(&'a mut self, from_focus: bool, source: direction::Relative)
&'a mut self, from_focus: bool, source: direction::Relative -> Box<Iterator<Item = (usize, &mut ListChild)> + 'a> {
) -> Box<Iterator<Item = (usize, &mut ListChild)> + 'a> {
match source { match source {
direction::Relative::Front => { direction::Relative::Front => {
let start = if from_focus { self.focus } else { 0 }; let start = if from_focus { self.focus } else { 0 };
@ -168,9 +171,8 @@ impl ListView {
} }
} }
fn move_focus( fn move_focus(&mut self, n: usize, source: direction::Direction)
&mut self, n: usize, source: direction::Direction -> EventResult {
) -> EventResult {
let i = if let Some(i) = source let i = if let Some(i) = source
.relative(direction::Orientation::Vertical) .relative(direction::Orientation::Vertical)
.and_then(|rel| { .and_then(|rel| {
@ -181,7 +183,8 @@ impl ListView {
.filter_map(|p| try_focus(p, source)) .filter_map(|p| try_focus(p, source))
.take(n) .take(n)
.last() .last()
}) { })
{
i i
} else { } else {
return EventResult::Ignored; return EventResult::Ignored;
@ -238,16 +241,17 @@ impl ListView {
} }
} }
fn try_focus( fn try_focus((i, child): (usize, &mut ListChild), source: direction::Direction)
(i, child): (usize, &mut ListChild), source: direction::Direction -> Option<usize> {
) -> Option<usize> {
match *child { match *child {
ListChild::Delimiter => None, ListChild::Delimiter => None,
ListChild::Row(_, ref mut view) => if view.take_focus(source) { ListChild::Row(_, ref mut view) => {
Some(i) if view.take_focus(source) {
} else { Some(i)
None } else {
}, None
}
}
} }
} }
@ -260,14 +264,16 @@ impl View for ListView {
let offset = self.labels_width() + 1; let offset = self.labels_width() + 1;
debug!("Offset: {}", offset); debug!("Offset: {}", offset);
self.scrollbase self.scrollbase.draw(
.draw(printer, |printer, i| match self.children[i] { printer,
|printer, i| match self.children[i] {
ListChild::Row(ref label, ref view) => { ListChild::Row(ref label, ref view) => {
printer.print((0, 0), label); printer.print((0, 0), label);
view.draw(&printer.offset((offset, 0), i == self.focus)); view.draw(&printer.offset((offset, 0), i == self.focus));
} }
ListChild::Delimiter => (), ListChild::Delimiter => (),
}); },
);
} }
fn required_size(&mut self, req: Vec2) -> Vec2 { fn required_size(&mut self, req: Vec2) -> Vec2 {
@ -294,6 +300,7 @@ impl View for ListView {
} }
fn layout(&mut self, size: Vec2) { fn layout(&mut self, size: Vec2) {
self.last_size = size;
self.scrollbase.set_heights(size.y, self.children.len()); self.scrollbase.set_heights(size.y, self.children.len());
// We'll show 2 columns: the labels, and the views. // We'll show 2 columns: the labels, and the views.
@ -307,8 +314,9 @@ impl View for ListView {
let spacing = 1; let spacing = 1;
let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 }; let scrollbar_width = if self.children.len() > size.y { 2 } else { 0 };
let available = size.x let available = size.x.saturating_sub(
.saturating_sub(label_width + spacing + scrollbar_width); label_width + spacing + scrollbar_width,
);
debug!("Available: {}", available); debug!("Available: {}", available);
@ -322,7 +330,43 @@ impl View for ListView {
return EventResult::Ignored; 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); self.check_focus_grab(&event);
// Send the event to the focused child. // Send the event to the focused child.
@ -350,13 +394,15 @@ impl View for ListView {
Event::Key(Key::PageDown) => { Event::Key(Key::PageDown) => {
self.move_focus(10, direction::Direction::up()) 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( self.move_focus(
usize::max_value(), usize::max_value(),
direction::Direction::back(), direction::Direction::back(),
) )
} }
Event::Key(Key::End) | Event::Ctrl(Key::End) => { Event::Key(Key::End) |
Event::Ctrl(Key::End) => {
self.move_focus( self.move_focus(
usize::max_value(), usize::max_value(),
direction::Direction::front(), direction::Direction::front(),
@ -368,32 +414,40 @@ impl View for ListView {
Event::Shift(Key::Tab) => { Event::Shift(Key::Tab) => {
self.move_focus(1, direction::Direction::back()) 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, _ => EventResult::Ignored,
} }
} }
fn take_focus(&mut self, source: direction::Direction) -> bool { fn take_focus(&mut self, source: direction::Direction) -> bool {
let rel = source.relative(direction::Orientation::Vertical); let rel = source.relative(direction::Orientation::Vertical);
let i = if let Some(i) = self.iter_mut( let i =
rel.is_none(), if let Some(i) = self.iter_mut(
rel.unwrap_or(direction::Relative::Front), rel.is_none(),
).filter_map(|p| try_focus(p, source)) rel.unwrap_or(direction::Relative::Front),
.next() ).filter_map(|p| try_focus(p, source))
{ .next()
i {
} else { i
// No one wants to be in focus } else {
return false; // No one wants to be in focus
}; return false;
};
self.focus = i; self.focus = i;
self.scrollbase.scroll_to(self.focus); self.scrollbase.scroll_to(self.focus);
true true
} }
fn call_on_any<'a>( fn call_on_any<'a>(&mut self, selector: &Selector, mut callback: Box<FnMut(&mut Any) + 'a>) {
&mut self, selector: &Selector,
mut callback: Box<FnMut(&mut Any) + 'a>,
) {
for view in self.children.iter_mut().filter_map(ListChild::view) { for view in self.children.iter_mut().filter_map(ListChild::view) {
view.call_on_any(selector, Box::new(|any| callback(any))); view.call_on_any(selector, Box::new(|any| callback(any)));
} }

View File

@ -339,19 +339,13 @@ impl<T: 'static> SelectView<T> {
Event::Key(Key::End) => { Event::Key(Key::End) => {
self.focus.set(self.items.len().saturating_sub(1)) self.focus.set(self.items.len().saturating_sub(1))
} }
Event::Mouse { Event::Mouse { event: MouseEvent::WheelDown, .. }
event: MouseEvent::WheelDown, if self.scrollbase.can_scroll_down() => {
..
} if self.scrollbase.can_scroll_down() =>
{
fix_scroll = false; fix_scroll = false;
self.scrollbase.scroll_down(5); self.scrollbase.scroll_down(5);
} }
Event::Mouse { Event::Mouse { event: MouseEvent::WheelUp, .. }
event: MouseEvent::WheelUp, if self.scrollbase.can_scroll_up() => {
..
} if self.scrollbase.can_scroll_up() =>
{
fix_scroll = false; fix_scroll = false;
self.scrollbase.scroll_up(5); self.scrollbase.scroll_up(5);
} }
@ -359,13 +353,13 @@ impl<T: 'static> SelectView<T> {
event: MouseEvent::Press(MouseButton::Left), event: MouseEvent::Press(MouseButton::Left),
position, position,
offset, offset,
} if position }
.checked_sub(offset) if position
.map(|position| { .checked_sub(offset)
.map(|position| {
self.scrollbase.start_drag(position, self.last_size.x) self.scrollbase.start_drag(position, self.last_size.x)
}) })
.unwrap_or(false) => .unwrap_or(false) => {
{
fix_scroll = false; fix_scroll = false;
} }
Event::Mouse { Event::Mouse {
@ -375,26 +369,31 @@ impl<T: 'static> SelectView<T> {
} => { } => {
// If the mouse is dragged, we always consume the event. // If the mouse is dragged, we always consume the event.
fix_scroll = false; fix_scroll = false;
position position.checked_sub(offset).map(|position| {
.checked_sub(offset) self.scrollbase.drag(position)
.map(|position| self.scrollbase.drag(position)); });
} }
Event::Mouse { Event::Mouse {
event: MouseEvent::Press(_), event: MouseEvent::Press(_),
position, position,
offset, offset,
} => if let Some(position) = position.checked_sub(offset) { } => {
let scrollbar_size = if self.scrollbase.scrollable() { if let Some(position) = position.checked_sub(offset) {
(2, 0) let scrollbar_size = if self.scrollbase.scrollable() {
} else { (2, 0)
(0, 0) } else {
}; (0, 0)
let clickable_size = self.last_size.saturating_sub(scrollbar_size); };
if position < clickable_size { let clickable_size =
fix_scroll = false; self.last_size.saturating_sub(scrollbar_size);
self.focus.set(position.y + self.scrollbase.start_line); if position < clickable_size {
fix_scroll = false;
self.focus.set(
position.y + self.scrollbase.start_line,
);
}
} }
}, }
Event::Mouse { Event::Mouse {
event: MouseEvent::Release(MouseButton::Left), event: MouseEvent::Release(MouseButton::Left),
position, position,
@ -409,10 +408,11 @@ impl<T: 'static> SelectView<T> {
} else { } else {
(0, 0) (0, 0)
}; };
let clickable_size = self.last_size.saturating_sub(scrollbar_size); let clickable_size =
if position < clickable_size self.last_size.saturating_sub(scrollbar_size);
&& (position.y + self.scrollbase.start_line) if position < clickable_size &&
== self.focus() (position.y + self.scrollbase.start_line) ==
self.focus()
{ {
return self.submit(); return self.submit();
} }
@ -429,9 +429,12 @@ impl<T: 'static> SelectView<T> {
// the list when we reach the end. // the list when we reach the end.
// This is achieved by chaining twice the iterator // This is achieved by chaining twice the iterator
let iter = self.items.iter().chain(self.items.iter()); let iter = self.items.iter().chain(self.items.iter());
if let Some((i, _)) = iter.enumerate() if let Some((i, _)) =
.skip(self.focus() + 1) iter.enumerate().skip(self.focus() + 1).find(
.find(|&(_, item)| item.label.starts_with(c)) |&(_, item)| {
item.label.starts_with(c)
},
)
{ {
// Apply modulo in case we have a hit // Apply modulo in case we have a hit
// from the chained iterator // from the chained iterator
@ -513,8 +516,7 @@ impl<T: 'static> SelectView<T> {
event: MouseEvent::Release(MouseButton::Left), event: MouseEvent::Release(MouseButton::Left),
position, position,
offset, offset,
} if position.fits_in_rect(offset, self.last_size) => } if position.fits_in_rect(offset, self.last_size) => {
{
self.open_popup() self.open_popup()
} }
_ => EventResult::Ignored, _ => EventResult::Ignored,
@ -603,15 +605,16 @@ impl<T: 'static> View for SelectView<T> {
&printer.sub_printer(Vec2::new(0, offset), printer.size, true); &printer.sub_printer(Vec2::new(0, offset), printer.size, true);
self.scrollbase.draw(printer, |printer, i| { self.scrollbase.draw(printer, |printer, i| {
printer.with_selection(i == self.focus(), |printer| { printer.with_selection(
if i != self.focus() && !self.enabled { i == self.focus(),
|printer| if i != self.focus() && !self.enabled {
printer.with_color(ColorStyle::Secondary, |printer| { printer.with_color(ColorStyle::Secondary, |printer| {
self.draw_item(printer, i) self.draw_item(printer, i)
}); });
} else { } else {
self.draw_item(printer, i); self.draw_item(printer, i);
} },
}); );
}); });
} }
} }