use std::rc::Rc;
use crate::base::{Point, Rect, Result, Size};
use crate::gfx::ImageSession;
use crate::input::{Event, EventReader};
use crate::reactive::{
self, drain_posted, flush_effects, take_frame_request, take_worker_failures,
};
use crate::render::{Cell, Compositor, FrameDiff, Glyph, PresentCaps, Presenter, Surface};
use crate::term::{ActiveProbe, Capabilities, EnterOptions, KittyFlags, Terminal, TerminalWaker};
use crate::theme::TokenId;
use crate::ui::SurfaceCanvas;
use super::events::{convert_event, is_default_quit};
use super::overlays::Overlays;
use super::theme::current_theme;
use super::App;
pub struct RunConfig {
pub caps: Option<Capabilities>,
pub enter: Option<EnterOptions>,
pub probe: bool,
}
impl Default for RunConfig {
fn default() -> Self {
RunConfig {
caps: None,
enter: None,
probe: true,
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct Turn {
pub events: usize,
pub rendered: bool,
pub emitted: bool,
pub quit: bool,
pub idle: bool,
}
struct WakeOnFrame(Option<TerminalWaker>);
impl crate::base::FrameRequester for WakeOnFrame {
fn request_frame(&self) {
if let Some(w) = &self.0 {
w.wake();
}
}
}
pub struct Driver {
reader: EventReader,
pub(super) caps: Capabilities,
present_caps: PresentCaps,
probe: Option<ActiveProbe>,
comp: Compositor,
diff: FrameDiff,
presenter: Presenter,
pub(super) overlays: Overlays,
pub(super) image_session: ImageSession,
pub(super) pending_image_bytes: Vec<(Vec<u8>, Point)>,
frame: Surface,
prev: Surface,
size: Size,
pending: Vec<Event>,
burst: Vec<Event>,
probe_grace: Option<std::time::Instant>,
now_fn: Option<Rc<dyn Fn() -> std::time::Instant>>,
out: Vec<u8>,
scratch_damage: Vec<Rect>,
}
impl Driver {
pub fn new(app: &mut App, term: &mut dyn Terminal, cfg: RunConfig) -> Result<Driver> {
let caps = cfg.caps.unwrap_or_else(Capabilities::detect_env);
let enter = cfg.enter.unwrap_or_else(|| EnterOptions {
kitty_keyboard: if caps.kitty_keyboard {
KittyFlags::standard()
} else {
KittyFlags(0)
},
..EnterOptions::default()
});
term.enter(&enter)?;
let size = term.size()?;
app.set_viewport(size);
let waker = term.waker();
if let Some(w) = waker.clone() {
reactive::set_wake_callback(move || w.wake());
}
reactive::set_frame_requester(Rc::new(WakeOnFrame(waker)));
let probe = if cfg.probe && !caps.dumb {
let probe = ActiveProbe::for_caps(&caps);
term.write(&probe.full_query_bytes())?;
term.flush()?;
Some(probe)
} else {
None
};
let blank = Cell::EMPTY;
let overlays = app.overlays();
overlays.ensure_root(size);
Ok(Driver {
reader: EventReader::new(),
present_caps: present_caps_from(&caps),
caps,
probe,
comp: Compositor::new(),
diff: FrameDiff::new(),
presenter: Presenter::new(),
overlays,
image_session: ImageSession::new(),
pending_image_bytes: Vec::new(),
frame: Surface::new(size, blank),
prev: Surface::new(size, blank),
size,
pending: Vec::new(),
burst: Vec::new(),
probe_grace: None,
now_fn: None,
out: Vec::new(),
scratch_damage: Vec::new(),
})
}
pub fn set_clock(&mut self, f: impl Fn() -> std::time::Instant + 'static) {
self.now_fn = Some(Rc::new(f));
}
fn now(&self) -> std::time::Instant {
match &self.now_fn {
Some(f) => f(),
None => std::time::Instant::now(),
}
}
pub fn caps(&self) -> &Capabilities {
&self.caps
}
pub fn turn(&mut self, app: &mut App, term: &mut dyn Terminal) -> Result<Turn> {
drain_posted();
let now = self.now();
reactive::run_due_timers(now);
reactive::run_frame_tasks(now);
flush_effects();
if let Some(deadline) = self.probe_grace {
if now >= deadline {
self.probe_grace = None;
if self.probe.take().is_some() {
self.apply_caps_upgrade(app);
}
}
}
let failures = take_worker_failures();
if !failures.is_empty() {
return Err(crate::base::Error::App(failures.join("; ")));
}
let mut events = 0usize;
let mut quit = false;
let pending: Vec<Event> = self.pending.drain(..).collect();
for ev in pending {
events += 1;
self.handle_event(app, ev, &mut quit);
}
let drain_deadline = std::time::Instant::now();
let mut burst = std::mem::take(&mut self.burst);
burst.clear();
self.reader
.poll_many(term, &mut burst, Some(drain_deadline))?;
coalesce_moves(&mut burst);
for ev in burst.drain(..) {
events += 1;
self.handle_event(app, ev, &mut quit);
}
self.burst = burst;
if app.quit_requested() {
quit = true;
}
if quit {
return Ok(Turn {
events,
quit: true,
..Turn::default()
});
}
let frame_requested = take_frame_request();
let wants_frame = frame_requested
|| app.tree().has_pending_work()
|| self.overlays.has_pending_work()
|| {
let store = self.overlays.store().borrow();
Compositor::any_dirty(&store.layers)
};
if !wants_frame {
return Ok(Turn {
events,
idle: events == 0,
..Turn::default()
});
}
let emitted = self.render_frame(app, term)?;
Ok(Turn {
events,
rendered: true,
emitted,
..Turn::default()
})
}
fn render_frame(&mut self, app: &mut App, term: &mut dyn Terminal) -> Result<bool> {
let theme = current_theme();
let text_fg = theme.tokens.get(TokenId::Text);
let bg = theme.tokens.get(TokenId::Bg);
app.tree().set_text_fg(text_fg);
self.comp.set_ground(Some(bg));
app.tree().layout();
self.overlays.layout_all();
let viewport = Rect::from_size(self.size);
let mut damage = app.tree().take_damage();
coalesce_damage(&mut damage, viewport);
let mut root_surface = self.steal_root_surface();
{
let clear = Cell::new(Glyph::SPACE).with_fg(text_fg).with_bg(bg);
for &rect in &damage {
root_surface.fill_rect(rect, clear);
}
let mut canvas = SurfaceCanvas::new(&mut root_surface);
app.tree().draw_damaged(&mut canvas, &damage);
}
self.render_images(&mut root_surface);
self.restore_root_surface(root_surface);
self.overlays.draw_all();
self.scratch_damage.clear();
{
let mut store = self.overlays.store().borrow_mut();
let flat = self.comp.flatten(&mut self.frame, &mut store.layers);
self.scratch_damage.extend_from_slice(flat);
}
self.out.clear();
let runs = self
.diff
.compute_scrolled(&self.prev, &self.frame, &self.scratch_damage);
self.presenter
.emit_scrolled(runs, &self.frame, &self.present_caps, &mut self.out);
for (bytes, at) in self.pending_image_bytes.drain(..) {
self.presenter.external_write(&mut self.out, &bytes, at);
}
let emitted = !self.out.is_empty();
if emitted {
term.write(&self.out)?;
term.flush()?; }
self.prev
.blit(&self.frame, Rect::from_size(self.size), Point::ZERO);
Ok(emitted)
}
pub fn wait_for_activity(&mut self, term: &mut dyn Terminal) -> Result<()> {
if let Some(ev) = self.reader.poll_event(term, None)? {
self.pending.push(ev);
}
Ok(())
}
pub fn wait_until(
&mut self,
term: &mut dyn Terminal,
deadline: std::time::Instant,
) -> Result<()> {
if let Some(ev) = self.reader.poll_event(term, Some(deadline))? {
self.pending.push(ev);
}
Ok(())
}
pub fn finish(&mut self, term: &mut dyn Terminal) -> Result<()> {
if self.image_session.live_slots() > 0 {
let mut bytes: Vec<(Vec<u8>, Point)> = Vec::new();
let mut sink = super::driver_images::BufSink(&mut bytes);
self.image_session
.release_all(&mut sink, &self.caps.graphics());
self.out.clear();
for (payload, at) in bytes {
self.presenter.external_write(&mut self.out, &payload, at);
}
if !self.out.is_empty() {
term.write(&self.out)?;
term.flush()?;
}
}
term.leave()
}
fn handle_event(&mut self, app: &mut App, event: Event, quit: &mut bool) {
match event {
Event::Resize(size) => self.apply_resize(app, size),
Event::CapsReply(reply) => {
if let Some(probe) = &mut self.probe {
if probe.on_reply(&reply, &mut self.caps) {
self.probe = None;
self.probe_grace = None;
self.apply_caps_upgrade(app);
} else if probe.sentinel_passed()
&& probe.awaiting_wrapped()
&& self.probe_grace.is_none()
{
let grace = crate::term::probe::TMUX_GRACE;
self.probe_grace = Some(self.now() + grace);
reactive::after(grace, || {});
}
}
}
other => {
if let Some(ui_event) = convert_event(&other) {
let mut consumed = match self.overlays.dispatch(&ui_event) {
Some(consumed) => consumed,
None => app.tree().dispatch(&ui_event),
};
if !consumed {
if let crate::ui::UiEvent::Key(k) = &ui_event {
let chord = crate::ui::KeyChord {
key: k.key,
mods: k.mods,
};
consumed = app.actions().dispatch_chord(chord);
}
}
if !consumed && is_default_quit(&ui_event) {
*quit = true;
}
if app.quit_requested() {
*quit = true;
}
}
}
}
}
fn apply_resize(&mut self, app: &mut App, size: Size) {
if size == self.size || size.is_empty() {
return;
}
self.size = size;
let blank = Cell::EMPTY;
self.overlays.ensure_root(size);
{
let mut store = self.overlays.store().borrow_mut();
for img in store.images.iter_mut() {
img.dirty = true;
}
}
self.frame.resize(size, blank);
self.prev.resize(size, blank);
self.poison_prev();
app.set_viewport(size);
}
fn apply_caps_upgrade(&mut self, _app: &mut App) {
let fresh = present_caps_from(&self.caps);
if fresh != self.present_caps {
self.present_caps = fresh;
self.poison_prev();
let mut store = self.overlays.store().borrow_mut();
for layer in store.layers.iter_mut() {
layer.surface_mut().damage_all();
}
for img in store.images.iter_mut() {
img.dirty = true;
}
drop(store);
reactive::request_frame();
}
}
fn poison_prev(&mut self) {
let poison = Cell::EMPTY
.with_fg(crate::base::Rgba::new(1, 2, 3, 7))
.with_bg(crate::base::Rgba::new(3, 2, 1, 7));
self.prev.fill_rect(Rect::from_size(self.size), poison);
}
}
fn present_caps_from(caps: &Capabilities) -> PresentCaps {
caps.present_caps()
}
fn coalesce_moves(events: &mut Vec<Event>) {
let is_move =
|e: &Event| matches!(e, Event::Mouse(m) if m.kind == crate::input::MouseKind::Move);
let mut keep = 0usize;
for i in 0..events.len() {
let dropped = is_move(&events[i]) && events.get(i + 1).map(&is_move).unwrap_or(false);
if !dropped {
events.swap(keep, i);
keep += 1;
}
}
events.truncate(keep);
}
fn coalesce_damage(damage: &mut Vec<Rect>, viewport: Rect) {
for r in damage.iter_mut() {
*r = r.intersect(viewport);
}
damage.retain(|r| !r.is_empty());
let mut kept: Vec<Rect> = Vec::with_capacity(damage.len());
for &r in damage.iter() {
let contained = kept.iter().any(|k| k.intersect(r) == r);
if !contained {
kept.retain(|k| r.intersect(*k) != *k); kept.push(r);
}
}
*damage = kept;
}
#[cfg(test)]
mod tests {
use super::*;
use crate::render::ColorDepth;
#[test]
fn coalesce_moves_keeps_last_of_runs_and_every_other_kind() {
use crate::base::Point;
use crate::input::{MouseButton as B, MouseEvent as M, MouseKind as K};
let mv = |x: i32| {
Event::Mouse(M::new(
K::Move,
B::Left,
Point::new(x, 0),
crate::input::Mods::NONE,
))
};
let down = Event::Mouse(M::new(
K::Down,
B::Left,
Point::new(9, 0),
crate::input::Mods::NONE,
));
let mut evs = vec![mv(1), mv(2), mv(3), down.clone(), mv(4), mv(5)];
coalesce_moves(&mut evs);
assert_eq!(evs.len(), 3);
assert!(matches!(&evs[0], Event::Mouse(m) if m.pos.x == 3));
assert!(matches!(&evs[1], Event::Mouse(m) if m.kind == K::Down));
assert!(matches!(&evs[2], Event::Mouse(m) if m.pos.x == 5));
let drag = |x: i32| {
Event::Mouse(M::new(
K::Drag,
B::Left,
Point::new(x, 0),
crate::input::Mods::NONE,
))
};
let mut drags = vec![drag(1), drag(2), drag(3)];
coalesce_moves(&mut drags);
assert_eq!(drags.len(), 3);
}
#[test]
fn coalesce_drops_contained_and_clips() {
let vp = Rect::new(0, 0, 20, 10);
let mut d = vec![
Rect::new(0, 0, 5, 5),
Rect::new(1, 1, 2, 2), Rect::new(18, 8, 10, 10), Rect::new(-5, -5, 3, 3), ];
coalesce_damage(&mut d, vp);
assert_eq!(d, vec![Rect::new(0, 0, 5, 5), Rect::new(18, 8, 2, 2)]);
}
#[test]
fn present_caps_mapping_delegates_to_kernel_including_underline() {
let mut caps = Capabilities::default();
assert_eq!(present_caps_from(&caps).color, ColorDepth::Ansi16);
caps.colors_256 = true;
assert_eq!(present_caps_from(&caps).color, ColorDepth::Xterm256);
caps.truecolor = true;
caps.undercurl = true;
caps.underline_color = true;
let pc = present_caps_from(&caps);
assert_eq!(pc.color, ColorDepth::TrueColor);
assert!(
pc.undercurl,
"undercurl capability must reach the presenter"
);
assert!(
pc.underline_color,
"underline color capability must reach the presenter"
);
}
}