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 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 {
Orientation::Horizontal => {
iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(b))
iter.fold(Vec2::zero(), |a, b| a.stack_horizontal(&b))
}
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))
.collect();
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);
// Does it fit?
@ -527,7 +527,7 @@ impl View for LinearLayout {
.iter_mut()
.map(|c| c.required_size(budget_req))
.collect();
let desperate = self.orientation.stack(min_sizes.iter());
let desperate = self.orientation.stack(min_sizes.iter().copied());
debug!("Min sizes: {:?}", min_sizes);
debug!("Desperate: {:?}", desperate);
@ -613,7 +613,7 @@ impl View for LinearLayout {
debug!("Final sizes2: {:?}", final_sizes);
// 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.
self.cache = Some(SizeCache::build(compromise, req));

View File

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

View File

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