mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Fix linear layout in constrained space
This commit is contained in:
parent
12593c8ea8
commit
03c400ad44
@ -1,19 +1,19 @@
|
||||
extern crate cursive;
|
||||
|
||||
use cursive::Cursive;
|
||||
use cursive::view::{Dialog,TextView,LinearLayout,BoxView};
|
||||
use cursive::view::{BoxView, Dialog, LinearLayout, TextView};
|
||||
use cursive::align::HAlign;
|
||||
|
||||
fn main() {
|
||||
let mut siv = Cursive::new();
|
||||
|
||||
// Some description text
|
||||
let text = "This is a very simple example of linear layout. Two views are present, a short title above, and this text. The text has a fixed width, and the title is centered horizontally.";
|
||||
let text = "This is a very simple example of linear layout. Two views \
|
||||
are present, a short title above, and this text. The text \
|
||||
has a fixed width, and the title is centered horizontally.";
|
||||
|
||||
// We'll create a dialog with a TextView serving as a title
|
||||
siv.add_layer(
|
||||
Dialog::new(
|
||||
LinearLayout::vertical()
|
||||
siv.add_layer(Dialog::new(LinearLayout::vertical()
|
||||
.child(TextView::new("Title").h_align(HAlign::Center))
|
||||
// Box the textview, so it doesn't get too wide.
|
||||
// A 0 height value means it will be unconstrained.
|
||||
|
@ -54,4 +54,11 @@ impl Orientation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new `Vec2` with `value` in `self`'s axis.
|
||||
pub fn make_vec(&self, value: usize) -> Vec2 {
|
||||
let mut result = Vec2::zero();
|
||||
*self.get_ref(&mut result) = value;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
@ -175,11 +175,11 @@ impl Printer {
|
||||
/// Returns a printer on a subset of this one's area.
|
||||
pub fn sub_printer<S: Into<Vec2>>(&self, offset: S, size: S, focused: bool)
|
||||
-> Printer {
|
||||
let offset_v = offset.into();
|
||||
let offset = offset.into().or_min(self.size);
|
||||
Printer {
|
||||
offset: self.offset + offset_v,
|
||||
offset: self.offset + offset,
|
||||
// We can't be larger than what remains
|
||||
size: Vec2::min(self.size - offset_v, size.into()),
|
||||
size: Vec2::min(self.size - offset, size.into()),
|
||||
focused: self.focused && focused,
|
||||
theme: self.theme.clone(),
|
||||
}
|
||||
|
16
src/vec.rs
16
src/vec.rs
@ -1,5 +1,6 @@
|
||||
//! Points on the 2D character grid.
|
||||
use XY;
|
||||
use orientation::Orientation;
|
||||
|
||||
use std::ops::{Add, Div, Mul, Sub};
|
||||
use std::cmp::{Ordering, max, min};
|
||||
@ -70,6 +71,21 @@ impl Vec2 {
|
||||
pub fn stack_horizontal(&self, other: &Vec2) -> Vec2 {
|
||||
Vec2::new(self.x + other.x, max(self.y, other.y))
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` could fit inside `other`.
|
||||
///
|
||||
/// Shortcut for `self.x <= other.x && self.y <= other.y`.
|
||||
pub fn fits_in<T: Into<Vec2>>(&self, other: T) -> bool {
|
||||
let other = other.into();
|
||||
self.x <= other.x && self.y <= other.y
|
||||
}
|
||||
|
||||
/// Returns a new `Vec2` with the axis `o` set to `value`.
|
||||
pub fn with(&self, o: Orientation, value: usize) -> Self {
|
||||
let mut other = self.clone();
|
||||
*o.get_ref(&mut other) = value;
|
||||
other
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(i32, i32)> for Vec2 {
|
||||
|
@ -68,6 +68,6 @@ impl<T: View> ViewWrapper for BoxView<T> {
|
||||
|
||||
Vec2::new(self.size.x.unwrap_or(child_size.x),
|
||||
self.size.y.unwrap_or(child_size.y))
|
||||
}
|
||||
}.or_min(req)
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,20 @@
|
||||
use XY;
|
||||
use view::View;
|
||||
use view::SizeCache;
|
||||
use vec::Vec2;
|
||||
use printer::Printer;
|
||||
use orientation::Orientation;
|
||||
use event::{Event, EventResult, Key};
|
||||
|
||||
use std::cmp::min;
|
||||
|
||||
/// Arranges its children linearly according to its orientation.
|
||||
pub struct LinearLayout {
|
||||
children: Vec<Child>,
|
||||
orientation: Orientation,
|
||||
focus: usize,
|
||||
|
||||
last_size: Option<Vec2>,
|
||||
cache: Option<XY<SizeCache>>,
|
||||
}
|
||||
|
||||
struct Child {
|
||||
@ -19,6 +23,13 @@ struct Child {
|
||||
weight: usize,
|
||||
}
|
||||
|
||||
impl Child {
|
||||
fn get_min_size(&mut self, req: Vec2) -> Vec2 {
|
||||
self.size = self.view.get_min_size(req);
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl LinearLayout {
|
||||
/// Creates a new layout with the given orientation.
|
||||
pub fn new(orientation: Orientation) -> Self {
|
||||
@ -26,7 +37,7 @@ impl LinearLayout {
|
||||
children: Vec::new(),
|
||||
orientation: orientation,
|
||||
focus: 0,
|
||||
last_size: None,
|
||||
cache: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,11 +57,16 @@ impl LinearLayout {
|
||||
size: Vec2::zero(),
|
||||
weight: 0,
|
||||
});
|
||||
self.last_size = None;
|
||||
self.invalidate();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
// Invalidate the view, to request a layout next time
|
||||
fn invalidate(&mut self) {
|
||||
self.cache = None;
|
||||
}
|
||||
|
||||
/// Creates a new vertical layout.
|
||||
pub fn vertical() -> Self {
|
||||
LinearLayout::new(Orientation::Vertical)
|
||||
@ -60,6 +76,31 @@ impl LinearLayout {
|
||||
pub fn horizontal() -> Self {
|
||||
LinearLayout::new(Orientation::Horizontal)
|
||||
}
|
||||
|
||||
// If the cache can be used, return the cached size.
|
||||
// Otherwise, return None.
|
||||
fn get_cache(&self, req: Vec2) -> Option<Vec2> {
|
||||
match self.cache {
|
||||
None => None,
|
||||
Some(ref cache) => {
|
||||
// Is our cache even valid?
|
||||
// Also, is any child invalidating the layout?
|
||||
if cache.x.accept(req.x) && cache.y.accept(req.y) &&
|
||||
self.children_are_sleeping() {
|
||||
Some(cache.map(|s| s.value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn children_are_sleeping(&self) -> bool {
|
||||
!self.children
|
||||
.iter()
|
||||
.map(|c| &*c.view)
|
||||
.any(View::needs_relayout)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the index of the maximum element.
|
||||
@ -129,23 +170,29 @@ impl View for LinearLayout {
|
||||
}
|
||||
|
||||
fn needs_relayout(&self) -> bool {
|
||||
if self.last_size == None {
|
||||
if self.cache.is_none() {
|
||||
return true;
|
||||
}
|
||||
|
||||
for child in &self.children {
|
||||
if child.view.needs_relayout() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
!self.children_are_sleeping()
|
||||
}
|
||||
|
||||
fn layout(&mut self, size: Vec2) {
|
||||
// Compute the very minimal required size
|
||||
// Look how mean we are: we offer the whole size to every child.
|
||||
// As if they could get it all.
|
||||
// If we can get away without breaking a sweat, you can bet we will.
|
||||
if self.get_cache(size).is_none() {
|
||||
self.get_min_size(size);
|
||||
}
|
||||
|
||||
for child in &mut self.children {
|
||||
// println_stderr!("Child size: {:?}", child.size);
|
||||
child.view.layout(child.size);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
// Need to compute things again...
|
||||
self.get_min_size(size);
|
||||
|
||||
let min_sizes: Vec<Vec2> = self.children
|
||||
.iter_mut()
|
||||
.map(|child| Vec2::min(size, child.view.get_min_size(size)))
|
||||
@ -180,26 +227,104 @@ impl View for LinearLayout {
|
||||
child.size = child_size;
|
||||
child.view.layout(child_size);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fn get_min_size(&mut self, req: Vec2) -> Vec2 {
|
||||
// Did anything change since last time?
|
||||
if let Some(size) = self.get_cache(req) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// First, make a naive scenario: everything will work fine.
|
||||
let sizes: Vec<Vec2> = self.children
|
||||
.iter_mut()
|
||||
.map(|view| view.view.get_min_size(req))
|
||||
.map(|c| c.get_min_size(req))
|
||||
.collect();
|
||||
self.orientation.stack(sizes.iter())
|
||||
// println_stderr!("Ideal sizes: {:?}", sizes);
|
||||
let ideal = self.orientation.stack(sizes.iter());
|
||||
// println_stderr!("Ideal result: {:?}", ideal);
|
||||
|
||||
|
||||
// Did it work? Champagne!
|
||||
// Does it fit?
|
||||
if ideal.fits_in(req) {
|
||||
// Champagne!
|
||||
self.cache = Some(SizeCache::build(ideal, req));
|
||||
return ideal;
|
||||
}
|
||||
|
||||
// Ok, so maybe it didn't.
|
||||
// Budget cuts, everyone.
|
||||
let budget_req = req.with(self.orientation, 1);
|
||||
// println_stderr!("Budget req: {:?}", budget_req);
|
||||
|
||||
let min_sizes: Vec<Vec2> = self.children
|
||||
.iter_mut()
|
||||
.map(|c| c.get_min_size(budget_req))
|
||||
.collect();
|
||||
let desperate = self.orientation.stack(min_sizes.iter());
|
||||
// println_stderr!("Min sizes: {:?}", min_sizes);
|
||||
// println_stderr!("Desperate: {:?}", desperate);
|
||||
|
||||
// I really hope it fits this time...
|
||||
if !desperate.fits_in(req) {
|
||||
// Just give up...
|
||||
// println_stderr!("Seriously? {:?} > {:?}???", desperate, req);
|
||||
self.cache = Some(SizeCache::build(desperate, req));
|
||||
return desperate;
|
||||
}
|
||||
|
||||
// This here is how much we're generously offered
|
||||
let mut available = self.orientation.get(&(req - desperate));
|
||||
// println_stderr!("Available: {:?}", available);
|
||||
|
||||
// Here, we have to make a compromise between the ideal
|
||||
// and the desperate solutions.
|
||||
let mut overweight: Vec<(usize, usize)> = sizes.iter()
|
||||
.map(|v| self.orientation.get(v))
|
||||
.zip(min_sizes.iter().map(|v| self.orientation.get(v)))
|
||||
.map(|(a, b)| a - b)
|
||||
.enumerate()
|
||||
.collect();
|
||||
// println_stderr!("Overweight: {:?}", overweight);
|
||||
|
||||
// So... distribute `available` to reduce the overweight...
|
||||
// TODO: use child weight in the distribution...
|
||||
overweight.sort_by_key(|&(_, weight)| weight);
|
||||
let mut allocations = vec![0; overweight.len()];
|
||||
|
||||
for (i, &(j, weight)) in overweight.iter().enumerate() {
|
||||
let remaining = overweight.len() - i;
|
||||
let budget = available / remaining;
|
||||
let spent = min(budget, weight);
|
||||
allocations[j] = spent;
|
||||
available -= spent;
|
||||
}
|
||||
// println_stderr!("Allocations: {:?}", allocations);
|
||||
|
||||
// Final lengths are the minimum ones + allocations
|
||||
let final_lengths: Vec<Vec2> = min_sizes.iter()
|
||||
.map(|v| self.orientation.get(v))
|
||||
.zip(allocations.iter())
|
||||
.map(|(a, b)| a + b)
|
||||
.map(|l| req.with(self.orientation, l))
|
||||
.collect();
|
||||
// println_stderr!("Final sizes: {:?}", final_lengths);
|
||||
|
||||
let final_sizes: Vec<Vec2> = self.children
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.map(|(i, c)| {
|
||||
c.get_min_size(final_lengths[i])
|
||||
})
|
||||
.collect();
|
||||
// println_stderr!("Final sizes2: {:?}", final_sizes);
|
||||
|
||||
|
||||
// TODO: Ok, so maybe it didn't.
|
||||
// Last chance: did someone lie about his needs?
|
||||
// Could we squash him a little?
|
||||
// (Maybe he'll just scroll and it'll be fine?)
|
||||
let compromise = self.orientation.stack(final_sizes.iter());
|
||||
self.cache = Some(SizeCache::build(compromise, req));
|
||||
|
||||
// Find out who's fluid, if any.
|
||||
compromise
|
||||
}
|
||||
|
||||
fn on_event(&mut self, event: Event) -> EventResult {
|
||||
|
@ -30,6 +30,7 @@ mod tracked_view;
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
use XY;
|
||||
use event::{Event, EventResult};
|
||||
use vec::Vec2;
|
||||
use printer::Printer;
|
||||
@ -105,6 +106,49 @@ pub trait View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache around a one-dimensional layout result
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub struct SizeCache {
|
||||
/// Cached value
|
||||
pub value: usize,
|
||||
/// `true` if the last size was constrained.
|
||||
///
|
||||
/// If unconstrained, any request larger than this value
|
||||
/// would return the same size.
|
||||
pub constrained: bool,
|
||||
}
|
||||
|
||||
impl SizeCache {
|
||||
/// Creates a new sized cache
|
||||
pub fn new(value: usize, constrained: bool) -> Self {
|
||||
SizeCache {
|
||||
value: value,
|
||||
constrained: constrained,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is still valid for the given `request`.
|
||||
pub fn accept(&self, request: usize) -> bool {
|
||||
if request < self.value {
|
||||
false
|
||||
} else if request == self.value {
|
||||
true
|
||||
} else {
|
||||
!self.constrained
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new bi-dimensional cache.
|
||||
///
|
||||
/// * `size` must fit inside `req`.
|
||||
/// * for each dimension, `constrained = (size == req)`
|
||||
fn build(size: Vec2, req: Vec2) -> XY<Self> {
|
||||
XY::new(SizeCache::new(size.x, size.x == req.x),
|
||||
SizeCache::new(size.y, size.y == req.y))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Selects a single view (if any) in the tree.
|
||||
pub enum Selector<'a> {
|
||||
/// Selects a view from its ID
|
||||
|
@ -1,6 +1,7 @@
|
||||
use XY;
|
||||
use vec::Vec2;
|
||||
use view::View;
|
||||
use view::SizeCache;
|
||||
use printer::Printer;
|
||||
use align::*;
|
||||
use event::*;
|
||||
@ -9,38 +10,6 @@ use super::scroll::ScrollBase;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
struct SizeCache {
|
||||
value: usize,
|
||||
// If unconstrained, any request larger than this value
|
||||
// would return the same size.
|
||||
constrained: bool,
|
||||
}
|
||||
|
||||
impl SizeCache {
|
||||
fn new(value: usize, constrained: bool) -> Self {
|
||||
SizeCache {
|
||||
value: value,
|
||||
constrained: constrained,
|
||||
}
|
||||
}
|
||||
|
||||
fn accept(&self, size: usize) -> bool {
|
||||
if size < self.value {
|
||||
false
|
||||
} else if size == self.value {
|
||||
true
|
||||
} else {
|
||||
!self.constrained
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cache_size(size: Vec2, req: Vec2) -> XY<SizeCache> {
|
||||
XY::new(SizeCache::new(size.x, size.x == req.x),
|
||||
SizeCache::new(size.y, size.y == req.y))
|
||||
|
||||
}
|
||||
|
||||
/// A simple view showing a fixed text
|
||||
pub struct TextView {
|
||||
@ -147,7 +116,7 @@ impl TextView {
|
||||
// Our resulting size.
|
||||
let my_size =
|
||||
size.or_min((self.width.unwrap_or(0), self.rows.len()));
|
||||
self.last_size = Some(cache_size(my_size, size));
|
||||
self.last_size = Some(SizeCache::build(my_size, size));
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,7 +236,7 @@ impl View for TextView {
|
||||
|
||||
fn get_min_size(&mut self, size: Vec2) -> Vec2 {
|
||||
self.compute_rows(size);
|
||||
Vec2::new(self.width.unwrap_or(0), self.rows.len())
|
||||
size.or_min((self.width.unwrap_or(0), self.rows.len()))
|
||||
}
|
||||
|
||||
fn take_focus(&mut self) -> bool {
|
||||
|
Loading…
Reference in New Issue
Block a user