mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-23 17:35:00 +00:00
Basic linear layout implementation
Not tested yet
This commit is contained in:
parent
14a18ce760
commit
df5ff808e3
@ -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};
|
||||
|
23
src/vec.rs
23
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<Ordering> {
|
||||
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.
|
||||
|
@ -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<T: View> {
|
||||
size: Vec2,
|
||||
|
||||
content: Box<View>,
|
||||
view: T,
|
||||
}
|
||||
|
||||
impl BoxView {
|
||||
impl <T: View> BoxView<T> {
|
||||
/// 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<S: ToVec2, V: View + 'static>(size: S, view: V) -> Self {
|
||||
pub fn new<S: ToVec2>(size: S, view: T) -> Self {
|
||||
BoxView {
|
||||
size: size.to_vec2(),
|
||||
content: Box::new(view),
|
||||
view: view,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewWrapper for BoxView {
|
||||
impl <T: View> ViewWrapper for BoxView<T> {
|
||||
|
||||
wrap_impl!(self.content);
|
||||
wrap_impl!(&self.view);
|
||||
|
||||
fn wrap_get_min_size(&self, _: SizeRequest) -> Vec2 {
|
||||
self.size
|
||||
|
183
src/view/linear_layout.rs
Normal file
183
src/view/linear_layout.rs
Normal file
@ -0,0 +1,183 @@
|
||||
use view::{View,SizeRequest,DimensionRequest};
|
||||
use vec::Vec2;
|
||||
use printer::Printer;
|
||||
|
||||
struct Child {
|
||||
view: Box<View>,
|
||||
size: Vec2,
|
||||
weight: usize,
|
||||
}
|
||||
|
||||
pub struct LinearLayout {
|
||||
children: Vec<Child>,
|
||||
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<Item=&'a Vec2>>(&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<V: View + 'static>(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>) -> 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<usize>) -> Vec<usize> {
|
||||
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<Vec2> = 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<Vec2> = 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.
|
||||
}
|
||||
}
|
110
src/view/mod.rs
110
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<T: ToVec2>(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
53
src/view/request.rs
Normal file
53
src/view/request.rs
Normal file
@ -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<T: ToVec2>(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ impl <T: ViewWrapper> 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 <T: ViewWrapper> 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<T: View> {
|
||||
|
Loading…
Reference in New Issue
Block a user