1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 05:42:03 +03:00

cut over to the new widget bits

This commit is contained in:
Wez Furlong 2018-07-29 12:45:18 -07:00
parent e71e6d2f5a
commit de38d1aad7
5 changed files with 635 additions and 705 deletions

View File

@ -1,6 +1,9 @@
## TODO
* [ ] Load key mapping information from terminfo
* [ ] Look at unicode width and graphemes for cells
* [ ] ensure that sgr is reset to default on drop
* [ ] Option to use alt screen when going raw
* [x] Mouse reporting mode (and restore on drop)
* [x] Bracketed paste mode (and restore on drop)

View File

@ -1,118 +0,0 @@
//! This example shows how to make a basic widget that accumulates
//! text input and renders it to the screen
extern crate failure;
extern crate termwiz;
use failure::Error;
use termwiz::caps::Capabilities;
use termwiz::cell::AttributeChange;
use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
use termwiz::input::*;
use termwiz::surface::Change;
use termwiz::terminal::buffered::BufferedTerminal;
use termwiz::terminal::Blocking;
use termwiz::terminal::{new_terminal, Terminal};
use termwiz::widgets::*;
#[derive(Default)]
struct MainScreen {}
impl Widget2 for MainScreen {
type State = String;
type Event = ();
fn init_state(&self) -> String {
String::new()
}
fn update_state(&self, args: &mut WidgetUpdate<Self>) {
let mut text = args.state_mut();
for event in args.events() {
match event {
WidgetEvent::Input(InputEvent::Key(KeyEvent {
key: KeyCode::Char(c),
..
})) => text.push(c),
WidgetEvent::Input(InputEvent::Key(KeyEvent {
key: KeyCode::Enter,
..
})) => {
text.push_str("\r\n");
}
WidgetEvent::Input(InputEvent::Paste(s)) => {
text.push_str(&s);
}
_ => {}
}
}
let mut surface = args.surface_mut();
surface.add_change(Change::ClearScreen(
ColorAttribute::TrueColorWithPaletteFallback(
RgbColor::new(0x31, 0x1B, 0x92),
AnsiColor::Black.into(),
),
));
surface.add_change(Change::Attribute(AttributeChange::Foreground(
ColorAttribute::TrueColorWithPaletteFallback(
RgbColor::new(0xB3, 0x88, 0xFF),
AnsiColor::Purple.into(),
),
)));
let dims = surface.dimensions();
surface.add_change(format!("🤷 surface size is {:?}\r\n", dims));
surface.add_change(text.clone());
*args.cursor_mut() = CursorShapeAndPosition {
coords: surface.cursor_position().into(),
shape: termwiz::surface::CursorShape::SteadyBar,
..Default::default()
};
}
}
fn main() -> Result<(), Error> {
let caps = Capabilities::new_from_env()?;
let mut buf = BufferedTerminal::new(new_terminal(caps)?)?;
buf.terminal().set_raw_mode()?;
let mut ui = Ui::new();
let main_id = WidgetIdNr::new();
ui.set_focus(main_id);
loop {
{
MainScreen {}.build_ui_root(main_id, &mut ui);
}
ui.render_to_screen(&mut buf)?;
buf.flush()?;
match buf.terminal().poll_input(Blocking::Wait) {
Ok(Some(InputEvent::Resized { rows, cols })) => {
buf.add_change(Change::ClearScreen(Default::default()));
buf.resize(cols, rows);
}
Ok(Some(input)) => match input {
InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
..
}) => {
break;
}
input @ _ => {
ui.queue_event(WidgetEvent::Input(input));
}
},
Ok(None) => {}
Err(e) => {
print!("{:?}\r\n", e);
break;
}
}
}
Ok(())
}

View File

@ -4,45 +4,74 @@ extern crate failure;
extern crate termwiz;
use failure::Error;
use std::cell::Cell;
use std::borrow::Cow;
use termwiz::caps::Capabilities;
use termwiz::cell::AttributeChange;
use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
use termwiz::input::*;
use termwiz::surface::{Change, Surface};
use termwiz::surface::Change;
use termwiz::terminal::buffered::BufferedTerminal;
use termwiz::terminal::Blocking;
use termwiz::terminal::{new_terminal, Terminal};
use termwiz::widgets::*;
#[derive(Default)]
struct MainScreen {
buf: String,
cursor: Cell<ParentRelativeCoords>,
/// This is a widget for our application
struct MainScreen<'a> {
/// Holds the input text that we wish the widget to display
text: &'a str,
}
impl WidgetImpl for MainScreen {
fn process_event(&mut self, event: &WidgetEvent) -> EventDisposition {
match event {
WidgetEvent::Input(InputEvent::Key(KeyEvent {
key: KeyCode::Char(c),
..
})) => {
self.buf.push(*c);
EventDisposition::Stop
}
WidgetEvent::Input(InputEvent::Key(KeyEvent {
key: KeyCode::Enter,
..
})) => {
self.buf.push_str("\r\n");
EventDisposition::Stop
}
_ => EventDisposition::Propagate,
}
impl<'a> MainScreen<'a> {
/// Initialize the widget with the input text
pub fn new(text: &'a str) -> Self {
Self { text }
}
}
impl<'a> Widget for MainScreen<'a> {
/// This particular widget doesn't require the Ui to hold any state
/// for it, as the Event always returns any changed text, and we
/// always pass in that text on the next loop iteration.
type State = ();
/// Our `update_state` method will return Some(text) if the result
/// of processing input changed the input text, or None if the
/// input text is unchanged.
type Event = Option<String>;
/// We have no State to initialize
fn init_state(&self) {}
/// Process any input events and potentially update the returned text.
/// Returns Some(text) if it was edited, else None.
fn update_state(&self, args: &mut WidgetUpdate<Self>) -> Option<String> {
// We use Cow here to defer making a clone of the input
// text until the input events require it.
let mut text = Cow::Borrowed(self.text);
for event in args.events() {
match event {
WidgetEvent::Input(InputEvent::Key(KeyEvent {
key: KeyCode::Char(c),
..
})) => text.to_mut().push(c),
WidgetEvent::Input(InputEvent::Key(KeyEvent {
key: KeyCode::Enter,
..
})) => {
text.to_mut().push_str("\r\n");
}
WidgetEvent::Input(InputEvent::Paste(s)) => {
text.to_mut().push_str(&s);
}
_ => {}
}
}
// Now that we've updated the text, let's render it to
// the display. Get a reference to the surface.
let mut surface = args.surface_mut();
fn render_to_surface(&self, surface: &mut Surface) {
surface.add_change(Change::ClearScreen(
ColorAttribute::TrueColorWithPaletteFallback(
RgbColor::new(0x31, 0x1B, 0x92),
@ -57,61 +86,106 @@ impl WidgetImpl for MainScreen {
)));
let dims = surface.dimensions();
surface.add_change(format!("🤷 surface size is {:?}\r\n", dims));
surface.add_change(self.buf.clone());
// Allow the surface rendering code to figure out where the
// cursor ends up, then stash a copy of that information for
// later retrieval by get_cursor_shape_and_position().
let (x, y) = surface.cursor_position();
self.cursor.set(ParentRelativeCoords::new(x, y));
}
surface.add_change(text.clone());
fn get_cursor_shape_and_position(&self) -> CursorShapeAndPosition {
CursorShapeAndPosition {
coords: self.cursor.get(),
// Place the cursor at the end of the text.
// A more advanced text editing widget would manage the
// cursor position differently.
*args.cursor_mut() = CursorShapeAndPosition {
coords: surface.cursor_position().into(),
shape: termwiz::surface::CursorShape::SteadyBar,
..Default::default()
};
// Return the new text if it changed, else None.
if text != self.text {
// updated!
Some(text.into_owned())
} else {
// unchanged
None
}
}
}
fn main() -> Result<(), Error> {
let caps = Capabilities::new_from_env()?;
// Start with an empty string; typing into the app will
// update this string.
let mut typed_text = String::new();
let mut buf = BufferedTerminal::new(new_terminal(caps)?)?;
buf.terminal().set_raw_mode()?;
{
// Create a terminal and put it into full screen raw mode
let caps = Capabilities::new_from_env()?;
let mut buf = BufferedTerminal::new(new_terminal(caps)?)?;
buf.terminal().set_raw_mode()?;
let mut screen = Screen::new(Widget::new(MainScreen::default()));
// Set up the UI
let mut ui = Ui::new();
// Assign an id to associate state with the MainScreen widget.
// We don't happen to retain anything in this example, but we
// still need to assign an id.
let main_id = WidgetId::new();
screen.render_to_screen(&mut buf)?;
buf.flush()?;
loop {
match buf.terminal().poll_input(Blocking::Wait) {
Ok(Some(InputEvent::Resized { rows, cols })) => {
buf.add_change(Change::ClearScreen(Default::default()));
buf.resize(cols, rows);
loop {
// Create the MainScreen and place it into the Ui.
// When `build_ui_root` is called, the Ui machinery will
// invoke `MainScreen::update_state` to process any input
// events.
// Because we've defined MainScreen to return Some(String)
// when the input text is edited, the effect of these next
// few lines is to have `MainScreen` take `typed_text`, apply
// any keyboard events to it, display it, and then update
// `typed_text` to hold the changed value.
// `MainScreen` doesn't live for longer then the scope of this
// short block.
if let Some(updated) = MainScreen::new(&typed_text).build_ui_root(main_id, &mut ui) {
typed_text = updated;
}
Ok(Some(input)) => match input {
InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
..
}) => {
// After updating and processing all of the widgets, compose them
// and render them to the screen.
if ui.render_to_screen(&mut buf)? {
// We have more events to process immediately; don't block waiting
// for input below, but jump to the top of the loop to re-run the
// updates.
continue;
}
// Compute an optimized delta to apply to the terminal and display it
buf.flush()?;
// Wait for user input
match buf.terminal().poll_input(Blocking::Wait) {
Ok(Some(InputEvent::Resized { rows, cols })) => {
// FIXME: this is working around a bug where we don't realize
// that we should redraw everything on resize in BufferedTerminal.
buf.add_change(Change::ClearScreen(Default::default()));
buf.resize(cols, rows);
}
Ok(Some(input)) => match input {
InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
..
}) => {
// Quit the app when escape is pressed
break;
}
input @ _ => {
// Feed input into the Ui
ui.queue_event(WidgetEvent::Input(input));
}
},
Ok(None) => {}
Err(e) => {
print!("{:?}\r\n", e);
break;
}
input @ _ => {
screen.route_event(&WidgetEvent::Input(input));
}
},
Ok(None) => {}
Err(e) => {
print!("{:?}\r\n", e);
break;
}
}
screen.render_to_screen(&mut buf)?;
buf.flush()?;
}
// After we've stopped the full screen raw terminal,
// print out the final edited value of the input text.
println!("The text you entered: {}", typed_text);
Ok(())
}

View File

@ -7,7 +7,7 @@ use cassowary::{AddConstraintError, Expression, Solver, SuggestValueError, Varia
use failure::{err_msg, Error};
use std::collections::HashMap;
use widgets::{ParentRelativeCoords, WidgetHandle};
use widgets::{Rect, WidgetId};
/// Expands to an Expression holding the value of the variable,
/// or if there is no variable, a constant with the specified
@ -163,19 +163,27 @@ pub struct LayoutState {
solver: Solver,
screen_width: Variable,
screen_height: Variable,
widget_states: HashMap<WidgetHandle, WidgetState>,
widget_states: HashMap<WidgetId, WidgetState>,
}
/// Each `WidgetHandle` has a `WidgetState` associated with it.
/// Each `WidgetId` has a `WidgetState` associated with it.
/// This allows us to look up the `Variable` for each of the
/// layout features of a widget by `WidgetHandle` after the solver
/// layout features of a widget by `WidgetId` after the solver
/// has computed the layout solution.
#[derive(Copy, Clone)]
#[derive(Clone)]
struct WidgetState {
left: Variable,
top: Variable,
width: Variable,
height: Variable,
constraints: Constraints,
children: Vec<WidgetId>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LaidOutWidget {
pub widget: WidgetId,
pub rect: Rect,
}
fn suggesterr(e: SuggestValueError) -> Error {
@ -190,18 +198,6 @@ fn adderr(e: AddConstraintError) -> Error {
}
impl LayoutState {
/// Convenience function that performs the entire layout operation
/// and updates the widget tree.
pub fn compute_layout(
screen_width: usize,
screen_height: usize,
root_widget: &WidgetHandle,
) -> Result<(), Error> {
let mut layout = Self::new();
layout.add_widget_recursive(root_widget);
layout.update_constraints(screen_width, screen_height, root_widget)
}
/// Create a new `LayoutState`
pub fn new() -> Self {
let mut solver = Solver::new();
@ -222,32 +218,32 @@ impl LayoutState {
}
/// Creates a WidgetState entry for a widget.
fn add_widget(&mut self, widget: &WidgetHandle) {
pub fn add_widget(
&mut self,
widget: WidgetId,
constraints: &Constraints,
children: &[WidgetId],
) {
let state = WidgetState {
left: Variable::new(),
top: Variable::new(),
width: Variable::new(),
height: Variable::new(),
constraints: constraints.clone(),
children: children.to_vec(),
};
self.widget_states.insert(widget.clone(), state);
}
pub fn add_widget_recursive(&mut self, widget: &WidgetHandle) {
self.add_widget(widget);
for child in &widget.borrow().children {
self.add_widget_recursive(child);
}
self.widget_states.insert(widget, state);
}
/// Assign the screen dimensions, compute constraints, solve
/// the layout and then apply the size and positioning information
/// to the widgets in the widget tree.
pub fn update_constraints(
pub fn compute_constraints(
&mut self,
screen_width: usize,
screen_height: usize,
root_widget: &WidgetHandle,
) -> Result<(), Error> {
root_widget: WidgetId,
) -> Result<Vec<LaidOutWidget>, Error> {
self.solver
.suggest_value(self.screen_width, screen_width as f64)
.map_err(suggesterr)?;
@ -267,33 +263,40 @@ impl LayoutState {
// feels easier and probably has a low enough cardinality that it won't
// feel too expensive.
self.solver.fetch_changes();
self.apply_widget_state(root_widget, 0, 0)?;
Ok(())
let mut results = Vec::new();
self.compute_widget_state(root_widget, 0, 0, &mut results)?;
Ok(results)
}
fn apply_widget_state(
fn compute_widget_state(
&self,
widget: &WidgetHandle,
widget: WidgetId,
parent_left: usize,
parent_top: usize,
results: &mut Vec<LaidOutWidget>,
) -> Result<(), Error> {
let state = *self.widget_states
.get(widget)
let state = self.widget_states
.get(&widget)
.ok_or_else(|| err_msg("widget has no solver state"))?;
let width = self.solver.get_value(state.width) as usize;
let height = self.solver.get_value(state.height) as usize;
let left = self.solver.get_value(state.left) as usize;
let top = self.solver.get_value(state.top) as usize;
widget.borrow_mut().set_size_and_position(
width,
height,
ParentRelativeCoords::new(left - parent_left, top - parent_top),
);
results.push(LaidOutWidget {
widget,
rect: Rect {
x: left - parent_left,
y: top - parent_top,
width,
height,
},
});
for child in &widget.borrow().children {
self.apply_widget_state(child, left, top)?;
for child in &state.children {
self.compute_widget_state(*child, left, top, results)?;
}
Ok(())
@ -301,15 +304,16 @@ impl LayoutState {
fn update_widget_constraint(
&mut self,
widget: &WidgetHandle,
widget: WidgetId,
parent_width: Variable,
parent_height: Variable,
parent_left: Option<Variable>,
parent_top: Option<Variable>,
) -> Result<(WidgetState, Constraints), Error> {
let state = *self.widget_states
.get(widget)
.ok_or_else(|| err_msg("widget has no solver state"))?;
) -> Result<WidgetState, Error> {
let state = self.widget_states
.get(&widget)
.ok_or_else(|| err_msg("widget has no solver state"))?
.clone();
let is_root_widget = parent_left.is_none();
@ -335,12 +339,10 @@ impl LayoutState {
.add_constraint(state.top | GE(REQUIRED) | parent_top.clone())
.map_err(adderr)?;
let constraints = widget.borrow().get_size_constraints();
if is_root_widget {
// We handle alignment on the root widget specially here;
// for non-root widgets, we handle it when assessing children
match constraints.halign {
match state.constraints.halign {
HorizontalAlignment::Left => self.solver
.add_constraint(state.left | EQ(STRONG) | 0.0)
.map_err(adderr)?,
@ -352,7 +354,7 @@ impl LayoutState {
.map_err(adderr)?,
}
match constraints.valign {
match state.constraints.valign {
VerticalAlignment::Top => self.solver
.add_constraint(state.top | EQ(STRONG) | 0.0)
.map_err(adderr)?,
@ -365,7 +367,7 @@ impl LayoutState {
}
}
match constraints.width.spec {
match state.constraints.width.spec {
DimensionSpec::Fixed(width) => {
self.solver
.add_constraint(state.width | EQ(STRONG) | f64::from(width))
@ -381,16 +383,18 @@ impl LayoutState {
}
self.solver
.add_constraint(
state.width | GE(STRONG) | f64::from(constraints.width.minimum.unwrap_or(1).max(1)),
state.width
| GE(STRONG)
| f64::from(state.constraints.width.minimum.unwrap_or(1).max(1)),
)
.map_err(adderr)?;
if let Some(max_width) = constraints.width.maximum {
if let Some(max_width) = state.constraints.width.maximum {
self.solver
.add_constraint(state.width | LE(STRONG) | f64::from(max_width))
.map_err(adderr)?;
}
match constraints.height.spec {
match state.constraints.height.spec {
DimensionSpec::Fixed(height) => {
self.solver
.add_constraint(state.height | EQ(STRONG) | f64::from(height))
@ -408,32 +412,32 @@ impl LayoutState {
.add_constraint(
state.height
| GE(STRONG)
| f64::from(constraints.height.minimum.unwrap_or(1).max(1)),
| f64::from(state.constraints.height.minimum.unwrap_or(1).max(1)),
)
.map_err(adderr)?;
if let Some(max_height) = constraints.height.maximum {
if let Some(max_height) = state.constraints.height.maximum {
self.solver
.add_constraint(state.height | LE(STRONG) | f64::from(max_height))
.map_err(adderr)?;
}
let has_children = !widget.borrow().children.is_empty();
let has_children = !state.children.is_empty();
if has_children {
let mut left_edge: Expression = state.left + 0.0;
let mut top_edge: Expression = state.top + 0.0;
let mut width_constraint = Expression::from_constant(0.0);
let mut height_constraint = Expression::from_constant(0.0);
for child in &widget.borrow().children {
let (child_state, child_constraints) = self.update_widget_constraint(
child,
for child in &state.children {
let child_state = self.update_widget_constraint(
*child,
state.width,
state.height,
Some(state.left),
Some(state.top),
)?;
match child_constraints.halign {
match child_state.constraints.halign {
HorizontalAlignment::Left => self.solver
.add_constraint(child_state.left | EQ(STRONG) | left_edge.clone())
.map_err(adderr)?,
@ -453,7 +457,7 @@ impl LayoutState {
.map_err(adderr)?,
}
match child_constraints.valign {
match child_state.constraints.valign {
VerticalAlignment::Top => self.solver
.add_constraint(child_state.top | EQ(STRONG) | top_edge.clone())
.map_err(adderr)?,
@ -473,7 +477,7 @@ impl LayoutState {
.map_err(adderr)?,
}
match constraints.child_orientation {
match state.constraints.child_orientation {
ChildOrientation::Horizontal => {
left_edge = child_state.left + child_state.width;
width_constraint = width_constraint + child_state.width;
@ -506,227 +510,302 @@ impl LayoutState {
.map_err(adderr)?;
}
Ok((state, constraints))
Ok(state)
}
}
#[cfg(test)]
mod test {
use super::*;
use surface::Surface;
use widgets::{Widget, WidgetImpl};
struct UnspecWidget {}
impl WidgetImpl for UnspecWidget {
fn render_to_surface(&self, _surface: &mut Surface) {}
}
struct ConstrainedWidget {
constraints: Constraints,
}
impl WidgetImpl for ConstrainedWidget {
fn render_to_surface(&self, _surface: &mut Surface) {}
fn get_size_constraints(&self) -> Constraints {
self.constraints
}
}
impl ConstrainedWidget {
fn new(constraints: Constraints) -> Self {
Self { constraints }
}
}
#[test]
fn single_widget_unspec() {
let widget = Widget::new(UnspecWidget {});
let mut layout = LayoutState::new();
layout.add_widget_recursive(&widget);
layout.update_constraints(40, 12, &widget).unwrap();
let main_id = WidgetId::new();
layout.add_widget(main_id, &Constraints::default(), &[]);
let results = layout.compute_constraints(40, 12, main_id).unwrap();
assert_eq!(
widget.borrow().get_size_and_position(),
(40, 12, ParentRelativeCoords::new(0, 0))
results,
vec![LaidOutWidget {
widget: main_id,
rect: Rect {
x: 0,
y: 0,
width: 40,
height: 12,
},
}]
);
}
#[test]
fn two_children_pct() {
let root = Widget::new(UnspecWidget {});
let a = Widget::new(ConstrainedWidget::new(
Constraints::default().set_pct_width(50).clone(),
));
let b = Widget::new(ConstrainedWidget::new(
Constraints::default().set_pct_width(50).clone(),
));
root.borrow_mut().add_child(&a);
root.borrow_mut().add_child(&b);
let root = WidgetId::new();
let a = WidgetId::new();
let b = WidgetId::new();
let mut layout = LayoutState::new();
layout.add_widget_recursive(&root);
layout.update_constraints(100, 100, &root).unwrap();
layout.add_widget(root, &Constraints::default(), &[a, b]);
layout.add_widget(a, Constraints::default().set_pct_width(50), &[]);
layout.add_widget(b, Constraints::default().set_pct_width(50), &[]);
let results = layout.compute_constraints(100, 100, root).unwrap();
assert_eq!(
(
root.borrow().get_size_and_position(),
a.borrow().get_size_and_position(),
b.borrow().get_size_and_position()
),
(
(100, 100, ParentRelativeCoords::new(0, 0)),
(50, 100, ParentRelativeCoords::new(0, 0)),
(50, 100, ParentRelativeCoords::new(50, 0))
)
results,
vec![
LaidOutWidget {
widget: root,
rect: Rect {
x: 0,
y: 0,
width: 100,
height: 100,
},
},
LaidOutWidget {
widget: a,
rect: Rect {
x: 0,
y: 0,
width: 50,
height: 100,
},
},
LaidOutWidget {
widget: b,
rect: Rect {
x: 50,
y: 0,
width: 50,
height: 100,
},
},
]
);
}
#[test]
fn three_children_pct() {
let root = Widget::new(UnspecWidget {});
let a = Widget::new(ConstrainedWidget::new(
Constraints::default().set_pct_width(20).clone(),
));
let b = Widget::new(ConstrainedWidget::new(
Constraints::default().set_pct_width(20).clone(),
));
let c = Widget::new(ConstrainedWidget::new(
Constraints::default().set_pct_width(20).clone(),
));
root.borrow_mut().add_child(&a);
root.borrow_mut().add_child(&b);
root.borrow_mut().add_child(&c);
let root = WidgetId::new();
let a = WidgetId::new();
let b = WidgetId::new();
let c = WidgetId::new();
let mut layout = LayoutState::new();
layout.add_widget_recursive(&root);
layout.update_constraints(100, 100, &root).unwrap();
layout.add_widget(root, &Constraints::default(), &[a, b, c]);
layout.add_widget(a, Constraints::default().set_pct_width(20), &[]);
layout.add_widget(b, Constraints::default().set_pct_width(20), &[]);
layout.add_widget(c, Constraints::default().set_pct_width(20), &[]);
let results = layout.compute_constraints(100, 100, root).unwrap();
assert_eq!(
(
root.borrow().get_size_and_position(),
a.borrow().get_size_and_position(),
b.borrow().get_size_and_position(),
c.borrow().get_size_and_position(),
),
(
(100, 100, ParentRelativeCoords::new(0, 0)),
(20, 100, ParentRelativeCoords::new(0, 0)),
(20, 100, ParentRelativeCoords::new(20, 0)),
(20, 100, ParentRelativeCoords::new(40, 0))
)
results,
vec![
LaidOutWidget {
widget: root,
rect: Rect {
x: 0,
y: 0,
width: 100,
height: 100,
},
},
LaidOutWidget {
widget: a,
rect: Rect {
x: 0,
y: 0,
width: 20,
height: 100,
},
},
LaidOutWidget {
widget: b,
rect: Rect {
x: 20,
y: 0,
width: 20,
height: 100,
},
},
LaidOutWidget {
widget: c,
rect: Rect {
x: 40,
y: 0,
width: 20,
height: 100,
},
},
]
);
}
#[test]
fn two_children_a_b() {
let root = Widget::new(UnspecWidget {});
let a = Widget::new(ConstrainedWidget::new(
Constraints::with_fixed_width_height(5, 2),
));
let b = Widget::new(ConstrainedWidget::new(
Constraints::with_fixed_width_height(3, 2),
));
root.borrow_mut().add_child(&a);
root.borrow_mut().add_child(&b);
let root = WidgetId::new();
let a = WidgetId::new();
let b = WidgetId::new();
let mut layout = LayoutState::new();
layout.add_widget_recursive(&root);
layout.update_constraints(100, 100, &root).unwrap();
layout.add_widget(root, &Constraints::default(), &[a, b]);
layout.add_widget(a, &Constraints::with_fixed_width_height(5, 2), &[]);
layout.add_widget(b, &Constraints::with_fixed_width_height(3, 2), &[]);
let results = layout.compute_constraints(100, 100, root).unwrap();
assert_eq!(
(
root.borrow().get_size_and_position(),
a.borrow().get_size_and_position(),
b.borrow().get_size_and_position()
),
(
(100, 100, ParentRelativeCoords::new(0, 0)),
(5, 2, ParentRelativeCoords::new(0, 0)),
(3, 2, ParentRelativeCoords::new(5, 0))
)
results,
vec![
LaidOutWidget {
widget: root,
rect: Rect {
x: 0,
y: 0,
width: 100,
height: 100,
},
},
LaidOutWidget {
widget: a,
rect: Rect {
x: 0,
y: 0,
width: 5,
height: 2,
},
},
LaidOutWidget {
widget: b,
rect: Rect {
x: 5,
y: 0,
width: 3,
height: 2,
},
},
]
);
}
#[test]
fn two_children_b_a() {
let root = Widget::new(UnspecWidget {});
let a = Widget::new(ConstrainedWidget::new(
Constraints::with_fixed_width_height(5, 2),
));
let b = Widget::new(ConstrainedWidget::new(
Constraints::with_fixed_width_height(3, 2),
));
root.borrow_mut().add_child(&b);
root.borrow_mut().add_child(&a);
let root = WidgetId::new();
let a = WidgetId::new();
let b = WidgetId::new();
let mut layout = LayoutState::new();
layout.add_widget_recursive(&root);
layout.update_constraints(100, 100, &root).unwrap();
layout.add_widget(root, &Constraints::default(), &[b, a]);
layout.add_widget(a, &Constraints::with_fixed_width_height(5, 2), &[]);
layout.add_widget(b, &Constraints::with_fixed_width_height(3, 2), &[]);
let results = layout.compute_constraints(100, 100, root).unwrap();
assert_eq!(
(
root.borrow().get_size_and_position(),
a.borrow().get_size_and_position(),
b.borrow().get_size_and_position()
),
(
(100, 100, ParentRelativeCoords::new(0, 0)),
(5, 2, ParentRelativeCoords::new(3, 0)),
(3, 2, ParentRelativeCoords::new(0, 0)),
)
results,
vec![
LaidOutWidget {
widget: root,
rect: Rect {
x: 0,
y: 0,
width: 100,
height: 100,
},
},
LaidOutWidget {
widget: b,
rect: Rect {
x: 0,
y: 0,
width: 3,
height: 2,
},
},
LaidOutWidget {
widget: a,
rect: Rect {
x: 3,
y: 0,
width: 5,
height: 2,
},
},
]
);
}
#[test]
fn two_children_overflow() {
let root = Widget::new(UnspecWidget {});
let a = Widget::new(ConstrainedWidget::new(
Constraints::with_fixed_width_height(5, 2),
));
let b = Widget::new(ConstrainedWidget::new(
Constraints::with_fixed_width_height(3, 2),
));
root.borrow_mut().add_child(&a);
root.borrow_mut().add_child(&b);
let root = WidgetId::new();
let a = WidgetId::new();
let b = WidgetId::new();
let mut layout = LayoutState::new();
layout.add_widget_recursive(&root);
layout.update_constraints(6, 2, &root).unwrap();
layout.add_widget(root, &Constraints::default(), &[a, b]);
layout.add_widget(a, &Constraints::with_fixed_width_height(5, 2), &[]);
layout.add_widget(b, &Constraints::with_fixed_width_height(3, 2), &[]);
let results = layout.compute_constraints(6, 2, root).unwrap();
assert_eq!(
(
root.borrow().get_size_and_position(),
a.borrow().get_size_and_position(),
b.borrow().get_size_and_position()
),
(
(8, 2, ParentRelativeCoords::new(0, 0)),
(5, 2, ParentRelativeCoords::new(0, 0)),
(3, 2, ParentRelativeCoords::new(5, 0)),
)
results,
vec![
LaidOutWidget {
widget: root,
rect: Rect {
x: 0,
y: 0,
width: 8,
height: 2,
},
},
LaidOutWidget {
widget: a,
rect: Rect {
x: 0,
y: 0,
width: 5,
height: 2,
},
},
LaidOutWidget {
widget: b,
rect: Rect {
x: 5,
y: 0,
width: 3,
height: 2,
},
},
]
);
}
macro_rules! single_constrain {
($name:ident, $constraint:expr, $width:expr, $height:expr, $coords:expr) => {
($name:ident, $constraint:expr, $width:expr, $height:expr, $x:expr, $y:expr) => {
#[test]
fn $name() {
let widget = Widget::new(ConstrainedWidget::new($constraint));
let root = WidgetId::new();
let mut layout = LayoutState::new();
layout.add_widget_recursive(&widget);
layout.update_constraints(100, 100, &widget).unwrap();
layout.add_widget(root, &$constraint, &[]);
let results = layout.compute_constraints(100, 100, root).unwrap();
assert_eq!(
widget.borrow().get_size_and_position(),
($width, $height, $coords)
results,
vec![LaidOutWidget {
widget: root,
rect: Rect {
x: $x,
y: $y,
width: $width,
height: $height,
},
}]
);
}
};
@ -737,7 +816,8 @@ mod test {
Constraints::with_fixed_width_height(10, 2),
10,
2,
ParentRelativeCoords::new(0, 0)
0,
0
);
single_constrain!(
@ -747,7 +827,8 @@ mod test {
.clone(),
10,
2,
ParentRelativeCoords::new(0, 98)
0,
98
);
single_constrain!(
@ -757,7 +838,8 @@ mod test {
.clone(),
10,
2,
ParentRelativeCoords::new(0, 49)
0,
49
);
single_constrain!(
@ -767,7 +849,8 @@ mod test {
.clone(),
10,
2,
ParentRelativeCoords::new(90, 0)
90,
0
);
single_constrain!(
@ -778,7 +861,7 @@ mod test {
.clone(),
10,
2,
ParentRelativeCoords::new(45, 98)
45,
98
);
}

View File

@ -4,11 +4,8 @@ use input::InputEvent;
use std::any::Any;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::{HashMap, VecDeque};
use std::fmt::{Debug, Error as FmtError, Formatter};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::rc::{Rc, Weak};
use surface::{Change, CursorShape, Position, SequenceNo, Surface};
pub mod layout;
@ -16,15 +13,6 @@ pub mod layout;
/// Describes an event that may need to be processed by the widget
pub enum WidgetEvent {
Input(InputEvent),
FocusLost,
FocusGained,
}
pub enum EventDisposition {
/// Allow the event to bubble up through the containing hierarchy
Propagate,
/// The widget processed the event and further processing should cease
Stop,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
@ -42,9 +30,14 @@ pub struct Rect {
pub height: usize,
}
pub struct WidgetUpdate<'a, T: Widget2> {
pub id: WidgetIdNr,
pub parent_id: Option<WidgetIdNr>,
/// WidgetUpdate provides access to the widget and UI state during
/// a call to `Widget::update_state`
pub struct WidgetUpdate<'a, T: Widget> {
/// The id of the current widget
pub id: WidgetId,
/// The id of its parent widget
pub parent_id: Option<WidgetId>,
/// The bounding rectangle of the widget relative to its parent
pub rect: Rect,
cursor: &'a RefCell<CursorShapeAndPosition>,
surface: &'a RefCell<Surface>,
@ -53,6 +46,9 @@ pub struct WidgetUpdate<'a, T: Widget2> {
_phantom: PhantomData<T>,
}
/// `StateRef` borrows a reference to the widget specific state.
/// It is returned via the `WidgetUpdate::state` method.
/// It derefs to the `Widget::State` type.
pub struct StateRef<'a, T: 'a> {
cell: Ref<'a, Box<Any + Send>>,
_phantom: PhantomData<T>,
@ -65,6 +61,9 @@ impl<'a, T: 'static> Deref for StateRef<'a, T> {
}
}
/// `StateRefMut` borrows a mutable reference to the widget specific state.
/// It is returned via the `WidgetUpdate::state` method.
/// It mutably derefs to the `Widget::State` type.
pub struct StateRefMut<'a, T: 'a> {
cell: RefMut<'a, Box<Any + Send>>,
_phantom: PhantomData<T>,
@ -83,7 +82,8 @@ impl<'a, T: 'static> DerefMut for StateRefMut<'a, T> {
}
}
impl<'a, T: Widget2> WidgetUpdate<'a, T> {
impl<'a, T: Widget> WidgetUpdate<'a, T> {
/// Borrow an immutable reference to the widget state
pub fn state(&'a self) -> StateRef<'a, T::State> {
StateRef {
cell: self.state.borrow(),
@ -91,10 +91,7 @@ impl<'a, T: Widget2> WidgetUpdate<'a, T> {
}
}
pub fn surface(&self) -> Ref<Surface> {
self.surface.borrow()
}
/// Borrow a mutable reference to the widget state
pub fn state_mut(&self) -> StateRefMut<'a, T::State> {
StateRefMut {
cell: self.state.borrow_mut(),
@ -102,22 +99,44 @@ impl<'a, T: Widget2> WidgetUpdate<'a, T> {
}
}
/// Borrow an immutable reference to the render `Surface`.
pub fn surface(&self) -> Ref<Surface> {
self.surface.borrow()
}
/// Borrow a mutable reference to the render `Surface`.
pub fn surface_mut(&self) -> RefMut<Surface> {
self.surface.borrow_mut()
}
/// Borrow a mutable reference to the cursor information.
/// The cursor information from the focused widget is used to
/// control the appearance of the cursor on the terminal screen
/// during rendering.
pub fn cursor_mut(&self) -> RefMut<CursorShapeAndPosition> {
self.cursor.borrow_mut()
}
/// Iterate over events that are specified to the current widget.
/// If the widget has focus then it will receive keyboard input
/// events, including pastes.
/// If the mouse is over the widget then mouse events, with the coordinates
/// adjusted to the same coordinate space as the widget, will be included
/// in the returned set of events.
pub fn events(&'a self) -> impl Iterator<Item = WidgetEvent> + 'a {
self.ui.input_queue.iter().filter_map(move |evt| {
match evt {
WidgetEvent::FocusLost | WidgetEvent::FocusGained => None,
WidgetEvent::Input(InputEvent::Resized { .. }) => None,
WidgetEvent::Input(InputEvent::Mouse(m)) => {
let mut m = m.clone();
// TODO: screen to client coords
// convert from screen to widget coords
let coords = self.ui.to_widget_coords(
self.id,
&ScreenRelativeCoords::new(m.x as usize, m.y as usize),
);
m.x = coords.x as u16;
m.y = coords.y as u16;
// TODO: exclude if these are outside of the rect!
Some(WidgetEvent::Input(InputEvent::Mouse(m)))
}
WidgetEvent::Input(InputEvent::Paste(s)) => match self.ui.focused {
@ -138,11 +157,25 @@ impl<'a, T: Widget2> WidgetUpdate<'a, T> {
}
}
pub trait Widget2: Sized {
/// Implementing the `Widget` trait allows for defining a potentially
/// interactive component in a UI layout.
/// The `State` associated with a widget is managed by the `Ui` instance.
/// The first time a given `WidgetId` is added to the `Ui`, the `init_state`
/// function is called to initialize that state, but subsequent updates will
/// use the recorded state.
/// The key method is `Widget::update_state` which processes events made
/// available via `WidgetUpdate` and applies them to the `State` and renders
/// them to the `Surface` associated with the widget.
pub trait Widget: Sized {
/// The state maintained and managed for this widget.
type State: Any + Send;
/// The data returned from `update_state`. It may simply be `()`, or
/// `State` to return the associated state, or it may be a more complex
/// type depending on the purpose of the widget.
type Event;
/// Called by the Ui the first time that a give WidgetIdNr is used.
/// Called by the Ui the first time that a given WidgetId is seen
/// by `build_ui_root` or `build_ui_child`.
/// The widget shall return its initial state value.
fn init_state(&self) -> Self::State;
@ -151,43 +184,29 @@ pub trait Widget2: Sized {
/// the surface embedded in `args`.
fn update_state(&self, args: &mut WidgetUpdate<Self>) -> Self::Event;
/// Override this to have your widget specify its layout constraints.
/// You may wish to have your widget constructor receive a `Constraints`
/// instance to make this more easily configurable in more generic widgets.
fn get_size_constraints(&self, _state: &Self::State) -> layout::Constraints {
Default::default()
}
fn build_ui_root(self, id: WidgetIdNr, ui: &mut Ui) -> Self::Event {
/// Consume the widget and configure it as the root of the Ui. The first
/// time the widget is seen it is given the keyboard focus.
fn build_ui_root(self, id: WidgetId, ui: &mut Ui) -> Self::Event {
ui.add_or_update_widget(id, None, self)
}
fn build_ui_child(self, id: WidgetIdNr, args: &mut WidgetUpdate<Self>) -> Self::Event {
/// Consume the widget and configure it as a child of the widget described
/// by the `WidgetUpdate` argument.
/// # Panics
/// If `id` was previously a child of a different widget, this function will
/// panic.
fn build_ui_child(self, id: WidgetId, args: &mut WidgetUpdate<Self>) -> Self::Event {
args.ui.add_or_update_widget(id, args.parent_id, self)
}
}
pub trait WidgetImpl {
/// Called once by the widget manager to inform the widget
/// of its identifier
fn set_widget_id(&mut self, _id: WidgetId) {}
/// Handle an event
fn process_event(&mut self, _event: &WidgetEvent) -> EventDisposition {
EventDisposition::Propagate
}
/// Interrogates the widget to ask if it has any sizing constraints
fn get_size_constraints(&self) -> layout::Constraints {
Default::default()
}
fn render_to_surface(&self, surface: &mut Surface);
/// Called for the focused widget to determine how to render
/// the cursor.
fn get_cursor_shape_and_position(&self) -> CursorShapeAndPosition {
Default::default()
}
}
/// Relative to the top left of the parent container
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ParentRelativeCoords {
@ -229,29 +248,36 @@ impl ScreenRelativeCoords {
static WIDGET_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;
/// The `WidgetId` uniquely describes an instance of a widget.
/// Creating a new `WidgetId` generates a new unique identifier which can
/// be safely copied and moved around; each copy refers to the same widget.
/// The intent is that you set up the identifiers once and re-use them,
/// rather than generating new ids on each iteration of the UI loop so that
/// the widget state is maintained correctly by the Ui.
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct WidgetIdNr(usize);
pub struct WidgetId(usize);
impl WidgetIdNr {
impl WidgetId {
pub fn new() -> Self {
WidgetIdNr(WIDGET_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed))
WidgetId(WIDGET_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed))
}
}
pub struct WidgetState {
struct WidgetState {
surface: RefCell<Surface>,
coordinates: ParentRelativeCoords,
cursor: RefCell<CursorShapeAndPosition>,
constraints: RefCell<layout::Constraints>,
children: Vec<WidgetIdNr>,
parent: Option<WidgetIdNr>,
children: Vec<WidgetId>,
parent: Option<WidgetId>,
state: RefCell<Box<Any + Send>>,
}
/// Manages the widgets on the display
pub struct Ui {
state: HashMap<WidgetIdNr, WidgetState>,
focused: Option<WidgetIdNr>,
root: Option<WidgetIdNr>,
state: HashMap<WidgetId, WidgetState>,
focused: Option<WidgetId>,
root: Option<WidgetId>,
input_queue: VecDeque<WidgetEvent>,
}
@ -265,20 +291,25 @@ impl Ui {
}
}
fn add_or_update_widget<W: Widget2>(
fn add_or_update_widget<W: Widget>(
&mut self,
id: WidgetIdNr,
parent: Option<WidgetIdNr>,
id: WidgetId,
parent: Option<WidgetId>,
w: W,
) -> W::Event {
let widget_state = self.state.remove(&id).unwrap_or_else(|| WidgetState {
surface: RefCell::new(Surface::new(80, 24)),
coordinates: ParentRelativeCoords::new(0, 0),
children: Vec::new(),
parent: parent,
state: RefCell::new(Box::new(w.init_state())),
cursor: RefCell::new(Default::default()),
constraints: RefCell::new(Default::default()),
let widget_state = self.state.remove(&id).unwrap_or_else(|| {
let state = w.init_state();
let constraints = w.get_size_constraints(&state);
WidgetState {
surface: RefCell::new(Surface::new(80, 24)),
coordinates: ParentRelativeCoords::new(0, 0),
children: Vec::new(),
parent: parent,
state: RefCell::new(Box::new(state)),
cursor: RefCell::new(Default::default()),
constraints: RefCell::new(constraints),
}
});
// Dealing with re-parenting is a PITA, so we just panic for now
@ -297,14 +328,17 @@ impl Ui {
None => {
// It has no parent, therefore it is the root
self.root = Some(id);
// The root, if it is the first widget being added,
// should have the focus by default
if self.state.len() == 0 {
self.focused = Some(id);
}
}
}
*widget_state.constraints.borrow_mut() =
w.get_size_constraints(widget_state.state.borrow().downcast_ref().unwrap());
// TODO: incrementally update the layout solver
let coords = widget_state.coordinates;
let dims = widget_state.surface.borrow().dimensions();
@ -332,17 +366,24 @@ impl Ui {
event
}
/// Queue up an event. Events are processed by the appropriate
/// `Widget::update_state` method. Events may be re-processed to
/// simplify handling for widgets. eg: a TODO: is to synthesize double
/// and triple click events.
pub fn queue_event(&mut self, event: WidgetEvent) {
self.input_queue.push_back(event);
}
pub fn set_focus(&mut self, id: WidgetIdNr) {
/// Assign keyboard focus to the specified widget.
pub fn set_focus(&mut self, id: WidgetId) {
self.focused = Some(id);
}
/// Helper for applying the surfaces from the widgets to the target
/// screen in the correct order (from the root to the leaves)
fn render_recursive(
&mut self,
id: WidgetIdNr,
id: WidgetId,
screen: &mut Surface,
abs_coords: &ScreenRelativeCoords,
) -> Result<(), Error> {
@ -374,10 +415,58 @@ impl Ui {
Ok(())
}
pub fn render_to_screen(&mut self, screen: &mut Surface) -> Result<(), Error> {
/// Reconsider the layout constraints and apply them.
/// Returns true if the layout was changed, false if no changes were made.
fn compute_layout(&mut self, width: usize, height: usize) -> Result<bool, Error> {
let mut layout = layout::LayoutState::new();
let root = self.root.unwrap();
self.add_widget_to_layout(&mut layout, root)?;
let mut changed = false;
for result in layout.compute_constraints(width, height, root)? {
if let Some(widget_state) = self.state.get_mut(&result.widget) {
let coords = ParentRelativeCoords::new(result.rect.x, result.rect.y);
if coords != widget_state.coordinates {
widget_state.coordinates = coords;
changed = true;
}
let mut surface = widget_state.surface.borrow_mut();
if (result.rect.width, result.rect.height) != surface.dimensions() {
surface.resize(result.rect.width, result.rect.height);
changed = true;
}
}
}
Ok(changed)
}
/// Recursive helper for building up the LayoutState
fn add_widget_to_layout(
&mut self,
layout: &mut layout::LayoutState,
widget: WidgetId,
) -> Result<(), Error> {
let children = {
let state = self.state.get(&widget).unwrap();
layout.add_widget(widget, &state.constraints.borrow(), &state.children);
state.children.clone()
};
for child in &children {
self.add_widget_to_layout(layout, *child)?;
}
Ok(())
}
/// Apply the current state of the widgets to the screen.
/// This has the side effect of clearing out any unconsumed input queue.
/// Returns true if the Ui may need to be updated again; for example,
/// if the most recent update operation changed layout.
pub fn render_to_screen(&mut self, screen: &mut Surface) -> Result<bool, Error> {
let root_id = self.root.unwrap();
self.render_recursive(root_id, screen, &ScreenRelativeCoords::new(0, 0))?;
self.input_queue.clear();
// TODO: garbage collect unreachable WidgetId's from self.state
match self.focused {
Some(id) => match self.state.get(&id) {
@ -399,12 +488,15 @@ impl Ui {
_ => {}
}
Ok(())
let (width, height) = screen.dimensions();
self.compute_layout(width, height)
}
/// Convert coordinates that are relative to widget into coordinates
/// that are relative to the screen origin (top left).
pub fn to_screen_coords(
&self,
widget: WidgetIdNr,
widget: WidgetId,
coords: &ParentRelativeCoords,
) -> ScreenRelativeCoords {
let mut x = coords.x;
@ -426,235 +518,31 @@ impl Ui {
}
ScreenRelativeCoords { x, y }
}
}
pub struct Widget {
id: WidgetId,
inner: Box<WidgetImpl>,
surface: Surface,
coordinates: ParentRelativeCoords,
children: Vec<WidgetHandle>,
parent: WidgetId,
}
#[derive(Clone)]
pub struct WidgetHandle {
inner: Rc<RefCell<Widget>>,
}
impl WidgetHandle {
pub fn borrow(&self) -> Ref<Widget> {
self.inner.borrow()
}
pub fn borrow_mut(&self) -> RefMut<Widget> {
self.inner.borrow_mut()
}
pub fn new(widget: Widget) -> Self {
Self {
inner: Rc::new(RefCell::new(widget)),
}
}
pub fn id(&self) -> WidgetId {
WidgetId {
inner: Rc::downgrade(&self.inner),
}
}
}
impl PartialEq for WidgetHandle {
fn eq(&self, other: &WidgetHandle) -> bool {
self.inner.as_ptr() == other.inner.as_ptr()
}
}
impl Eq for WidgetHandle {}
impl Hash for WidgetHandle {
fn hash<H: Hasher>(&self, state: &mut H) {
self.inner.as_ptr().hash(state)
}
}
impl Debug for WidgetHandle {
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
write!(f, "WidgetHandle({:?})", self.inner.as_ptr())
}
}
/// An identifier assigned by the widget management machinery.
#[derive(Clone)]
pub struct WidgetId {
inner: Weak<RefCell<Widget>>,
}
impl WidgetId {
pub fn new() -> Self {
Self { inner: Weak::new() }
}
pub fn handle(&self) -> Option<WidgetHandle> {
self.inner.upgrade().map(|r| WidgetHandle { inner: r })
}
}
impl Widget {
pub fn new<W: WidgetImpl + 'static>(widget: W) -> WidgetHandle {
let widget = Box::new(widget);
let surface = Surface::new(1, 1);
let coordinates = ParentRelativeCoords::new(0, 0);
let children = Vec::new();
let id = WidgetId::new();
let handle = WidgetHandle::new(Widget {
id,
inner: widget,
surface,
coordinates,
children,
parent: WidgetId::new(),
});
let id = handle.id();
{
let mut widget = handle.borrow_mut();
widget.id = id.clone();
widget.inner.set_widget_id(id);
}
handle
}
pub fn widget_id(&self) -> WidgetId {
self.id.clone()
}
pub fn process_event(&mut self, event: &WidgetEvent) -> EventDisposition {
self.inner.process_event(event)
}
pub fn get_size_constraints(&self) -> layout::Constraints {
self.inner.get_size_constraints()
}
pub fn render_to_screen(&mut self, screen: &mut Surface) {
self.inner.render_to_surface(&mut self.surface);
for child in &mut self.children {
child.borrow_mut().render_to_screen(&mut self.surface);
}
self.surface
.flush_changes_older_than(SequenceNo::max_value());
screen.draw_from_screen(&self.surface, self.coordinates.x, self.coordinates.y);
}
pub fn get_size_and_position(&self) -> (usize, usize, ParentRelativeCoords) {
let (width, height) = self.surface.dimensions();
(width, height, self.coordinates)
}
pub fn set_size_and_position(
&mut self,
width: usize,
height: usize,
coords: ParentRelativeCoords,
) {
self.surface.resize(width, height);
self.coordinates = coords;
}
pub fn parent(&self) -> Option<WidgetHandle> {
self.parent.handle()
}
pub fn add_child(&mut self, widget: &WidgetHandle) {
self.children.push(widget.clone());
}
pub fn to_screen_coords(&self, coords: &ParentRelativeCoords) -> ScreenRelativeCoords {
/// Convert coordinates that are relative to the screen origin (top left)
/// into coordinates that are relative to the widget.
pub fn to_widget_coords(
&self,
widget: WidgetId,
coords: &ScreenRelativeCoords,
) -> ParentRelativeCoords {
let mut x = coords.x;
let mut y = coords.y;
let mut widget = self.parent();
let mut widget = widget;
loop {
let parent = match widget {
Some(parent) => {
let p = parent.borrow();
x += p.coordinates.x;
y += p.coordinates.y;
p.parent()
let parent = match self.state.get(&widget) {
Some(widget_state) => {
x -= widget_state.coordinates.x;
y -= widget_state.coordinates.y;
match widget_state.parent {
Some(parent) => parent,
None => break,
}
}
None => break,
};
widget = parent;
}
ScreenRelativeCoords { x, y }
}
}
pub struct Screen {
focused_widget: WidgetHandle,
root_widget: WidgetHandle,
}
impl Screen {
pub fn new(root_widget: WidgetHandle) -> Self {
let focused_widget = root_widget.clone();
Self {
focused_widget,
root_widget,
}
}
/// Change the focused widget using the new widget handle
pub fn set_focus(&mut self, widget: WidgetHandle) {
self.focused_widget
.borrow_mut()
.process_event(&WidgetEvent::FocusLost);
self.focused_widget = widget;
self.focused_widget
.borrow_mut()
.process_event(&WidgetEvent::FocusGained);
}
/// Route an event to an appropriate widget.
/// Routing starts with the focused widget and then propagates
/// up through its parents until we reach the root widget.
pub fn route_event(&self, event: &WidgetEvent) {
let mut widget = self.focused_widget.clone();
loop {
match widget.borrow_mut().process_event(event) {
EventDisposition::Stop => return,
EventDisposition::Propagate => {}
}
let parent = match widget.borrow().parent() {
Some(p) => p,
None => return,
};
widget = parent;
}
}
/// Rendering starts at the root (which can be considered the lowest layer),
/// and then progresses up through its children.
pub fn render_to_screen(&mut self, screen: &mut Surface) -> Result<(), Error> {
let (width, height) = screen.dimensions();
layout::LayoutState::compute_layout(width, height, &self.root_widget)?;
self.root_widget.borrow_mut().render_to_screen(screen);
let focused = self.focused_widget.borrow();
let cursor = focused.inner.get_cursor_shape_and_position();
let coords = focused.to_screen_coords(&cursor.coords);
screen.add_changes(vec![
Change::CursorShape(cursor.shape),
Change::CursorColor(cursor.color),
Change::CursorPosition {
x: Position::Absolute(coords.x),
y: Position::Absolute(coords.y),
},
]);
Ok(())
ParentRelativeCoords { x, y }
}
}