#![allow(clippy::new_without_default)]
use crate::color::ColorAttribute;
use crate::input::InputEvent;
use crate::surface::{Change, CursorShape, CursorVisibility, Position, SequenceNo, Surface};
use crate::Result;
use fnv::FnvHasher;
use std::collections::{HashMap, VecDeque};
use std::hash::BuildHasherDefault;
type FnvHashMap<K, V> = HashMap<K, V, BuildHasherDefault<FnvHasher>>;
pub mod layout;
pub enum WidgetEvent {
Input(InputEvent),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CursorShapeAndPosition {
pub shape: CursorShape,
pub coords: ParentRelativeCoords,
pub color: ColorAttribute,
pub visibility: CursorVisibility,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Rect {
pub x: usize,
pub y: usize,
pub width: usize,
pub height: usize,
}
pub struct RenderArgs<'a> {
pub id: WidgetId,
pub is_focused: bool,
pub cursor: &'a mut CursorShapeAndPosition,
pub surface: &'a mut Surface,
}
pub struct UpdateArgs<'a> {
pub id: WidgetId,
pub cursor: &'a mut CursorShapeAndPosition,
}
pub trait Widget {
fn render(&mut self, args: &mut RenderArgs);
fn get_size_constraints(&self) -> layout::Constraints {
Default::default()
}
fn process_event(&mut self, _event: &WidgetEvent, _args: &mut UpdateArgs) -> bool {
false
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ParentRelativeCoords {
pub x: usize,
pub y: usize,
}
impl ParentRelativeCoords {
pub fn new(x: usize, y: usize) -> Self {
Self { x, y }
}
}
impl From<(usize, usize)> for ParentRelativeCoords {
fn from(coords: (usize, usize)) -> ParentRelativeCoords {
ParentRelativeCoords::new(coords.0, coords.1)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ScreenRelativeCoords {
pub x: usize,
pub y: usize,
}
impl ScreenRelativeCoords {
pub fn new(x: usize, y: usize) -> Self {
Self { x, y }
}
pub fn offset_by(&self, rel: &ParentRelativeCoords) -> Self {
Self {
x: self.x + rel.x,
y: self.y + rel.y,
}
}
}
static WIDGET_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::AtomicUsize::new(0);
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct WidgetId(usize);
impl WidgetId {
pub fn new() -> Self {
WidgetId(WIDGET_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed))
}
}
impl Default for WidgetId {
fn default() -> Self {
Self::new()
}
}
struct RenderData<'widget> {
surface: Surface,
cursor: CursorShapeAndPosition,
coordinates: ParentRelativeCoords,
widget: Box<dyn 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(&self, id: WidgetId) -> &[WidgetId] {
self.children
.get(&id)
.map(|v| v.as_slice())
.unwrap_or_else(|| &[])
}
}
#[derive(Default)]
pub struct Ui<'widget> {
graph: Graph,
render: FnvHashMap<WidgetId, RenderData<'widget>>,
input_queue: VecDeque<WidgetEvent>,
focused: Option<WidgetId>,
}
impl<'widget> Ui<'widget> {
pub fn new() -> Self {
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();
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;
self.do_deliver(id, &WidgetEvent::Input(InputEvent::Mouse(m)))
}
WidgetEvent::Input(InputEvent::Paste(_))
| WidgetEvent::Input(InputEvent::PixelMouse(_))
| WidgetEvent::Input(InputEvent::Key(_))
| WidgetEvent::Input(InputEvent::Wake) => self.do_deliver(id, event),
};
if handled {
return;
}
id = match self.graph.parent.get(&id) {
Some(parent) => *parent,
None => return,
};
}
}
fn hovered_widget(&self, coords: &ScreenRelativeCoords) -> Option<WidgetId> {
let root = match self.graph.root {
Some(id) => id,
_ => return None,
};
let depth = 0;
let mut best = (depth, root);
self.hovered_recursive(root, depth, coords.x, coords.y, &mut best);
Some(best.1)
}
fn hovered_recursive(
&self,
widget: WidgetId,
depth: usize,
x: usize,
y: usize,
best: &mut (usize, WidgetId),
) {
let render = &self.render[&widget];
if depth >= best.0 && x >= render.coordinates.x && y >= render.coordinates.y {
let (width, height) = render.surface.dimensions();
if (x - render.coordinates.x < width) && (y - render.coordinates.y < height) {
*best = (depth, widget);
}
}
for child in self.graph.children(widget) {
self.hovered_recursive(
*child,
depth + 1,
x + render.coordinates.x,
y + render.coordinates.y,
best,
);
}
}
pub fn process_event_queue(&mut self) -> Result<()> {
while let Some(event) = self.input_queue.pop_front() {
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);
}
}
WidgetEvent::Input(InputEvent::Key(_))
| WidgetEvent::Input(InputEvent::Paste(_))
| WidgetEvent::Input(InputEvent::PixelMouse(_))
| WidgetEvent::Input(InputEvent::Wake) => {
if let Some(focus) = self.focused {
self.deliver_event(focus, &event);
}
}
}
}
Ok(())
}
pub fn queue_event(&mut self, event: WidgetEvent) {
self.input_queue.push_back(event);
}
pub fn set_focus(&mut self, id: WidgetId) {
self.focused = Some(id);
}
fn render_recursive(
&mut self,
id: WidgetId,
screen: &mut Surface,
abs_coords: &ScreenRelativeCoords,
) -> Result<()> {
let coords = {
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);
}
screen.draw_from_screen(
surface,
abs_coords.x + render_data.coordinates.x,
abs_coords.y + render_data.coordinates.y,
);
surface.flush_changes_older_than(SequenceNo::max_value());
render_data.coordinates
};
for child in self.graph.children(id).to_vec() {
self.render_recursive(
child,
screen,
&ScreenRelativeCoords::new(coords.x + abs_coords.x, coords.y + abs_coords.y),
)?;
}
Ok(())
}
fn compute_layout(&mut self, width: usize, height: usize) -> Result<bool> {
let mut layout = layout::LayoutState::new();
let root = self.graph.root.unwrap();
self.add_widget_to_layout(&mut layout, root)?;
let mut changed = false;
#[cfg_attr(feature = "cargo-clippy", allow(clippy::identity_conversion))]
for result in layout.compute_constraints(width, height, root)? {
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;
}
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)
}
fn add_widget_to_layout(
&mut self,
layout: &mut layout::LayoutState,
widget: WidgetId,
) -> Result<()> {
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(())
}
pub fn render_to_screen(&mut self, screen: &mut Surface) -> Result<bool> {
if let Some(root) = self.graph.root {
let (width, height) = screen.dimensions();
let mut alt_screen = Surface::new(width, height);
self.render_recursive(root, &mut alt_screen, &ScreenRelativeCoords::new(0, 0))?;
let diff = screen.diff_screens(&alt_screen);
screen.add_changes(diff);
}
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::CursorVisibility(cursor.visibility),
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)
}
pub fn to_screen_coords(
&self,
widget: WidgetId,
coords: &ParentRelativeCoords,
) -> ScreenRelativeCoords {
let (x, y) = self.coord_walk(widget, coords.x, coords.y, |a, b| a + b);
ScreenRelativeCoords { x, y }
}
pub fn to_widget_coords(
&self,
widget: WidgetId,
coords: &ScreenRelativeCoords,
) -> ParentRelativeCoords {
let (x, y) = self.coord_walk(widget, coords.x, coords.y, |a, b| a - b);
ParentRelativeCoords { x, y }
}
}
#[cfg(test)]
mod test {
use super::*;
struct CursorHider {}
impl Widget for CursorHider {
fn render(&mut self, args: &mut RenderArgs) {
args.cursor.visibility = CursorVisibility::Hidden;
}
}
#[test]
fn hide_cursor() {
let mut ui = Ui::new();
ui.set_root(CursorHider {});
let mut surface = Surface::new(10, 10);
assert_eq!(CursorVisibility::Visible, surface.cursor_visibility());
ui.render_to_screen(&mut surface).unwrap();
assert_eq!(CursorVisibility::Hidden, surface.cursor_visibility());
}
}