diff --git a/examples/linear.rs b/examples/linear.rs index 0a5341d..ddb0805 100644 --- a/examples/linear.rs +++ b/examples/linear.rs @@ -17,6 +17,9 @@ fn main() { .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. + .child(BoxView::fixed_width(30, TextView::new(text))) + .child(BoxView::fixed_width(30, TextView::new(text))) + .child(BoxView::fixed_width(30, TextView::new(text))) .child(BoxView::fixed_width(30, TextView::new(text)))) .button("Quit", |s| s.quit()) .h_align(HAlign::Center)); diff --git a/src/direction.rs b/src/direction.rs new file mode 100644 index 0000000..cb85539 --- /dev/null +++ b/src/direction.rs @@ -0,0 +1,203 @@ +//! Direction-related structures. +//! +//! This module defines two main concepts: [Orientation] and [Direction]. +//! +//! ### Orientation +//! +//! `Orientation` is a simple enum that can take two values: +//! `Horizontal` or `Vertical`. +//! +//! ### Direction +//! +//! `Direction` is a bit more complex, and can be of two kinds: +//! * Absolute direction: left, up, right, or down +//! * Relative direction: front or back. +//! Its actual direction depends on the orientation. + +use vec::Vec2; + +/// Describes a vertical or horizontal orientation for a view. +#[derive(Clone,Copy,Debug,PartialEq)] +pub enum Orientation { + /// Horizontal orientation + Horizontal, + /// Vertical orientation + Vertical, +} + +impl Orientation { + /// Returns the component of `v` corresponding to this orientation. + /// + /// (`Horizontal` will return the x value, + /// and `Vertical` will return the y value.) + pub fn get(&self, v: &Vec2) -> usize { + *v.get(*self) + } + + /// Returns the other orientation. + pub fn swap(&self) -> Self { + match *self { + Orientation::Horizontal => Orientation::Vertical, + Orientation::Vertical => Orientation::Horizontal, + } + } + + /// Returns a mutable reference to the component of the given vector + /// corresponding to this orientation. + pub 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, + } + } + + /// Takes an iterator on sizes, and stack them in the current orientation, + /// returning the size of the required bounding box. + /// + /// For an horizontal view, returns (Sum(x), Max(y)). + /// For a vertical view, returns (Max(x),Sum(y)). + pub 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)) + } + } + } + + /// 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 + } +} + +/// Represents a direction, either absolute or orientation-dependent. +/// +/// * Absolute directions are Up, Down, Left, and Right. +/// * Relative directions are Front and Back. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Direction { + /// An absolute direction. + Abs(Absolute), + /// A direction relative to the current orientation. + Rel(Relative), +} + +impl Direction { + /// Returns the relative direction for the given orientation. + pub fn relative(self, orientation: Orientation) -> Option { + match self { + Direction::Abs(abs) => abs.relative(orientation), + Direction::Rel(rel) => Some(rel), + } + } + + /// Returns the absolute direction in the given `orientation`. + pub fn absolute(self, orientation: Orientation) -> Absolute { + match self { + Direction::Abs(abs) => abs, + Direction::Rel(rel) => rel.absolute(orientation), + } + } + + /// Shortcut to create `Direction::Rel(Relative::Back)` + pub fn back() -> Self { + Direction::Rel(Relative::Back) + } + + /// Shortcut to create `Direction::Rel(Relative::Front)` + pub fn front() -> Self { + Direction::Rel(Relative::Front) + } + + /// Shortcut to create `Direction::Abs(Absolute::Left)` + pub fn left() -> Self { + Direction::Abs(Absolute::Left) + } + + /// Shortcut to create `Direction::Abs(Absolute::Right)` + pub fn right() -> Self { + Direction::Abs(Absolute::Right) + } + + /// Shortcut to create `Direction::Abs(Absolute::Up)` + pub fn up() -> Self { + Direction::Abs(Absolute::Up) + } + + /// Shortcut to create `Direction::Abs(Absolute::Down)` + pub fn down() -> Self { + Direction::Abs(Absolute::Down) + } + + /// Shortcut to create `Direction::Abs(Absolute::None)` + pub fn none() -> Self { + Direction::Abs(Absolute::None) + } +} + +/// Direction relative to an orientation. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Relative { + /// Front relative direction. + /// + /// * Horizontally, this means `Left` + /// * Vertically, this means `Up` + /// + /// TODO: handle right-to-left? + Front, + + /// Back relative direction. + /// + /// * Horizontally, this means `Right` + /// * Vertically, this means `Down`. + Back, +} + +impl Relative { + /// Returns the absolute direction in the given `orientation`. + pub fn absolute(self, orientation: Orientation) -> Absolute { + match (orientation, self) { + (Orientation::Horizontal, Relative::Front) => Absolute::Left, + (Orientation::Horizontal, Relative::Back) => Absolute::Right, + (Orientation::Vertical, Relative::Front) => Absolute::Up, + (Orientation::Vertical, Relative::Back) => Absolute::Down, + } + } +} + +/// Absolute direction (up, down, left, right). +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Absolute { + /// Left + Left, + /// Up + Up, + /// Right + Right, + /// Down + Down, + + /// No real direction + None, +} + +impl Absolute { + /// Returns the relative direction for the given orientation. + /// + /// Returns `None` when the direction does not apply to the given + /// orientation (ex: `Left` and `Vertical`). + pub fn relative(self, orientation: Orientation) -> Option { + match (orientation, self) { + (Orientation::Horizontal, Absolute::Left) | + (Orientation::Vertical, Absolute::Up) => Some(Relative::Front), + (Orientation::Horizontal, Absolute::Right) | + (Orientation::Vertical, Absolute::Down) => Some(Relative::Back), + _ => None, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 536b357..9622118 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,12 +52,12 @@ pub mod vec; pub mod theme; pub mod align; pub mod menu; +pub mod direction; // This probably doesn't need to be public? mod printer; mod menubar; mod xy; -mod orientation; mod div; mod utf8; @@ -65,7 +65,6 @@ mod utf8; mod backend; pub use xy::XY; -pub use orientation::Orientation; pub use printer::Printer; use backend::{Backend, NcursesBackend}; diff --git a/src/menu.rs b/src/menu.rs index 55cac5a..dd4f2a5 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -89,7 +89,9 @@ impl MenuTree { } /// Adds a actionnable leaf to the end of this tree - chainable variant. - pub fn leaf(self, title: &str, cb: F) -> Self { + pub fn leaf(self, title: &str, cb: F) -> Self + where F: 'static + Fn(&mut Cursive) + { self.with(|menu| menu.add_leaf(title, cb)) } diff --git a/src/orientation.rs b/src/orientation.rs index fc7e03d..e69de29 100644 --- a/src/orientation.rs +++ b/src/orientation.rs @@ -1,61 +0,0 @@ -//! Define an Orientation and associated methods. -use vec::Vec2; - -/// Describes a vertical or horizontal orientation for a view. -#[derive(Clone,Copy,Debug,PartialEq)] -pub enum Orientation { - /// Horizontal orientation - Horizontal, - /// Vertical orientation - Vertical, -} - -impl Orientation { - /// Returns the component of `v` corresponding to this orientation. - /// - /// (`Horizontal` will return the x value, - /// and `Vertical` will return the y value.) - pub fn get(&self, v: &Vec2) -> usize { - *v.get(*self) - } - - /// Returns the other orientation. - pub fn swap(&self) -> Self { - match *self { - Orientation::Horizontal => Orientation::Vertical, - Orientation::Vertical => Orientation::Horizontal, - } - } - - /// Returns a mutable reference to the component of the given vector - /// corresponding to this orientation. - pub 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, - } - } - - /// Takes an iterator on sizes, and stack them in the current orientation, - /// returning the size of the required bounding box. - /// - /// For an horizontal view, returns (Sum(x), Max(y)). - /// For a vertical view, returns (Max(x),Sum(y)). - pub 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)) - } - } - } - - /// 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 - } -} diff --git a/src/vec.rs b/src/vec.rs index df8e595..b394c2c 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -1,6 +1,6 @@ //! Points on the 2D character grid. use XY; -use orientation::Orientation; +use direction::Orientation; use std::ops::{Add, Div, Mul, Sub}; use std::cmp::{Ordering, max, min}; diff --git a/src/view/button.rs b/src/view/button.rs index 744114d..1967f82 100644 --- a/src/view/button.rs +++ b/src/view/button.rs @@ -1,5 +1,6 @@ use std::rc::Rc; +use direction::Direction; use theme::ColorStyle; use Cursive; use vec::Vec2; @@ -58,7 +59,7 @@ impl View for Button { } } - fn take_focus(&mut self) -> bool { + fn take_focus(&mut self, _: Direction) -> bool { true } } diff --git a/src/view/dialog.rs b/src/view/dialog.rs index d55cb0a..267c34b 100644 --- a/src/view/dialog.rs +++ b/src/view/dialog.rs @@ -2,6 +2,7 @@ use std::cmp::max; use std::any::Any; use Cursive; +use direction; use align::*; use event::*; use theme::ColorStyle; @@ -246,16 +247,34 @@ impl View for Dialog { EventResult::Ignored => { match event { // Up goes back to the content - Event::Key(Key::Up) | - Event::Key(Key::Tab) | - Event::Shift(Key::Tab) => { - if self.content.take_focus() { + Event::Key(Key::Up) => { + if self.content + .take_focus(direction::Direction::down()) { self.focus = Focus::Content; EventResult::Consumed(None) } else { EventResult::Ignored } } + Event::Shift(Key::Tab) => { + if self.content + .take_focus(direction::Direction::back()) { + self.focus = Focus::Content; + EventResult::Consumed(None) + } else { + EventResult::Ignored + } + } + Event::Key(Key::Tab) => { + if self.content + .take_focus(direction::Direction::front()) { + self.focus = Focus::Content; + EventResult::Consumed(None) + } else { + EventResult::Ignored + } + + } // Left and Right move to other buttons Event::Key(Key::Right) if i + 1 < self.buttons @@ -276,9 +295,10 @@ impl View for Dialog { } } - fn take_focus(&mut self) -> bool { - // TODO: add a direction to the focus. Meanwhile, takes button first. - if self.content.take_focus() { + fn take_focus(&mut self, source: direction::Direction) -> bool { + // Dialogs aren't meant to be used in layouts, so... + // Let's be super lazy and not even care about the focus source. + if self.content.take_focus(source) { self.focus = Focus::Content; true } else if !self.buttons.is_empty() { diff --git a/src/view/edit_view.rs b/src/view/edit_view.rs index 0b82743..1ac29ef 100644 --- a/src/view/edit_view.rs +++ b/src/view/edit_view.rs @@ -1,6 +1,7 @@ use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; +use direction::Direction; use theme::{ColorStyle, Effect}; use vec::Vec2; use view::{IdView, View}; @@ -144,7 +145,7 @@ impl View for EditView { Vec2::new(self.min_length, 1) } - fn take_focus(&mut self) -> bool { + fn take_focus(&mut self, _: Direction) -> bool { true } diff --git a/src/view/full_view.rs b/src/view/full_view.rs index 8435458..00e2851 100644 --- a/src/view/full_view.rs +++ b/src/view/full_view.rs @@ -1,5 +1,5 @@ use view::{View, ViewWrapper}; -use orientation::Orientation; +use direction::Orientation; use vec::Vec2; /// Simple wrapper view that asks for all the space it can get. diff --git a/src/view/linear_layout.rs b/src/view/linear_layout.rs index ca70e88..1028760 100644 --- a/src/view/linear_layout.rs +++ b/src/view/linear_layout.rs @@ -1,9 +1,9 @@ use XY; +use direction; use view::View; use view::SizeCache; use vec::Vec2; use Printer; -use orientation::Orientation; use event::{Event, EventResult, Key}; use std::cmp::min; @@ -11,7 +11,7 @@ use std::cmp::min; /// Arranges its children linearly according to its orientation. pub struct LinearLayout { children: Vec, - orientation: Orientation, + orientation: direction::Orientation, focus: usize, cache: Option>, @@ -40,7 +40,7 @@ impl Child { impl LinearLayout { /// Creates a new layout with the given orientation. - pub fn new(orientation: Orientation) -> Self { + pub fn new(orientation: direction::Orientation) -> Self { LinearLayout { children: Vec::new(), orientation: orientation, @@ -77,12 +77,12 @@ impl LinearLayout { /// Creates a new vertical layout. pub fn vertical() -> Self { - LinearLayout::new(Orientation::Vertical) + LinearLayout::new(direction::Orientation::Vertical) } /// Creates a new horizontal layout. pub fn horizontal() -> Self { - LinearLayout::new(Orientation::Horizontal) + LinearLayout::new(direction::Orientation::Horizontal) } // If the cache can be used, return the cached size. @@ -110,15 +110,60 @@ impl LinearLayout { .any(View::needs_relayout) } + /// Returns a cyclic mutable iterator starting with the child in focus + fn iter_mut<'a>(&'a mut self, from_focus: bool, + direction: direction::Relative) + -> Box + 'a> { + + match direction { + direction::Relative::Front => { + let start = if from_focus { + self.focus + } else { + 0 + }; + + Box::new(self.children.iter_mut().enumerate().skip(start)) + } + direction::Relative::Back => { + let end = if from_focus { + self.focus + 1 + } else { + self.children.len() + }; + Box::new(self.children[..end].iter_mut().enumerate().rev()) + } + } + } + + fn move_focus(&mut self, source: direction::Direction) -> EventResult { + + let i = if let Some(i) = source.relative(self.orientation) + .and_then(|rel| { + // The iterator starts at the focused element. + // We don't want that one. + self.iter_mut(true, rel) + .skip(1) + .filter_map(|p| try_focus(p, source)) + .next() + }) { + i + } else { + return EventResult::Ignored; + }; + self.focus = i; + EventResult::Consumed(None) + } + fn focus_prev(&mut self) -> EventResult { if let Some(i) = self.children[..self.focus] .iter_mut() .rev() .map(Child::as_mut) - .position(View::take_focus) { + .position(|v| v.take_focus(direction::Direction::back())) { // We're looking at the list in reverse - self.focus -= i+1; + self.focus -= i + 1; EventResult::Consumed(None) } else { EventResult::Ignored @@ -130,7 +175,7 @@ impl LinearLayout { .iter_mut() .rev() .map(Child::as_mut) - .position(View::take_focus) { + .position(|v| v.take_focus(direction::Direction::front())) { // Our slice doesn't start at 0 self.focus += i + 1; EventResult::Consumed(None) @@ -140,6 +185,15 @@ impl LinearLayout { } } +fn try_focus((i, child): (usize, &mut Child), source: direction::Direction) + -> Option { + if child.view.take_focus(source) { + Some(i) + } else { + None + } +} + impl View for LinearLayout { fn draw(&mut self, printer: &Printer) { // Use pre-computed sizes @@ -282,16 +336,25 @@ impl View for LinearLayout { compromise } - fn take_focus(&mut self) -> bool { - if let Some(i) = self.children - .iter_mut() - .map(Child::as_mut) - .position(View::take_focus) { - self.focus = i; - true + fn take_focus(&mut self, source: direction::Direction) -> bool { + // In what order will we iterate on the children? + let rel = source.relative(self.orientation); + // We activate from_focus only if coming from the "sides". + let i = if let Some(i) = self.iter_mut(rel.is_none(), + rel.unwrap_or(direction::Relative::Front)) + .filter_map(|p| try_focus(p, source)) + .next() { + + // ... we can't update `self.focus` here, + // because rustc thinks we still borrow `self`. + // :( + i } else { - false - } + return false; + }; + + self.focus = i; + true } fn on_event(&mut self, event: Event) -> EventResult { @@ -299,31 +362,36 @@ impl View for LinearLayout { EventResult::Ignored => { match event { Event::Shift(Key::Tab) if self.focus > 0 => { - self.focus_prev() + self.move_focus(direction::Direction::back()) } Event::Key(Key::Tab) if self.focus + 1 < self.children.len() => { - self.focus_next() + self.move_focus(direction::Direction::front()) } - Event::Key(Key::Left) if self.orientation == - Orientation::Horizontal && - self.focus > 0 => { - self.focus_prev() + Event::Key(Key::Left) + if self.orientation == + direction::Orientation::Horizontal && + self.focus > 0 => { + self.move_focus(direction::Direction::right()) } Event::Key(Key::Up) if self.orientation == - Orientation::Vertical && - self.focus > 0 => self.focus_prev(), - Event::Key(Key::Right) if self.orientation == - Orientation::Horizontal && - self.focus + 1 < - self.children.len() => { - self.focus_next() + direction::Orientation::Vertical && + self.focus > 0 => { + self.move_focus(direction::Direction::down()) } - Event::Key(Key::Down) if self.orientation == - Orientation::Vertical && - self.focus + 1 < - self.children.len() => { - self.focus_next() + Event::Key(Key::Right) + if self.orientation == + direction::Orientation::Horizontal && + self.focus + 1 < + self.children.len() => { + self.move_focus(direction::Direction::left()) + } + Event::Key(Key::Down) + if self.orientation == + direction::Orientation::Vertical && + self.focus + 1 < + self.children.len() => { + self.move_focus(direction::Direction::up()) } _ => EventResult::Ignored, } diff --git a/src/view/mod.rs b/src/view/mod.rs index 0517792..5175188 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -25,7 +25,8 @@ //! * By default, `View::layout()` should be called before any call to //! `View::draw()` with the same available size. The only exceptions is //! when both following conditions are met: -//! * The available size has not changed since the last call to `View::layout()` +//! * The available size has not changed since the last call to +//! `View::layout()` //! * `View::needs_relayout()` returns `false` //! //! In this case, it is safe to omit the call to `View::layout()`. @@ -65,6 +66,7 @@ mod tracked_view; use std::any::Any; use XY; +use direction::Direction; use event::{Event, EventResult}; use vec::Vec2; use Printer; @@ -135,7 +137,11 @@ pub trait View { } /// This view is offered focus. Will it take it? - fn take_focus(&mut self) -> bool { + /// + /// `source` indicates where the focus comes from. + /// When the source is unclear, `Front` is usually used. + fn take_focus(&mut self, source: Direction) -> bool { + let _ = source; false } } @@ -178,7 +184,7 @@ impl SizeCache { /// * for each dimension, `constrained = (size == req)` fn build(size: Vec2, req: Vec2) -> XY { XY::new(SizeCache::new(size.x, size.x == req.x), - SizeCache::new(size.y, size.y == req.y)) + SizeCache::new(size.y, size.y == req.y)) } } diff --git a/src/view/select_view.rs b/src/view/select_view.rs index e2d9d41..394ca07 100644 --- a/src/view/select_view.rs +++ b/src/view/select_view.rs @@ -2,6 +2,7 @@ use std::cmp::min; use std::rc::Rc; use Cursive; +use direction::Direction; use view::{IdView, View}; use align::{Align, HAlign, VAlign}; use view::scroll::ScrollBase; @@ -215,7 +216,7 @@ impl View for SelectView { EventResult::Consumed(None) } - fn take_focus(&mut self) -> bool { + fn take_focus(&mut self, _: Direction) -> bool { true } diff --git a/src/view/stack_view.rs b/src/view/stack_view.rs index 8ed6bab..7bb634a 100644 --- a/src/view/stack_view.rs +++ b/src/view/stack_view.rs @@ -1,5 +1,6 @@ use std::any::Any; +use direction::Direction; use backend::Backend; use vec::Vec2; use view::{Offset, Position, Selector, ShadowView, View}; @@ -96,7 +97,7 @@ impl View for StackView { // We do it here instead of when adding a new layer because...? // (TODO: try to make it during layer addition) if layer.virgin { - layer.view.take_focus(); + layer.view.take_focus(Direction::none()); layer.virgin = false; } } @@ -111,10 +112,10 @@ impl View for StackView { .fold(Vec2::new(1, 1), Vec2::max) } - fn take_focus(&mut self) -> bool { + fn take_focus(&mut self, source: Direction) -> bool { match self.layers.last_mut() { None => false, - Some(mut v) => v.view.take_focus(), + Some(mut v) => v.view.take_focus(source), } } diff --git a/src/view/text_view.rs b/src/view/text_view.rs index 5cc94cb..6473a24 100644 --- a/src/view/text_view.rs +++ b/src/view/text_view.rs @@ -1,4 +1,5 @@ use XY; +use direction::Direction; use vec::Vec2; use view::View; use view::SizeCache; @@ -239,7 +240,7 @@ impl View for TextView { size.or_min((self.width.unwrap_or(0), self.rows.len())) } - fn take_focus(&mut self) -> bool { + fn take_focus(&mut self, _: Direction) -> bool { self.scrollbase.scrollable() } diff --git a/src/view/view_wrapper.rs b/src/view/view_wrapper.rs index 0c9b98e..de73b7d 100644 --- a/src/view/view_wrapper.rs +++ b/src/view/view_wrapper.rs @@ -1,5 +1,6 @@ use std::any::Any; +use direction::Direction; use vec::Vec2; use view::{Selector, View}; use Printer; @@ -37,8 +38,8 @@ pub trait ViewWrapper { } /// Wraps the `take_focus` method. - fn wrap_take_focus(&mut self) -> bool { - self.get_view_mut().take_focus() + fn wrap_take_focus(&mut self, source: Direction) -> bool { + self.get_view_mut().take_focus(source) } /// Wraps the `find` method. @@ -69,8 +70,8 @@ impl View for T { self.wrap_layout(size); } - fn take_focus(&mut self) -> bool { - self.wrap_take_focus() + fn take_focus(&mut self, source: Direction) -> bool { + self.wrap_take_focus(source) } fn find(&mut self, selector: &Selector) -> Option<&mut Any> { diff --git a/src/xy.rs b/src/xy.rs index f7839dc..e09639e 100644 --- a/src/xy.rs +++ b/src/xy.rs @@ -1,4 +1,4 @@ -use orientation::Orientation; +use direction::Orientation; use std::iter; @@ -46,14 +46,12 @@ impl XY { } /// Returns a new XY by calling `f` on `self` and `other` for each axis. - pub fn zip_map V>(self, other: XY, f: F) -> XY { - XY::new(f(self.x, other.x), - f(self.y, other.y)) + pub fn zip_map V>(self, other: XY, f: F) -> XY { + XY::new(f(self.x, other.x), f(self.y, other.y)) } } -impl XY> { - +impl XY> { /// Returns a new XY by calling `unwrap_or` on each axis. pub fn unwrap_or(self, other: XY) -> XY { self.zip_map(other, |s, o| s.unwrap_or(o))