Enable multi-rows views in ListView

This commit is contained in:
Alexandre Bury 2020-07-07 00:10:28 -07:00
parent 63b438188f
commit 56008db796
4 changed files with 65 additions and 35 deletions

View File

@ -85,13 +85,13 @@ impl Orientation {
/// ///
/// * For an horizontal view, returns `(Sum(x), Max(y))`. /// * For an horizontal view, returns `(Sum(x), Max(y))`.
/// * For a vertical view, returns `(Max(x), Sum(y))`. /// * For a vertical view, returns `(Max(x), Sum(y))`.
pub fn stack<'a, T: Iterator<Item = &'a Vec2>>(self, iter: T) -> Vec2 { pub fn stack<'a, T: Iterator<Item = Vec2>>(self, iter: T) -> Vec2 {
match self { match self {
Orientation::Horizontal => { Orientation::Horizontal => {
iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(b)) iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(&b))
} }
Orientation::Vertical => { Orientation::Vertical => {
iter.fold(Vec2::zero(), |a, b| a.stack_vertical(b)) iter.fold(Vec2::zero(), |a, b| a.stack_vertical(&b))
} }
} }
} }

View File

@ -503,7 +503,7 @@ impl View for LinearLayout {
.map(|c| c.required_size(req)) .map(|c| c.required_size(req))
.collect(); .collect();
debug!("Ideal sizes: {:?}", ideal_sizes); debug!("Ideal sizes: {:?}", ideal_sizes);
let ideal = self.orientation.stack(ideal_sizes.iter()); let ideal = self.orientation.stack(ideal_sizes.iter().copied());
debug!("Ideal result: {:?}", ideal); debug!("Ideal result: {:?}", ideal);
// Does it fit? // Does it fit?
@ -527,7 +527,7 @@ impl View for LinearLayout {
.iter_mut() .iter_mut()
.map(|c| c.required_size(budget_req)) .map(|c| c.required_size(budget_req))
.collect(); .collect();
let desperate = self.orientation.stack(min_sizes.iter()); let desperate = self.orientation.stack(min_sizes.iter().copied());
debug!("Min sizes: {:?}", min_sizes); debug!("Min sizes: {:?}", min_sizes);
debug!("Desperate: {:?}", desperate); debug!("Desperate: {:?}", desperate);
@ -613,7 +613,7 @@ impl View for LinearLayout {
debug!("Final sizes2: {:?}", final_sizes); debug!("Final sizes2: {:?}", final_sizes);
// Let's stack everything to see what it looks like. // Let's stack everything to see what it looks like.
let compromise = self.orientation.stack(final_sizes.iter()); let compromise = self.orientation.stack(final_sizes.iter().copied());
// Phew, that was a lot of work! I'm not doing it again. // Phew, that was a lot of work! I'm not doing it again.
self.cache = Some(SizeCache::build(compromise, req)); self.cache = Some(SizeCache::build(compromise, req));

View File

@ -37,6 +37,7 @@ impl ListChild {
/// Displays a list of elements. /// Displays a list of elements.
pub struct ListView { pub struct ListView {
children: Vec<ListChild>, children: Vec<ListChild>,
children_heights: Vec<usize>,
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<dyn Fn(&mut Cursive, &String)>>, on_select: Option<Rc<dyn Fn(&mut Cursive, &String)>>,
@ -50,6 +51,7 @@ impl ListView {
pub fn new() -> Self { pub fn new() -> Self {
ListView { ListView {
children: Vec::new(), children: Vec::new(),
children_heights: Vec::new(),
focus: 0, focus: 0,
on_select: None, on_select: None,
last_size: Vec2::zero(), last_size: Vec2::zero(),
@ -96,11 +98,13 @@ impl ListView {
let mut view = view.as_boxed_view(); let mut view = view.as_boxed_view();
view.take_focus(direction::Direction::none()); view.take_focus(direction::Direction::none());
self.children.push(ListChild::Row(label.to_string(), view)); self.children.push(ListChild::Row(label.to_string(), view));
self.children_heights.push(0);
} }
/// Removes all children from this view. /// Removes all children from this view.
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.children.clear(); self.children.clear();
self.children_heights.clear();
self.focus = 0; self.focus = 0;
} }
@ -118,6 +122,7 @@ impl ListView {
/// Adds a delimiter to the end of the list. /// Adds a delimiter to the end of the list.
pub fn add_delimiter(&mut self) { pub fn add_delimiter(&mut self) {
self.children.push(ListChild::Delimiter); self.children.push(ListChild::Delimiter);
self.children_heights.push(0);
} }
/// Adds a delimiter to the end of the list. /// Adds a delimiter to the end of the list.
@ -133,6 +138,7 @@ impl ListView {
/// ///
/// If `index >= self.len()`. /// If `index >= self.len()`.
pub fn remove_child(&mut self, index: usize) -> ListChild { pub fn remove_child(&mut self, index: usize) -> ListChild {
self.children_heights.remove(index);
self.children.remove(index) self.children.remove(index)
} }
@ -232,7 +238,7 @@ impl ListView {
return; return;
} }
let position = match position.checked_sub(offset) { let mut position = match position.checked_sub(offset) {
None => return, None => return,
Some(pos) => pos, Some(pos) => pos,
}; };
@ -240,14 +246,21 @@ impl ListView {
// eprintln!("Rel pos: {:?}", position); // eprintln!("Rel pos: {:?}", position);
// Now that we have a relative position, checks for buttons? // Now that we have a relative position, checks for buttons?
let focus = position.y; for (i, (child, height)) in self
if focus >= self.children.len() { .children
return; .iter_mut()
} .zip(&self.children_heights)
.enumerate()
if let ListChild::Row(_, ref mut view) = self.children[focus] { {
if position.y < *height {
if let ListChild::Row(_, ref mut view) = child {
if view.take_focus(direction::Direction::none()) { if view.take_focus(direction::Direction::none()) {
self.focus = focus; self.focus = i;
}
}
break;
} else {
position.y -= height;
} }
} }
} }
@ -277,18 +290,25 @@ impl View for ListView {
} }
let offset = self.labels_width() + 1; let offset = self.labels_width() + 1;
let mut y = 0;
debug!("Offset: {}", offset); debug!("Offset: {}", offset);
for (i, child) in self.children.iter().enumerate() { for (i, (child, &height)) in
self.children.iter().zip(&self.children_heights).enumerate()
{
match child { match child {
ListChild::Row(ref label, ref view) => { ListChild::Row(ref label, ref view) => {
printer.print((0, i), label); printer.print((0, y), label);
view.draw( view.draw(
&printer.offset((offset, i)).focused(i == self.focus), &printer
.offset((offset, y))
.cropped((printer.size.x, height))
.focused(i == self.focus),
); );
} }
ListChild::Delimiter => (), ListChild::Delimiter => (), // TODO: draw delimiters?
} }
y += height;
} }
} }
@ -302,15 +322,14 @@ impl View for ListView {
.max() .max()
.unwrap_or(0); .unwrap_or(0);
let view_size = self let view_size = direction::Orientation::Vertical.stack(
.children self.children.iter_mut().map(|c| match c {
.iter_mut() ListChild::Delimiter => Vec2::new(0, 1),
.filter_map(ListChild::view) ListChild::Row(_, ref mut view) => view.required_size(req),
.map(|v| v.required_size(req).x) }),
.max() );
.unwrap_or(0);
Vec2::new(label_width + 1 + view_size, self.children.len()) view_size + (1 + label_width, 0)
} }
fn layout(&mut self, size: Vec2) { fn layout(&mut self, size: Vec2) {
@ -331,8 +350,16 @@ impl View for ListView {
debug!("Available: {}", available); debug!("Available: {}", available);
for child in self.children.iter_mut().filter_map(ListChild::view) { self.children_heights.resize(self.children.len(), 0);
child.layout(Vec2::new(available, 1)); for (child, height) in self
.children
.iter_mut()
.filter_map(ListChild::view)
.zip(&mut self.children_heights)
{
// TODO: Find the child height?
*height = child.required_size(size).y;
child.layout(Vec2::new(available, *height));
} }
} }
@ -346,9 +373,8 @@ impl View for ListView {
// Send the event to the focused child. // Send the event to the focused child.
let labels_width = self.labels_width(); let labels_width = self.labels_width();
if let ListChild::Row(_, ref mut view) = self.children[self.focus] { if let ListChild::Row(_, ref mut view) = self.children[self.focus] {
// If self.focus < self.scrollbase.start_line, it means the focus is not let y = self.children_heights[..self.focus].iter().sum();
// in view. Something's fishy, so don't send the event. let offset = (labels_width + 1, y);
let offset = (labels_width + 1, self.focus);
let result = view.on_event(event.relativized(offset)); let result = view.on_event(event.relativized(offset));
if result.is_consumed() { if result.is_consumed() {
return result; return result;

View File

@ -1,6 +1,9 @@
use cursive::traits::*; use cursive::{
use cursive::views::{ traits::*,
Checkbox, Dialog, EditView, LinearLayout, ListView, SelectView, TextView, views::{
Checkbox, Dialog, EditView, LinearLayout, ListView, SelectView,
TextArea, TextView,
},
}; };
// This example uses a ListView. // This example uses a ListView.
@ -18,6 +21,7 @@ fn main() {
ListView::new() ListView::new()
// Each child is a single-line view with a label // Each child is a single-line view with a label
.child("Name", EditView::new().fixed_width(10)) .child("Name", EditView::new().fixed_width(10))
.child("Presentation", TextArea::new().min_height(4))
.child( .child(
"Receive spam?", "Receive spam?",
Checkbox::new().on_change(|s, checked| { Checkbox::new().on_change(|s, checked| {