Model list arrow flips when open; temperatures in a fixed order; bigger avatar; clock font, weight and digit width as settings; Cancel at the top of the event form, Esc cancels it
This commit is contained in:
@@ -321,7 +321,9 @@ pub fn card(width: f32, height: f32) -> Rectangle {
|
||||
let theme = theme::now();
|
||||
|
||||
// the model, as a chip that opens the list of the others
|
||||
let mut buttons: Vec<Box<dyn Widget>> = vec![Box::new(pods::chip(&settings::model(), Some(Glyph::Chevron), pick))];
|
||||
// the model, as a chip that opens the list of the others; its arrow points up while the list is open
|
||||
let arrow = if ai.models.is_some() { Glyph::ChevronUp } else { Glyph::Chevron };
|
||||
let mut buttons: Vec<Box<dyn Widget>> = vec![Box::new(pods::chip(&settings::model(), Some(arrow), pick))];
|
||||
|
||||
if ai.busy {
|
||||
buttons.push(Box::new(pods::chip("Stop", None, stop)));
|
||||
|
||||
+5
-2
@@ -518,7 +518,7 @@ settings! {
|
||||
interval_ms: u64 = 1000, 200..=10000; "How often the system monitor reads the system"
|
||||
history: usize = 60, 10..=600; "How many readings the graphs show"
|
||||
graph_height: f32 = 34.0, 10..=200; "Height of each graph"
|
||||
temperatures: usize = 6, 0..=20; "How many temperature sensors to show, hottest first"
|
||||
temperatures: usize = 6, 0..=20; "How many temperature sensors to show, always in the same order: CPU, iGPU, eGPU, NVMe, memory, board, then the rest"
|
||||
warm: f32 = 65.0, 20..=120; "From this temperature (°C) a sensor shows in the warm colour"
|
||||
hot: f32 = 85.0, 20..=130; "From this temperature (°C) a sensor shows in the hot colour"
|
||||
cool_color: Color = Color::rgb(120, 200, 255); "Colour of a sensor below the warm temperature"
|
||||
@@ -532,9 +532,12 @@ settings! {
|
||||
home {
|
||||
width: f32 = 820.0, 300..=1600; "Width of the home card. It opens rightwards from beside the island, so on a 1920 px wide monitor about 820 fits"
|
||||
clock_size: f32 = 64.0, 20..=200; "Size of the big clock"
|
||||
clock_font: String = String::new(); "The big clock's font family, any installed one (fc-list : family lists them); empty takes the shell's font"
|
||||
clock_weight: u64 = 700, 100..=900; "The big clock's weight, 100 (thin) to 900 (black); a font without that weight shows its nearest one"
|
||||
clock_letter_width: f32 = 0.0, 0..=2; "Width of one of the clock's digits as a share of its size, so the weather beside it makes room; 0 takes the shell font's"
|
||||
seconds: bool = false; "Show seconds on the big clock"
|
||||
avatar: String = String::from("~/.face"); "The picture shown as the user's avatar; a drawn person without one"
|
||||
avatar_size: f32 = 48.0, 24..=120; "Size of the avatar; the greeting row is as tall"
|
||||
avatar_size: f32 = 84.0, 24..=200; "Size of the avatar; the greeting row is as tall"
|
||||
gap: f32 = 12.0, 0..=60; "Space between the home card's parts"
|
||||
city: String = String::from("Munich"); "The weather's place, as shown"
|
||||
latitude: f32 = 48.137, -90.0..=90.0; "The weather's latitude"
|
||||
|
||||
@@ -12,6 +12,7 @@ pub enum Glyph {
|
||||
Muted,
|
||||
Bluetooth,
|
||||
Chevron,
|
||||
ChevronUp,
|
||||
// pointing left, for a menu's way back
|
||||
Back,
|
||||
Note,
|
||||
@@ -138,6 +139,7 @@ pub fn glyph_shapes(glyph: Glyph, size: f32, color: Color, weight: f32, origin:
|
||||
.cap(Cap::Round)
|
||||
],
|
||||
Glyph::Chevron => shapes![Path::new().move_to(x(0.12), y(0.32)).line_to(x(0.5), y(0.7)).line_to(x(0.88), y(0.32)).stroke(1.5, color).cap(Cap::Round)],
|
||||
Glyph::ChevronUp => shapes![Path::new().move_to(x(0.12), y(0.68)).line_to(x(0.5), y(0.3)).line_to(x(0.88), y(0.68)).stroke(1.5, color).cap(Cap::Round)],
|
||||
Glyph::Back => shapes![Path::new().move_to(x(0.64), y(0.14)).line_to(x(0.3), y(0.5)).line_to(x(0.64), y(0.86)).stroke(thin.max(1.6), color).cap(Cap::Round)],
|
||||
// an eighth note: round head, stem, a curved flag
|
||||
Glyph::Note => shapes![
|
||||
|
||||
+36
-11
@@ -350,6 +350,22 @@ fn sync_settings() -> (String, String, String, bool) {
|
||||
}
|
||||
|
||||
// whether the card has a form to type into: then it keeps the keyboard and stays open
|
||||
// the event form closes without saving
|
||||
fn cancel() {
|
||||
Agenda::write().form = None;
|
||||
}
|
||||
|
||||
// Esc in the card: closes the event form if it's open (then true), else leaves it to the card
|
||||
pub fn escape() -> bool {
|
||||
let open = Agenda::read().form.is_some();
|
||||
|
||||
if open {
|
||||
cancel();
|
||||
}
|
||||
|
||||
open
|
||||
}
|
||||
|
||||
pub fn typing() -> bool {
|
||||
Agenda::read().form.is_some()
|
||||
}
|
||||
@@ -1197,18 +1213,18 @@ fn top_row(clock: &Clock, width: f32, shapes: &mut Vec<Box<dyn Shape>>) -> Box<d
|
||||
|
||||
// the big clock, and the weather beside it; `y` is where the row starts in the card
|
||||
fn middle_row(clock: &Clock, forecast: &Forecast, width: f32, today: i64, y: f32, shapes: &mut Vec<Box<dyn Shape>>) -> Box<dyn Widget> {
|
||||
let theme = theme::now();
|
||||
let gap = settings::gap();
|
||||
|
||||
let seconds_today = clock.now.rem_euclid(86_400);
|
||||
let (hour, minute, second) = (seconds_today / 3_600, seconds_today % 3_600 / 60, seconds_today % 60);
|
||||
let time = if settings::seconds() { format!("{hour:02}:{minute:02}:{second:02}") } else { format!("{hour:02}:{minute:02}") };
|
||||
|
||||
let clock_w = (settings::clock_size() * font::letter_width() * time.chars().count() as f32).min(width / 2.0);
|
||||
let letter = if settings::clock_letter_width() > 0.0 { settings::clock_letter_width() } else { font::letter_width() };
|
||||
let clock_w = (settings::clock_size() * letter * time.chars().count() as f32).min(width / 2.0);
|
||||
let weather_w = (width - clock_w - gap).max(0.0);
|
||||
let height = middle_h();
|
||||
|
||||
let big = Rectangle::new().width(clock_w).height(height).align_child(Start, Center).child(Text::new(time).size(settings::clock_size()).weight(Weight::Bold).color(theme.text).tight());
|
||||
let big = Rectangle::new().width(clock_w).height(height).align_child(Start, Center).child(clock_text(time));
|
||||
let weather = weather(forecast, weather_w, today, (clock_w + gap, y + (height - weather_h()) / 2.0), shapes);
|
||||
|
||||
Box::new(
|
||||
@@ -1219,6 +1235,14 @@ fn middle_row(clock: &Clock, forecast: &Forecast, width: f32, today: i64, y: f32
|
||||
)
|
||||
}
|
||||
|
||||
// the big clock's digits in its own font and weight
|
||||
fn clock_text(time: String) -> Text {
|
||||
let digits = Text::new(time).size(settings::clock_size()).weight(settings::clock_weight().min(1000) as u16).color(theme::now().text).tight();
|
||||
let family = settings::clock_font();
|
||||
|
||||
if family.trim().is_empty() { digits } else { digits.font(family.trim()) }
|
||||
}
|
||||
|
||||
// the weather now over the coming days, at `origin` in the card
|
||||
fn weather(forecast: &Forecast, width: f32, today: i64, origin: (f32, f32), shapes: &mut Vec<Box<dyn Shape>>) -> Rectangle {
|
||||
let theme = theme::now();
|
||||
@@ -1744,6 +1768,7 @@ fn event_form(agenda: &Agenda, form: &Form, width: f32, origin: (f32, f32), shap
|
||||
};
|
||||
|
||||
let mut top: Vec<Box<dyn Widget>> = vec![Box::new(Text::new(heading).size(text::heading()).weight(Weight::Bold).color(theme.text))];
|
||||
let mut actions: Vec<Box<dyn Widget>> = Vec::new();
|
||||
|
||||
if form.editing.is_some() {
|
||||
let words = match (form.confirm, series) {
|
||||
@@ -1753,11 +1778,16 @@ fn event_form(agenda: &Agenda, form: &Form, width: f32, origin: (f32, f32), shap
|
||||
};
|
||||
|
||||
let button = button_size();
|
||||
let at = (origin.0 + width - choice_width(words, button / 2.0 + 6.0), origin.1 + (pods::header_height() - button) / 2.0);
|
||||
let cancel_w = choice_width("Cancel", 0.0);
|
||||
let at = (origin.0 + width - cancel_w - gap - choice_width(words, button / 2.0 + 6.0), origin.1 + (pods::header_height() - button) / 2.0);
|
||||
|
||||
top.push(Box::new(icon_chip(words, form.confirm, Glyph::Trash, at, shapes, remove)));
|
||||
actions.push(Box::new(icon_chip(words, form.confirm, Glyph::Trash, at, shapes, remove)));
|
||||
}
|
||||
|
||||
// Cancel at the top, where it's always in sight (Esc does the same)
|
||||
actions.push(Box::new(pods::chip("Cancel", None, cancel)));
|
||||
top.push(Box::new(Row::new(actions).gap(gap).align(Center)));
|
||||
|
||||
let mut parts: Vec<Box<dyn Widget>> = vec![
|
||||
Box::new(Rectangle::new().width(width).height(pods::header_height()).child(Row::new(top).width(width).justify(SpaceBetween).align(Center))),
|
||||
Box::new(field(title, width)),
|
||||
@@ -1834,12 +1864,7 @@ fn event_form(agenda: &Agenda, form: &Form, width: f32, origin: (f32, f32), shap
|
||||
parts.push(Box::new(wrapped(problem.clone(), overlays::low_battery(), width)));
|
||||
}
|
||||
|
||||
let buttons: Vec<Box<dyn Widget>> = vec![
|
||||
Box::new(choice(if form.saving { "Saving…" } else { "Save" }, true, None, save)),
|
||||
Box::new(pods::chip("Cancel", None, || Agenda::write().form = None)),
|
||||
];
|
||||
|
||||
parts.push(Box::new(Row::new(buttons).gap(gap).align(Center)));
|
||||
parts.push(Box::new(choice(if form.saving { "Saving…" } else { "Save" }, true, None, save)));
|
||||
|
||||
Box::new(Rectangle::new().width(width).height(calendar_h()).child(Column::new(parts).gap(gap)))
|
||||
}
|
||||
|
||||
+2
-1
@@ -334,7 +334,8 @@ fn side(monitor: &Monitor, left: bool) -> LayerWindow {
|
||||
|
||||
let window = if typing {
|
||||
window.keyboard(Keyboard::OnDemand).on_key(|key| {
|
||||
if key == Key::Escape {
|
||||
// Esc closes the home card's event form first, then the card
|
||||
if key == Key::Escape && !home::escape() {
|
||||
close();
|
||||
}
|
||||
})
|
||||
|
||||
+12
-4
@@ -156,12 +156,19 @@ fn gpu_clock() -> (f32, String) {
|
||||
(0.0, String::from("no clock to read"))
|
||||
}
|
||||
|
||||
// every hwmon device's hottest sensor, named for what it is, hottest first
|
||||
// what the temperatures are shown in, always: a sensor keeps its place whatever its reading
|
||||
const ORDER: [&str; 8] = ["CPU", "iGPU", "eGPU", "NVMe", "Memory", "Board", "ThinkPad", "Wi-Fi"];
|
||||
|
||||
// every hwmon device's hottest sensor, named for what it is, in ORDER (then by name, then by device)
|
||||
fn temperatures() -> Vec<(String, Glyph, f32)> {
|
||||
let mut found: Vec<(String, Glyph, f32)> = Vec::new();
|
||||
|
||||
for entry in fs::read_dir("/sys/class/hwmon").into_iter().flatten().flatten() {
|
||||
let device = entry.path();
|
||||
// the devices in their numbers' order (hwmon2 before hwmon10), so two of a kind keep theirs too
|
||||
let mut devices: Vec<PathBuf> = fs::read_dir("/sys/class/hwmon").into_iter().flatten().flatten().map(|entry| entry.path()).collect();
|
||||
|
||||
devices.sort_by_key(|path| path.file_name().and_then(|name| name.to_str()).and_then(|name| name.trim_start_matches("hwmon").parse::<u32>().ok()).unwrap_or(u32::MAX));
|
||||
|
||||
for device in devices {
|
||||
let kind = read(device.join("name")).trim().to_string();
|
||||
|
||||
let hottest = fs::read_dir(&device)
|
||||
@@ -191,7 +198,8 @@ fn temperatures() -> Vec<(String, Glyph, f32)> {
|
||||
found.push((name.to_string(), glyph, celsius));
|
||||
}
|
||||
|
||||
found.sort_by(|a, b| b.2.total_cmp(&a.2));
|
||||
// a stable sort: same kinds keep the devices' order
|
||||
found.sort_by_key(|(name, ..)| (ORDER.iter().position(|kind| kind == name).unwrap_or(ORDER.len()), name.clone()));
|
||||
found.truncate(settings::temperatures());
|
||||
|
||||
// a kind shown more than once is numbered, in the order shown
|
||||
|
||||
Reference in New Issue
Block a user