sql_cli/ui/rendering/
render_state.rs

1//! Render State Manager - Tracks when UI needs re-rendering
2//!
3//! This module provides centralized tracking of UI state changes
4//! and determines when the table (and other components) need to be re-rendered.
5
6use std::time::{Duration, Instant};
7use tracing::{debug, trace};
8
9/// Reasons why a re-render might be needed
10#[derive(Debug, Clone, PartialEq)]
11pub enum RenderReason {
12    /// Initial render
13    Initial,
14    /// User input/key press
15    UserInput,
16    /// Search results updated
17    SearchUpdate,
18    /// Navigation/cursor moved
19    NavigationChange,
20    /// Data changed (filter, sort, etc.)
21    DataChange,
22    /// Window resized
23    WindowResize,
24    /// Periodic refresh
25    PeriodicRefresh,
26    /// Debounced action completed
27    DebouncedAction,
28}
29
30/// Manages rendering state and dirty flags
31pub struct RenderState {
32    /// Whether the UI needs re-rendering
33    dirty: bool,
34    /// Reason for the dirty state
35    dirty_reason: Option<RenderReason>,
36    /// Last render time
37    last_render: Instant,
38    /// Minimum time between renders (to prevent excessive redraws)
39    min_render_interval: Duration,
40    /// Force render on next check
41    force_render: bool,
42    /// Track if we're in a search/input mode that needs frequent updates
43    high_frequency_mode: bool,
44}
45
46impl RenderState {
47    /// Create a new render state manager
48    pub fn new() -> Self {
49        Self {
50            dirty: true, // Start dirty to trigger initial render
51            dirty_reason: Some(RenderReason::Initial),
52            last_render: Instant::now(),
53            min_render_interval: Duration::from_millis(16), // ~60 FPS max
54            force_render: false,
55            high_frequency_mode: false,
56        }
57    }
58
59    /// Mark the UI as needing re-render
60    pub fn mark_dirty(&mut self, reason: RenderReason) {
61        if !self.dirty {
62            debug!("Marking render state dirty: {:?}", reason);
63        }
64        self.dirty = true;
65        self.dirty_reason = Some(reason);
66    }
67
68    /// Check if re-render is needed
69    pub fn needs_render(&self) -> bool {
70        if self.force_render {
71            return true;
72        }
73
74        if !self.dirty {
75            return false;
76        }
77
78        // Check if enough time has passed since last render
79        let elapsed = self.last_render.elapsed();
80        if elapsed < self.min_render_interval && !self.high_frequency_mode {
81            trace!("Skipping render, only {:?} elapsed", elapsed);
82            return false;
83        }
84
85        true
86    }
87
88    /// Mark that a render has occurred
89    pub fn rendered(&mut self) {
90        trace!("Render completed, reason was: {:?}", self.dirty_reason);
91        self.dirty = false;
92        self.dirty_reason = None;
93        self.last_render = Instant::now();
94        self.force_render = false;
95    }
96
97    /// Force a render on the next check
98    pub fn force_render(&mut self) {
99        debug!("Forcing render on next check");
100        self.force_render = true;
101        self.dirty = true;
102    }
103
104    /// Set high-frequency mode (for search/input)
105    pub fn set_high_frequency_mode(&mut self, enabled: bool) {
106        if self.high_frequency_mode != enabled {
107            debug!("High-frequency render mode: {}", enabled);
108            self.high_frequency_mode = enabled;
109            if enabled {
110                // Reduce minimum interval for more responsive updates
111                self.min_render_interval = Duration::from_millis(8); // ~120 FPS max
112            } else {
113                self.min_render_interval = Duration::from_millis(16); // ~60 FPS max
114            }
115        }
116    }
117
118    /// Get the current dirty reason
119    pub fn dirty_reason(&self) -> Option<&RenderReason> {
120        self.dirty_reason.as_ref()
121    }
122
123    /// Check if currently dirty
124    pub fn is_dirty(&self) -> bool {
125        self.dirty
126    }
127}
128
129/// Helper methods for common state changes
130impl RenderState {
131    /// Navigation changed (cursor moved)
132    pub fn on_navigation_change(&mut self) {
133        self.mark_dirty(RenderReason::NavigationChange);
134    }
135
136    /// Search results updated
137    pub fn on_search_update(&mut self) {
138        self.mark_dirty(RenderReason::SearchUpdate);
139        // Search updates should render immediately
140        self.force_render = true;
141    }
142
143    /// Data changed (filter, sort, etc.)
144    pub fn on_data_change(&mut self) {
145        self.mark_dirty(RenderReason::DataChange);
146    }
147
148    /// User input received
149    pub fn on_user_input(&mut self) {
150        self.mark_dirty(RenderReason::UserInput);
151    }
152
153    /// Window resized
154    pub fn on_window_resize(&mut self) {
155        self.mark_dirty(RenderReason::WindowResize);
156        self.force_render = true;
157    }
158
159    /// Debounced action completed
160    pub fn on_debounced_action(&mut self) {
161        self.mark_dirty(RenderReason::DebouncedAction);
162        // Debounced actions should render immediately to show results
163        self.force_render = true;
164    }
165}