use crate::backend::{CrosstermBackend, TerminalBackend};
use crate::buffer::Buffer;
use crate::component::Component;
use crate::dirty::Dirty;
use std::cell::RefCell;
use crate::event::{Command, Event, EventPhase, Key, KeyEvent};
use crate::geom::Rect;
use crate::geom::Pos;
use crate::node::{Node, NodeId};
use crate::Result;
thread_local! {
pub static COMMAND_QUEUE: RefCell<Vec<Command>> = RefCell::new(Vec::new());
pub static DEBUG_MODE: RefCell<bool> = RefCell::new(false);
pub static FORCE_FULL_PAINT: RefCell<bool> = RefCell::new(false);
}
pub struct Runtime {
root: Node,
backend: CrosstermBackend,
front: Buffer,
back: Buffer,
root_rect: Rect,
quit: bool,
full_paint: bool,
dirty: Dirty,
focused_id: Option<NodeId>,
tick_rate: std::time::Duration,
task_rx: std::sync::mpsc::Receiver<String>,
task_tx: std::sync::mpsc::Sender<String>,
}
const ZERO_RECT: Rect = Rect {
x: 0,
y: 0,
width: 0,
height: 0,
};
impl Runtime {
pub fn new(root: impl Component + 'static) -> Result<Self> {
let (tx, rx) = std::sync::mpsc::channel::<String>();
Ok(Self {
root: Node::root(root),
backend: CrosstermBackend::new()?,
front: Buffer::new(crate::geom::Size { width: 1, height: 1 }),
back: Buffer::new(crate::geom::Size { width: 1, height: 1 }),
root_rect: ZERO_RECT,
quit: false,
full_paint: false,
dirty: Dirty::NONE,
focused_id: None,
tick_rate: std::time::Duration::from_millis(250),
task_rx: rx,
task_tx: tx,
})
}
pub fn tick_rate(mut self, rate: std::time::Duration) -> Self {
self.tick_rate = rate;
self
}
pub fn run(&mut self) -> Result<()> {
self.backend.enter()?;
self.initial_layout_and_paint()?;
while !self.quit {
let commands: Vec<Command> = COMMAND_QUEUE.with(|q| q.borrow_mut().drain(..).collect());
for cmd in commands {
match cmd {
Command::Quit => self.quit = true,
Command::FocusNext => self.focus_next(false),
Command::FocusPrev => self.focus_next(true),
Command::Custom(_) => {}
}
}
while let Ok(result) = self.task_rx.try_recv() {
self.dispatch_event(Event::TaskComplete(result))?;
if self.dirty.contains(Dirty::PAINT) {
self.paint()?;
self.dirty = Dirty::NONE;
}
}
let event = match self.backend.poll_event(
if self.task_rx.try_recv().is_ok() {
std::time::Duration::from_millis(0)
} else {
self.tick_rate
},
)? {
Some(e) => e,
None => Event::Tick,
};
self.dispatch_event(event)?;
if self.dirty.intersects(Dirty::LAYOUT | Dirty::TREE) {
self.layout()?;
}
if self.dirty.contains(Dirty::PAINT) {
self.paint()?;
self.dirty = Dirty::NONE;
}
}
self.root.unmount(&mut self.dirty, &mut self.quit, Some(self.task_tx.clone()));
self.backend.leave()?;
Ok(())
}
fn initial_layout_and_paint(&mut self) -> Result<()> {
let size = self.backend.size()?;
self.root_rect = Rect {
x: 0,
y: 0,
width: size.width,
height: size.height,
};
self.front = Buffer::new(size);
self.back = Buffer::new(size);
self.full_paint = true;
self.dirty = Dirty::LAYOUT | Dirty::PAINT;
self.layout()?;
self.root.mount(&mut self.dirty, &mut self.quit, Some(self.task_tx.clone()));
let mut ids = Vec::new();
self.root.collect_focusable(&mut ids);
if !ids.is_empty() {
self.focused_id = Some(ids[0]);
}
self.paint()?;
self.dirty = Dirty::NONE;
Ok(())
}
fn dispatch_event(&mut self, event: Event) -> Result<()> {
match event {
Event::Resize(size) => {
let new_rect = Rect {
x: 0,
y: 0,
width: size.width,
height: size.height,
};
if new_rect != self.root_rect {
self.root_rect = new_rect;
self.front.resize(size);
self.back.resize(size);
self.full_paint = true;
self.dirty = Dirty::LAYOUT | Dirty::PAINT;
}
}
Event::Key(KeyEvent { key: Key::Tab, modifiers }) => {
self.focus_next(modifiers.shift);
}
Event::Mouse(mouse_event) => {
let pos = Pos {
x: mouse_event.x,
y: mouse_event.y,
};
if let Some(id) = self.root.hit_test(pos) {
self.dispatch_to_target(id, &Event::Mouse(mouse_event));
}
}
Event::Focus | Event::Blur => {
}
other => {
let target = self.focused_id.unwrap_or(NodeId::ROOT);
self.dispatch_to_target(target, &other);
}
}
Ok(())
}
fn focus_next(&mut self, backwards: bool) {
let mut ids = Vec::new();
self.root.collect_focusable(&mut ids);
if ids.is_empty() {
return;
}
let old_id = self.focused_id;
let new_idx = match old_id {
None => {
if backwards {
ids.len() - 1
} else {
0
}
}
Some(old) => {
if let Some(pos) = ids.iter().position(|id| *id == old) {
if backwards {
if pos == 0 {
ids.len() - 1
} else {
pos - 1
}
} else if pos + 1 >= ids.len() {
0
} else {
pos + 1
}
} else {
0
}
}
};
let new_id = ids[new_idx];
self.set_focus(new_id);
}
fn dispatch_to_target(&mut self, target_id: NodeId, event: &Event) {
let (path, effective_target) = match self.root.find_path_to(target_id) {
Some(p) => (p, target_id),
None => (vec![NodeId::ROOT], NodeId::ROOT),
};
let mut stopped = false;
for &node_id in &path[..path.len().saturating_sub(1)] {
if stopped {
break;
}
self.root.send_event(
node_id,
event,
&mut self.dirty,
&mut self.quit,
EventPhase::Capture,
&mut stopped,
Some(self.task_tx.clone()),
);
}
if !stopped {
self.root.send_event(
effective_target,
event,
&mut self.dirty,
&mut self.quit,
EventPhase::Target,
&mut stopped,
Some(self.task_tx.clone()),
);
}
for &node_id in path[..path.len().saturating_sub(1)].iter().rev() {
if stopped {
break;
}
self.root.send_event(
node_id,
event,
&mut self.dirty,
&mut self.quit,
EventPhase::Bubble,
&mut stopped,
Some(self.task_tx.clone()),
);
}
}
fn set_focus(&mut self, new_id: NodeId) {
let old_id = self.focused_id;
if let Some(old) = old_id {
if old != new_id {
let mut stopped = false;
self.root.send_event(
old,
&Event::Blur,
&mut self.dirty,
&mut self.quit,
EventPhase::Target,
&mut stopped,
Some(self.task_tx.clone()),
);
}
}
self.focused_id = Some(new_id);
if old_id != Some(new_id) {
let mut stopped = false;
self.root.send_event(
new_id,
&Event::Focus,
&mut self.dirty,
&mut self.quit,
EventPhase::Target,
&mut stopped,
Some(self.task_tx.clone()),
);
}
self.dirty |= Dirty::PAINT;
}
fn layout(&mut self) -> Result<()> {
self.root.layout(self.root_rect);
Ok(())
}
fn paint(&mut self) -> Result<()> {
self.back.clear();
self.root.render_with_clip(&mut self.back, self.focused_id, None);
if DEBUG_MODE.with(|d| *d.borrow()) {
self.root.debug_render(&mut self.back, self.focused_id);
}
if self.full_paint || FORCE_FULL_PAINT.with(|f| f.replace(false)) {
let ops = self.back.all_ops();
self.backend.flush(&ops)?;
self.full_paint = false;
} else {
let ops = self.front.diff(&self.back);
self.backend.flush(&ops)?;
}
std::mem::swap(&mut self.front, &mut self.back);
Ok(())
}
}