From 7f530e34b755bdd739b688578b61e20732bed057 Mon Sep 17 00:00:00 2001 From: Alexandre Bury Date: Tue, 12 Jul 2016 21:01:11 -0700 Subject: [PATCH] Add XY generic struct --- src/lib.rs | 3 +++ src/printer.rs | 2 +- src/vec.rs | 38 ++++++++++++++---------------- src/view/box_view.rs | 17 +++++++------ src/view/position.rs | 14 ++--------- src/view/text_view.rs | 55 ++++++++++++++++++++++++++++++++----------- src/xy.rs | 48 +++++++++++++++++++++++++++++++++++++ 7 files changed, 121 insertions(+), 56 deletions(-) create mode 100644 src/xy.rs diff --git a/src/lib.rs b/src/lib.rs index ba64014..38a8c2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,12 +47,15 @@ pub mod menu; // This probably doesn't need to be public? mod menubar; +mod xy; mod div; mod utf8; mod backend; +pub use xy::XY; + use backend::{Backend, NcursesBackend}; use std::any::Any; diff --git a/src/printer.rs b/src/printer.rs index 91d2b82..a556348 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -125,7 +125,7 @@ impl Printer { /// # let printer = Printer::new((6,4), theme::load_default()); /// printer.print_box((0,0), (6,4)); /// ``` - pub fn print_box>(&self, start: T, size: T) { + pub fn print_box, S: Into>(&self, start: T, size: S) { let start_v = start.into(); let size_v = size.into() - (1, 1); diff --git a/src/vec.rs b/src/vec.rs index e8051dc..b3ad12e 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -1,16 +1,11 @@ //! Points on the 2D character grid. +use XY; use std::ops::{Add, Div, Mul, Sub}; use std::cmp::{Ordering, max, min}; /// Simple 2D size, in characters. -#[derive(Clone,Copy,PartialEq,Debug)] -pub struct Vec2 { - /// X coordinate (column), from left to right. - pub x: usize, - /// Y coordinate (row), from top to bottom. - pub y: usize, -} +pub type Vec2 = XY; impl PartialOrd for Vec2 { fn partial_cmp(&self, other: &Vec2) -> Option { @@ -27,21 +22,30 @@ impl PartialOrd for Vec2 { } impl Vec2 { - /// Creates a new Vec2 from coordinates. - pub fn new(x: usize, y: usize) -> Self { - Vec2 { x: x, y: y } - } - /// Returns a new Vec2 that is a maximum per coordinate. - pub fn max(a: Self, b: Self) -> Self { + pub fn max, B: Into>(a: A, b: B) -> Self { + let a = a.into(); + let b = b.into(); Vec2::new(max(a.x, b.x), max(a.y, b.y)) } /// Returns a new Vec2 that is no larger than any input in both dimensions. - pub fn min(a: Self, b: Self) -> Self { + pub fn min, B: Into>(a: A, b: B) -> Self { + let a = a.into(); + let b = b.into(); Vec2::new(min(a.x, b.x), min(a.y, b.y)) } + /// Returns the minimum of `self` and `other`. + pub fn or_min>(self, other: T) -> Self { + Vec2::min(self, other) + } + + /// Returns the maximum of `self` and `other`. + pub fn or_max>(self, other: T) -> Self { + Vec2::max(self, other) + } + /// Returns a vector with the X component of self, and y=0. pub fn keep_x(&self) -> Self { Vec2::new(self.x, 0) @@ -68,12 +72,6 @@ impl Vec2 { } } -impl From<(usize, usize)> for Vec2 { - fn from((x, y): (usize, usize)) -> Self { - Vec2::new(x, y) - } -} - impl From<(i32, i32)> for Vec2 { fn from((x, y): (i32, i32)) -> Self { (x as usize, y as usize).into() diff --git a/src/view/box_view.rs b/src/view/box_view.rs index 40776ec..989dfb7 100644 --- a/src/view/box_view.rs +++ b/src/view/box_view.rs @@ -1,5 +1,6 @@ use std::cmp; +use XY; use vec::Vec2; use super::{View, ViewWrapper}; @@ -13,8 +14,7 @@ use super::{View, ViewWrapper}; /// let view = BoxView::fixed_size((20,4), TextView::new("Hello!")); /// ``` pub struct BoxView { - width: Option, - height: Option, + size: XY>, view: T, } @@ -31,8 +31,7 @@ impl BoxView { /// `None` values will use the wrapped view's preferences. pub fn new(width: Option, height: Option, view: T) -> Self { BoxView { - width: width, - height: height, + size: (width, height).into(), view: view, } } @@ -60,15 +59,15 @@ impl ViewWrapper for BoxView { fn wrap_get_min_size(&mut self, req: Vec2) -> Vec2 { - if let (Some(w), Some(h)) = (self.width, self.height) { + if let (Some(w), Some(h)) = self.size.pair() { Vec2::new(w, h) } else { - let req = Vec2::new(min(req.x, self.width), - min(req.y, self.height)); + let req = Vec2::new(min(req.x, self.size.x), + min(req.y, self.size.y)); let child_size = self.view.get_min_size(req); - Vec2::new(self.width.unwrap_or(child_size.x), - self.height.unwrap_or(child_size.y)) + Vec2::new(self.size.x.unwrap_or(child_size.x), + self.size.y.unwrap_or(child_size.y)) } } } diff --git a/src/view/position.rs b/src/view/position.rs index 8f18d35..4e6eb6c 100644 --- a/src/view/position.rs +++ b/src/view/position.rs @@ -1,21 +1,11 @@ use std::cmp::min; +use XY; use vec::Vec2; /// Location of the view on screen -#[derive(PartialEq,Debug,Clone)] -pub struct Position { - /// Horizontal offset - pub x: Offset, - /// Vertical offset - pub y: Offset, -} +pub type Position = XY; impl Position { - /// Creates a new position with the given offsets. - pub fn new(x: Offset, y: Offset) -> Self { - Position { x: x, y: y } - } - /// Returns a position centered on both axis. pub fn center() -> Self { Position::new(Offset::Center, Offset::Center) diff --git a/src/view/text_view.rs b/src/view/text_view.rs index 600c084..d204cf0 100644 --- a/src/view/text_view.rs +++ b/src/view/text_view.rs @@ -1,3 +1,4 @@ +use XY; use vec::Vec2; use view::View; use printer::Printer; @@ -8,6 +9,39 @@ 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 { + 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 { content: String, @@ -17,7 +51,7 @@ pub struct TextView { // ScrollBase make many scrolling-related things easier scrollbase: ScrollBase, - last_size: Option, + last_size: Option>, width: Option, } @@ -87,13 +121,7 @@ impl TextView { fn is_cache_valid(&self, size: Vec2) -> bool { match self.last_size { None => false, - Some(last) => { - if last.x != size.x { - false - } else { - (last.y < self.rows.len()) == (size.y < self.rows.len()) - } - } + Some(ref last) => last.x.accept(size.x) && last.y.accept(size.y), } } @@ -116,7 +144,10 @@ impl TextView { .max() .map(|w| w + scrollbar); - self.last_size = Some(size); + // 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)); } } @@ -231,14 +262,10 @@ impl View for TextView { } fn needs_relayout(&self) -> bool { - self.last_size == None + self.last_size.is_none() } fn get_min_size(&mut self, size: Vec2) -> Vec2 { - // If we have no directive, ask for a single big line. - // TODO: what if the text has newlines?? - // Don't _force_ the max width, but take it if we have to. - self.compute_rows(size); Vec2::new(self.width.unwrap_or(0), self.rows.len()) } diff --git a/src/xy.rs b/src/xy.rs new file mode 100644 index 0000000..e3cab3e --- /dev/null +++ b/src/xy.rs @@ -0,0 +1,48 @@ +use std::iter; + +/// A generic structure with a value for each axis. +#[derive(Debug,Clone,Copy,PartialEq)] +pub struct XY { + /// X-axis value + pub x: T, + /// Y-axis value + pub y: T, +} + +impl XY { + /// Creates a new `XY` from the given values. + pub fn new(x: T, y: T) -> Self { + XY { + x: x, + y: y, + } + } + + /// Destructure self into a pair. + pub fn pair(self) -> (T, T) { + (self.x, self.y) + } + + /// Return a XY with references to this one's values. + pub fn as_ref(&self) -> XY<&T> { + XY::new(&self.x, &self.y) + } + + /// Creates an iterator that returns references to `x`, then `y`. + pub fn iter(&self) -> iter::Chain, iter::Once<&T>> { + iter::once(&self.x).chain(iter::once(&self.y)) + } +} + +impl XY { + /// Creates a `XY` with both `x` and `y` set to `value`. + pub fn both(value: T) -> Self { + XY::new(value, value) + } +} + +impl From<(T, T)> for XY { + fn from((x, y): (T, T)) -> Self { + XY::new(x, y) + } +}