Menu
cosmic-mobile
publicLatest change 975a13e7afc3dea7e34c04e77e3879cf635a1bf7 - Upgrade system app components to iOS design language by AkurAI Build
use crate::ios;
use eframe::egui::*;
use std::{
path::Path,
time::{Duration, Instant},
};
pub fn save_note(path: &Path, text: &str) -> std::io::Result<()> {
if let Some(p) = path.parent() {
std::fs::create_dir_all(p)?;
}
let tmp = path.with_extension("tmp");
std::fs::write(&tmp, text)?;
std::fs::rename(tmp, path)
}
pub struct Calculator {
pub display: String,
value: f64,
op: String,
fresh: bool,
}
impl Default for Calculator {
fn default() -> Self {
Self {
display: "0".into(),
value: 0.,
op: String::new(),
fresh: true,
}
}
}
impl Calculator {
pub fn key(&mut self, k: &str) {
if k == "AC" {
*self = Self::default();
return;
}
if k.chars().all(|c| c.is_ascii_digit()) || k == "." {
if self.fresh {
self.display = "0".into();
self.fresh = false;
}
if k == "." {
if !self.display.contains('.') {
self.display.push('.');
}
} else if self.display.len() < 15 {
if self.display == "0" {
self.display.clear();
}
self.display.push_str(k);
}
return;
}
let Ok(n) = self.display.parse::<f64>() else {
return;
};
if k == "±" {
self.display = (-n).to_string();
return;
}
if k == "%" {
self.display = (n / 100.).to_string();
return;
}
if !self.op.is_empty() && !self.fresh {
let v = match self.op.as_str() {
"+" => self.value + n,
"−" => self.value - n,
"×" => self.value * n,
"÷" => self.value / n,
_ => n,
};
if !v.is_finite() {
self.display = "Error".into();
self.op.clear();
self.fresh = true;
return;
}
self.display = v.to_string();
}
self.value = self.display.parse().unwrap_or(0.);
self.op = if k == "=" { String::new() } else { k.into() };
self.fresh = true;
}
pub fn ui(&mut self, ui: &mut Ui) {
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
ui.label(RichText::new(&self.display).size(72.));
});
ui.add_space(16.);
let gap = 12.;
let size = ((ui.available_width() - 3. * gap) / 4.).clamp(56., 84.);
for row in [
["AC", "±", "%", "÷"],
["7", "8", "9", "×"],
["4", "5", "6", "−"],
["1", "2", "3", "+"],
["0", ".", "=", ""],
] {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = gap;
for k in row {
if k.is_empty() {
continue;
}
let (fill, text) = if ["÷", "×", "−", "+", "="].contains(&k) {
(ios::ORANGE, Color32::WHITE)
} else if ["AC", "±", "%"].contains(&k) {
(ios::KEY_LIGHT, Color32::BLACK)
} else {
(ios::KEY, Color32::WHITE)
};
if ios::key(ui, k, size, fill, text).clicked() {
self.key(k);
}
}
});
ui.add_space(gap);
}
}
}
#[derive(Default)]
pub struct Stopwatch {
elapsed: Duration,
started: Option<Instant>,
}
impl Stopwatch {
pub fn ui(&mut self, ui: &mut Ui) {
let elapsed = self.elapsed + self.started.map(|s| s.elapsed()).unwrap_or_default();
ui.vertical_centered(|ui| {
ui.label(
RichText::new(format!(
"{:02}:{:02}.{:01}",
elapsed.as_secs() / 60,
elapsed.as_secs() % 60,
elapsed.subsec_millis() / 100
))
.size(56.),
);
});
ui.add_space(24.);
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = ui.available_width() - 2. * 84.;
if ios::key(ui, "Reset", 84., ios::KEY, Color32::WHITE).clicked() {
*self = Self::default();
}
let (label, fill) = if self.started.is_some() {
("Stop", ios::RED)
} else {
("Start", ios::GREEN)
};
if ios::key(ui, label, 84., fill, Color32::WHITE).clicked() {
if let Some(s) = self.started.take() {
self.elapsed += s.elapsed();
} else {
self.started = Some(Instant::now());
}
}
});
if self.started.is_some() {
ui.ctx().request_repaint_after(Duration::from_millis(100));
}
}
}