From df5ff808e32075da44522961dcc6be3ca591cc7c Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Sun, 7 Jun 2015 20:58:10 -0700 Subject: [PATCH] Basic linear layout implementation Not tested yet --- src/lib.rs | 2 +- src/vec.rs | 23 ++++- src/view/box_view.rs | 15 ++-- src/view/linear_layout.rs | 183 ++++++++++++++++++++++++++++++++++++++ src/view/mod.rs | 110 +++++++---------------- src/view/request.rs | 53 +++++++++++ src/view/view_wrapper.rs | 5 +- 7 files changed, 300 insertions(+), 91 deletions(-) create mode 100644 src/view/linear_layout.rs create mode 100644 src/view/request.rs diff --git a/src/lib.rs b/src/lib.rs index 4b06900..f53abda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,8 +39,8 @@ use std::collections::HashMap; use std::path::Path; use vec::Vec2; -use view::View; use printer::Printer; +use view::View; use view::{StackView,Selector}; use event::{Event,ToEvent,Key,EventResult,Callback}; diff --git a/src/vec.rs b/src/vec.rs index 9965c69..eec50ce 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -1,10 +1,10 @@ //! Points on the 2D character grid. use std::ops::{Add, Sub, Mul, Div}; -use std::cmp::{min,max}; +use std::cmp::{min,max,Ordering}; /// Simple 2D size, in characters. -#[derive(Clone,Copy)] +#[derive(Clone,Copy,PartialEq)] pub struct Vec2 { /// X coordinate (column), from left to right. pub x: usize, @@ -12,6 +12,15 @@ pub struct Vec2 { pub y: usize, } +impl PartialOrd for Vec2 { + fn partial_cmp(&self, other: &Vec2) -> Option { + if self == other { Some(Ordering::Equal) } + else if self.x < other.x && self.y < other.y { Some(Ordering::Less) } + else if self.x > other.x && self.y > other.y { Some(Ordering::Greater) } + else { None } + } +} + impl Vec2 { /// Creates a new Vec2 from coordinates. pub fn new(x: usize, y: usize) -> Self { @@ -45,6 +54,16 @@ impl Vec2 { pub fn zero() -> Self { Vec2::new(0,0) } + + /// Returns (max(self.x,other.x), self.y+other.y) + pub fn stack_vertical(&self, other: &Vec2) -> Vec2 { + Vec2::new(max(self.x, other.x), self.y + other.y) + } + + /// Returns (self.x+other.x, max(self.y,other.y)) + pub fn stack_horizontal(&self, other: &Vec2) -> Vec2 { + Vec2::new(self.x + other.x, max(self.y, other.y)) + } } /// A generic trait for converting a value into a 2D vector. diff --git a/src/view/box_view.rs b/src/view/box_view.rs index 3c42016..f300138 100644 --- a/src/view/box_view.rs +++ b/src/view/box_view.rs @@ -2,13 +2,12 @@ use vec::{Vec2,ToVec2}; use super::{View,ViewWrapper,SizeRequest}; /// BoxView is a wrapper around an other view, with a given minimum size. -pub struct BoxView { +pub struct BoxView { size: Vec2, - - content: Box, + view: T, } -impl BoxView { +impl BoxView { /// Creates a new BoxView with the given minimum size and content /// /// # Example @@ -18,17 +17,17 @@ impl BoxView { /// // Creates a 20x4 BoxView with a TextView content. /// let view = BoxView::new((20,4), TextView::new("Hello!")); /// ``` - pub fn new(size: S, view: V) -> Self { + pub fn new(size: S, view: T) -> Self { BoxView { size: size.to_vec2(), - content: Box::new(view), + view: view, } } } -impl ViewWrapper for BoxView { +impl ViewWrapper for BoxView { - wrap_impl!(self.content); + wrap_impl!(&self.view); fn wrap_get_min_size(&self, _: SizeRequest) -> Vec2 { self.size diff --git a/src/view/linear_layout.rs b/src/view/linear_layout.rs new file mode 100644 index 0000000..c069154 --- /dev/null +++ b/src/view/linear_layout.rs @@ -0,0 +1,183 @@ +use view::{View,SizeRequest,DimensionRequest}; +use vec::Vec2; +use printer::Printer; + +struct Child { + view: Box, + size: Vec2, + weight: usize, +} + +pub struct LinearLayout { + children: Vec, + orientation: Orientation, +} + +pub enum Orientation { + Horizontal, + Vertical, +} + +impl Orientation { + fn get(&self, v: &Vec2) -> usize { + match *self { + Orientation::Horizontal => v.x, + Orientation::Vertical => v.y, + } + } + + fn swap(&self) -> Self { + match *self { + Orientation::Horizontal => Orientation::Vertical, + Orientation::Vertical => Orientation::Horizontal, + } + } + + fn get_ref<'a,'b>(&'a self, v: &'b mut Vec2) -> &'b mut usize { + match *self { + Orientation::Horizontal => &mut v.x, + Orientation::Vertical => &mut v.y, + } + } + + fn stack<'a,T: Iterator>(&self, iter: T) -> Vec2 { + match *self { + Orientation::Horizontal => iter.fold(Vec2::zero(), |a,b| a.stack_horizontal(&b)), + Orientation::Vertical => iter.fold(Vec2::zero(), |a,b| a.stack_vertical(&b)), + } + } +} + +impl LinearLayout { + pub fn new(orientation: Orientation) -> Self { + LinearLayout { + children: Vec::new(), + orientation: orientation, + } + } + + pub fn weight(mut self, weight: usize) -> Self { + self.children.last_mut().unwrap().weight = weight; + + self + } + + pub fn child(mut self, view: V) -> Self { + self.children.push(Child { + view: Box::new(view), + size: Vec2::zero(), + weight: 0, + }); + + self + } + + pub fn vertical() -> Self { + LinearLayout::new(Orientation::Vertical) + } + pub fn horizontal() -> Self { + LinearLayout::new(Orientation::Horizontal) + } +} + +fn find_max(list: &Vec) -> usize { + let mut max_value = 0; + let mut max = 0; + for (i,&x) in list.iter().enumerate() { + if x > max_value { + max_value = x; + max = i; + } + } + max +} + +fn share(total: usize, weights: Vec) -> Vec { + if weights.len() == 0 { return Vec::new(); } + + let sum_weight = weights.iter().fold(0,|a,b| a+b); + if sum_weight == 0 { + return (0..weights.len()).map(|_| 0).collect(); + } + + let mut base = Vec::with_capacity(weights.len()); + let mut rest = Vec::with_capacity(weights.len()); + let mut extra = total; + + for weight in weights.iter() { + let b = total * weight / sum_weight; + extra -= b; + base.push(b); + rest.push(total * weight - b*sum_weight); + } + + // TODO: better to sort (base,rest) as one array and pick the extra first. + for _ in 0..extra { + let i = find_max(&rest); + rest[i] = 0; + base[i] += 1; + } + + base +} + +impl View for LinearLayout { + fn draw(&mut self, printer: &Printer) { + // Use pre-computed sizes + let mut offset = Vec2::zero(); + for child in self.children.iter_mut() { + child.view.draw(&printer.sub_printer(offset, child.size, true)); + + *self.orientation.get_ref(&mut offset) += self.orientation.get(&child.size); + } + } + + fn layout(&mut self, size: Vec2) { + // Compute the very minimal required size + let req = SizeRequest{ + w: DimensionRequest::AtMost(size.x), + h: DimensionRequest::AtMost(size.y), + }; + let min_sizes: Vec = self.children.iter().map(|child| child.view.get_min_size(req)).collect(); + let min_size = self.orientation.stack(min_sizes.iter()); + + // Emulate 'non-strict inequality' on integers + // (default comparison on Vec2 is strict) + if !(min_size < size+(1,1)) { + // Error! Not enough space! Emergency procedures! + return + } + + // Now share this extra space among everyone + + let extras = { + let extra = size - min_size; + let space = self.orientation.get(&extra); + share(space, self.children.iter().map(|child| child.weight).collect()) + }; + + for (child,(child_size,extra)) in self.children.iter_mut().zip(min_sizes.iter().zip(extras.iter())) { + let mut child_size = *child_size; + *self.orientation.get_ref(&mut child_size) += *extra; + *self.orientation.swap().get_ref(&mut child_size) = self.orientation.swap().get(&size); + child.size = child_size; + child.view.layout(child_size); + } + } + + fn get_min_size(&self, req: SizeRequest) -> Vec2 { + // First, make a naive scenario: everything will work fine. + let sizes: Vec = self.children.iter().map(|view| view.view.get_min_size(req)).collect(); + self.orientation.stack(sizes.iter()) + + + // Did it work? Champagne! + + + // Ok, so maybe it didn't. + // Last chance: did someone lie about his needs? + // Could we squash him a little? + + // Find out who's fluid, if any. + } +} diff --git a/src/view/mod.rs b/src/view/mod.rs index dcb02eb..cee7053 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -1,43 +1,50 @@ //! Defines various views to use when creating the layout. #[macro_use] mod view_wrapper; + mod box_view; -mod stack_view; -mod text_view; -mod key_event_view; -mod view_path; -mod dialog; mod button; -mod sized_view; +mod dialog; +mod edit_view; mod full_view; mod id_view; +mod key_event_view; +mod linear_layout; +mod request; mod shadow_view; -mod edit_view; -mod select_view; - mod scroll; +mod select_view; +mod sized_view; +mod stack_view; +mod text_view; +mod view_path; + use std::any::Any; -pub use self::view_path::ViewPath; -pub use self::key_event_view::KeyEventView; -pub use self::box_view::BoxView; -pub use self::stack_view::StackView; -pub use self::text_view::TextView; -pub use self::dialog::Dialog; -pub use self::button::Button; -pub use self::sized_view::SizedView; -pub use self::view_wrapper::ViewWrapper; -pub use self::full_view::FullView; -pub use self::id_view::IdView; -pub use self::shadow_view::ShadowView; -pub use self::edit_view::EditView; -pub use self::select_view::SelectView; +use event::{Event,EventResult}; +use vec::Vec2; +use printer::Printer; + +pub use self::request::{DimensionRequest,SizeRequest}; pub use self::scroll::ScrollBase; -use event::{Event,EventResult}; -use vec::{Vec2,ToVec2}; -use printer::Printer; +pub use self::box_view::BoxView; +pub use self::button::Button; +pub use self::dialog::Dialog; +pub use self::edit_view::EditView; +pub use self::full_view::FullView; +pub use self::id_view::IdView; +pub use self::key_event_view::KeyEventView; +pub use self::linear_layout::LinearLayout; +pub use self::view_path::ViewPath; +pub use self::select_view::SelectView; +pub use self::shadow_view::ShadowView; +pub use self::stack_view::StackView; +pub use self::text_view::TextView; +pub use self::sized_view::SizedView; +pub use self::view_wrapper::ViewWrapper; + /// Main trait defining a view behaviour. pub trait View { @@ -68,54 +75,3 @@ pub enum Selector<'a> { Path(&'a ViewPath), } -/// Describe constraints on a view layout in one dimension. -#[derive(PartialEq,Clone,Copy)] -pub enum DimensionRequest { - /// The view must use exactly the attached size. - Fixed(usize), - /// The view is free to choose its size if it stays under the limit. - AtMost(usize), - /// No clear restriction apply. - Unknown, -} - -impl DimensionRequest { - /// Returns a new request, reduced from the original by the given offset. - pub fn reduced(self, offset: usize) -> Self { - match self { - DimensionRequest::Fixed(w) => DimensionRequest::Fixed(w - offset), - DimensionRequest::AtMost(w) => DimensionRequest::AtMost(w - offset), - DimensionRequest::Unknown => DimensionRequest::Unknown, - } - } -} - -/// Describes constraints on a view layout. -#[derive(PartialEq,Clone,Copy)] -pub struct SizeRequest { - /// Restriction on the view width - pub w: DimensionRequest, - /// Restriction on the view height - pub h: DimensionRequest, -} - -impl SizeRequest { - /// Returns a new SizeRequest, reduced from the original by the given offset. - pub fn reduced(self, offset: T) -> Self { - let ov = offset.to_vec2(); - SizeRequest { - w: self.w.reduced(ov.x), - h: self.h.reduced(ov.y), - } - } - - /// Creates a new dummy request, with no restriction. - pub fn dummy() -> Self { - SizeRequest { - w: DimensionRequest::Unknown, - h: DimensionRequest::Unknown, - } - } -} - - diff --git a/src/view/request.rs b/src/view/request.rs new file mode 100644 index 0000000..e5a02d4 --- /dev/null +++ b/src/view/request.rs @@ -0,0 +1,53 @@ +use vec::ToVec2; + +/// Describe constraints on a view layout in one dimension. +#[derive(PartialEq,Clone,Copy)] +pub enum DimensionRequest { + /// The view must use exactly the attached size. + Fixed(usize), + /// The view is free to choose its size if it stays under the limit. + AtMost(usize), + /// No clear restriction apply. + Unknown, +} + +impl DimensionRequest { + /// Returns a new request, reduced from the original by the given offset. + pub fn reduced(self, offset: usize) -> Self { + match self { + DimensionRequest::Fixed(w) => DimensionRequest::Fixed(w - offset), + DimensionRequest::AtMost(w) => DimensionRequest::AtMost(w - offset), + DimensionRequest::Unknown => DimensionRequest::Unknown, + } + } +} + +/// Describes constraints on a view layout. +#[derive(PartialEq,Clone,Copy)] +pub struct SizeRequest { + /// Restriction on the view width + pub w: DimensionRequest, + /// Restriction on the view height + pub h: DimensionRequest, +} + +impl SizeRequest { + /// Returns a new SizeRequest, reduced from the original by the given offset. + pub fn reduced(self, offset: T) -> Self { + let ov = offset.to_vec2(); + SizeRequest { + w: self.w.reduced(ov.x), + h: self.h.reduced(ov.y), + } + } + + /// Creates a new dummy request, with no restriction. + pub fn dummy() -> Self { + SizeRequest { + w: DimensionRequest::Unknown, + h: DimensionRequest::Unknown, + } + } +} + + diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index f70e585..77c4a3c 100644 --- a/src/view/view_wrapper.rs +++ b/src/view/view_wrapper.rs @@ -77,7 +77,7 @@ impl View for T { /// /// If the wrapped view is in a box, just name it in the macro: /// -/// ```rust +/// ```no_run /// # #[macro_use] extern crate cursive; /// # use cursive::view::{View,ViewWrapper}; /// struct BoxFooView { @@ -87,13 +87,12 @@ impl View for T { /// impl ViewWrapper for BoxFooView { /// wrap_impl!(self.content); /// } -/// /// # fn main() { } /// ``` /// /// If the content is directly a view, reference it: /// -/// ``` +/// ```no_run /// # #[macro_use] extern crate cursive; /// # use cursive::view::{View,ViewWrapper}; /// struct FooView {