mirror of
https://github.com/wez/wezterm.git
synced 2024-12-24 22:01:47 +03:00
cut over to the new widget bits
This commit is contained in:
parent
e71e6d2f5a
commit
de38d1aad7
@ -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)
|
||||
|
||||
|
@ -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(())
|
||||
}
|
@ -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(())
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user