1use smallvec::SmallVec;
6use std::{collections::HashMap, fmt::Debug, time::Instant};
7
8use crate::{
9 component::{
10 bindings::{BindingQuery, DynamicBindings, KeySequenceSlice, NamedBindingQuery},
11 layout::{LaidCanvas, LaidComponent, Layout},
12 template::{ComponentId, DynamicMessage, DynamicProperties, Renderable},
13 LinkMessage, ShouldRender,
14 },
15 terminal::{Canvas, Event, Key, Position, Rect, Size},
16};
17
18pub trait MessageSender: Debug + Send + 'static {
19 fn send(&self, message: ComponentMessage);
20
21 fn clone_box(&self) -> Box<dyn MessageSender>;
22}
23
24#[derive(Debug)]
25pub struct ComponentMessage(pub(crate) LinkMessage);
26
27#[derive(Copy, Clone, Debug, PartialEq)]
28pub enum PollState {
29 Clean,
30 Dirty(Option<Size>),
31 Exit,
32}
33
34#[derive(Debug)]
35struct AppRuntime {
36 screen: Canvas,
37 poll_state: PollState,
38 num_frame: usize,
39}
40
41impl AppRuntime {
42 fn new(size: Size) -> Self {
43 Self {
44 screen: Canvas::new(size),
45 poll_state: PollState::Dirty(None),
46 num_frame: 0,
47 }
48 }
49}
50
51pub struct App {
69 root: Layout,
70 components: HashMap<ComponentId, MountedComponent>,
71 layouts: HashMap<ComponentId, Layout>,
72 subscriptions: ComponentSubscriptions,
73 controller: InputController,
74 runtime: AppRuntime,
75 sender: Box<dyn MessageSender>,
76}
77
78impl App {
79 pub fn new(sender: impl MessageSender, size: Size, root: Layout) -> Self {
134 Self {
135 root,
136 components: HashMap::new(),
137 layouts: HashMap::new(),
138 subscriptions: ComponentSubscriptions::new(),
139 controller: InputController::new(),
140 runtime: AppRuntime::new(size),
141 sender: Box::new(sender),
142 }
143 }
144
145 #[inline]
147 pub fn poll_state(&self) -> PollState {
148 self.runtime.poll_state
149 }
150
151 #[inline]
153 pub fn is_tickable(&mut self) -> bool {
154 !self.subscriptions.tickable.is_empty()
155 }
156
157 #[inline]
159 pub fn tick(&mut self) {
160 for TickSubscription {
161 component_id,
162 message,
163 } in self.subscriptions.tickable.drain(..)
164 {
165 match self.components.get_mut(&component_id) {
166 Some(component) => {
167 if component.update(message) {
168 self.runtime.poll_state.merge(PollState::Dirty(None));
169 }
170 }
171 None => {
172 log::debug!(
173 "Received message for nonexistent component (id: {}).",
174 component_id,
175 );
176 }
177 }
178 }
179 }
180
181 #[inline]
187 pub fn draw(&mut self) -> &Canvas {
188 match self.runtime.poll_state {
189 PollState::Dirty(maybe_new_size) => {
190 let now = Instant::now();
192 if let Some(new_size) = maybe_new_size {
193 log::debug!(
194 "Screen resized {}x{} -> {}x{}",
195 self.runtime.screen.size().width,
196 self.runtime.screen.size().height,
197 new_size.width,
198 new_size.height
199 );
200 self.runtime.screen.resize(new_size);
201 }
202
203 let frame = Rect::new(Position::new(0, 0), self.runtime.screen.size());
204 let statistics = self.draw_tree(frame, self.runtime.num_frame);
205 let drawn_time = now.elapsed();
206
207 log::debug!(
213 "Frame {}: {} comps [{}] draw {:.1}ms",
214 self.runtime.num_frame,
215 self.components.len(),
216 statistics,
217 drawn_time.as_secs_f64() * 1000.0,
218 );
221 self.runtime.num_frame += 1;
222 }
223 PollState::Exit => {
224 panic!("tried drawing while the app is exiting");
225 }
226 _ => {}
227 }
228 self.runtime.poll_state = PollState::Clean;
229 &self.runtime.screen
230 }
231
232 pub fn handle_resize(&mut self, size: Size) {
235 self.runtime.poll_state.merge(PollState::Dirty(Some(size)));
236 }
237
238 #[inline]
239 pub fn handle_message(&mut self, message: ComponentMessage) {
240 match message.0 {
241 LinkMessage::Component(component_id, dyn_message) => {
242 let should_render = self
243 .components
244 .get_mut(&component_id)
245 .map(|component| component.update(dyn_message))
246 .unwrap_or_else(|| {
247 log::debug!(
248 "Received message for nonexistent component (id: {}).",
249 component_id,
250 );
251 false
252 });
253 self.runtime.poll_state.merge(if should_render {
254 PollState::Dirty(None)
255 } else {
256 PollState::Clean
257 });
258 }
259 LinkMessage::Exit => {
260 self.runtime.poll_state.merge(PollState::Exit);
261 }
262 }
263 }
264
265 #[inline]
266 pub fn handle_input(&mut self, event: Event) {
267 match event {
268 Event::KeyPress(key) => {
269 self.handle_key(key);
270 self.runtime.poll_state.merge(PollState::Dirty(None));
272 }
273 }
274 }
275
276 #[inline]
277 fn handle_key(&mut self, key: Key) {
278 let Self {
279 ref mut components,
280 ref subscriptions,
281 controller: ref mut input_controller,
282 ..
283 } = *self;
284 let mut clear_controller = true;
285 let mut binding_queries = SmallVec::<[_; 4]>::with_capacity(subscriptions.focused.len());
286
287 input_controller.push(key);
288 for component_id in subscriptions.focused.iter() {
289 let focused_component = components
290 .get_mut(component_id)
291 .expect("focused component to be mounted");
292
293 let binding_query = focused_component
294 .bindings
295 .keymap()
296 .check_sequence(&input_controller.keys);
297 binding_queries.push(binding_query.map(|binding_query| {
298 NamedBindingQuery::new(focused_component.bindings.keymap(), binding_query)
299 }));
300 match focused_component
301 .bindings
302 .keymap()
303 .check_sequence(&input_controller.keys)
304 {
305 Some(BindingQuery::Match(command_id)) => {
306 if let Some(message) = focused_component.renderable.run_command(
307 &focused_component.bindings,
308 *command_id,
309 &input_controller.keys,
310 ) {
311 focused_component.update(message);
312 }
313 }
314 Some(BindingQuery::PrefixOf(prefix_of)) => {
315 log::info!(
316 "{} ({} commands)",
317 KeySequenceSlice::from(input_controller.keys.as_slice()),
318 prefix_of.len()
319 );
320 clear_controller = false;
321 }
322 None => {}
323 }
324 }
325
326 for component_id in subscriptions.notify.iter() {
327 let notify_component = components
328 .get_mut(component_id)
329 .expect("component to be mounted");
330 notify_component
331 .renderable
332 .notify_binding_queries(&binding_queries, &input_controller.keys);
333 }
334
335 if clear_controller {
337 input_controller.keys.clear();
338 }
339 }
340
341 #[inline]
342 fn draw_tree(&mut self, frame: Rect, generation: Generation) -> DrawStatistics {
343 let Self {
344 ref mut components,
345 ref mut layouts,
346 ref mut runtime,
347 ref mut subscriptions,
348 ref sender,
349 ..
350 } = *self;
351
352 subscriptions.clear();
353
354 let mut first = true;
355 let mut pending = Vec::new();
356 let mut statistics = DrawStatistics::default();
357 loop {
358 let (layout, frame2, position_hash, parent_changed) = if first {
359 first = false;
360 (&mut self.root, frame, 0, false)
361 } else if let Some((component_id, frame, position_hash)) = pending.pop() {
362 let component = components
363 .get_mut(&component_id)
364 .expect("Layout is cached only for mounted components");
365 let layout = layouts
366 .entry(component_id)
367 .or_insert_with(|| component.view());
368 let changed = component.should_render;
369 if changed {
370 *layout = component.view()
371 }
372 component.set_generation(generation);
373 (layout, frame, position_hash, changed)
374 } else {
375 break;
376 };
377
378 layout.0.crawl(
379 frame2,
380 position_hash,
381 &mut |LaidComponent {
382 frame,
383 position_hash,
384 template,
385 }| {
386 let component_id = template.generate_id(position_hash);
387 let mut new_component = false;
388 let component = components.entry(component_id).or_insert_with(|| {
389 new_component = true;
390 let (renderable, bindings) =
391 template.create(component_id, frame, sender.clone_box());
392 MountedComponent {
393 renderable,
394 frame,
395 bindings,
396 should_render: ShouldRender::Yes.into(),
397 generation,
398 }
399 });
400
401 if !new_component {
402 let mut changed =
403 parent_changed && component.change(template.dynamic_properties());
404 if frame != component.frame {
405 changed = component.resize(frame) || changed;
406 }
407 if changed {
408 statistics.changed += 1;
409 } else {
410 statistics.nop += 1;
411 }
412 } else {
413 statistics.new += 1;
414 }
415
416 component.update_bindings();
417 if component.bindings.focused() {
418 subscriptions.add_focused(component_id);
419 }
420
421 if component.bindings.notify() {
422 subscriptions.add_notify(component_id);
423 }
424
425 if let Some(message) = component.tick() {
426 subscriptions.add_tickable(component_id, message);
427 }
428
429 pending.push((component_id, frame, position_hash));
430 },
431 &mut |LaidCanvas { frame, canvas, .. }| {
432 runtime.screen.copy_region(canvas, frame);
433 },
434 );
435 }
436
437 components.retain(
440 |component_id,
441 &mut MountedComponent {
442 generation: component_generation,
443 ..
444 }| {
445 if component_generation < generation {
446 statistics.deleted += 1;
447 layouts.remove(component_id);
448 false
449 } else {
450 true
451 }
452 },
453 );
454
455 statistics
456 }
457}
458
459struct ComponentSubscriptions {
460 focused: SmallVec<[ComponentId; 2]>,
461 notify: SmallVec<[ComponentId; 2]>,
462 tickable: SmallVec<[TickSubscription; 2]>,
463}
464
465impl ComponentSubscriptions {
466 fn new() -> Self {
467 Self {
468 focused: SmallVec::new(),
469 notify: SmallVec::new(),
470 tickable: SmallVec::new(),
471 }
472 }
473
474 #[inline]
475 fn clear(&mut self) {
476 self.focused.clear();
477 self.notify.clear();
478 self.tickable.clear();
479 }
480
481 #[inline]
482 fn add_focused(&mut self, component_id: ComponentId) {
483 self.focused.push(component_id);
484 }
485
486 #[inline]
487 fn add_notify(&mut self, component_id: ComponentId) {
488 self.notify.push(component_id);
489 }
490
491 #[inline]
492 fn add_tickable(&mut self, component_id: ComponentId, message: DynamicMessage) {
493 self.tickable.push(TickSubscription {
494 component_id,
495 message,
496 });
497 }
498}
499
500struct TickSubscription {
501 component_id: ComponentId,
502 message: DynamicMessage,
503}
504
505impl PollState {
506 pub fn dirty(&self) -> bool {
507 matches!(*self, Self::Dirty(_))
508 }
509
510 pub fn resized(&self) -> bool {
511 matches!(*self, Self::Dirty(Some(_)))
512 }
513
514 pub fn exit(&self) -> bool {
515 matches!(*self, Self::Exit)
516 }
517
518 pub fn merge(&mut self, poll_state: PollState) {
519 *self = match (*self, poll_state) {
520 (Self::Exit, _) | (_, Self::Exit) => Self::Exit,
521 (Self::Clean, other) | (other, Self::Clean) => other,
522 (Self::Dirty(_), resized @ Self::Dirty(Some(_))) => resized,
523 (resized @ Self::Dirty(Some(_)), Self::Dirty(None)) => resized,
524 (Self::Dirty(None), Self::Dirty(None)) => Self::Dirty(None),
525 }
526 }
527}
528
529type Generation = usize;
530
531struct MountedComponent {
532 renderable: Box<dyn Renderable>,
533 frame: Rect,
534 bindings: DynamicBindings,
535 generation: Generation,
536 should_render: bool,
537}
538
539impl MountedComponent {
540 #[inline]
541 fn change(&mut self, properties: DynamicProperties) -> bool {
542 self.should_render = self.renderable.change(properties).into() || self.should_render;
543 self.should_render
544 }
545
546 #[inline]
547 fn resize(&mut self, frame: Rect) -> bool {
548 self.should_render = self.renderable.resize(frame).into() || self.should_render;
549 self.frame = frame;
550 self.should_render
551 }
552
553 #[inline]
554 fn update(&mut self, message: DynamicMessage) -> bool {
555 self.should_render = self.renderable.update(message).into() || self.should_render;
556 self.should_render
557 }
558
559 #[inline]
560 fn view(&mut self) -> Layout {
561 self.should_render = false;
562 self.renderable.view()
563 }
564
565 #[inline]
566 fn update_bindings(&mut self) {
567 self.renderable.bindings(&mut self.bindings)
568 }
569
570 #[inline]
571 fn tick(&self) -> Option<DynamicMessage> {
572 self.renderable.tick()
573 }
574
575 #[inline]
576 fn set_generation(&mut self, generation: Generation) {
577 self.generation = generation;
578 }
579}
580
581struct InputController {
582 keys: SmallVec<[Key; 8]>,
583}
584
585impl InputController {
586 fn new() -> Self {
587 Self {
588 keys: SmallVec::new(),
589 }
590 }
591
592 fn push(&mut self, key: Key) {
593 self.keys.push(key);
594 }
595}
596
597impl std::fmt::Display for InputController {
598 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
599 for key in self.keys.iter() {
600 match key {
601 Key::Char(' ') => write!(formatter, "SPC ")?,
602 Key::Char('\n') => write!(formatter, "RET ")?,
603 Key::Char('\t') => write!(formatter, "TAB ")?,
604 Key::Char(char) => write!(formatter, "{} ", char)?,
605 Key::Ctrl(char) => write!(formatter, "C-{} ", char)?,
606 Key::Alt(char) => write!(formatter, "A-{} ", char)?,
607 Key::F(number) => write!(formatter, "F{} ", number)?,
608 Key::Esc => write!(formatter, "ESC ")?,
609 key => write!(formatter, "{:?} ", key)?,
610 }
611 }
612 Ok(())
613 }
614}
615
616#[derive(Default)]
617struct DrawStatistics {
618 new: usize,
619 changed: usize,
620 deleted: usize,
621 nop: usize,
622}
623
624impl std::fmt::Display for DrawStatistics {
625 fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
626 write!(
627 formatter,
628 "{} new {} upd {} del {} nop",
629 self.new, self.changed, self.deleted, self.nop
630 )
631 }
632}
633
634#[cfg(test)]
635mod tests {
636 use std::sync::mpsc;
637
638 use super::*;
639 use crate::{
640 component::ComponentExt,
641 components::text::{Text, TextProperties},
642 };
643
644 #[derive(Clone, Debug)]
645 struct MessageQueue(mpsc::Sender<ComponentMessage>);
646
647 impl MessageSender for MessageQueue {
648 fn send(&self, message: ComponentMessage) {
649 self.0.send(message).unwrap();
650 }
651
652 fn clone_box(&self) -> Box<dyn MessageSender> {
653 Box::new(self.clone())
654 }
655 }
656
657 impl MessageQueue {
658 fn new(sender: mpsc::Sender<ComponentMessage>) -> Self {
659 Self(sender)
660 }
661 }
662
663 #[test]
664 fn trivial_message_queue() {
665 let (sender, _receiver) = mpsc::channel();
666 let message_queue = MessageQueue::new(sender);
667
668 let mut app = App::new(
669 message_queue,
670 Size::new(10, 10),
671 Text::with(TextProperties::new().content("Hello")),
672 );
673
674 #[allow(clippy::never_loop)]
675 loop {
676 app.handle_resize(Size::new(20, 20));
684
685 let canvas = app.draw();
687 eprintln!("{}", canvas);
688
689 break;
690 }
691 }
692
693 #[test]
694 fn sizes() {
695 eprintln!(
696 "std::mem::size_of::<(ComponentId, DynamicMessage)>() == {}",
697 std::mem::size_of::<(ComponentId, DynamicMessage)>()
698 );
699 eprintln!(
700 "std::mem::size_of::<LinkMessage>() == {}",
701 std::mem::size_of::<LinkMessage>()
702 );
703 }
704}