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:
parent
de38d1aad7
commit
db49040d5d
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -1,5 +1,6 @@
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
extern crate fnv;
|
||||
extern crate libc;
|
||||
extern crate palette;
|
||||
extern crate semver;
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user