1#[derive(Debug, Clone)]
3pub struct RichLogState {
4 pub entries: Vec<RichLogEntry>,
6 pub(crate) scroll_offset: usize,
8 pub auto_scroll: bool,
10 pub max_entries: Option<usize>,
12}
13
14#[derive(Debug, Clone)]
16pub struct RichLogEntry {
17 pub segments: Vec<(String, Style)>,
19}
20
21impl RichLogState {
22 pub fn new() -> Self {
24 Self {
25 entries: Vec::new(),
26 scroll_offset: 0,
27 auto_scroll: true,
28 max_entries: None,
29 }
30 }
31
32 pub fn push(&mut self, text: impl Into<String>, style: Style) {
34 self.push_segments(vec![(text.into(), style)]);
35 }
36
37 pub fn push_plain(&mut self, text: impl Into<String>) {
39 self.push(text, Style::new());
40 }
41
42 pub fn push_segments(&mut self, segments: Vec<(String, Style)>) {
44 self.entries.push(RichLogEntry { segments });
45
46 if let Some(max_entries) = self.max_entries {
47 if self.entries.len() > max_entries {
48 let remove_count = self.entries.len() - max_entries;
49 self.entries.drain(0..remove_count);
50 self.scroll_offset = self.scroll_offset.saturating_sub(remove_count);
51 }
52 }
53
54 if self.auto_scroll {
55 self.scroll_offset = usize::MAX;
56 }
57 }
58
59 pub fn clear(&mut self) {
61 self.entries.clear();
62 self.scroll_offset = 0;
63 }
64
65 pub fn len(&self) -> usize {
67 self.entries.len()
68 }
69
70 pub fn is_empty(&self) -> bool {
72 self.entries.is_empty()
73 }
74}
75
76impl Default for RichLogState {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct CalendarState {
85 pub year: i32,
87 pub month: u32,
89 pub selected_day: Option<u32>,
91 pub(crate) cursor_day: u32,
92}
93
94impl CalendarState {
95 pub fn new() -> Self {
97 let (year, month) = Self::current_year_month();
98 Self::from_ym(year, month)
99 }
100
101 pub fn from_ym(year: i32, month: u32) -> Self {
103 let month = month.clamp(1, 12);
104 Self {
105 year,
106 month,
107 selected_day: None,
108 cursor_day: 1,
109 }
110 }
111
112 pub fn selected_date(&self) -> Option<(i32, u32, u32)> {
114 self.selected_day.map(|day| (self.year, self.month, day))
115 }
116
117 pub fn prev_month(&mut self) {
119 if self.month == 1 {
120 self.month = 12;
121 self.year -= 1;
122 } else {
123 self.month -= 1;
124 }
125 self.clamp_days();
126 }
127
128 pub fn next_month(&mut self) {
130 if self.month == 12 {
131 self.month = 1;
132 self.year += 1;
133 } else {
134 self.month += 1;
135 }
136 self.clamp_days();
137 }
138
139 pub(crate) fn days_in_month(year: i32, month: u32) -> u32 {
140 match month {
141 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
142 4 | 6 | 9 | 11 => 30,
143 2 => {
144 if Self::is_leap_year(year) {
145 29
146 } else {
147 28
148 }
149 }
150 _ => 30,
151 }
152 }
153
154 pub(crate) fn first_weekday(year: i32, month: u32) -> u32 {
155 let month = month.clamp(1, 12);
156 let offsets = [0_i32, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
157 let mut y = year;
158 if month < 3 {
159 y -= 1;
160 }
161 let sunday_based = (y + y / 4 - y / 100 + y / 400 + offsets[(month - 1) as usize] + 1) % 7;
162 ((sunday_based + 6) % 7) as u32
163 }
164
165 fn clamp_days(&mut self) {
166 let max_day = Self::days_in_month(self.year, self.month);
167 self.cursor_day = self.cursor_day.clamp(1, max_day);
168 if let Some(day) = self.selected_day {
169 self.selected_day = Some(day.min(max_day));
170 }
171 }
172
173 fn is_leap_year(year: i32) -> bool {
174 (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
175 }
176
177 fn current_year_month() -> (i32, u32) {
178 let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) else {
179 return (1970, 1);
180 };
181 let days_since_epoch = (duration.as_secs() / 86_400) as i64;
182 let (year, month, _) = Self::civil_from_days(days_since_epoch);
183 (year, month)
184 }
185
186 fn civil_from_days(days_since_epoch: i64) -> (i32, u32, u32) {
187 let z = days_since_epoch + 719_468;
188 let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
189 let doe = z - era * 146_097;
190 let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
191 let mut year = (yoe as i32) + (era as i32) * 400;
192 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
193 let mp = (5 * doy + 2) / 153;
194 let day = (doy - (153 * mp + 2) / 5 + 1) as u32;
195 let month = (mp + if mp < 10 { 3 } else { -9 }) as u32;
196 if month <= 2 {
197 year += 1;
198 }
199 (year, month, day)
200 }
201}
202
203impl Default for CalendarState {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209#[non_exhaustive]
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
220pub enum ButtonVariant {
221 #[default]
223 Default,
224 Primary,
226 Danger,
228 Outline,
230}
231
232#[non_exhaustive]
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum Trend {
236 Up,
238 Down,
240}
241
242