tui-lipan 0.1.0

Opinionated, component-based TUI framework for Rust - declarative components, reconciliation, layout engine, focus, overlays, and rich widgets on top of ratatui.
Documentation
use crate::Result;
use crate::callback::ScopeId;
use crate::core::component::{Component, UpdateLevel};

use crate::app::input::focus;

use super::{AppRunner, DirtyTracker, FrameworkCommandAction};

impl<C: Component> AppRunner<C> {
    pub(super) fn drain_messages_and_commands(&mut self, dirty: &mut DirtyTracker) -> Result<()> {
        self.core.drain_commands();
        self.process_pending_messages(dirty)?;

        self.core.drain_commands();
        self.process_pending_messages(dirty)
    }

    pub(super) fn process_pending_messages(&mut self, dirty: &mut DirtyTracker) -> Result<()> {
        while let Some((scope, msg)) = { self.core.queue.borrow_mut().pop_front() } {
            let update_level = self.core.update_from_boxed(scope, msg)?;
            match update_level {
                UpdateLevel::None => {}
                UpdateLevel::Paint => {
                    #[cfg(debug_assertions)]
                    if scope == ScopeId(1) {
                        self.debug_paint_claim_root = true;
                    }
                    dirty.mark_paint();
                }
                UpdateLevel::Layout => {
                    if scope == ScopeId(1) {
                        self.dirty_component_scopes.clear();
                        self.dirty_scope_set.clear();
                        self.dirty_scope_set.insert(ScopeId(1));
                        self.dirty_component_scopes.push(ScopeId(1));
                    } else if self.dirty_scope_set.insert(scope) {
                        self.dirty_component_scopes.push(scope);
                    }
                    dirty.mark_layout();
                }
                UpdateLevel::Full => {
                    if scope == ScopeId(1) {
                        self.dirty_component_scopes.clear();
                        self.dirty_scope_set.clear();
                    }
                    dirty.mark_full();
                }
            }
        }
        Ok(())
    }

    pub(super) fn apply_framework_commands(&mut self) -> bool {
        let mut actions = self.framework_command_queue.borrow_mut();
        if actions.is_empty() {
            return false;
        }

        let pending: Vec<FrameworkCommandAction> = actions.drain(..).collect();
        drop(actions);

        let mut dirty = false;
        for action in pending {
            match action {
                FrameworkCommandAction::Quit => {
                    self.core.ctx.quit();
                    dirty = true;
                }
                FrameworkCommandAction::ToggleDevtools => {
                    self.core.ctx.toggle_devtools();
                    dirty = true;
                }
                FrameworkCommandAction::FocusNext => {
                    let before = self.focus.focused;
                    if self.focus_overlay_next() {
                        dirty = true;
                    } else {
                        focus::focus_next(
                            &self.core.tree,
                            &mut self.focus.focused,
                            &mut self.focus.focused_key,
                            &mut self.focus.focused_tag,
                        );
                        if self.focus.focused != before {
                            self.animation.reset_blink();
                            dirty = true;
                        }
                    }
                }
                FrameworkCommandAction::FocusPrev => {
                    let before = self.focus.focused;
                    if self.focus_overlay_prev() {
                        dirty = true;
                    } else {
                        focus::focus_prev(
                            &self.core.tree,
                            &mut self.focus.focused,
                            &mut self.focus.focused_key,
                            &mut self.focus.focused_tag,
                        );
                        if self.focus.focused != before {
                            self.animation.reset_blink();
                            dirty = true;
                        }
                    }
                }
                FrameworkCommandAction::DismissOverlay => {
                    if self.handle_overlay_escape() {
                        dirty = true;
                    }
                }
            }
        }

        dirty
    }
}