mirror of
https://github.com/FliegendeWurst/cursive.git
synced 2024-11-08 18:30:40 +00:00
Add FixedLayout
This commit is contained in:
parent
28cd51c265
commit
bd6386fd74
@ -154,6 +154,14 @@ impl Direction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the direction opposite `self`.
|
||||
pub fn opposite(self) -> Self {
|
||||
match self {
|
||||
Direction::Abs(abs) => Direction::Abs(abs.opposite()),
|
||||
Direction::Rel(rel) => Direction::Rel(rel.swap()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortcut to create `Direction::Rel(Relative::Back)`
|
||||
pub fn back() -> Self {
|
||||
Direction::Rel(Relative::Back)
|
||||
@ -191,7 +199,7 @@ impl Direction {
|
||||
}
|
||||
|
||||
/// Direction relative to an orientation.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Relative {
|
||||
// TODO: handle right-to-left? (Arabic, ...)
|
||||
/// Front relative direction.
|
||||
@ -217,6 +225,39 @@ impl Relative {
|
||||
(Orientation::Vertical, Relative::Back) => Absolute::Down,
|
||||
}
|
||||
}
|
||||
|
||||
/// Picks one of the two values in a tuple.
|
||||
///
|
||||
/// First one is `self` is `Front`, second one if `self` is `Back`.
|
||||
pub fn pick<T>(self, (front, back): (T, T)) -> T {
|
||||
match self {
|
||||
Relative::Front => front,
|
||||
Relative::Back => back,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the other relative direction.
|
||||
pub fn swap(self) -> Self {
|
||||
match self {
|
||||
Relative::Front => Relative::Back,
|
||||
Relative::Back => Relative::Front,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the relative position of `a` to `b`.
|
||||
///
|
||||
/// If `a < b`, it would be `Front`.
|
||||
/// If `a > b`, it would be `Back`.
|
||||
/// If `a == b`, returns `None`.
|
||||
pub fn a_to_b(a: usize, b: usize) -> Option<Self> {
|
||||
use std::cmp::Ordering;
|
||||
|
||||
match a.cmp(&b) {
|
||||
Ordering::Less => Some(Relative::Front),
|
||||
Ordering::Greater => Some(Relative::Back),
|
||||
Ordering::Equal => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Absolute direction (up, down, left, right).
|
||||
@ -251,4 +292,29 @@ impl Absolute {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the direction opposite `self`.
|
||||
pub fn opposite(self) -> Self {
|
||||
match self {
|
||||
Absolute::Left => Absolute::Right,
|
||||
Absolute::Right => Absolute::Left,
|
||||
Absolute::Up => Absolute::Down,
|
||||
Absolute::Down => Absolute::Up,
|
||||
Absolute::None => Absolute::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits this absolute direction into an orientation and relative direction.
|
||||
///
|
||||
/// For example, `Right` will give `(Horizontal, Back)`.
|
||||
pub fn split(self) -> (Orientation, Relative) {
|
||||
match self {
|
||||
Absolute::Left => (Orientation::Horizontal, Relative::Front),
|
||||
Absolute::Right => (Orientation::Horizontal, Relative::Back),
|
||||
Absolute::Up => (Orientation::Vertical, Relative::Front),
|
||||
Absolute::Down => (Orientation::Vertical, Relative::Back),
|
||||
// TODO: Remove `Absolute::None`
|
||||
Absolute::None => panic!("None direction not supported here"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -522,6 +522,8 @@ pub enum Event {
|
||||
|
||||
// Having a doc-hidden event prevents people from having exhaustive
|
||||
// matches, allowing us to add events in the future.
|
||||
//
|
||||
// In addition we may not want people to listen to the exit event?
|
||||
#[doc(hidden)]
|
||||
/// The application is about to exit.
|
||||
Exit,
|
||||
|
@ -2,12 +2,14 @@
|
||||
|
||||
use crate::backend::Backend;
|
||||
use crate::direction::Orientation;
|
||||
use crate::rect::Rect;
|
||||
use crate::theme::{
|
||||
BorderStyle, ColorStyle, Effect, PaletteColor, Style, Theme,
|
||||
};
|
||||
use crate::utils::lines::simple::{prefix, suffix};
|
||||
use crate::with::With;
|
||||
use crate::Vec2;
|
||||
|
||||
use enumset::EnumSet;
|
||||
use std::cmp::min;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
@ -541,6 +543,13 @@ impl<'a, 'b> Printer<'a, 'b> {
|
||||
self.clone().with(|s| s.enabled &= enabled)
|
||||
}
|
||||
|
||||
/// Returns a new sub-printer for the given viewport.
|
||||
///
|
||||
/// This is a combination of offset + cropped.
|
||||
pub fn windowed(&self, viewport: Rect) -> Self {
|
||||
self.offset(viewport.top_left()).cropped(viewport.size())
|
||||
}
|
||||
|
||||
/// Returns a new sub-printer with a cropped area.
|
||||
///
|
||||
/// The new printer size will be the minimum of `size` and its current size.
|
||||
@ -601,6 +610,9 @@ impl<'a, 'b> Printer<'a, 'b> {
|
||||
}
|
||||
|
||||
/// Returns a new sub-printer with a content offset.
|
||||
///
|
||||
/// This is useful for parent views that only show a subset of their
|
||||
/// child, like `ScrollView`.
|
||||
pub fn content_offset<S>(&self, offset: S) -> Self
|
||||
where
|
||||
S: Into<Vec2>,
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Rectangles on the 2D character grid.
|
||||
use crate::direction::{Absolute, Orientation};
|
||||
use crate::Vec2;
|
||||
|
||||
use std::ops::Add;
|
||||
|
||||
/// A non-empty rectangle on the 2D grid.
|
||||
@ -7,6 +9,7 @@ use std::ops::Add;
|
||||
pub struct Rect {
|
||||
/// Top-left corner, inclusive
|
||||
top_left: Vec2,
|
||||
|
||||
/// Bottom-right corner, inclusive
|
||||
bottom_right: Vec2,
|
||||
}
|
||||
@ -90,6 +93,30 @@ impl Rect {
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the start and end coordinate of one side of this rectangle.
|
||||
///
|
||||
/// Both start and end are inclusive.
|
||||
pub fn side(self, orientation: Orientation) -> (usize, usize) {
|
||||
match orientation {
|
||||
Orientation::Vertical => (self.top(), self.bottom()),
|
||||
Orientation::Horizontal => (self.left(), self.right()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the coordinate of the given edge.
|
||||
///
|
||||
/// All edges are inclusive.
|
||||
pub fn edge(self, side: Absolute) -> usize {
|
||||
match side {
|
||||
Absolute::Left => self.left(),
|
||||
Absolute::Right => self.right(),
|
||||
Absolute::Up => self.top(),
|
||||
Absolute::Down => self.bottom(),
|
||||
// TODO: Remove `None` from `Absolute` enum
|
||||
Absolute::None => panic!("None is not a valid edge."),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the given offset to this rectangle.
|
||||
pub fn offset<V>(&mut self, offset: V)
|
||||
where
|
||||
|
@ -87,6 +87,21 @@ impl XY<usize> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Checked addition with a signed vec.
|
||||
///
|
||||
/// Will return `None` if any coordinates exceeds bounds.
|
||||
pub fn checked_add<O: Into<XY<isize>>>(&self, other: O) -> Option<Self> {
|
||||
let other = other.into();
|
||||
self.zip_map(other, |s, o| {
|
||||
if o > 0 {
|
||||
s.checked_add(o as usize)
|
||||
} else {
|
||||
s.checked_sub((-o) as usize)
|
||||
}
|
||||
})
|
||||
.both()
|
||||
}
|
||||
|
||||
/// Term-by-term integer division that rounds up.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -83,9 +83,7 @@ pub trait View: Any + AnyView {
|
||||
/// View groups should implement this to forward the call to each children.
|
||||
///
|
||||
/// Default implementation is a no-op.
|
||||
fn call_on_any<'a>(&mut self, _: &Selector<'_>, _: AnyCb<'a>) {
|
||||
// TODO: FnMut -> FnOnce once it works
|
||||
}
|
||||
fn call_on_any<'a>(&mut self, _: &Selector<'_>, _: AnyCb<'a>) {}
|
||||
|
||||
/// Moves the focus to the view identified by the given selector.
|
||||
///
|
||||
@ -99,12 +97,10 @@ pub trait View: Any + AnyView {
|
||||
/// This view is offered focus. Will it take it?
|
||||
///
|
||||
/// `source` indicates where the focus comes from.
|
||||
/// When the source is unclear, `Front` is usually used.
|
||||
/// When the source is unclear (for example mouse events),
|
||||
/// `Direction::none()` can be used.
|
||||
///
|
||||
/// Default implementation always return `false`.
|
||||
///
|
||||
/// If the source is `Direction::Abs(Absolute::None)`, it is _recommended_
|
||||
/// not to change the current focus selection.
|
||||
fn take_focus(&mut self, source: Direction) -> bool {
|
||||
let _ = source;
|
||||
false
|
||||
@ -125,6 +121,8 @@ pub trait View: Any + AnyView {
|
||||
/// Returns the type of this view.
|
||||
///
|
||||
/// Useful when you have a `&dyn View`.
|
||||
///
|
||||
/// View implementation don't usually have to override this.
|
||||
fn type_name(&self) -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
269
cursive-core/src/views/fixed_layout.rs
Normal file
269
cursive-core/src/views/fixed_layout.rs
Normal file
@ -0,0 +1,269 @@
|
||||
use crate::direction::{Absolute, Direction, Relative};
|
||||
use crate::event::{Event, EventResult, Key};
|
||||
use crate::rect::Rect;
|
||||
use crate::view::IntoBoxedView;
|
||||
use crate::{Printer, Vec2, View, With};
|
||||
|
||||
/// Arranges its children in a fixed layout.
|
||||
///
|
||||
/// Usually meant to use an external layout engine.
|
||||
pub struct FixedLayout {
|
||||
children: Vec<Child>,
|
||||
focus: usize,
|
||||
}
|
||||
|
||||
struct Child {
|
||||
view: Box<dyn View>,
|
||||
position: Rect,
|
||||
}
|
||||
|
||||
new_default!(FixedLayout);
|
||||
|
||||
impl FixedLayout {
|
||||
/// Returns a new, empty `FixedLayout`.
|
||||
pub fn new() -> Self {
|
||||
FixedLayout {
|
||||
children: Vec::new(),
|
||||
focus: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a child. Chainable variant.
|
||||
pub fn child<V: IntoBoxedView>(self, position: Rect, view: V) -> Self {
|
||||
self.with(|s| s.add_child(position, view))
|
||||
}
|
||||
|
||||
/// Adds a child.
|
||||
pub fn add_child<V: IntoBoxedView>(&mut self, position: Rect, view: V) {
|
||||
self.children.push(Child {
|
||||
view: view.as_boxed_view(),
|
||||
position,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns index of focused inner view
|
||||
pub fn get_focus_index(&self) -> usize {
|
||||
self.focus
|
||||
}
|
||||
|
||||
/// Attemps to set the focus on the given child.
|
||||
///
|
||||
/// Returns `Err(())` if `index >= self.len()`, or if the view at the
|
||||
/// given index does not accept focus.
|
||||
pub fn set_focus_index(&mut self, index: usize) -> Result<(), ()> {
|
||||
if self
|
||||
.children
|
||||
.get_mut(index)
|
||||
.map(|child| child.view.take_focus(Direction::none()))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
self.focus = index;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// How many children are in this view.
|
||||
pub fn len(&self) -> usize {
|
||||
self.children.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if this view has no children.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.children.is_empty()
|
||||
}
|
||||
|
||||
/// Returns a reference to a child.
|
||||
pub fn get_child(&self, i: usize) -> Option<&dyn View> {
|
||||
self.children.get(i).map(|c| &*c.view)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to a child.
|
||||
pub fn get_child_mut(&mut self, i: usize) -> Option<&mut dyn View> {
|
||||
self.children.get_mut(i).map(|c| &mut *c.view)
|
||||
}
|
||||
|
||||
/// Sets the position for the given child.
|
||||
pub fn set_child_position(&mut self, i: usize, position: Rect) {
|
||||
self.children[i].position = position;
|
||||
}
|
||||
|
||||
/// Removes a child.
|
||||
///
|
||||
/// If `i` is within bounds, the removed child will be returned.
|
||||
pub fn remove_child(&mut self, i: usize) -> Option<Box<dyn View>> {
|
||||
if i >= self.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.focus > i
|
||||
|| (self.focus != 0 && self.focus == self.children.len() - 1)
|
||||
{
|
||||
self.focus -= 1;
|
||||
}
|
||||
|
||||
Some(self.children.remove(i).view)
|
||||
}
|
||||
|
||||
fn iter_mut<'a>(
|
||||
source: Direction,
|
||||
children: &'a mut [Child],
|
||||
) -> Box<dyn Iterator<Item = (usize, &mut Child)> + 'a> {
|
||||
let children = children.iter_mut().enumerate();
|
||||
match source {
|
||||
Direction::Rel(Relative::Front) => Box::new(children),
|
||||
Direction::Rel(Relative::Back) => Box::new(children.rev()),
|
||||
Direction::Abs(abs) => {
|
||||
// Sort children by the given direction
|
||||
let mut children: Vec<_> = children.collect();
|
||||
children.sort_by_key(|(_, c)| c.position.edge(abs));
|
||||
Box::new(children.into_iter())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_focus(&mut self, target: Absolute) -> EventResult {
|
||||
let source = Direction::Abs(target.opposite());
|
||||
let (orientation, rel) = target.split();
|
||||
|
||||
fn intersects(a: (usize, usize), b: (usize, usize)) -> bool {
|
||||
a.1 >= b.0 && a.0 <= b.1
|
||||
}
|
||||
|
||||
let current_position = self.children[self.focus].position;
|
||||
let current_side = current_position.side(orientation.swap());
|
||||
let current_edge = current_position.edge(target);
|
||||
|
||||
let children =
|
||||
Self::iter_mut(source, &mut self.children).filter(|(_, c)| {
|
||||
// Only select children actually aligned with us
|
||||
Some(rel)
|
||||
== Relative::a_to_b(current_edge, c.position.edge(target))
|
||||
&& intersects(
|
||||
c.position.side(orientation.swap()),
|
||||
current_side,
|
||||
)
|
||||
});
|
||||
|
||||
for (i, c) in children {
|
||||
if c.view.take_focus(source) {
|
||||
self.focus = i;
|
||||
return EventResult::Consumed(None);
|
||||
}
|
||||
}
|
||||
|
||||
EventResult::Ignored
|
||||
}
|
||||
|
||||
fn check_focus_grab(&mut self, event: &Event) {
|
||||
if let Event::Mouse {
|
||||
offset,
|
||||
position,
|
||||
event,
|
||||
} = *event
|
||||
{
|
||||
if !event.grabs_focus() {
|
||||
return;
|
||||
}
|
||||
|
||||
let position = match position.checked_sub(offset) {
|
||||
None => return,
|
||||
Some(pos) => pos,
|
||||
};
|
||||
|
||||
for (i, child) in self.children.iter_mut().enumerate() {
|
||||
if child.position.contains(position)
|
||||
&& child.view.take_focus(Direction::none())
|
||||
{
|
||||
self.focus = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl View for FixedLayout {
|
||||
fn draw(&self, printer: &Printer) {
|
||||
for child in &self.children {
|
||||
child.view.draw(&printer.windowed(child.position));
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(&mut self, _size: Vec2) {
|
||||
// TODO: re-compute children positions?
|
||||
for child in &mut self.children {
|
||||
child.view.layout(child.position.size());
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(&mut self, event: Event) -> EventResult {
|
||||
if self.is_empty() {
|
||||
return EventResult::Ignored;
|
||||
}
|
||||
|
||||
self.check_focus_grab(&event);
|
||||
|
||||
let child = &mut self.children[self.focus];
|
||||
|
||||
let result = child
|
||||
.view
|
||||
.on_event(event.relativized(child.position.top_left()));
|
||||
|
||||
match result {
|
||||
EventResult::Ignored => match event {
|
||||
Event::Key(Key::Tab) => unimplemented!(),
|
||||
Event::Key(Key::Left) => self.move_focus(Absolute::Left),
|
||||
Event::Key(Key::Right) => self.move_focus(Absolute::Right),
|
||||
Event::Key(Key::Up) => self.move_focus(Absolute::Up),
|
||||
Event::Key(Key::Down) => self.move_focus(Absolute::Down),
|
||||
_ => EventResult::Ignored,
|
||||
},
|
||||
res => res,
|
||||
}
|
||||
}
|
||||
|
||||
fn important_area(&self, size: Vec2) -> Rect {
|
||||
if self.is_empty() {
|
||||
return Rect::from_size((0, 0), size);
|
||||
}
|
||||
|
||||
let child = &self.children[self.focus];
|
||||
|
||||
child.view.important_area(child.position.size())
|
||||
+ child.position.top_left()
|
||||
}
|
||||
|
||||
fn required_size(&mut self, _constraint: Vec2) -> Vec2 {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|c| c.position.bottom_left() + (1, 1))
|
||||
.fold(Vec2::zero(), Vec2::max)
|
||||
}
|
||||
|
||||
fn take_focus(&mut self, source: Direction) -> bool {
|
||||
// TODO: what if source = None?
|
||||
match source {
|
||||
Direction::Abs(Absolute::None) => {
|
||||
// For now, take focus if any view is focusable.
|
||||
for child in &mut self.children {
|
||||
if child.view.take_focus(source) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
source => {
|
||||
for (i, c) in Self::iter_mut(source, &mut self.children) {
|
||||
if c.view.take_focus(source) {
|
||||
self.focus = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -319,7 +319,7 @@ impl LinearLayout {
|
||||
.any(View::needs_relayout)
|
||||
}
|
||||
|
||||
/// Returns a cyclic mutable iterator starting with the child in focus
|
||||
/// Returns a mutable iterator starting with the child in focus
|
||||
fn iter_mut<'a>(
|
||||
&'a mut self,
|
||||
from_focus: bool,
|
||||
|
@ -69,6 +69,7 @@ mod dialog;
|
||||
mod dummy;
|
||||
mod edit_view;
|
||||
mod enableable_view;
|
||||
mod fixed_layout;
|
||||
mod hideable_view;
|
||||
mod last_size_view;
|
||||
mod layer;
|
||||
@ -103,6 +104,7 @@ pub use self::dialog::{Dialog, DialogFocus};
|
||||
pub use self::dummy::DummyView;
|
||||
pub use self::edit_view::EditView;
|
||||
pub use self::enableable_view::EnableableView;
|
||||
pub use self::fixed_layout::FixedLayout;
|
||||
pub use self::hideable_view::HideableView;
|
||||
pub use self::last_size_view::LastSizeView;
|
||||
pub use self::layer::Layer;
|
||||
|
@ -437,6 +437,25 @@ impl<T> XY<Option<T>> {
|
||||
pub fn unwrap_or(self, other: XY<T>) -> XY<T> {
|
||||
self.zip_map(other, Option::unwrap_or)
|
||||
}
|
||||
|
||||
/// Returns a new `XY` if both components are present in `self`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use cursive_core::XY;
|
||||
/// assert_eq!(XY::new(Some(1), None).both(), None);
|
||||
/// assert_eq!(XY::new(Some(1), Some(2)).both(), Some(XY::new(1, 2)));
|
||||
/// ```
|
||||
pub fn both(self) -> Option<XY<T>> {
|
||||
match self {
|
||||
XY {
|
||||
x: Some(x),
|
||||
y: Some(y),
|
||||
} => Some(XY::new(x, y)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XY<bool> {
|
||||
|
15
examples/src/bin/fixed_layout.rs
Normal file
15
examples/src/bin/fixed_layout.rs
Normal file
@ -0,0 +1,15 @@
|
||||
fn main() {
|
||||
let mut siv = cursive::default();
|
||||
|
||||
siv.add_layer(
|
||||
cursive::views::Dialog::around(
|
||||
cursive::views::FixedLayout::new().child(
|
||||
cursive::Rect::from_size((0, 0), (10, 1)),
|
||||
cursive::views::TextView::new("abc"),
|
||||
),
|
||||
)
|
||||
.button("Quit", |s| s.quit()),
|
||||
);
|
||||
|
||||
siv.run();
|
||||
}
|
Loading…
Reference in New Issue
Block a user