system settings
git clone https://git.lucas.co/cce-system-interface.git
Update components and system settings
src/main.rs | 242 ++++++++++++++++++++++++++++++++++++++-------
src/pages/display.rs | 113 +++++++++++++++++----
src/pages/input.rs | 152 +++++++++++++++++++++++++---
src/pages/notifications.rs | 84 +++++++++++++---
src/pages/status.rs | 55 ++++++++---
5 files changed, 549 insertions(+), 97 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 1bbf4b3..5960ebf 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -139,6 +139,8 @@ struct SystemInterface {
width: u32,
height: u32,
needs_rebuild: bool,
+ scroll_y: f32,
+ max_scroll_y: f32,
}
impl SystemInterface {
@@ -301,7 +303,7 @@ impl SystemInterface {
app,
font_system, swash_cache, text_atlas, text_renderer, text_viewport,
widgets: Vec::new(), text_items: Vec::new(), page_buttons: Vec::new(),
- sidebar_width: 140.0, header_height: 0.0, status_height: 28.0,
+ sidebar_width: 140.0, header_height: 0.0, status_height: 0.0,
cursor_x: 0.0, cursor_y: 0.0,
scale_factor,
rx_power, rx_audio, rx_display, rx_network, rx_layout, rx_input,
@@ -310,6 +312,8 @@ impl SystemInterface {
tx_color_selector, rx_color_selector,
width: size.width, height: size.height,
needs_rebuild: true,
+ scroll_y: 0.0,
+ max_scroll_y: 0.0,
};
this.rebuild_layout(sw, sh);
this
@@ -379,9 +383,24 @@ impl SystemInterface {
// Page content in LOGICAL coordinates, then scale to physical
let pc = self.render_page_content(lcx, lcy, lcw, lch);
+ let mut max_y = 0.0f32;
+ for (_, _, y, _, h) in &pc.rects {
+ max_y = max_y.max(y + h);
+ }
+ for (_, size, _, y, _) in &pc.texts {
+ max_y = max_y.max(y + size);
+ }
+ for btn in &pc.buttons {
+ max_y = max_y.max(btn.y + btn.h);
+ }
+ self.max_scroll_y = (max_y - lch).max(0.0);
+ self.scroll_y = self.scroll_y.min(self.max_scroll_y);
+
+ let scroll_offset_y = self.scroll_y;
+
for (c, x, y, w, h) in &pc.rects {
widgets.push(AppWidget {
- x: *x * s, y: *y * s, w: *w * s, h: *h * s,
+ x: *x * s, y: (*y - scroll_offset_y) * s, w: *w * s, h: *h * s,
color: *c, hover_color: *c,
hovering: false, kind: WidgetKind::Static,
});
@@ -389,7 +408,7 @@ impl SystemInterface {
for (t, size, x, y, tc) in &pc.texts {
text_items.push(TextItem {
buffer: make_text_buffer(&mut self.font_system, t, *size * s),
- x: *x * s, y: *y * s,
+ x: *x * s, y: (*y - scroll_offset_y) * s,
color: glyphon::Color::rgb(
(tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
),
@@ -397,7 +416,7 @@ impl SystemInterface {
}
for btn in &pc.buttons {
widgets.push(AppWidget {
- x: btn.x * s, y: btn.y * s, w: btn.w * s, h: btn.h * s,
+ x: btn.x * s, y: (btn.y - scroll_offset_y) * s, w: btn.w * s, h: btn.h * s,
color: btn.bg, hover_color: btn.hover_bg,
hovering: false,
kind: WidgetKind::ActionButton(btn.action.clone()),
@@ -407,7 +426,7 @@ impl SystemInterface {
let lh = btn.label_size * s * 1.4;
text_items.push(TextItem {
buffer: buf,
- x: btn.x * s + (btn.w * s - tw) / 2.0, y: btn.y * s + (btn.h * s - lh) / 2.0,
+ x: btn.x * s + (btn.w * s - tw) / 2.0, y: (btn.y - scroll_offset_y) * s + (btn.h * s - lh) / 2.0,
color: glyphon::Color::rgb(
(btn.label_color[0] * 255.0) as u8,
(btn.label_color[1] * 255.0) as u8,
@@ -415,16 +434,11 @@ impl SystemInterface {
),
});
let mut cb = btn.clone();
- cb.x *= s; cb.y *= s; cb.w *= s; cb.h *= s;
+ cb.x *= s; cb.y = (cb.y - scroll_offset_y) * s; cb.w *= s; cb.h *= s;
page_buttons.push(cb);
}
- // Status bar
- widgets.push(AppWidget {
- x: 0.0, y: sh - st_h, w: sw, h: st_h,
- color: color::STATUS_BG, hover_color: color::STATUS_BG,
- hovering: false, kind: WidgetKind::Static,
- });
+
self.widgets = widgets;
self.text_items = text_items;
@@ -443,7 +457,7 @@ impl SystemInterface {
Page::Processors => processors::view(&self.app.processors, cx, cy, cw, ch),
Page::Input => input::view(&mut self.app.input, cx, cy, cw, ch),
Page::System => system_info::view(&self.app.system_info, cx, cy, cw, ch),
- Page::Status => status::view(&self.app.status, cx, cy, cw, ch),
+ Page::Status => status::view(&mut self.app.status, cx, cy, cw, ch),
Page::Storage => storage::view(&self.app.storage, cx, cy, cw, ch),
Page::Notifications => notifications::view(&mut self.app.notifications, cx, cy, cw, ch),
Page::Backup => backup::view(&self.app.backup, cx, cy, cw, ch),
@@ -644,9 +658,29 @@ impl SystemInterface {
fn handle_event(&mut self, event: &WindowEvent) -> bool {
match event {
+ WindowEvent::MouseWheel { delta, .. } => {
+ let s = self.scale_factor as f32;
+ if self.cursor_x >= self.sidebar_width * s {
+ let scroll_speed = 24.0;
+ let dy = match delta {
+ winit::event::MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
+ winit::event::MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
+ };
+ let old_scroll = self.scroll_y;
+ self.scroll_y = (self.scroll_y + dy).max(0.0).min(self.max_scroll_y);
+ if (self.scroll_y - old_scroll).abs() > 0.01 {
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ false
+ }
WindowEvent::CursorMoved { position, .. } => {
self.cursor_x = position.x as f32;
self.cursor_y = position.y as f32;
+ let s = self.scale_factor as f32;
+ let lx = self.cursor_x / s;
+ let ly = self.cursor_y / s + self.scroll_y;
let mut changed = false;
for w in &mut self.widgets {
let was = w.hovering;
@@ -658,76 +692,115 @@ impl SystemInterface {
}
}
if self.app.current_page == Page::Layout {
- let s = self.scale_factor as f32;
for sb in &mut self.app.layout.spinboxes {
- if sb.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if sb.cursor_moved(lx, ly) {
changed = true;
}
}
- if self.app.layout.cascade_offset_spinbox.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.layout.cascade_offset_spinbox.cursor_moved(lx, ly) {
changed = true;
}
- if self.app.layout.edge_gap_spinbox.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.layout.edge_gap_spinbox.cursor_moved(lx, ly) {
changed = true;
}
- if self.app.layout.top_gap_spinbox.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly) {
changed = true;
}
for cp in &mut self.app.layout.color_selectors {
- if cp.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if cp.cursor_moved(lx, ly) {
changed = true;
}
}
}
if self.app.current_page == Page::Input {
- let s = self.scale_factor as f32;
- if self.app.input.rate_spinbox.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.input.rate_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.delay_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.tap_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.scroll_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.scroll_friction_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.pointer_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.input.pointer_friction_spinbox.cursor_moved(lx, ly) {
changed = true;
}
- if self.app.input.delay_spinbox.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.input.trackpad_toggle.cursor_moved(lx, ly) {
changed = true;
}
- if self.app.input.tap_toggle.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.input.trackpad_friction_spinbox.cursor_moved(lx, ly) {
changed = true;
}
}
if self.app.current_page == Page::Audio {
- let s = self.scale_factor as f32;
for sb in &mut self.app.audio.sink_spinboxes {
- if sb.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if sb.cursor_moved(lx, ly) {
changed = true;
}
}
for sb in &mut self.app.audio.source_spinboxes {
- if sb.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if sb.cursor_moved(lx, ly) {
changed = true;
}
}
}
if self.app.current_page == Page::Display {
- let s = self.scale_factor as f32;
- if self.app.display.brightness_spinbox.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.display.brightness_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.display.night_light_label.cursor_moved(lx, ly) {
changed = true;
}
+ for out in &mut self.app.display.outputs {
+ if out.name_label.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if out.resolution_label.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if let Some(ref mut scale_lbl) = out.scale_label {
+ if scale_lbl.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ }
+ }
}
if self.app.current_page == Page::Notifications {
- let s = self.scale_factor as f32;
- if self.app.notifications.enable_toggle.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.notifications.enable_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.notifications.bell_toggle.cursor_moved(lx, ly) {
changed = true;
}
- if self.app.notifications.bell_toggle.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.notifications.duration_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ }
+ if self.app.current_page == Page::Status {
+ if self.app.status.status_label.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.status.size_label.cursor_moved(lx, ly) {
changed = true;
}
}
if self.app.current_page == Page::Typeface {
- let s = self.scale_factor as f32;
- if self.app.typeface.sans_box.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.typeface.sans_box.cursor_moved(lx, ly) {
changed = true;
}
- if self.app.typeface.serif_box.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.typeface.serif_box.cursor_moved(lx, ly) {
changed = true;
}
- if self.app.typeface.mono_box.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ if self.app.typeface.mono_box.cursor_moved(lx, ly) {
changed = true;
}
}
@@ -802,6 +875,19 @@ impl SystemInterface {
return true;
}
}
+ if self.app.current_page == Page::Notifications {
+ let sb = &mut self.app.notifications.duration_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ let new_val = sb.value;
+ drop(sb);
+ if new_val != old {
+ self.handle_action(&AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(new_val)));
+ }
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
if self.app.current_page == Page::Input {
if self.app.input.rate_spinbox.keyboard_input(event) {
self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyRepeat));
@@ -813,6 +899,21 @@ impl SystemInterface {
self.needs_rebuild = true;
return true;
}
+ if self.app.input.scroll_friction_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
+ self.needs_rebuild = true;
+ return true;
+ }
+ if self.app.input.pointer_friction_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
+ self.needs_rebuild = true;
+ return true;
+ }
+ if self.app.input.trackpad_friction_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
+ self.needs_rebuild = true;
+ return true;
+ }
}
if self.app.current_page == Page::Audio {
let mut actions = Vec::new();
@@ -909,6 +1010,7 @@ impl SystemInterface {
if let WidgetKind::PageButton(p) = &w.kind {
if self.app.current_page != *p {
self.app.current_page = *p;
+ self.scroll_y = 0.0;
self.needs_rebuild = true;
return true;
}
@@ -918,7 +1020,7 @@ impl SystemInterface {
}
let s = self.scale_factor as f32;
let lx = self.cursor_x / s;
- let ly = self.cursor_y / s;
+ let ly = self.cursor_y / s + self.scroll_y;
let mut actions = Vec::new();
if *state == ElementState::Pressed && self.app.current_page == Page::Layout {
for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
@@ -988,6 +1090,32 @@ impl SystemInterface {
if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
actions.push(AppAction::Input(pages::input::InputMessage::ApplyRepeat));
}
+ let sb = &mut self.app.input.scroll_friction_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyScrollFriction));
+ }
+ let sb = &mut self.app.input.pointer_friction_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyPointerFriction));
+ }
+ let sb = &mut self.app.input.trackpad_friction_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpadFriction));
+ }
+ }
+ if *state == ElementState::Pressed && self.app.current_page == Page::Notifications {
+ let sb = &mut self.app.notifications.duration_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::SetDuration(sb.value)));
+ }
}
if self.app.current_page == Page::Input {
let toggle = &mut self.app.input.tap_toggle;
@@ -995,6 +1123,21 @@ impl SystemInterface {
if toggle.take_click() {
actions.push(AppAction::Input(pages::input::InputMessage::ToggleTapToClick));
}
+ let toggle = &mut self.app.input.scroll_toggle;
+ toggle.mouse_input(*button, *state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialScroll));
+ }
+ let toggle = &mut self.app.input.pointer_toggle;
+ toggle.mouse_input(*button, *state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialPointer));
+ }
+ let toggle = &mut self.app.input.trackpad_toggle;
+ toggle.mouse_input(*button, *state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ToggleInertialTrackpad));
+ }
}
if self.app.current_page == Page::Notifications {
let toggle = &mut self.app.notifications.enable_toggle;
@@ -1033,6 +1176,33 @@ impl SystemInterface {
if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
actions.push(AppAction::Display(pages::display::DisplayMessage::BrightnessSet(sb.value as u32)));
}
+ let lbl = &mut self.app.display.night_light_label;
+ if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
+ lbl.mouse_input(*button, *state, lx, ly);
+
+ for out in &mut self.app.display.outputs {
+ let lbl = &mut out.name_label;
+ if !lbl.hit_test(lx, ly) { lbl.unfocus(); }
+ lbl.mouse_input(*button, *state, lx, ly);
+
+ let lbl2 = &mut out.resolution_label;
+ if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
+ lbl2.mouse_input(*button, *state, lx, ly);
+
+ if let Some(ref mut scale_lbl) = out.scale_label {
+ if !scale_lbl.hit_test(lx, ly) { scale_lbl.unfocus(); }
+ scale_lbl.mouse_input(*button, *state, lx, ly);
+ }
+ }
+ }
+ if *state == ElementState::Pressed && self.app.current_page == Page::Status {
+ let lbl1 = &mut self.app.status.status_label;
+ if !lbl1.hit_test(lx, ly) { lbl1.unfocus(); }
+ lbl1.mouse_input(*button, *state, lx, ly);
+
+ let lbl2 = &mut self.app.status.size_label;
+ if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
+ lbl2.mouse_input(*button, *state, lx, ly);
}
if *state == ElementState::Pressed && self.app.current_page == Page::Typeface {
let tb = &mut self.app.typeface.sans_box;
diff --git a/src/pages/display.rs b/src/pages/display.rs
index dc350fb..20d96d9 100644
--- a/src/pages/display.rs
+++ b/src/pages/display.rs
@@ -1,6 +1,6 @@
use crate::app::PageContent;
use clear_ui::layout::{render_widget, Section};
-use clear_ui::widget::{Spinbox, Widget};
+use clear_ui::widget::{Spinbox, Label, Widget};
#[derive(Debug, Clone)]
pub struct DisplayOutput {
@@ -9,6 +9,37 @@ pub struct DisplayOutput {
pub refresh: String,
pub scale: f32,
pub connected: bool,
+ pub name_label: Label,
+ pub resolution_label: Label,
+ pub scale_label: Option<Label>,
+}
+
+impl DisplayOutput {
+ pub fn update_labels(&mut self) {
+ if self.connected {
+ self.name_label.set_text(&self.name);
+ self.resolution_label.set_text(&format!("{} @ {}Hz", self.resolution, self.refresh));
+ if self.scale > 1.0 {
+ let scale_str = if let Some((w_str, h_str)) = self.resolution.rsplit_once('x') {
+ if let (Ok(w), Ok(h)) = (w_str.parse::<u32>(), h_str.parse::<u32>()) {
+ format!("logical {:.0}x{:.0} | scale {:.0}x", w as f32 / self.scale, h as f32 / self.scale, self.scale)
+ } else { format!("scale {:.0}x", self.scale) }
+ } else { format!("scale {:.0}x", self.scale) };
+
+ self.scale_label = Some(Label::new(&scale_str)
+ .with_font_size(11.0)
+ .with_color([135, 135, 150]));
+ } else {
+ self.scale_label = None;
+ }
+ } else {
+ self.name_label.set_text(&self.name);
+ self.name_label.set_color([135, 135, 150]);
+ self.resolution_label.set_text("(disconnected)");
+ self.resolution_label.set_color([135, 135, 150]);
+ self.scale_label = None;
+ }
+ }
}
#[derive(Debug, Clone)]
@@ -19,6 +50,7 @@ pub struct DisplayState {
pub outputs: Vec<DisplayOutput>,
pub night_light: bool,
pub brightness_spinbox: Spinbox,
+ pub night_light_label: Label,
}
impl Default for DisplayState {
@@ -30,6 +62,7 @@ impl Default for DisplayState {
outputs: Vec::new(),
night_light: false,
brightness_spinbox: Spinbox::new(50, 0, 100, 5),
+ night_light_label: Label::new("Night Light: OFF").with_font_size(13.0).with_color([0xd4, 0xd4, 0xd4]),
}
}
}
@@ -51,6 +84,9 @@ pub async fn fetch_display_state() -> DisplayState {
loaded: true,
brightness, max_brightness, outputs, night_light,
brightness_spinbox: Spinbox::new(pct, 0, 100, 5),
+ night_light_label: Label::new(if night_light { "Night Light: ON" } else { "Night Light: OFF" })
+ .with_font_size(13.0)
+ .with_color([0xd4, 0xd4, 0xd4]),
}
}
@@ -78,11 +114,19 @@ async fn fetch_outputs() -> Vec<DisplayOutput> {
for line in output.lines() {
let trimmed = line.trim();
if !trimmed.starts_with(' ') && trimmed.contains('"') {
- if let Some(prev) = current.take() { displays.push(prev); }
+ if let Some(mut prev) = current.take() {
+ prev.update_labels();
+ displays.push(prev);
+ }
let name = trimmed.split_whitespace().next().unwrap_or("").to_string();
+ let name_label = Label::new(&name).with_font_size(12.0).with_color([212, 212, 212]);
+ let resolution_label = Label::new("").with_font_size(12.0).with_color([212, 212, 212]);
current = Some(DisplayOutput {
name, resolution: String::new(), refresh: String::new(),
scale: 1.0, connected: true,
+ name_label,
+ resolution_label,
+ scale_label: None,
});
continue;
}
@@ -100,7 +144,10 @@ async fn fetch_outputs() -> Vec<DisplayOutput> {
}
}
}
- if let Some(prev) = current.take() { displays.push(prev); }
+ if let Some(mut prev) = current.take() {
+ prev.update_labels();
+ displays.push(prev);
+ }
displays
}
@@ -160,9 +207,12 @@ pub fn view(state: &mut DisplayState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Pa
let mut sec = Section::new(&mut pc, cx, y, cw, "Night Light");
if !state.loaded {
sec.text(&mut pc, "Loading...", 12.0, 0.0, 12.0, TEXT_DIM);
+ sec.spacing(18.0);
} else {
let nl_label = if state.night_light { "Night Light: ON" } else { "Night Light: OFF" };
- sec.text(&mut pc, nl_label, 12.0, 0.0, 13.0, TEXT_FG);
+ state.night_light_label.set_text(nl_label);
+ sec.widget(&mut pc, &mut state.night_light_label, 12.0, cw - 24.0, 20.0);
+ sec.spacing(8.0);
}
y = sec.finish(&mut pc);
@@ -173,21 +223,22 @@ pub fn view(state: &mut DisplayState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Pa
sec.text(&mut pc, "Loading outputs...", 12.0, 0.0, 12.0, TEXT_DIM);
sec.spacing(18.0);
} else {
- for out in &state.outputs {
- if out.connected {
- let scale_info = if out.scale > 1.0 {
- if let Some((w_str, h_str)) = out.resolution.rsplit_once('x') {
- if let (Ok(w), Ok(h)) = (w_str.parse::<u32>(), h_str.parse::<u32>()) {
- format!(" logical {:.0}x{:.0} | scale {:.0}x", w as f32 / out.scale, h as f32 / out.scale, out.scale)
- } else { format!(" scale {:.0}x", out.scale) }
- } else { String::new() }
- } else { String::new() };
- sec.text(&mut pc, &format!("{} {} @ {}Hz{}", out.name, out.resolution, out.refresh, scale_info),
- 14.0, 0.0, 12.0, TEXT_FG);
- } else {
- sec.text(&mut pc, &format!("{} (disconnected)", out.name), 14.0, 0.0, 12.0, TEXT_DIM);
+ for out in &mut state.outputs {
+ let yt = sec.ay();
+
+ // Name label at x = 14.0
+ render_widget(&mut pc, &mut out.name_label, sec.ax(14.0), yt, 100.0, 20.0);
+
+ // Resolution label at x = 120.0
+ render_widget(&mut pc, &mut out.resolution_label, sec.ax(120.0), yt, 160.0, 20.0);
+
+ // Scale label at x = 290.0 if present
+ if let Some(ref mut scale_lbl) = out.scale_label {
+ render_widget(&mut pc, scale_lbl, sec.ax(290.0), yt, cw - 304.0, 20.0);
}
- sec.spacing(18.0);
+
+ sec.content_y += 20.0;
+ sec.spacing(12.0);
}
}
sec.finish(&mut pc);
@@ -197,7 +248,31 @@ pub fn view(state: &mut DisplayState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Pa
pub fn update(state: &mut DisplayState, msg: DisplayMessage) {
match msg {
- DisplayMessage::Refreshed(new) => { *state = new; }
+ DisplayMessage::Refreshed(new) => {
+ let was_nl_hovered = state.night_light_label.hovered();
+
+ let mut hovers = std::collections::HashMap::new();
+ for out in &state.outputs {
+ hovers.insert(out.name.clone(), (
+ out.name_label.hovered(),
+ out.resolution_label.hovered(),
+ out.scale_label.as_ref().map(|l| l.hovered()).unwrap_or(false)
+ ));
+ }
+
+ *state = new;
+ state.night_light_label.set_hovered(was_nl_hovered);
+
+ for out in &mut state.outputs {
+ if let Some(&(name_h, res_h, scale_h)) = hovers.get(&out.name) {
+ out.name_label.set_hovered(name_h);
+ out.resolution_label.set_hovered(res_h);
+ if let Some(ref mut scale_lbl) = out.scale_label {
+ scale_lbl.set_hovered(scale_h);
+ }
+ }
+ }
+ }
DisplayMessage::BrightnessSet(pct) => {
let pct = pct.clamp(0, 100);
state.brightness = pct as f32 / 100.0 * state.max_brightness;
diff --git a/src/pages/input.rs b/src/pages/input.rs
index b9a82fd..3cade7f 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -1,9 +1,9 @@
use std::fs;
use std::io::Write;
-use crate::app::{AppAction, PageContent};
-use clear_ui::layout::{render_widget, Section};
-use clear_ui::widget::{Spinbox, Toggle, Widget};
+use crate::app::PageContent;
+use clear_ui::layout::Section;
+use clear_ui::widget::{Spinbox, Toggle};
const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
const CLEARWM_SOCK: &str = "/tmp/clearwm.sock";
@@ -25,6 +25,21 @@ pub struct InputState {
pub delay_spinbox: Spinbox,
pub tap_toggle: Toggle,
pub keybinds: Vec<Keybind>,
+
+ // Inertial settings
+ pub inertial_scroll: bool,
+ pub scroll_friction: u16,
+ pub inertial_pointer: bool,
+ pub pointer_friction: u16,
+ pub inertial_trackpad: bool,
+ pub trackpad_friction: u16,
+
+ pub scroll_toggle: Toggle,
+ pub scroll_friction_spinbox: Spinbox,
+ pub pointer_toggle: Toggle,
+ pub pointer_friction_spinbox: Spinbox,
+ pub trackpad_toggle: Toggle,
+ pub trackpad_friction_spinbox: Spinbox,
}
impl Default for InputState {
@@ -35,8 +50,22 @@ impl Default for InputState {
repeat_delay: 300,
rate_spinbox: Spinbox::new(50, 1, 100, 1).with_label("Repeat Rate").with_unit("ms"),
delay_spinbox: Spinbox::new(300, 100, 2000, 10).with_label("Repeat Delay").with_unit("ms"),
- tap_toggle: Toggle::new().with_label("Tap to Click"),
+ tap_toggle: Toggle::new().with_label("Tap to Click"),
keybinds: Vec::new(),
+
+ inertial_scroll: true,
+ scroll_friction: 90,
+ inertial_pointer: false,
+ pointer_friction: 95,
+ inertial_trackpad: false,
+ trackpad_friction: 95,
+
+ scroll_toggle: Toggle::new().with_label("Inertial Scroll"),
+ scroll_friction_spinbox: Spinbox::new(90, 50, 99, 1).with_label("Scroll Friction").with_unit("%"),
+ pointer_toggle: Toggle::new().with_label("Inertial Pointer (Trackpoint)"),
+ pointer_friction_spinbox: Spinbox::new(95, 50, 99, 1).with_label("Pointer Friction").with_unit("%"),
+ trackpad_toggle: Toggle::new().with_label("Inertial Pointer (Trackpad)"),
+ trackpad_friction_spinbox: Spinbox::new(95, 50, 99, 1).with_label("Trackpad Friction").with_unit("%"),
}
}
}
@@ -46,6 +75,13 @@ pub enum InputMessage {
ToggleTapToClick,
ApplyRepeat,
Refreshed(InputState),
+
+ ToggleInertialScroll,
+ ApplyScrollFriction,
+ ToggleInertialPointer,
+ ApplyPointerFriction,
+ ToggleInertialTrackpad,
+ ApplyTrackpadFriction,
}
pub fn read_input_config() -> InputState {
@@ -53,6 +89,14 @@ pub fn read_input_config() -> InputState {
let rate = parse_u16_key(&content, "rate", 50);
let delay = parse_u16_key(&content, "delay", 300);
let tap = parse_bool_from(&content, "tap_to_click");
+
+ let inertial_scroll = parse_bool_from_default(&content, "inertial_scroll", true);
+ let scroll_friction = parse_u16_key(&content, "scroll_friction", 90);
+ let inertial_pointer = parse_bool_from_default(&content, "inertial_pointer", false);
+ let pointer_friction = parse_u16_key(&content, "pointer_friction", 95);
+ let inertial_trackpad = parse_bool_from_default(&content, "inertial_trackpad", false);
+ let trackpad_friction = parse_u16_key(&content, "trackpad_friction", 95);
+
InputState {
tap_to_click: tap,
repeat_rate: rate,
@@ -61,6 +105,20 @@ pub fn read_input_config() -> InputState {
delay_spinbox: Spinbox::new(delay as i32, 100, 2000, 10).with_label("Repeat Delay").with_unit("ms"),
tap_toggle: Toggle::new().with_label("Tap to Click"),
keybinds: parse_keybinds(&content),
+
+ inertial_scroll,
+ scroll_friction,
+ inertial_pointer,
+ pointer_friction,
+ inertial_trackpad,
+ trackpad_friction,
+
+ scroll_toggle: Toggle::new().with_label("Inertial Scroll"),
+ scroll_friction_spinbox: Spinbox::new(scroll_friction as i32, 50, 99, 1).with_label("Scroll Friction").with_unit("%"),
+ pointer_toggle: Toggle::new().with_label("Inertial Pointer (Trackpoint)"),
+ pointer_friction_spinbox: Spinbox::new(pointer_friction as i32, 50, 99, 1).with_label("Pointer Friction").with_unit("%"),
+ trackpad_toggle: Toggle::new().with_label("Inertial Pointer (Trackpad)"),
+ trackpad_friction_spinbox: Spinbox::new(trackpad_friction as i32, 50, 99, 1).with_label("Trackpad Friction").with_unit("%"),
}
}
@@ -71,6 +129,13 @@ fn parse_bool_from(content: &str, key: &str) -> bool {
.unwrap_or(false)
}
+fn parse_bool_from_default(content: &str, key: &str, default: bool) -> bool {
+ content.lines().find(|l| l.trim().starts_with(key))
+ .and_then(|l| l.split('=').nth(1))
+ .map(|v| v.trim() == "true")
+ .unwrap_or(default)
+}
+
fn parse_u16_key(content: &str, key: &str, default: u16) -> u16 {
content.lines().find(|l| l.trim().starts_with(key))
.and_then(|l| l.split('=').nth(1))
@@ -124,7 +189,16 @@ fn write_config_value(key: &str, value: &str) {
.join("\n");
if !found {
- let section = if key == "tap_to_click" { "[input]" } else { "[repeat]" };
+ let section = if key == "tap_to_click" {
+ "[input]"
+ } else if key == "inertial_scroll" || key == "scroll_friction"
+ || key == "inertial_pointer" || key == "pointer_friction"
+ || key == "inertial_trackpad" || key == "trackpad_friction" {
+ "[inertial]"
+ } else {
+ "[repeat]"
+ };
+
let mut result = String::new();
let mut in_section = false;
let mut inserted = false;
@@ -162,11 +236,6 @@ fn apply_repeat_config(rate: u16, delay: u16) {
const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
-const ACCENT: [f32; 4] = [0.36, 0.56, 0.38, 1.0];
-const BTN_HOVER: [f32; 4] = [0.25, 0.30, 0.26, 1.0];
-const TOGGLE_ON: [f32; 4] = [0.16, 0.41, 0.18, 1.0];
-const TOGGLE_OFF: [f32; 4] = [0.16, 0.16, 0.24, 1.0];
-const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
let mut pc = PageContent::new();
@@ -175,13 +244,11 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Page
// ── Touchpad ──
let mut sec = Section::new(&mut pc, cx, y, cw, "Touchpad");
- let yt = sec.ay();
let toggle_w = 48.0;
let toggle_h = 24.0;
state.tap_toggle.set_toggled(state.tap_to_click);
- state.tap_toggle.set_row_rect(sec.ax(8.0), cw - 16.0);
- render_widget(&mut pc, &mut state.tap_toggle, sec.ax(100.0), yt, toggle_w, toggle_h);
- sec.content_y += toggle_h + 12.0;
+ sec.widget(&mut pc, &mut state.tap_toggle, 14.0, toggle_w, toggle_h);
+ sec.spacing(4.0);
y = sec.finish(&mut pc);
// ── Keyboard ──
@@ -194,6 +261,32 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Page
sec.spacing(8.0);
y = sec.finish(&mut pc);
+ // ── Inertial Input ──
+ let mut sec = Section::new(&mut pc, cx, y, cw, "Inertial Input");
+
+ state.scroll_toggle.set_toggled(state.inertial_scroll);
+ sec.widget(&mut pc, &mut state.scroll_toggle, 14.0, toggle_w, toggle_h);
+ sec.spacing(12.0);
+
+ sec.widget(&mut pc, &mut state.scroll_friction_spinbox, 14.0, 200.0, 26.0);
+ sec.spacing(16.0);
+
+ state.pointer_toggle.set_toggled(state.inertial_pointer);
+ sec.widget(&mut pc, &mut state.pointer_toggle, 14.0, toggle_w, toggle_h);
+ sec.spacing(12.0);
+
+ sec.widget(&mut pc, &mut state.pointer_friction_spinbox, 14.0, 200.0, 26.0);
+ sec.spacing(16.0);
+
+ state.trackpad_toggle.set_toggled(state.inertial_trackpad);
+ sec.widget(&mut pc, &mut state.trackpad_toggle, 14.0, toggle_w, toggle_h);
+ sec.spacing(12.0);
+
+ sec.widget(&mut pc, &mut state.trackpad_friction_spinbox, 14.0, 200.0, 26.0);
+ sec.spacing(8.0);
+
+ y = sec.finish(&mut pc);
+
// ── Keybindings ──
let mut sec = Section::new(&mut pc, cx, y, cw, "Keyboard Bindings");
@@ -231,6 +324,35 @@ pub fn update(state: &mut InputState, msg: InputMessage) {
state.repeat_delay = delay;
apply_repeat_config(rate, delay);
}
- InputMessage::Refreshed(new) => { *state = new; }
+ InputMessage::ToggleInertialScroll => {
+ state.inertial_scroll = !state.inertial_scroll;
+ write_config_value("inertial_scroll", &state.inertial_scroll.to_string());
+ }
+ InputMessage::ApplyScrollFriction => {
+ let friction = state.scroll_friction_spinbox.value.max(50).min(99) as u16;
+ state.scroll_friction = friction;
+ write_config_value("scroll_friction", &friction.to_string());
+ }
+ InputMessage::ToggleInertialPointer => {
+ state.inertial_pointer = !state.inertial_pointer;
+ write_config_value("inertial_pointer", &state.inertial_pointer.to_string());
+ }
+ InputMessage::ApplyPointerFriction => {
+ let friction = state.pointer_friction_spinbox.value.max(50).min(99) as u16;
+ state.pointer_friction = friction;
+ write_config_value("pointer_friction", &friction.to_string());
+ }
+ InputMessage::ToggleInertialTrackpad => {
+ state.inertial_trackpad = !state.inertial_trackpad;
+ write_config_value("inertial_trackpad", &state.inertial_trackpad.to_string());
+ }
+ InputMessage::ApplyTrackpadFriction => {
+ let friction = state.trackpad_friction_spinbox.value.max(50).min(99) as u16;
+ state.trackpad_friction = friction;
+ write_config_value("trackpad_friction", &friction.to_string());
+ }
+ InputMessage::Refreshed(new) => {
+ *state = new;
+ }
}
}
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index 441dd5b..db56d2c 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -2,8 +2,8 @@ use std::fs;
use std::io::Write;
use crate::app::{AppAction, PageContent};
-use clear_ui::layout::{render_widget, Section};
-use clear_ui::widget::{Toggle, Widget};
+use clear_ui::layout::Section;
+use clear_ui::widget::{Toggle, Spinbox};
const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
const CLEARWM_SOCK: &str = "/tmp/clearwm.sock";
@@ -14,6 +14,8 @@ pub struct NotificationsState {
pub enable_toggle: Toggle,
pub bell: bool,
pub bell_toggle: Toggle,
+ pub duration: i32,
+ pub duration_spinbox: Spinbox,
}
impl Default for NotificationsState {
@@ -23,6 +25,10 @@ impl Default for NotificationsState {
enable_toggle: Toggle::new().with_label("Enable Notifications"),
bell: false,
bell_toggle: Toggle::new().with_label("Play Bell Sound"),
+ duration: 5,
+ duration_spinbox: Spinbox::new(5, 1, 60, 1)
+ .with_label("Notification Duration")
+ .with_unit("s"),
}
}
}
@@ -31,6 +37,7 @@ impl Default for NotificationsState {
pub enum NotificationsMessage {
ToggleEnable,
ToggleBell,
+ SetDuration(i32),
SendTestNotification,
Refreshed(NotificationsState),
}
@@ -39,11 +46,16 @@ pub fn read_notifications_config() -> NotificationsState {
let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
let enable = parse_notifications_enable(&content);
let bell = parse_notifications_bell(&content);
+ let duration = parse_notifications_duration(&content);
NotificationsState {
enable,
enable_toggle: Toggle::new().with_label("Enable Notifications"),
bell,
bell_toggle: Toggle::new().with_label("Play Bell Sound"),
+ duration,
+ duration_spinbox: Spinbox::new(duration, 1, 60, 1)
+ .with_label("Notification Duration")
+ .with_unit("s"),
}
}
@@ -87,6 +99,28 @@ fn parse_notifications_bell(content: &str) -> bool {
false // default to false
}
+fn parse_notifications_duration(content: &str) -> i32 {
+ let mut in_section = false;
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if trimmed == "[notifications]" {
+ in_section = true;
+ continue;
+ }
+ if trimmed.starts_with('[') && in_section {
+ break;
+ }
+ if in_section && trimmed.starts_with("duration") {
+ if let Some(val) = trimmed.split('=').nth(1) {
+ if let Ok(d) = val.trim().parse::<i32>() {
+ return d;
+ }
+ }
+ }
+ }
+ 5 // default to 5 seconds
+}
+
fn send_ipc_command(cmd: &str) {
if let Ok(mut stream) = std::os::unix::net::UnixStream::connect(CLEARWM_SOCK) {
let _ = stream.write_all(format!("{}\n", cmd).as_bytes());
@@ -164,9 +198,9 @@ fn write_enable_notifications(enabled: bool) {
send_ipc_command("reload");
}
-const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
-const ACCENT: [f32; 4] = [0.36, 0.56, 0.38, 1.0];
-const BTN_HOVER: [f32; 4] = [0.25, 0.30, 0.26, 1.0];
+const BTN_BG: [f32; 4] = [0.20, 0.40, 0.65, 1.0];
+const BTN_HOVER: [f32; 4] = [0.28, 0.50, 0.78, 1.0];
+const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
pub fn view(state: &mut NotificationsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
let mut pc = PageContent::new();
@@ -174,19 +208,20 @@ pub fn view(state: &mut NotificationsState, cx: f32, cy: f32, cw: f32, _ch: f32)
let mut sec = Section::new(&mut pc, cx, y, cw, "System Notifications");
- let yt = sec.ay();
let toggle_w = 48.0;
let toggle_h = 24.0;
state.enable_toggle.set_toggled(state.enable);
- state.enable_toggle.set_row_rect(sec.ax(8.0), cw - 16.0);
- render_widget(&mut pc, &mut state.enable_toggle, sec.ax(100.0), yt, toggle_w, toggle_h);
- sec.content_y += toggle_h + 12.0;
+ sec.widget(&mut pc, &mut state.enable_toggle, 14.0, toggle_w, toggle_h);
+ sec.spacing(8.0);
- let yt2 = sec.ay();
state.bell_toggle.set_toggled(state.bell);
- state.bell_toggle.set_row_rect(sec.ax(8.0), cw - 16.0);
- render_widget(&mut pc, &mut state.bell_toggle, sec.ax(100.0), yt2, toggle_w, toggle_h);
- sec.content_y += toggle_h + 24.0;
+ sec.widget(&mut pc, &mut state.bell_toggle, 14.0, toggle_w, toggle_h);
+ sec.spacing(16.0);
+
+ state.duration_spinbox.value = state.duration;
+ state.duration_spinbox.set_label("Notification Duration");
+ sec.widget(&mut pc, &mut state.duration_spinbox, 14.0, 200.0, 26.0);
+ sec.spacing(16.0);
let btn_w = 160.0;
let btn_h = 32.0;
@@ -198,9 +233,9 @@ pub fn view(state: &mut NotificationsState, cx: f32, cy: f32, cw: f32, _ch: f32)
btn_y,
btn_w,
btn_h,
- ACCENT,
+ BTN_BG,
BTN_HOVER,
- TEXT_FG,
+ WHITE,
AppAction::Notifications(NotificationsMessage::SendTestNotification),
);
});
@@ -220,6 +255,10 @@ pub fn update(state: &mut NotificationsState, msg: NotificationsMessage) {
state.bell = !state.bell;
write_config_value("bell", &state.bell.to_string());
}
+ NotificationsMessage::SetDuration(d) => {
+ state.duration = d;
+ write_config_value("duration", &state.duration.to_string());
+ }
NotificationsMessage::SendTestNotification => {
send_ipc_command("notify \"clearwm\" \"System notifications are working correctly!\"");
}
@@ -280,4 +319,19 @@ enable = true
";
assert!(!parse_notifications_enable(content));
}
+
+ #[test]
+ fn test_parse_notifications_duration_default() {
+ assert_eq!(parse_notifications_duration(""), 5);
+ assert_eq!(parse_notifications_duration("[notifications]\n"), 5);
+ }
+
+ #[test]
+ fn test_parse_notifications_duration_explicit() {
+ let content = "\
+[notifications]
+duration = 10
+";
+ assert_eq!(parse_notifications_duration(content), 10);
+ }
}
diff --git a/src/pages/status.rs b/src/pages/status.rs
index 353c07a..d47da52 100644
--- a/src/pages/status.rs
+++ b/src/pages/status.rs
@@ -1,11 +1,26 @@
use crate::app::{AppAction, PageContent};
use clear_ui::layout::Section;
+use clear_ui::widget::{Label, Widget};
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone)]
pub struct StatusState {
pub font_size: u16,
pub running: bool,
pub loaded: bool,
+ pub status_label: Label,
+ pub size_label: Label,
+}
+
+impl Default for StatusState {
+ fn default() -> Self {
+ Self {
+ font_size: 13,
+ running: false,
+ loaded: false,
+ status_label: Label::new("Waybar: Stopped").with_font_size(14.0).with_color([170, 51, 51]),
+ size_label: Label::new("Font size: 13px").with_font_size(13.0).with_color([212, 212, 212]),
+ }
+ }
}
#[derive(Debug, Clone)]
@@ -23,7 +38,18 @@ pub async fn fetch_status_state() -> StatusState {
.unwrap_or(false);
let font_size = read_waybar_font_size().unwrap_or(13);
- StatusState { font_size, running, loaded: true }
+ let status_color = if running { [92, 143, 97] } else { [170, 51, 51] };
+ StatusState {
+ font_size,
+ running,
+ loaded: true,
+ status_label: Label::new(&format!("Waybar: {}", if running { "Running" } else { "Stopped" }))
+ .with_font_size(14.0)
+ .with_color(status_color),
+ size_label: Label::new(&format!("Font size: {}px", font_size))
+ .with_font_size(13.0)
+ .with_color([212, 212, 212]),
+ }
}
fn read_waybar_font_size() -> Option<u16> {
@@ -66,10 +92,9 @@ const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
const BTN_ACTIVE: [f32; 4] = [0.20, 0.40, 0.22, 1.0];
const BTN_INACTIVE: [f32; 4] = [0.13, 0.18, 0.14, 1.0];
const BTN_HOVER: [f32; 4] = [0.25, 0.30, 0.26, 1.0];
-const ACCENT: [f32; 4] = [0.36, 0.56, 0.38, 1.0];
const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
-pub fn view(state: &StatusState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut StatusState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
let mut pc = PageContent::new();
let y = cy + 12.0;
@@ -80,15 +105,15 @@ pub fn view(state: &StatusState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageCon
sec.spacing(18.0);
} else {
// Status
- let status_color = if state.running { ACCENT } else { [0.67, 0.20, 0.20, 1.0] };
- let status_text = if state.running { "Running" } else { "Stopped" };
- sec.text(&mut pc, "Waybar", 12.0, 0.0, 14.0, TEXT_FG);
- sec.text(&mut pc, status_text, 80.0, 0.0, 14.0, status_color);
- sec.spacing(22.0);
+ let status_text = if state.running { "Waybar: Running" } else { "Waybar: Stopped" };
+ state.status_label.set_text(status_text);
+ sec.widget(&mut pc, &mut state.status_label, 12.0, cw - 24.0, 20.0);
+ sec.spacing(12.0);
// Font size
- sec.text(&mut pc, &format!("Font size: {}px", state.font_size), 12.0, 0.0, 13.0, TEXT_FG);
- sec.spacing(20.0);
+ state.size_label.set_text(&format!("Font size: {}px", state.font_size));
+ sec.widget(&mut pc, &mut state.size_label, 12.0, cw - 24.0, 20.0);
+ sec.spacing(12.0);
let btn_h = 28.0;
let yt = sec.ay();
@@ -115,7 +140,13 @@ pub fn view(state: &StatusState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageCon
pub fn update(state: &mut StatusState, msg: StatusMessage) {
match msg {
- StatusMessage::Refreshed(new) => { *state = new; }
+ StatusMessage::Refreshed(new) => {
+ let was_status_hovered = state.status_label.hovered();
+ let was_size_hovered = state.size_label.hovered();
+ *state = new;
+ state.status_label.set_hovered(was_status_hovered);
+ state.size_label.set_hovered(was_size_hovered);
+ }
StatusMessage::FontSizeUp => {
if state.font_size < 28 {
state.font_size += 1;