1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-21 19:58:15 +03:00

rethink the widget stuff

This feels a bit more idiomatic and flexible
This commit is contained in:
Wez Furlong 2018-08-01 07:56:24 -07:00
parent de38d1aad7
commit db49040d5d
4 changed files with 283 additions and 421 deletions

View File

@ -18,6 +18,7 @@ libc = "~0.2"
bitflags = "~1.0"
cassowary = "~0.3"
memmem = "~0.1"
fnv = "~1.0"
[dependencies.num-derive]
version = "~0.2"

View File

@ -4,7 +4,6 @@ extern crate failure;
extern crate termwiz;
use failure::Error;
use std::borrow::Cow;
use termwiz::caps::Capabilities;
use termwiz::cell::AttributeChange;
use termwiz::color::{AnsiColor, ColorAttribute, RgbColor};
@ -18,93 +17,66 @@ use termwiz::widgets::*;
/// This is a widget for our application
struct MainScreen<'a> {
/// Holds the input text that we wish the widget to display
text: &'a str,
text: &'a mut String,
}
impl<'a> MainScreen<'a> {
/// Initialize the widget with the input text
pub fn new(text: &'a str) -> Self {
pub fn new(text: &'a mut String) -> 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);
}
_ => {}
fn process_event(&mut self, event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
match event {
WidgetEvent::Input(InputEvent::Key(KeyEvent {
key: KeyCode::Char(c),
..
})) => self.text.push(*c),
WidgetEvent::Input(InputEvent::Key(KeyEvent {
key: KeyCode::Enter,
..
})) => {
self.text.push_str("\r\n");
}
WidgetEvent::Input(InputEvent::Paste(s)) => {
self.text.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();
true // handled it all
}
surface.add_change(Change::ClearScreen(
/// Draw ourselves into the surface provided by RenderArgs
fn render(&mut self, args: &mut RenderArgs) {
args.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.surface
.add_change(Change::Attribute(AttributeChange::Foreground(
ColorAttribute::TrueColorWithPaletteFallback(
RgbColor::new(0xB3, 0x88, 0xFF),
AnsiColor::Purple.into(),
),
)));
let dims = args.surface.dimensions();
args.surface
.add_change(format!("🤷 surface size is {:?}\r\n", dims));
args.surface.add_change(self.text.clone());
// 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(),
*args.cursor = CursorShapeAndPosition {
coords: args.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
}
}
}
@ -121,26 +93,11 @@ fn main() -> Result<(), Error> {
// 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();
ui.set_root(MainScreen::new(&mut typed_text));
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;
}
ui.process_event_queue()?;
// After updating and processing all of the widgets, compose them
// and render them to the screen.

View File

@ -1,5 +1,6 @@
#[macro_use]
extern crate failure;
extern crate fnv;
extern crate libc;
extern crate palette;
extern crate semver;

View File

@ -1,13 +1,14 @@
use color::ColorAttribute;
use failure::Error;
use fnv::FnvHasher;
use input::InputEvent;
use std::any::Any;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::{HashMap, VecDeque};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::hash::BuildHasherDefault;
use surface::{Change, CursorShape, Position, SequenceNo, Surface};
/// fnv is a more appropriate hasher for the WidgetIds we use in this module.
type FnvHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FnvHasher>>;
pub mod layout;
/// Describes an event that may need to be processed by the widget
@ -30,180 +31,43 @@ pub struct Rect {
pub height: usize,
}
/// WidgetUpdate provides access to the widget and UI state during
/// a call to `Widget::update_state`
pub struct WidgetUpdate<'a, T: Widget> {
pub struct RenderArgs<'a> {
/// 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>,
state: &'a RefCell<Box<Any + Send>>,
ui: &'a mut Ui,
_phantom: PhantomData<T>,
pub is_focused: bool,
pub cursor: &'a mut CursorShapeAndPosition,
pub surface: &'a mut Surface,
}
/// `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>,
}
impl<'a, T: 'static> Deref for StateRef<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.cell.downcast_ref().unwrap()
}
}
/// `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>,
}
impl<'a, T: 'static> Deref for StateRefMut<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.cell.downcast_ref().unwrap()
}
}
impl<'a, T: 'static> DerefMut for StateRefMut<'a, T> {
fn deref_mut(&mut self) -> &mut T {
self.cell.downcast_mut().unwrap()
}
}
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(),
_phantom: PhantomData,
}
}
/// Borrow a mutable reference to the widget state
pub fn state_mut(&self) -> StateRefMut<'a, T::State> {
StateRefMut {
cell: self.state.borrow_mut(),
_phantom: PhantomData,
}
}
/// 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::Input(InputEvent::Resized { .. }) => None,
WidgetEvent::Input(InputEvent::Mouse(m)) => {
let mut m = m.clone();
// 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 {
Some(id) if id == self.id => {
Some(WidgetEvent::Input(InputEvent::Paste(s.clone())))
}
_ => None,
},
WidgetEvent::Input(InputEvent::Key(key)) => match self.ui.focused {
Some(id) if id == self.id => {
let key = key.clone();
Some(WidgetEvent::Input(InputEvent::Key(key)))
}
_ => None,
},
}
})
}
/// UpdateArgs provides access to the widget and UI state during
/// a call to `Widget::update_state`
pub struct UpdateArgs<'a> {
/// The id of the current widget
pub id: WidgetId,
pub cursor: &'a mut CursorShapeAndPosition,
}
/// 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 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;
/// Called by the Ui on each update cycle.
/// The widget should process state updates and draw itself to
/// the surface embedded in `args`.
fn update_state(&self, args: &mut WidgetUpdate<Self>) -> Self::Event;
pub trait Widget {
/// Draw the widget to the RenderArgs::surface, and optionally
/// update RenderArgs::cursor to reflect the cursor position and
/// display attributes.
fn render(&mut self, args: &mut RenderArgs);
/// 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 {
fn get_size_constraints(&self) -> layout::Constraints {
Default::default()
}
/// 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)
}
/// 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)
/// Override this to allow your widget to respond to keyboard, mouse and
/// other widget events.
/// Return `true` if your widget handled the event, or `false` to allow
/// the event to propagate to the widget parent.
fn process_event(&mut self, _event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
false
}
}
@ -263,107 +127,162 @@ impl WidgetId {
}
}
struct WidgetState {
surface: RefCell<Surface>,
struct RenderData<'widget> {
surface: Surface,
cursor: CursorShapeAndPosition,
coordinates: ParentRelativeCoords,
cursor: RefCell<CursorShapeAndPosition>,
constraints: RefCell<layout::Constraints>,
children: Vec<WidgetId>,
parent: Option<WidgetId>,
state: RefCell<Box<Any + Send>>,
widget: Box<Widget + 'widget>,
}
#[derive(Default)]
struct Graph {
root: Option<WidgetId>,
children: FnvHashMap<WidgetId, Vec<WidgetId>>,
parent: FnvHashMap<WidgetId, WidgetId>,
}
impl Graph {
fn add(&mut self, parent: Option<WidgetId>) -> WidgetId {
let id = WidgetId::new();
if self.root.is_none() {
self.root = Some(id);
}
self.children.insert(id, Vec::new());
if let Some(parent) = parent {
self.parent.insert(id, parent);
self.children.get_mut(&parent).unwrap().push(id);
}
id
}
fn children<'a>(&'a self, id: WidgetId) -> &[WidgetId] {
self.children
.get(&id)
.map(|v| v.as_slice())
.unwrap_or_else(|| &[])
}
}
/// Manages the widgets on the display
pub struct Ui {
state: HashMap<WidgetId, WidgetState>,
focused: Option<WidgetId>,
root: Option<WidgetId>,
#[derive(Default)]
pub struct Ui<'widget> {
graph: Graph,
render: FnvHashMap<WidgetId, RenderData<'widget>>,
input_queue: VecDeque<WidgetEvent>,
focused: Option<WidgetId>,
}
impl Ui {
impl<'widget> Ui<'widget> {
pub fn new() -> Self {
Self {
state: HashMap::new(),
focused: None,
root: None,
input_queue: VecDeque::new(),
Default::default()
}
pub fn add<W: Widget + 'widget>(&mut self, parent: Option<WidgetId>, w: W) -> WidgetId {
let id = self.graph.add(parent);
self.render.insert(
id,
RenderData {
surface: Surface::new(1, 1),
cursor: Default::default(),
coordinates: Default::default(),
widget: Box::new(w),
},
);
if parent.is_none() && self.focused.is_none() {
self.focused = Some(id);
}
id
}
pub fn set_root<W: Widget + 'widget>(&mut self, w: W) -> WidgetId {
self.add(None, w)
}
pub fn add_child<W: Widget + 'widget>(&mut self, parent: WidgetId, w: W) -> WidgetId {
self.add(Some(parent), w)
}
fn do_deliver(&mut self, id: WidgetId, event: &WidgetEvent) -> bool {
let render_data = self.render.get_mut(&id).unwrap();
let mut args = UpdateArgs {
id,
cursor: &mut render_data.cursor,
};
render_data.widget.process_event(event, &mut args)
}
fn deliver_event(&mut self, mut id: WidgetId, event: &WidgetEvent) {
loop {
let handled = match event {
WidgetEvent::Input(InputEvent::Resized { .. }) => true,
WidgetEvent::Input(InputEvent::Mouse(m)) => {
let mut m = m.clone();
// convert from screen to widget coords
let coords = self.to_widget_coords(
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!
self.do_deliver(id, &WidgetEvent::Input(InputEvent::Mouse(m)))
}
WidgetEvent::Input(InputEvent::Paste(_))
| WidgetEvent::Input(InputEvent::Key(_)) => self.do_deliver(id, event),
};
if handled {
return;
}
id = match self.graph.parent.get(&id) {
Some(parent) => *parent,
None => return,
};
}
}
fn add_or_update_widget<W: Widget>(
&mut self,
id: WidgetId,
parent: Option<WidgetId>,
w: W,
) -> W::Event {
let widget_state = self.state.remove(&id).unwrap_or_else(|| {
let state = w.init_state();
let constraints = w.get_size_constraints(&state);
// TODO: we should find the best matching widget that
// is under the mouse cursor, and then propagate up
// its graph to the root
fn hovered_widget(&self, _coords: &ScreenRelativeCoords) -> Option<WidgetId> {
self.graph.root
}
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
assert_eq!(parent, widget_state.parent);
// Ensure that the parent links to this child
match widget_state.parent {
Some(parent_id) => match self.state.get_mut(&parent_id) {
Some(parent) => {
if !parent.children.contains(&id) {
parent.children.push(id);
pub fn process_event_queue(&mut self) -> Result<(), Error> {
loop {
let event = match self.input_queue.pop_front() {
Some(event) => event,
None => break,
};
match event {
WidgetEvent::Input(InputEvent::Resized { rows, cols }) => {
self.compute_layout(cols, rows)?;
}
WidgetEvent::Input(InputEvent::Mouse(ref m)) => {
if let Some(hover) =
self.hovered_widget(&ScreenRelativeCoords::new(m.x as usize, m.y as usize))
{
self.deliver_event(hover, &event);
}
}
None => panic!("missing parent!"),
},
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);
WidgetEvent::Input(InputEvent::Key(_))
| WidgetEvent::Input(InputEvent::Paste(_)) => {
if let Some(focus) = self.focused {
self.deliver_event(focus, &event);
}
}
}
}
*widget_state.constraints.borrow_mut() =
w.get_size_constraints(widget_state.state.borrow().downcast_ref().unwrap());
let coords = widget_state.coordinates;
let dims = widget_state.surface.borrow().dimensions();
let event = {
let mut args = WidgetUpdate {
id,
parent_id: None,
rect: Rect {
x: coords.x,
y: coords.y,
width: dims.0,
height: dims.1,
},
surface: &widget_state.surface,
state: &widget_state.state,
cursor: &widget_state.cursor,
ui: self,
_phantom: PhantomData,
};
w.update_state(&mut args)
};
self.state.insert(id, widget_state);
event
Ok(())
}
/// Queue up an event. Events are processed by the appropriate
@ -387,29 +306,29 @@ impl Ui {
screen: &mut Surface,
abs_coords: &ScreenRelativeCoords,
) -> Result<(), Error> {
let (child_ids, abs_coords) = match self.state.get_mut(&id) {
Some(widget_state) => {
let abs_coords = ScreenRelativeCoords::new(
widget_state.coordinates.x + abs_coords.x,
widget_state.coordinates.y + abs_coords.y,
);
screen.draw_from_screen(
&widget_state.surface.borrow_mut(),
abs_coords.x,
abs_coords.y,
);
widget_state
.surface
.borrow_mut()
.flush_changes_older_than(SequenceNo::max_value());
(widget_state.children.clone(), abs_coords)
let (x, y) = {
let render_data = self.render.get_mut(&id).unwrap();
let surface = &mut render_data.surface;
{
let mut args = RenderArgs {
id,
cursor: &mut render_data.cursor,
surface,
is_focused: self.focused.map(|f| f == id).unwrap_or(false),
};
render_data.widget.render(&mut args);
}
None => bail!("missing state for widget {:?}", id),
screen.draw_from_screen(surface, abs_coords.x, abs_coords.y);
surface.flush_changes_older_than(SequenceNo::max_value());
(render_data.coordinates.x, render_data.coordinates.y)
};
for child in child_ids {
self.render_recursive(child, screen, &abs_coords)?;
for child in self.graph.children(id).to_vec() {
self.render_recursive(
child,
screen,
&ScreenRelativeCoords::new(x + abs_coords.x, y + abs_coords.y),
)?;
}
Ok(())
@ -419,23 +338,24 @@ impl Ui {
/// 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();
let root = self.graph.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 render_data = self.render.get_mut(&result.widget).unwrap();
let coords = ParentRelativeCoords::new(result.rect.x, result.rect.y);
if coords != render_data.coordinates {
render_data.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;
}
if (result.rect.width, result.rect.height) != render_data.surface.dimensions() {
render_data
.surface
.resize(result.rect.width, result.rect.height);
changed = true;
}
}
Ok(changed)
@ -447,13 +367,13 @@ impl Ui {
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)?;
let constraints = self.render[&widget].widget.get_size_constraints();
let children = self.graph.children(widget).to_vec();
layout.add_widget(widget, &constraints, &children);
for child in children {
self.add_widget_to_layout(layout, child)?;
}
Ok(())
}
@ -463,35 +383,50 @@ impl Ui {
/// 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();
if let Some(root) = self.graph.root {
self.render_recursive(root, screen, &ScreenRelativeCoords::new(0, 0))?;
}
// TODO: garbage collect unreachable WidgetId's from self.state
match self.focused {
Some(id) => match self.state.get(&id) {
Some(widget_state) => {
let cursor = widget_state.cursor.borrow();
let coords = self.to_screen_coords(id, &cursor.coords);
if let Some(id) = self.focused {
let cursor = &self.render[&id].cursor;
let coords = self.to_screen_coords(id, &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),
},
]);
}
_ => {}
},
_ => {}
screen.add_changes(vec![
Change::CursorShape(cursor.shape),
Change::CursorColor(cursor.color),
Change::CursorPosition {
x: Position::Absolute(coords.x),
y: Position::Absolute(coords.y),
},
]);
}
let (width, height) = screen.dimensions();
self.compute_layout(width, height)
}
fn coord_walk<F: Fn(usize, usize) -> usize>(
&self,
widget: WidgetId,
mut x: usize,
mut y: usize,
f: F,
) -> (usize, usize) {
let mut widget = widget;
loop {
let render = &self.render[&widget];
x = f(x, render.coordinates.x);
y = f(y, render.coordinates.y);
widget = match self.graph.parent.get(&widget) {
Some(parent) => *parent,
None => break,
};
}
(x, y)
}
/// Convert coordinates that are relative to widget into coordinates
/// that are relative to the screen origin (top left).
pub fn to_screen_coords(
@ -499,23 +434,7 @@ impl Ui {
widget: WidgetId,
coords: &ParentRelativeCoords,
) -> ScreenRelativeCoords {
let mut x = coords.x;
let mut y = coords.y;
let mut widget = widget;
loop {
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;
}
let (x, y) = self.coord_walk(widget, coords.x, coords.y, |a, b| a + b);
ScreenRelativeCoords { x, y }
}
@ -526,23 +445,7 @@ impl Ui {
widget: WidgetId,
coords: &ScreenRelativeCoords,
) -> ParentRelativeCoords {
let mut x = coords.x;
let mut y = coords.y;
let mut widget = widget;
loop {
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;
}
let (x, y) = self.coord_walk(widget, coords.x, coords.y, |a, b| a - b);
ParentRelativeCoords { x, y }
}
}