sql_cli/ui/rendering/
render_state.rs1use std::time::{Duration, Instant};
7use tracing::{debug, trace};
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum RenderReason {
12 Initial,
14 UserInput,
16 SearchUpdate,
18 NavigationChange,
20 DataChange,
22 WindowResize,
24 PeriodicRefresh,
26 DebouncedAction,
28}
29
30pub struct RenderState {
32 dirty: bool,
34 dirty_reason: Option<RenderReason>,
36 last_render: Instant,
38 min_render_interval: Duration,
40 force_render: bool,
42 high_frequency_mode: bool,
44}
45
46impl Default for RenderState {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl RenderState {
53 #[must_use]
55 pub fn new() -> Self {
56 Self {
57 dirty: true, dirty_reason: Some(RenderReason::Initial),
59 last_render: Instant::now(),
60 min_render_interval: Duration::from_millis(16), force_render: false,
62 high_frequency_mode: false,
63 }
64 }
65
66 pub fn mark_dirty(&mut self, reason: RenderReason) {
68 if !self.dirty {
69 debug!("Marking render state dirty: {:?}", reason);
70 }
71 self.dirty = true;
72 self.dirty_reason = Some(reason);
73 }
74
75 pub fn needs_render(&self) -> bool {
77 if self.force_render {
78 return true;
79 }
80
81 if !self.dirty {
82 return false;
83 }
84
85 let elapsed = self.last_render.elapsed();
87 if elapsed < self.min_render_interval && !self.high_frequency_mode {
88 trace!("Skipping render, only {:?} elapsed", elapsed);
89 return false;
90 }
91
92 true
93 }
94
95 pub fn rendered(&mut self) {
97 trace!("Render completed, reason was: {:?}", self.dirty_reason);
98 self.dirty = false;
99 self.dirty_reason = None;
100 self.last_render = Instant::now();
101 self.force_render = false;
102 }
103
104 pub fn force_render(&mut self) {
106 debug!("Forcing render on next check");
107 self.force_render = true;
108 self.dirty = true;
109 }
110
111 pub fn set_high_frequency_mode(&mut self, enabled: bool) {
113 if self.high_frequency_mode != enabled {
114 debug!("High-frequency render mode: {}", enabled);
115 self.high_frequency_mode = enabled;
116 if enabled {
117 self.min_render_interval = Duration::from_millis(8); } else {
120 self.min_render_interval = Duration::from_millis(16); }
122 }
123 }
124
125 #[must_use]
127 pub fn dirty_reason(&self) -> Option<&RenderReason> {
128 self.dirty_reason.as_ref()
129 }
130
131 #[must_use]
133 pub fn is_dirty(&self) -> bool {
134 self.dirty
135 }
136}
137
138impl RenderState {
140 pub fn on_navigation_change(&mut self) {
142 self.mark_dirty(RenderReason::NavigationChange);
143 }
144
145 pub fn on_search_update(&mut self) {
147 self.mark_dirty(RenderReason::SearchUpdate);
148 self.force_render = true;
150 }
151
152 pub fn on_data_change(&mut self) {
154 self.mark_dirty(RenderReason::DataChange);
155 }
156
157 pub fn on_user_input(&mut self) {
159 self.mark_dirty(RenderReason::UserInput);
160 }
161
162 pub fn on_window_resize(&mut self) {
164 self.mark_dirty(RenderReason::WindowResize);
165 self.force_render = true;
166 }
167
168 pub fn on_debounced_action(&mut self) {
170 self.mark_dirty(RenderReason::DebouncedAction);
171 self.force_render = true;
173 }
174}