#[derive(Debug, Clone)]
pub struct RichLogState {
pub entries: Vec<RichLogEntry>,
pub(crate) scroll_offset: usize,
pub auto_scroll: bool,
pub max_entries: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct RichLogEntry {
pub segments: Vec<(String, Style)>,
}
impl RichLogState {
pub const DEFAULT_MAX_ENTRIES: usize = 10_000;
pub fn new() -> Self {
Self {
max_entries: Some(Self::DEFAULT_MAX_ENTRIES),
..Self::new_unbounded()
}
}
pub fn new_unbounded() -> Self {
Self {
entries: Vec::new(),
scroll_offset: 0,
auto_scroll: true,
max_entries: None,
}
}
pub fn push(&mut self, text: impl Into<String>, style: Style) {
self.push_segments(vec![(text.into(), style)]);
}
pub fn push_plain(&mut self, text: impl Into<String>) {
self.push(text, Style::new());
}
pub fn push_segments(&mut self, segments: Vec<(String, Style)>) {
self.entries.push(RichLogEntry { segments });
if let Some(max_entries) = self.max_entries
&& self.entries.len() > max_entries
{
let remove_count = self.entries.len() - max_entries;
self.entries.drain(0..remove_count);
self.scroll_offset = self.scroll_offset.saturating_sub(remove_count);
}
if self.auto_scroll {
self.scroll_offset = usize::MAX;
}
}
pub fn clear(&mut self) {
self.entries.clear();
self.scroll_offset = 0;
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for RichLogState {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CalDate {
pub year: i32,
pub month: u32,
pub day: u32,
}
impl CalDate {
fn key(&self) -> (i32, u32, u32) {
(self.year, self.month, self.day)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CalendarSelect {
#[default]
Single,
Range,
}
#[derive(Debug, Clone)]
pub struct CalendarState {
pub year: i32,
pub month: u32,
pub selected_day: Option<u32>,
pub(crate) cursor_day: u32,
pub(crate) mode: CalendarSelect,
pub(crate) anchor: Option<CalDate>,
pub(crate) extent: Option<CalDate>,
pub(crate) time_enabled: bool,
pub(crate) hour: u8,
pub(crate) minute: u8,
}
impl CalendarState {
pub fn new() -> Self {
let (year, month) = Self::current_year_month();
Self::from_ym(year, month)
}
pub fn from_ym(year: i32, month: u32) -> Self {
let month = month.clamp(1, 12);
Self {
year,
month,
selected_day: None,
cursor_day: 1,
mode: CalendarSelect::Single,
anchor: None,
extent: None,
time_enabled: false,
hour: 0,
minute: 0,
}
}
pub fn with_range(&mut self) -> &mut Self {
self.mode = CalendarSelect::Range;
self
}
pub fn with_time(&mut self) -> &mut Self {
self.time_enabled = true;
self
}
pub fn mode(&self) -> CalendarSelect {
self.mode
}
pub fn selected_date(&self) -> Option<(i32, u32, u32)> {
self.selected_day.map(|day| (self.year, self.month, day))
}
pub fn selected_range(&self) -> Option<(CalDate, CalDate)> {
if self.mode != CalendarSelect::Range {
return None;
}
let anchor = self.anchor?;
let extent = self.extent.unwrap_or(anchor);
if anchor.key() <= extent.key() {
Some((anchor, extent))
} else {
Some((extent, anchor))
}
}
pub fn selected_time(&self) -> Option<(u8, u8)> {
self.time_enabled.then_some((self.hour, self.minute))
}
pub(crate) fn cursor_date(&self) -> CalDate {
CalDate {
year: self.year,
month: self.month,
day: self.cursor_day,
}
}
pub(crate) fn set_anchor_to_cursor(&mut self) {
let cur = self.cursor_date();
self.anchor = Some(cur);
self.extent = None;
}
pub(crate) fn extend_to_cursor(&mut self) {
let cur = self.cursor_date();
if self.anchor.is_none() {
self.anchor = Some(cur);
}
self.extent = Some(cur);
}
pub(crate) fn in_range(&self, d: CalDate) -> bool {
match self.selected_range() {
Some((start, end)) => start.key() <= d.key() && d.key() <= end.key(),
None => false,
}
}
pub(crate) fn is_range_endpoint(&self, d: CalDate) -> bool {
match self.selected_range() {
Some((start, end)) => d == start || d == end,
None => false,
}
}
pub fn prev_month(&mut self) {
if self.month == 1 {
self.month = 12;
self.year -= 1;
} else {
self.month -= 1;
}
self.clamp_days();
}
pub fn next_month(&mut self) {
if self.month == 12 {
self.month = 1;
self.year += 1;
} else {
self.month += 1;
}
self.clamp_days();
}
pub(crate) fn days_in_month(year: i32, month: u32) -> u32 {
match month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 => {
if Self::is_leap_year(year) {
29
} else {
28
}
}
_ => 30,
}
}
pub(crate) fn first_weekday(year: i32, month: u32) -> u32 {
let month = month.clamp(1, 12);
let offsets = [0_i32, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
let mut y = year;
if month < 3 {
y -= 1;
}
let sunday_based = (y + y / 4 - y / 100 + y / 400 + offsets[(month - 1) as usize] + 1) % 7;
((sunday_based + 6) % 7) as u32
}
fn clamp_days(&mut self) {
let max_day = Self::days_in_month(self.year, self.month);
self.cursor_day = self.cursor_day.clamp(1, max_day);
if let Some(day) = self.selected_day {
self.selected_day = Some(day.min(max_day));
}
}
fn is_leap_year(year: i32) -> bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
fn current_year_month() -> (i32, u32) {
let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) else {
return (1970, 1);
};
let days_since_epoch = (duration.as_secs() / 86_400) as i64;
let (year, month, _) = Self::civil_from_days(days_since_epoch);
(year, month)
}
fn civil_from_days(days_since_epoch: i64) -> (i32, u32, u32) {
let z = days_since_epoch + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let mut year = (yoe as i32) + (era as i32) * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let day = (doy - (153 * mp + 2) / 5 + 1) as u32;
let month = (mp + if mp < 10 { 3 } else { -9 }) as u32;
if month <= 2 {
year += 1;
}
(year, month, day)
}
}
impl Default for CalendarState {
fn default() -> Self {
Self::new()
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ButtonVariant {
#[default]
Default,
Primary,
Danger,
Outline,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trend {
Up,
Down,
}
pub(crate) enum SchedKind {
Once {
deadline: std::time::Instant,
fired: bool,
},
Every {
interval: std::time::Duration,
last: std::time::Instant,
},
Debounce {
dur: std::time::Duration,
deadline: std::time::Instant,
fired: bool,
},
}
pub(crate) struct SchedulerSlot {
pub(crate) started: std::time::Instant,
pub(crate) kind: SchedKind,
pub(crate) touched_this_frame: bool,
}
#[derive(Default)]
pub struct SchedulerState {
pub(crate) named: std::collections::HashMap<&'static str, SchedulerSlot>,
pub(crate) keyed: std::collections::HashMap<String, SchedulerSlot>,
pub(crate) exclusive: std::collections::HashMap<String, ExclusiveGroup>,
}
#[derive(Default)]
pub(crate) struct ExclusiveGroup {
pub(crate) winner: String,
pub(crate) retired: std::collections::HashSet<String>,
}
pub(crate) fn intervals_elapsed(
elapsed: std::time::Duration,
interval: std::time::Duration,
) -> u32 {
let nanos = interval.as_nanos().max(1);
let count = elapsed.as_nanos() / nanos;
count.min(u32::MAX as u128) as u32
}
impl SchedulerState {
pub(crate) fn gc_untouched(&mut self) {
self.named.retain(|_, slot| slot.touched_this_frame);
self.keyed.retain(|_, slot| slot.touched_this_frame);
for slot in self.named.values_mut() {
slot.touched_this_frame = false;
}
for slot in self.keyed.values_mut() {
slot.touched_this_frame = false;
}
}
#[cfg(test)]
pub(crate) fn slot_count(&self) -> usize {
self.named.len() + self.keyed.len()
}
}