system settings
git clone https://git.lucas.co/cce-system-interface.git
Add controls for status separators, underline, and padding to the status interface configuration page
Cargo.lock | 2 +
src/main.rs | 333 +++++++++++++++++++++++++++++++++------------
src/pages/colors.rs | 240 ++++++++++++++++++++++----------
src/pages/display.rs | 4 +-
src/pages/input.rs | 6 +-
src/pages/layout.rs | 101 +++++++-------
src/pages/notifications.rs | 123 ++++++++++++++++-
src/pages/status.rs | 216 ++++++++++++++++++++++++++++-
src/pages/typeface.rs | 18 ++-
9 files changed, 811 insertions(+), 232 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 08b9097..ce5956a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -358,7 +358,9 @@ dependencies = [
"serde",
"smithay-client-toolkit",
"tokio",
+ "wayland-backend",
"wayland-client",
+ "wayland-scanner",
"wgpu",
"xkeysym",
]
diff --git a/src/main.rs b/src/main.rs
index 281a0f1..b844821 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,7 @@
use std::sync::Arc;
use clear_ui::color;
-use clear_ui::widget::{Spinbox, Widget, Finger};
+use clear_ui::widget::{Spinbox, Widget, Finger, Slider, hover_animation};
use glyphon::{
Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas,
TextBounds, TextRenderer, Viewport,
@@ -165,7 +165,6 @@ struct AppWidget {
#[derive(Clone)]
enum WidgetKind {
- PageButton(Page),
ActionButton(AppAction),
Static,
}
@@ -237,8 +236,10 @@ struct SystemInterface {
needs_rebuild: bool,
scroll_y: f32,
max_scroll_y: f32,
+ opacity_dragging: bool,
page_root_container: clear_ui::widget::Container,
page_sec_containers: Vec<clear_ui::widget::Container>,
+ paginator: clear_ui::widget::Paginator,
sans_serif_family: String,
serif_family: String,
monospace_family: String,
@@ -273,7 +274,7 @@ impl SystemInterface {
});
let wgpu_surface = instance.create_surface(wayland_handle).expect("surface");
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
- power_preference: wgpu::PowerPreference::HighPerformance,
+ power_preference: wgpu::PowerPreference::LowPower,
compatible_surface: Some(&wgpu_surface),
force_fallback_adapter: false,
}).await.expect("adapter");
@@ -451,6 +452,9 @@ impl SystemInterface {
let (sans_family, serif_family, monospace_family, _, _, _, _) = pages::typeface::read_preferred_fonts();
+ let pages_names = Page::ALL.iter().map(|p| p.label().to_string()).collect::<Vec<_>>();
+ let paginator = clear_ui::widget::Paginator::new(140.0, pages_names);
+
let mut this = Self {
window, surface, wgpu_surface, device, queue, config, render_pipeline,
vertex_buffer, vertex_count: 0,
@@ -468,8 +472,10 @@ impl SystemInterface {
needs_rebuild: true,
scroll_y: 0.0,
max_scroll_y: 0.0,
+ opacity_dragging: false,
page_root_container: clear_ui::widget::Container::new(),
page_sec_containers: Vec::new(),
+ paginator,
sans_serif_family: sans_family,
serif_family,
monospace_family,
@@ -497,59 +503,46 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
let mut text_items = Vec::new();
let mut page_buttons = Vec::new();
- let sb_w = self.sidebar_width * s;
- let hdr_h = self.header_height * s;
- let st_h = self.status_height * s;
+ clear_ui::widget::hover_animation::reset_frame_registration();
+ clear_ui::widget::hover_animation::set_scroll_offset(self.scroll_y);
+ clear_ui::widget::hover_animation::set_cursor_pos(self.cursor_x / s, self.cursor_y / s);
- // Sidebar bg
- widgets.push(AppWidget {
- x: 0.0, y: hdr_h, w: sb_w, h: sh - hdr_h - st_h,
- color: [0.16, 0.16, 0.26, 1.0],
- hover_color: [0.16, 0.16, 0.26, 1.0],
- hovering: false, kind: WidgetKind::Static,
- });
+ let lcx = self.sidebar_width;
+ let lcy = self.header_height;
+ let lcw = sw / s - self.sidebar_width;
+ let lch = sh / s - self.header_height - self.status_height;
+
+ let page_idx = Page::ALL.iter().position(|&p| p == self.app.current_page).unwrap_or(0);
+ self.paginator.set_selected_page(page_idx);
- // Sidebar page buttons
- let btn_h = 32.0 * s;
- let btn_margin = 4.0 * s;
- let total = Page::ALL.len() as f32;
- let sb_h = sh - hdr_h - st_h;
- let start_y = hdr_h + (sb_h - total * (btn_h + btn_margin)) / 2.0;
- let start_y = start_y.max(hdr_h + 8.0 * s);
-
- for (i, page) in Page::ALL.iter().enumerate() {
- let active = *page == self.app.current_page;
- let y = start_y + i as f32 * (btn_h + btn_margin);
- let bg = if active { color::BUTTON_PRESS } else { [0.18, 0.18, 0.28, 1.0] };
- let hov = if active { color::BUTTON_IDLE } else { [0.22, 0.22, 0.34, 1.0] };
+ let mut paginator_pc = PageContent::new();
+ clear_ui::layout::render_widget(&mut paginator_pc, &mut self.paginator, 0.0, 0.0, sw / s, sh / s);
+
+ for (c, x, y, w, h) in &paginator_pc.rects {
widgets.push(AppWidget {
- x: 6.0 * s, y, w: sb_w - 12.0 * s, h: btn_h,
- color: bg, hover_color: hov,
- hovering: false,
- kind: WidgetKind::PageButton(*page),
+ x: *x * s, y: *y * s, w: *w * s, h: *h * s,
+ color: *c, hover_color: *c,
+ hovering: false, kind: WidgetKind::Static,
});
+ }
+ for (t, size, x, y, tc, font_opt) in &paginator_pc.texts {
text_items.push(TextItem {
- buffer: make_text_buffer(&mut self.font_system, page.label(), 12.0 * s),
- x: 16.0 * s, y: y + 9.0 * s,
- color: glyphon::Color::rgb(0x33, 0x33, 0x4a),
+ buffer: make_text_buffer_with_font(
+ &mut self.font_system,
+ t,
+ *size * s,
+ font_opt.as_deref(),
+ &self.sans_serif_family,
+ &self.serif_family,
+ &self.monospace_family,
+ ),
+ x: *x * s, y: *y * s,
+ color: glyphon::Color::rgb(
+ (tc[0] * 255.0) as u8, (tc[1] * 255.0) as u8, (tc[2] * 255.0) as u8,
+ ),
});
}
- // Content area
- let lcx = self.sidebar_width;
- let lcy = self.header_height;
- let lcw = sw / s - self.sidebar_width;
- let lch = sh / s - self.header_height - self.status_height;
- let p_cx = lcx * s;
- let p_cy = lcy * s;
- let p_cw = lcw * s;
- let p_ch = lch * s;
- widgets.push(AppWidget {
- x: p_cx, y: p_cy, w: p_cw, h: p_ch,
- color: color::CONTENT_BG, hover_color: color::CONTENT_BG,
- hovering: false, kind: WidgetKind::Static,
- });
-
// Page content in LOGICAL coordinates, then scale to physical
let mut pc = self.render_page_content(lcx, lcy, lcw, lch);
@@ -709,6 +702,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
self.app.notifications.duration_spinbox.clear_children(); self.app.notifications.duration_spinbox.set_parent(None);
+ self.app.notifications.opacity_slider.clear_children(); self.app.notifications.opacity_slider.set_parent(None);
self.app.input.rate_spinbox.clear_children(); self.app.input.rate_spinbox.set_parent(None);
self.app.input.delay_spinbox.clear_children(); self.app.input.delay_spinbox.set_parent(None);
@@ -728,6 +722,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
self.app.display.brightness_spinbox.clear_children(); self.app.display.brightness_spinbox.set_parent(None);
+ self.app.status.padding_spinbox.clear_children(); self.app.status.padding_spinbox.set_parent(None);
use clear_ui::widget::focus::link_parent_child;
match self.app.current_page {
@@ -765,34 +760,21 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
link_parent_child(&mut self.page_root_container, &mut self.app.network.wifi_list_box.scroll_box);
}
Page::Layout => {
- self.page_sec_containers.resize_with(6, clear_ui::widget::Container::new);
+ self.page_sec_containers.resize_with(3, clear_ui::widget::Container::new);
- for i in 0..6 {
+ for i in 0..3 {
link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[i]);
}
- if !self.app.layout.spinboxes.is_empty() {
- link_parent_child(&mut self.page_sec_containers[0], &mut self.app.layout.spinboxes[0]);
- }
- if self.app.layout.spinboxes.len() > 1 {
- link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.spinboxes[1]);
+ for sb in &mut self.app.layout.spinboxes {
+ link_parent_child(&mut self.page_sec_containers[0], sb);
}
+
link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.cascade_offset_spinbox);
link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.edge_gap_spinbox);
link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.top_gap_spinbox);
- if self.app.layout.spinboxes.len() > 2 {
- link_parent_child(&mut self.page_sec_containers[2], &mut self.app.layout.spinboxes[2]);
- }
- if self.app.layout.spinboxes.len() > 3 {
- link_parent_child(&mut self.page_sec_containers[3], &mut self.app.layout.spinboxes[3]);
- }
- if self.app.layout.spinboxes.len() > 4 {
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.layout.spinboxes[4]);
- }
- if self.app.layout.spinboxes.len() > 5 {
- link_parent_child(&mut self.page_sec_containers[5], &mut self.app.layout.spinboxes[5]);
- }
+ link_parent_child(&mut self.page_sec_containers[2], &mut self.app.layout.transition_duration_spinbox);
}
Page::Colors => {
for cs in &mut self.app.colors.color_selectors {
@@ -801,6 +783,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
Page::Notifications => {
link_parent_child(&mut self.page_root_container, &mut self.app.notifications.duration_spinbox);
+ link_parent_child(&mut self.page_root_container, &mut self.app.notifications.opacity_slider);
}
Page::Input => {
self.page_sec_containers.resize_with(4, clear_ui::widget::Container::new);
@@ -837,9 +820,27 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
Page::Display => {
link_parent_child(&mut self.page_root_container, &mut self.app.display.brightness_spinbox);
}
+ Page::Status => {
+ link_parent_child(&mut self.page_root_container, &mut self.app.status.padding_spinbox);
+ }
_ => {}
}
+ // Draw global hover highlight if active
+ clear_ui::widget::hover_animation::post_render_check();
+ if let Some((qx, qy, qw, qh, qc)) = clear_ui::widget::hover_animation::get_quad() {
+ widgets.push(AppWidget {
+ x: qx * s,
+ y: (qy - self.scroll_y) * s,
+ w: qw * s,
+ h: qh * s,
+ color: qc,
+ hover_color: qc,
+ hovering: false,
+ kind: WidgetKind::Static,
+ });
+ }
+
// Render context menu overlay if visible
if clear_ui::widget::context_menu::is_visible() {
let cx = clear_ui::widget::context_menu::x();
@@ -886,6 +887,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
}
+
+
self.widgets = widgets;
self.text_items = text_items;
self.page_buttons = page_buttons;
@@ -973,6 +976,27 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
}
+ fn tick(&mut self, dt: f32) -> bool {
+ let mut needs_redraw = false;
+ if hover_animation::tick(dt) {
+ needs_redraw = true;
+ self.needs_rebuild = true;
+ }
+ if self.paginator.tick(dt) {
+ needs_redraw = true;
+ self.needs_rebuild = true;
+ }
+ if let Some(root_ptr) = self.get_page_root_widget() {
+ unsafe {
+ if (*root_ptr).tick(dt) {
+ needs_redraw = true;
+ self.needs_rebuild = true;
+ }
+ }
+ }
+ needs_redraw
+ }
+
fn poll_background_updates(&mut self) {
use pages::*;
while let Ok(s) = self.rx_audio.try_recv() {
@@ -1098,10 +1122,10 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
self.cursor_x = x;
self.cursor_y = y;
let s = self.scale_factor as f32;
+ let lx_no_scroll = x / s;
+ let ly_no_scroll = y / s;
if clear_ui::widget::context_menu::is_visible() {
- let lx_no_scroll = x / s;
- let ly_no_scroll = y / s;
if clear_ui::widget::context_menu::cursor_moved(lx_no_scroll, ly_no_scroll) {
self.needs_rebuild = true;
return true;
@@ -1111,7 +1135,11 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
let lx = self.cursor_x / s;
let ly = self.cursor_y / s + self.scroll_y;
+ clear_ui::widget::hover_animation::set_cursor_pos(lx, ly_no_scroll);
let mut changed = false;
+ if self.paginator.cursor_moved(lx_no_scroll, ly_no_scroll) {
+ changed = true;
+ }
for w in &mut self.widgets {
let was = w.hovering;
w.hovering = self.cursor_x >= w.x && self.cursor_x <= w.x + w.w
@@ -1136,6 +1164,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly) {
changed = true;
}
+ if self.app.layout.transition_duration_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
}
if self.app.current_page == Page::Colors {
for cp in &mut self.app.colors.color_selectors {
@@ -1215,7 +1246,11 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
}
}
- if self.app.current_page == Page::Notifications {
+ if self.opacity_dragging && self.app.current_page == Page::Notifications {
+ if self.app.notifications.opacity_slider.drag_update(lx, ly) {
+ changed = true;
+ }
+ } else if self.app.current_page == Page::Notifications {
if self.app.notifications.enable_toggle.cursor_moved(lx, ly) {
changed = true;
}
@@ -1225,6 +1260,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
if self.app.notifications.duration_spinbox.cursor_moved(lx, ly) {
changed = true;
}
+ if self.app.notifications.opacity_slider.cursor_moved(lx, ly) {
+ changed = true;
+ }
}
if self.app.current_page == Page::Hardware {
if self.app.hardware.cpu_label.cursor_moved(lx, ly) {
@@ -1246,6 +1284,15 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
if self.app.status.size_label.cursor_moved(lx, ly) {
changed = true;
}
+ if self.app.status.separators_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.status.underline_toggle.cursor_moved(lx, ly) {
+ changed = true;
+ }
+ if self.app.status.padding_spinbox.cursor_moved(lx, ly) {
+ changed = true;
+ }
}
if self.app.current_page == Page::Typefaces {
if self.app.typeface.sans_box.cursor_moved(lx, ly) {
@@ -1336,6 +1383,19 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
}
+ if self.paginator.mouse_input(button, state, lx_no_scroll, ly_no_scroll) {
+ if self.paginator.take_click() {
+ let idx = self.paginator.selected_page();
+ if idx < Page::ALL.len() {
+ clear_ui::widget::focus::clear_focus();
+ self.app.current_page = Page::ALL[idx];
+ self.scroll_y = 0.0;
+ }
+ }
+ self.needs_rebuild = true;
+ return true;
+ }
+
if button != clear_ui::widget::MouseButton::Left && button != clear_ui::widget::MouseButton::Right { return false; }
if button == clear_ui::widget::MouseButton::Left && state == clear_ui::widget::ElementState::Released {
let (px, py) = (self.cursor_x, self.cursor_y);
@@ -1346,19 +1406,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
return true;
}
}
- for w in &self.widgets {
- if px >= w.x && px <= w.x + w.w && py >= w.y && py <= w.y + w.h {
- if let WidgetKind::PageButton(p) = &w.kind {
- if self.app.current_page != *p {
- clear_ui::widget::focus::clear_focus();
- self.app.current_page = *p;
- self.scroll_y = 0.0;
- self.needs_rebuild = true;
- return true;
- }
- }
- }
- }
}
let lx = self.cursor_x / s;
let ly = self.cursor_y / s + self.scroll_y;
@@ -1374,6 +1421,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
if self.app.layout.cascade_offset_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
if self.app.layout.edge_gap_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
if self.app.layout.top_gap_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+ if self.app.layout.transition_duration_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
}
Page::Colors => {
for cp in &mut self.app.colors.color_selectors {
@@ -1391,6 +1439,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
Page::Notifications => {
if self.app.notifications.duration_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+ if self.app.notifications.opacity_slider.hit_test(lx, ly) { clicked_any_focusable = true; }
}
Page::Audio => {
for sb in &mut self.app.audio.sink_spinboxes {
@@ -1414,6 +1463,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
Page::Status => {
if self.app.status.status_label.hit_test(lx, ly) { clicked_any_focusable = true; }
if self.app.status.size_label.hit_test(lx, ly) { clicked_any_focusable = true; }
+ if self.app.status.padding_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
}
Page::Typefaces => {
let tf = &mut self.app.typeface;
@@ -1489,6 +1539,14 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
));
}
+ let sb = &mut self.app.layout.transition_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::Layout(
+ pages::layout::LayoutMessage::SetTransitionDuration(sb.value as u16)
+ ));
+ }
}
if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Colors {
for (i, cp) in self.app.colors.color_selectors.iter_mut().enumerate() {
@@ -1497,16 +1555,26 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
cp.mouse_input(button, state, lx, ly);
if cp.take_click() {
actions.push(AppAction::Colors(match i {
- 0 => pages::colors::ColorsMessage::PickLowColor,
+ 0 => pages::colors::ColorsMessage::PickPageLowColor,
1 => pages::colors::ColorsMessage::PickHighColor,
- _ => pages::colors::ColorsMessage::PickDisabledColor,
+ 2 => pages::colors::ColorsMessage::PickVisualGuides,
+ 3 => pages::colors::ColorsMessage::PickDisabledColor,
+ 4 => pages::colors::ColorsMessage::PickSeparatorColor,
+ 5 => pages::colors::ColorsMessage::PickSliderTrackColor,
+ 6 => pages::colors::ColorsMessage::PickColorBordersColor,
+ _ => pages::colors::ColorsMessage::PickLowColor,
}));
}
if cp.color != old {
actions.push(AppAction::Colors(match i {
- 0 => pages::colors::ColorsMessage::SetLowColor(cp.color),
+ 0 => pages::colors::ColorsMessage::SetPageLowColor(cp.color),
1 => pages::colors::ColorsMessage::SetHighColor(cp.color),
- _ => pages::colors::ColorsMessage::SetDisabledColor(cp.color),
+ 2 => pages::colors::ColorsMessage::SetVisualGuidesColor(cp.color),
+ 3 => pages::colors::ColorsMessage::SetDisabledColor(cp.color),
+ 4 => pages::colors::ColorsMessage::SetSeparatorColor(cp.color),
+ 5 => pages::colors::ColorsMessage::SetSliderTrackColor(cp.color),
+ 6 => pages::colors::ColorsMessage::SetColorBordersColor(cp.color),
+ _ => pages::colors::ColorsMessage::SetLowColor(cp.color),
}));
}
}
@@ -1617,6 +1685,24 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
if toggle.take_click() {
actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::ToggleBell));
}
+ let slider = &mut self.app.notifications.opacity_slider;
+ if button == clear_ui::widget::MouseButton::Left {
+ if state == clear_ui::widget::ElementState::Pressed {
+ if slider.hit_test(lx, ly) {
+ slider.drag_begin(lx, ly);
+ self.opacity_dragging = true;
+ self.needs_rebuild = true;
+ }
+ } else if state == clear_ui::widget::ElementState::Released {
+ if self.opacity_dragging {
+ self.opacity_dragging = false;
+ slider.drag_end();
+ let val = slider.value() as f32 / 100.0;
+ actions.push(AppAction::Notifications(pages::notifications::NotificationsMessage::SetOpacity(val)));
+ self.needs_rebuild = true;
+ }
+ }
+ }
}
if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Audio {
for (i, sb) in self.app.audio.sink_spinboxes.iter_mut().enumerate() {
@@ -1670,6 +1756,26 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
let lbl2 = &mut self.app.status.size_label;
if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
lbl2.mouse_input(button, state, lx, ly);
+
+ let sb = &mut self.app.status.padding_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::Status(pages::status::StatusMessage::SetPadding(sb.value as u16)));
+ }
+ }
+ if self.app.current_page == Page::Status {
+ let toggle = &mut self.app.status.separators_toggle;
+ toggle.mouse_input(button, state, lx, ly);
+ if toggle.take_click() {
+ actions.push(AppAction::Status(pages::status::StatusMessage::ToggleSeparators));
+ }
+
+ let toggle2 = &mut self.app.status.underline_toggle;
+ toggle2.mouse_input(button, state, lx, ly);
+ if toggle2.take_click() {
+ actions.push(AppAction::Status(pages::status::StatusMessage::ToggleUnderline));
+ }
}
if self.app.current_page == Page::Typefaces {
let tb = &mut self.app.typeface.sans_box;
@@ -2043,6 +2149,16 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
}
changed = true;
}
+ let sb = &mut self.app.layout.transition_duration_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event) {
+ if sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetTransitionDuration(sb.value as u16)
+ ));
+ }
+ changed = true;
+ }
for a in &actions {
self.handle_action(a);
}
@@ -2059,9 +2175,14 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
if cp.keyboard_input(event) {
if cp.color != old {
actions.push(AppAction::Colors(match i {
- 0 => pages::colors::ColorsMessage::SetLowColor(cp.color),
+ 0 => pages::colors::ColorsMessage::SetPageLowColor(cp.color),
1 => pages::colors::ColorsMessage::SetHighColor(cp.color),
- _ => pages::colors::ColorsMessage::SetDisabledColor(cp.color),
+ 2 => pages::colors::ColorsMessage::SetVisualGuidesColor(cp.color),
+ 3 => pages::colors::ColorsMessage::SetDisabledColor(cp.color),
+ 4 => pages::colors::ColorsMessage::SetSeparatorColor(cp.color),
+ 5 => pages::colors::ColorsMessage::SetSliderTrackColor(cp.color),
+ 6 => pages::colors::ColorsMessage::SetColorBordersColor(cp.color),
+ _ => pages::colors::ColorsMessage::SetLowColor(cp.color),
}));
}
changed = true;
@@ -2166,6 +2287,19 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
return true;
}
}
+ if self.app.current_page == Page::Status {
+ let sb = &mut self.app.status.padding_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::Status(pages::status::StatusMessage::SetPadding(new_val as u16)));
+ }
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
if self.app.current_page == Page::Typefaces {
if event.state == clear_ui::widget::ElementState::Pressed {
let is_down = match (&event.logical_key, event.ctrl) {
@@ -2422,13 +2556,22 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f
});
{
+ let r_clear = (self.app.colors.page_low_color[0] as f64 / 255.0).powf(2.2);
+ let g_clear = (self.app.colors.page_low_color[1] as f64 / 255.0).powf(2.2);
+ let b_clear = (self.app.colors.page_low_color[2] as f64 / 255.0).powf(2.2);
+
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.06, g: 0.06, b: 0.08, a: 1.0 }),
+ load: wgpu::LoadOp::Clear(wgpu::Color {
+ r: r_clear,
+ g: g_clear,
+ b: b_clear,
+ a: 1.0,
+ }),
store: wgpu::StoreOp::Store,
},
})],
@@ -2947,6 +3090,7 @@ fn main() {
const KEY_REPEAT_DELAY: std::time::Duration = std::time::Duration::from_millis(500);
const KEY_REPEAT_INTERVAL: std::time::Duration = std::time::Duration::from_millis(50);
+ let mut last_tick = std::time::Instant::now();
loop {
event_loop
.dispatch(std::time::Duration::from_millis(16), &mut app)
@@ -2954,7 +3098,16 @@ fn main() {
if app.exit {
break;
}
+
+ let now = std::time::Instant::now();
+ let mut dt = now.duration_since(last_tick).as_secs_f32();
+ last_tick = now;
+ if dt > 0.1 {
+ dt = 0.1;
+ }
+
if let Some(state) = &mut app.state {
+ state.tick(dt);
state.poll_background_updates();
if state.needs_rebuild {
app.redraw = true;
diff --git a/src/pages/colors.rs b/src/pages/colors.rs
index 9b7cf7e..6659c9b 100644
--- a/src/pages/colors.rs
+++ b/src/pages/colors.rs
@@ -4,12 +4,12 @@ use crate::app::PageContent;
use clear_ui::layout::Section;
use clear_ui::widget::ColorSelector;
-const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/ccec/config.toml";
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
- Ok(display) => format!("/tmp/clearwm-{}.sock", display),
- Err(_) => "/tmp/clearwm.sock".to_string(),
+ Ok(display) => format!("/tmp/ccec-{}.sock", display),
+ Err(_) => "/tmp/ccec.sock".to_string(),
}
}
@@ -18,8 +18,12 @@ pub struct ColorsState {
pub low_color: [u8; 3],
pub high_color: [u8; 3],
pub disabled_color: [u8; 3],
+ pub separator_color: [u8; 3],
+ pub visual_guides_color: [u8; 3],
+ pub slider_track_color: [u8; 3],
+ pub page_low_color: [u8; 3],
+ pub color_borders_color: [u8; 3],
pub color_selectors: Vec<ColorSelector>,
- pub preset_colors: Vec<(&'static str, [u8; 3])>,
}
impl Default for ColorsState {
@@ -28,12 +32,21 @@ impl Default for ColorsState {
low_color: [0x0a, 0x1a, 0x0e],
high_color: [0x3e, 0x3e, 0x3e],
disabled_color: [0x55, 0x55, 0x55],
+ separator_color: [124, 124, 137],
+ visual_guides_color: [0xff, 0x8c, 0x00],
+ slider_track_color: [116, 116, 128],
+ page_low_color: [71, 71, 81],
+ color_borders_color: [124, 124, 137],
color_selectors: vec![
- ColorSelector::new([0x0a, 0x1a, 0x0e]).with_label("Low Color"),
- ColorSelector::new([0x3e, 0x3e, 0x3e]).with_label("High Color"),
- ColorSelector::new([0x55, 0x55, 0x55]).with_label("Disabled"),
+ ColorSelector::new([71, 71, 81]).with_label("Low Color"), // 0: Pages - Low Color
+ ColorSelector::new([0x3e, 0x3e, 0x3e]).with_label("High Color"), // 1: Layout - High Color
+ ColorSelector::new([0xff, 0x8c, 0x00]).with_label("Visual Guides"), // 2: Layout - Visual Guides
+ ColorSelector::new([0x55, 0x55, 0x55]).with_label("Disabled"), // 3: Status - Disabled
+ ColorSelector::new([124, 124, 137]).with_label("Separators"), // 4: Status - Separators
+ ColorSelector::new([116, 116, 128]).with_label("Slider Track"), // 5: Controls - Slider Track
+ ColorSelector::new([124, 124, 137]).with_label("Borders"), // 6: Controls - Borders
+ ColorSelector::new([0x0a, 0x1a, 0x0e]).with_label("Low Color"), // 7: Layout - Low Color
],
- preset_colors: preset_colors(),
}
}
}
@@ -43,28 +56,23 @@ pub enum ColorsMessage {
SetLowColor([u8; 3]),
SetHighColor([u8; 3]),
SetDisabledColor([u8; 3]),
+ SetSeparatorColor([u8; 3]),
+ SetVisualGuidesColor([u8; 3]),
+ SetSliderTrackColor([u8; 3]),
+ SetPageLowColor([u8; 3]),
+ SetColorBordersColor([u8; 3]),
PickLowColor,
PickHighColor,
PickDisabledColor,
+ PickSeparatorColor,
+ PickVisualGuides,
+ PickSliderTrackColor,
+ PickPageLowColor,
+ PickColorBordersColor,
Refreshed(ColorsState),
}
-fn preset_colors() -> Vec<(&'static str, [u8; 3])> {
- vec![
- ("Black", [0x00, 0x00, 0x00]),
- ("Dark Gray", [0x1a, 0x1a, 0x2e]),
- ("Slate", [0x2d, 0x2d, 0x3d]),
- ("Dark Forest", [0x0a, 0x1a, 0x0e]),
- ("Forest", [0x1a, 0x2a, 0x1c]),
- ("Dark Teal", [0x0a, 0x1a, 0x1e]),
- ("Navy", [0x0a, 0x0f, 0x2e]),
- ("Dark Wine", [0x1e, 0x0a, 0x14]),
- ("Dark Brown", [0x1e, 0x16, 0x0e]),
- ("Charcoal", [0x22, 0x22, 0x22]),
- ("Midnight", [0x10, 0x10, 0x20]),
- ("Deep Sea", [0x06, 0x14, 0x1e]),
- ]
-}
+
pub fn read_colors_config() -> ColorsState {
let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
@@ -84,16 +92,35 @@ pub fn read_colors_config() -> ColorsState {
let disabled = parse_color_from_key(&content, "disabled_color", [0x55, 0x55, 0x55]);
+ let separator = parse_color_from_key(&content, "status_separator_color", [124, 124, 137]);
+
+ let visual_guides = parse_color_from_key(&content, "visual_guides_color", [0xff, 0x8c, 0x00]);
+
+ let slider_track = parse_color_from_key(&content, "slider_track_color", [116, 116, 128]);
+
+ let page_low = parse_color_from_key(&content, "page_low_color", [71, 71, 81]);
+
+ let color_borders = parse_color_from_key(&content, "color_borders_color", [124, 124, 137]);
+
ColorsState {
low_color: bg,
high_color: border,
disabled_color: disabled,
+ separator_color: separator,
+ visual_guides_color: visual_guides,
+ slider_track_color: slider_track,
+ page_low_color: page_low,
+ color_borders_color: color_borders,
color_selectors: vec![
- ColorSelector::new(bg).with_label("Low Color"),
- ColorSelector::new(border).with_label("High Color"),
- ColorSelector::new(disabled).with_label("Disabled"),
+ ColorSelector::new(page_low).with_label("Low Color"), // 0: Pages - Low Color
+ ColorSelector::new(border).with_label("High Color"), // 1: Layout - High Color
+ ColorSelector::new(visual_guides).with_label("Visual Guides"), // 2: Layout - Visual Guides
+ ColorSelector::new(disabled).with_label("Disabled"), // 3: Status - Disabled
+ ColorSelector::new(separator).with_label("Separators"), // 4: Status - Separators
+ ColorSelector::new(slider_track).with_label("Slider Track"), // 5: Controls - Slider Track
+ ColorSelector::new(color_borders).with_label("Borders"), // 6: Controls - Borders
+ ColorSelector::new(bg).with_label("Low Color"), // 7: Layout - Low Color
],
- preset_colors: preset_colors(),
}
}
@@ -189,64 +216,100 @@ fn apply_disabled_color(rgb: [u8; 3]) {
send_ipc_command(&format!("layout disabled_color #{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]));
}
+fn status_interface_reload() {
+ let _ = std::process::Command::new("pkill")
+ .args(["-f", "clear-status-interface"])
+ .status();
+ std::thread::sleep(std::time::Duration::from_millis(150));
+ send_ipc_command("spawn clear-status-interface");
+}
+
+fn apply_separator_color(rgb: [u8; 3]) {
+ let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+ write_config_value("status_separator_color", &hex);
+ send_ipc_command(&format!("layout status_separator_color #{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]));
+ status_interface_reload();
+}
+
+fn apply_visual_guides_color(rgb: [u8; 3]) {
+ let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+ write_config_value("visual_guides_color", &hex);
+}
+
+fn apply_slider_track_color(rgb: [u8; 3]) {
+ let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+ write_config_value("slider_track_color", &hex);
+ let r = clear_ui::color::srgb_to_linear(rgb[0] as f32 / 255.0);
+ let g = clear_ui::color::srgb_to_linear(rgb[1] as f32 / 255.0);
+ let b = clear_ui::color::srgb_to_linear(rgb[2] as f32 / 255.0);
+ clear_ui::color::set_slider_track([r, g, b, 1.0]);
+}
+
+fn apply_page_low_color(rgb: [u8; 3]) {
+ let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+ write_config_value("page_low_color", &hex);
+ let r = clear_ui::color::srgb_to_linear(rgb[0] as f32 / 255.0);
+ let g = clear_ui::color::srgb_to_linear(rgb[1] as f32 / 255.0);
+ let b = clear_ui::color::srgb_to_linear(rgb[2] as f32 / 255.0);
+ clear_ui::color::set_page_low_color([r, g, b, 1.0]);
+}
+
+fn apply_color_borders_color(rgb: [u8; 3]) {
+ let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+ write_config_value("color_borders_color", &hex);
+ let r = clear_ui::color::srgb_to_linear(rgb[0] as f32 / 255.0);
+ let g = clear_ui::color::srgb_to_linear(rgb[1] as f32 / 255.0);
+ let b = clear_ui::color::srgb_to_linear(rgb[2] as f32 / 255.0);
+ clear_ui::color::set_color_borders_color([r, g, b, 1.0]);
+}
+
pub fn view(state: &mut ColorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
let mut pc = PageContent::new();
let mut y = cy + 12.0;
- // 1. Layout Section
- let mut sec = Section::new(&mut pc, cx, y, cw, "Layout");
+ // 1. Pages Section
+ let mut sec = Section::new(&mut pc, cx, y, cw, "Pages");
sec.spacing(8.0);
- state.color_selectors[0].color = state.low_color;
+ state.color_selectors[0].color = state.page_low_color;
sec.widget(&mut pc, &mut state.color_selectors[0], 12.0, 220.0, 22.0);
sec.spacing(8.0);
+ y = sec.finish(&mut pc);
+
+ // 2. Layout Section
+ let mut sec = Section::new(&mut pc, cx, y, cw, "Layout");
+ sec.spacing(8.0);
+ state.color_selectors[7].color = state.low_color;
+ sec.widget(&mut pc, &mut state.color_selectors[7], 12.0, 220.0, 22.0);
+ sec.spacing(8.0);
state.color_selectors[1].color = state.high_color;
sec.widget(&mut pc, &mut state.color_selectors[1], 12.0, 220.0, 22.0);
sec.spacing(8.0);
+ state.color_selectors[2].color = state.visual_guides_color;
+ sec.widget(&mut pc, &mut state.color_selectors[2], 12.0, 220.0, 22.0);
+ sec.spacing(8.0);
y = sec.finish(&mut pc);
- // 2. Status Section
+ // 3. Status Section
let mut sec = Section::new(&mut pc, cx, y, cw, "Status");
sec.spacing(8.0);
- state.color_selectors[2].color = state.disabled_color;
- sec.widget(&mut pc, &mut state.color_selectors[2], 12.0, 220.0, 22.0);
+ state.color_selectors[3].color = state.disabled_color;
+ sec.widget(&mut pc, &mut state.color_selectors[3], 12.0, 220.0, 22.0);
+ sec.spacing(8.0);
+ state.color_selectors[4].color = state.separator_color;
+ sec.widget(&mut pc, &mut state.color_selectors[4], 12.0, 220.0, 22.0);
sec.spacing(8.0);
y = sec.finish(&mut pc);
- // 3. Preset Background Colors Grid
- let mut sec = Section::new(&mut pc, cx, y, cw, "Preset Backgrounds");
- let cols = 4;
- let gap = 8.0;
- let btn_w = (cw - 24.0 - (gap * (cols - 1) as f32)) / cols as f32;
- let btn_h = 28.0;
-
- for (i, (name, rgb)) in state.preset_colors.iter().enumerate() {
- let col = i % cols;
- let row = i / cols;
- let bx = cx + 12.0 + col as f32 * (btn_w + gap);
- let by = sec.ay() + row as f32 * (btn_h + gap);
-
- let r = rgb[0] as f32 / 255.0;
- let g = rgb[1] as f32 / 255.0;
- let b = rgb[2] as f32 / 255.0;
- let luminance = 0.299 * r + 0.587 * g + 0.114 * b;
- let text_color = if luminance > 0.5 { [0.08, 0.08, 0.12, 1.0] } else { [0.90, 0.90, 0.95, 1.0] };
-
- pc.button(
- name,
- bx,
- by,
- btn_w,
- btn_h,
- [r, g, b, 0.8],
- [r, g, b, 1.0],
- text_color,
- crate::app::AppAction::Colors(ColorsMessage::SetLowColor(*rgb)),
- );
- }
-
- let rows = (state.preset_colors.len() + cols - 1) / cols;
- sec.content_y += rows as f32 * (btn_h + gap) + 4.0;
- sec.finish(&mut pc);
+ // 4. Controls Section
+ let mut sec = Section::new(&mut pc, cx, y, cw, "Controls");
+ sec.spacing(8.0);
+ state.color_selectors[5].color = state.slider_track_color;
+ sec.widget(&mut pc, &mut state.color_selectors[5], 12.0, 220.0, 22.0);
+ sec.spacing(8.0);
+ state.color_selectors[6].color = state.color_borders_color;
+ sec.widget(&mut pc, &mut state.color_selectors[6], 12.0, 220.0, 22.0);
+ sec.spacing(8.0);
+ y = sec.finish(&mut pc);
pc
}
@@ -257,6 +320,10 @@ pub fn update(state: &mut ColorsState, msg: ColorsMessage) {
state.low_color = rgb;
apply_background(rgb);
}
+ ColorsMessage::SetPageLowColor(rgb) => {
+ state.page_low_color = rgb;
+ apply_page_low_color(rgb);
+ }
ColorsMessage::SetHighColor(rgb) => {
state.high_color = rgb;
apply_border_color(rgb);
@@ -265,7 +332,23 @@ pub fn update(state: &mut ColorsState, msg: ColorsMessage) {
state.disabled_color = rgb;
apply_disabled_color(rgb);
}
- ColorsMessage::PickLowColor | ColorsMessage::PickHighColor | ColorsMessage::PickDisabledColor => {}
+ ColorsMessage::SetSeparatorColor(rgb) => {
+ state.separator_color = rgb;
+ apply_separator_color(rgb);
+ }
+ ColorsMessage::SetVisualGuidesColor(rgb) => {
+ state.visual_guides_color = rgb;
+ apply_visual_guides_color(rgb);
+ }
+ ColorsMessage::SetSliderTrackColor(rgb) => {
+ state.slider_track_color = rgb;
+ apply_slider_track_color(rgb);
+ }
+ ColorsMessage::SetColorBordersColor(rgb) => {
+ state.color_borders_color = rgb;
+ apply_color_borders_color(rgb);
+ }
+ ColorsMessage::PickLowColor | ColorsMessage::PickHighColor | ColorsMessage::PickDisabledColor | ColorsMessage::PickSeparatorColor | ColorsMessage::PickVisualGuides | ColorsMessage::PickSliderTrackColor | ColorsMessage::PickPageLowColor | ColorsMessage::PickColorBordersColor => {}
ColorsMessage::Refreshed(new) => {
*state = new;
}
@@ -286,10 +369,15 @@ mod tests {
#[test]
fn test_parse_color_from_key() {
- let content = "\n[layout]\nlow_color = \"#112233\"\nhigh_color = \"#445566\"\ndisabled_color = \"#778899\"\n";
+ let content = "\n[layout]\nlow_color = \"#112233\"\nhigh_color = \"#445566\"\ndisabled_color = \"#778899\"\nstatus_separator_color = \"#aabbcc\"\nvisual_guides_color = \"#ddeeff\"\nslider_track_color = \"#123456\"\npage_low_color = \"#474751\"\ncolor_borders_color = \"#abcdef\"\n";
assert_eq!(parse_color_from_key(content, "low_color", [0, 0, 0]), [17, 34, 51]);
assert_eq!(parse_color_from_key(content, "high_color", [0, 0, 0]), [68, 85, 102]);
assert_eq!(parse_color_from_key(content, "disabled_color", [0, 0, 0]), [119, 136, 153]);
+ assert_eq!(parse_color_from_key(content, "status_separator_color", [0, 0, 0]), [170, 187, 204]);
+ assert_eq!(parse_color_from_key(content, "visual_guides_color", [0, 0, 0]), [221, 238, 255]);
+ assert_eq!(parse_color_from_key(content, "slider_track_color", [0, 0, 0]), [18, 52, 86]);
+ assert_eq!(parse_color_from_key(content, "page_low_color", [0, 0, 0]), [71, 71, 81]);
+ assert_eq!(parse_color_from_key(content, "color_borders_color", [0, 0, 0]), [171, 205, 239]);
assert_eq!(parse_color_from_key(content, "non_existent", [1, 2, 3]), [1, 2, 3]);
}
@@ -316,6 +404,16 @@ mod tests {
assert!(updated2.contains("disabled_color = \"#666666\""));
assert!(!updated2.contains("disabled_color = \"#555555\""));
+ // 4. Write visual_guides_color which does not exist yet
+ assert!(write_config_value_path(path_str, "visual_guides_color", "\"#ff8c00\""));
+ let updated3 = fs::read_to_string(path_str).unwrap();
+ assert!(updated3.contains("visual_guides_color = \"#ff8c00\""));
+
+ // 5. Write slider_track_color which does not exist yet
+ assert!(write_config_value_path(path_str, "slider_track_color", "\"#123456\""));
+ let updated4 = fs::read_to_string(path_str).unwrap();
+ assert!(updated4.contains("slider_track_color = \"#123456\""));
+
// Clean up
let _ = fs::remove_file(path_str);
}
diff --git a/src/pages/display.rs b/src/pages/display.rs
index 1ad3c68..804c258 100644
--- a/src/pages/display.rs
+++ b/src/pages/display.rs
@@ -83,7 +83,7 @@ pub async fn fetch_display_state() -> DisplayState {
DisplayState {
loaded: true,
brightness, max_brightness, outputs, night_light,
- brightness_spinbox: Spinbox::new(pct, 0, 100, 5).with_unit("%"),
+ brightness_spinbox: Spinbox::new(pct.max(1), 1, 100, 5).with_unit("%"),
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]),
@@ -163,7 +163,7 @@ async fn is_night_light_on() -> bool {
fn spawn_brightness(pct: u32) {
let _ = tokio::process::Command::new("brightnessctl")
- .args(["set", &format!("{}%", pct)]).spawn();
+ .args(["set", &format!("{}%", pct), "-n"]).spawn();
}
const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
diff --git a/src/pages/input.rs b/src/pages/input.rs
index 2aeefb6..0a69bef 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -5,12 +5,12 @@ use crate::app::PageContent;
use clear_ui::layout::Section;
use clear_ui::widget::{Dropdown, Spinbox, Toggle, Widget, Finger, Trackpad};
-const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/ccec/config.toml";
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
- Ok(display) => format!("/tmp/clearwm-{}.sock", display),
- Err(_) => "/tmp/clearwm.sock".to_string(),
+ Ok(display) => format!("/tmp/ccec-{}.sock", display),
+ Err(_) => "/tmp/ccec.sock".to_string(),
}
}
diff --git a/src/pages/layout.rs b/src/pages/layout.rs
index a525036..0ea1459 100644
--- a/src/pages/layout.rs
+++ b/src/pages/layout.rs
@@ -5,32 +5,30 @@ use crate::app::PageContent;
use clear_ui::layout::Section;
use clear_ui::widget::Spinbox;
-const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/ccec/config.toml";
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
- Ok(display) => format!("/tmp/clearwm-{}.sock", display),
- Err(_) => "/tmp/clearwm.sock".to_string(),
+ Ok(display) => format!("/tmp/ccec-{}.sock", display),
+ Err(_) => "/tmp/ccec.sock".to_string(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WidthParam {
- Fullscreen, Cascade, Grid, Vsplit, Hsplit, Floating,
+ Fullscreen, Cascade, Grid, Floating,
}
impl WidthParam {
- pub const ALL: [WidthParam; 6] = [
+ pub const ALL: [WidthParam; 4] = [
WidthParam::Fullscreen, WidthParam::Cascade, WidthParam::Grid,
- WidthParam::Vsplit, WidthParam::Hsplit, WidthParam::Floating,
+ WidthParam::Floating,
];
pub fn key(self) -> &'static str {
match self {
WidthParam::Fullscreen => "fullscreen_border_width",
WidthParam::Cascade => "cascade_border_width",
WidthParam::Grid => "grid_border_width",
- WidthParam::Vsplit => "vsplit_border_width",
- WidthParam::Hsplit => "hsplit_border_width",
WidthParam::Floating => "floating_border_width",
}
}
@@ -39,20 +37,16 @@ impl WidthParam {
WidthParam::Fullscreen => "Fullscreen",
WidthParam::Cascade => "Cascade",
WidthParam::Grid => "Grid",
- WidthParam::Vsplit => "Vsplit",
- WidthParam::Hsplit => "Hsplit",
WidthParam::Floating => "Floating",
}
}
}
-fn make_spinboxes(fs: u16, ca: u16, g: u16, v: u16, h: u16, fl: u16) -> Vec<Spinbox> {
+fn make_spinboxes(fs: u16, ca: u16, g: u16, fl: u16) -> Vec<Spinbox> {
vec![
Spinbox::new(fs as i32, 0, 100, 1),
Spinbox::new(ca as i32, 0, 100, 1),
Spinbox::new(g as i32, 0, 100, 1),
- Spinbox::new(v as i32, 0, 100, 1),
- Spinbox::new(h as i32, 0, 100, 1),
Spinbox::new(fl as i32, 0, 100, 1),
]
}
@@ -62,16 +56,16 @@ pub struct LayoutState {
pub fullscreen_border_width: u16,
pub cascade_border_width: u16,
pub grid_border_width: u16,
- pub vsplit_border_width: u16,
- pub hsplit_border_width: u16,
pub floating_border_width: u16,
pub cascade_offset: u16,
pub edge_gap: u16,
pub top_gap: u16,
+ pub transition_duration: u16,
pub spinboxes: Vec<Spinbox>,
pub cascade_offset_spinbox: Spinbox,
pub edge_gap_spinbox: Spinbox,
pub top_gap_spinbox: Spinbox,
+ pub transition_duration_spinbox: Spinbox,
}
impl Default for LayoutState {
@@ -80,16 +74,16 @@ impl Default for LayoutState {
fullscreen_border_width: 0,
cascade_border_width: 6,
grid_border_width: 6,
- vsplit_border_width: 6,
- hsplit_border_width: 6,
floating_border_width: 6,
cascade_offset: 20,
edge_gap: 48,
top_gap: 48,
- spinboxes: make_spinboxes(0, 6, 6, 6, 6, 6),
+ transition_duration: 300,
+ spinboxes: make_spinboxes(0, 6, 6, 6),
cascade_offset_spinbox: Spinbox::new(20, 0, 200, 1),
edge_gap_spinbox: Spinbox::new(48, 0, 200, 1),
top_gap_spinbox: Spinbox::new(48, 0, 200, 1),
+ transition_duration_spinbox: Spinbox::new(300, 0, 2000, 50),
}
}
}
@@ -100,6 +94,7 @@ pub enum LayoutMessage {
SetCascadeOffset(u16),
SetEdgeGap(u16),
SetTopGap(u16),
+ SetTransitionDuration(u16),
Refreshed(LayoutState),
}
@@ -108,26 +103,25 @@ pub fn read_layout_config() -> LayoutState {
let fs = parse_u16_from(&content, "fullscreen_border_width", 0);
let ca = parse_u16_from(&content, "cascade_border_width", 6);
let g = parse_u16_from(&content, "grid_border_width", 6);
- let v = parse_u16_from(&content, "vsplit_border_width", 6);
- let h = parse_u16_from(&content, "hsplit_border_width", 6);
let fl = parse_u16_from(&content, "floating_border_width", 6);
let co = parse_u16_from(&content, "cascade_offset", 20);
let gl = parse_u16_from(&content, "gap_left", 48);
let gt = parse_u16_from(&content, "gap_top", 48);
+ let td = parse_u16_from(&content, "transition_duration", 300);
LayoutState {
fullscreen_border_width: fs,
cascade_border_width: ca,
grid_border_width: g,
- vsplit_border_width: v,
- hsplit_border_width: h,
floating_border_width: fl,
cascade_offset: co,
edge_gap: gl,
top_gap: gt,
- spinboxes: make_spinboxes(fs, ca, g, v, h, fl),
+ transition_duration: td,
+ spinboxes: make_spinboxes(fs, ca, g, fl),
cascade_offset_spinbox: Spinbox::new(co as i32, 0, 200, 1),
edge_gap_spinbox: Spinbox::new(gl as i32, 0, 200, 1),
top_gap_spinbox: Spinbox::new(gt as i32, 0, 200, 1),
+ transition_duration_spinbox: Spinbox::new(td as i32, 0, 2000, 50),
}
}
@@ -179,39 +173,50 @@ fn apply_all_widths(s: &LayoutState) {
w("fullscreen_border_width", s.fullscreen_border_width);
w("cascade_border_width", s.cascade_border_width);
w("grid_border_width", s.grid_border_width);
- w("vsplit_border_width", s.vsplit_border_width);
- w("hsplit_border_width", s.hsplit_border_width);
w("floating_border_width", s.floating_border_width);
w("cascade_offset", s.cascade_offset);
w("gap_left", s.edge_gap);
w("gap_right", s.edge_gap);
w("gap_bottom", s.edge_gap);
w("gap_top", s.top_gap);
+ w("transition_duration", s.transition_duration);
}
pub fn view(state: &mut LayoutState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_focused: &[bool]) -> PageContent {
let mut pc = PageContent::new();
let mut y = cy + 12.0;
+ // 1. Border Width Section
+ let mut sec_bw = Section::new(&mut pc, cx, y, cw, "Border Width");
+ sec_bw.spacing(8.0);
for (i, param) in WidthParam::ALL.iter().enumerate() {
- let mut sec = Section::new(&mut pc, cx, y, cw, param.label());
- sec.spacing(8.0);
- state.spinboxes[i].set_label("Border Width");
- sec.widget(&mut pc, &mut state.spinboxes[i], 14.0, 200.0, 26.0);
- if *param == WidthParam::Cascade {
- sec.spacing(8.0);
- state.cascade_offset_spinbox.set_label("Offset");
- sec.widget(&mut pc, &mut state.cascade_offset_spinbox, 14.0, 200.0, 26.0);
- sec.spacing(8.0);
- state.edge_gap_spinbox.set_label("Edge Gap");
- sec.widget(&mut pc, &mut state.edge_gap_spinbox, 14.0, 200.0, 26.0);
- sec.spacing(8.0);
- state.top_gap_spinbox.set_label("Top Gap");
- sec.widget(&mut pc, &mut state.top_gap_spinbox, 14.0, 200.0, 26.0);
- }
- sec.spacing(8.0);
- y = sec.finish_focused(&mut pc, sec_focused.get(i).copied().unwrap_or(false));
+ state.spinboxes[i].set_label(param.label());
+ sec_bw.widget(&mut pc, &mut state.spinboxes[i], 14.0, 200.0, 26.0);
+ sec_bw.spacing(8.0);
}
+ y = sec_bw.finish_focused(&mut pc, sec_focused.get(0).copied().unwrap_or(false));
+
+ // 2. Cascade Section
+ let mut sec_cascade = Section::new(&mut pc, cx, y, cw, "Cascade");
+ sec_cascade.spacing(8.0);
+ state.cascade_offset_spinbox.set_label("Offset");
+ sec_cascade.widget(&mut pc, &mut state.cascade_offset_spinbox, 14.0, 200.0, 26.0);
+ sec_cascade.spacing(8.0);
+ state.edge_gap_spinbox.set_label("Edge Gap");
+ sec_cascade.widget(&mut pc, &mut state.edge_gap_spinbox, 14.0, 200.0, 26.0);
+ sec_cascade.spacing(8.0);
+ state.top_gap_spinbox.set_label("Top Gap");
+ sec_cascade.widget(&mut pc, &mut state.top_gap_spinbox, 14.0, 200.0, 26.0);
+ sec_cascade.spacing(8.0);
+ y = sec_cascade.finish_focused(&mut pc, sec_focused.get(1).copied().unwrap_or(false));
+
+ // 3. Movement Section
+ let mut movement_sec = Section::new(&mut pc, cx, y, cw, "Movement");
+ movement_sec.spacing(8.0);
+ state.transition_duration_spinbox.set_label("Duration (ms)");
+ movement_sec.widget(&mut pc, &mut state.transition_duration_spinbox, 14.0, 200.0, 26.0);
+ movement_sec.spacing(8.0);
+ y = movement_sec.finish_focused(&mut pc, sec_focused.get(2).copied().unwrap_or(false));
pc
}
@@ -222,8 +227,6 @@ fn set_width(state: &mut LayoutState, param: WidthParam, val: u16) {
WidthParam::Fullscreen => state.fullscreen_border_width = val,
WidthParam::Cascade => state.cascade_border_width = val,
WidthParam::Grid => state.grid_border_width = val,
- WidthParam::Vsplit => state.vsplit_border_width = val,
- WidthParam::Hsplit => state.hsplit_border_width = val,
WidthParam::Floating => state.floating_border_width = val,
}
state.spinboxes[param_idx(param)].value = val as i32;
@@ -235,9 +238,7 @@ fn param_idx(p: WidthParam) -> usize {
WidthParam::Fullscreen => 0,
WidthParam::Cascade => 1,
WidthParam::Grid => 2,
- WidthParam::Vsplit => 3,
- WidthParam::Hsplit => 4,
- WidthParam::Floating => 5,
+ WidthParam::Floating => 3,
}
}
@@ -262,6 +263,12 @@ pub fn update(state: &mut LayoutState, msg: LayoutMessage) {
state.top_gap_spinbox.value = val as i32;
apply_all_widths(state);
}
+ LayoutMessage::SetTransitionDuration(v) => {
+ let val = v.min(2000);
+ state.transition_duration = val;
+ state.transition_duration_spinbox.value = val as i32;
+ apply_all_widths(state);
+ }
LayoutMessage::Refreshed(new) => { *state = new; }
}
}
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
index fdfe873..531d766 100644
--- a/src/pages/notifications.rs
+++ b/src/pages/notifications.rs
@@ -3,14 +3,14 @@ use std::io::Write;
use crate::app::{AppAction, PageContent};
use clear_ui::layout::Section;
-use clear_ui::widget::{Toggle, Spinbox};
+use clear_ui::widget::{Toggle, Spinbox, Slider};
-const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/ccec/config.toml";
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
- Ok(display) => format!("/tmp/clearwm-{}.sock", display),
- Err(_) => "/tmp/clearwm.sock".to_string(),
+ Ok(display) => format!("/tmp/ccec-{}.sock", display),
+ Err(_) => "/tmp/ccec.sock".to_string(),
}
}
@@ -22,6 +22,8 @@ pub struct NotificationsState {
pub bell_toggle: Toggle,
pub duration: i32,
pub duration_spinbox: Spinbox,
+ pub opacity: f32,
+ pub opacity_slider: Slider,
}
impl Default for NotificationsState {
@@ -35,6 +37,10 @@ impl Default for NotificationsState {
duration_spinbox: Spinbox::new(5, 1, 60, 1)
.with_label("Notification Duration")
.with_unit("s"),
+ opacity: 0.9,
+ opacity_slider: Slider::new()
+ .with_label("Transparency")
+ .with_value(0.9),
}
}
}
@@ -44,6 +50,7 @@ pub enum NotificationsMessage {
ToggleEnable,
ToggleBell,
SetDuration(i32),
+ SetOpacity(f32),
SendTestNotification,
Refreshed(NotificationsState),
}
@@ -53,6 +60,7 @@ pub fn read_notifications_config() -> NotificationsState {
let enable = parse_notifications_enable(&content);
let bell = parse_notifications_bell(&content);
let duration = parse_notifications_duration(&content);
+ let opacity = parse_transparency_opacity(&content);
NotificationsState {
enable,
enable_toggle: Toggle::new().with_label("Enable Notifications"),
@@ -62,6 +70,10 @@ pub fn read_notifications_config() -> NotificationsState {
duration_spinbox: Spinbox::new(duration, 1, 60, 1)
.with_label("Notification Duration")
.with_unit("s"),
+ opacity,
+ opacity_slider: Slider::new()
+ .with_label("Transparency")
+ .with_value(opacity),
}
}
@@ -208,6 +220,94 @@ 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];
+fn parse_transparency_opacity(content: &str) -> f32 {
+ let mut in_section = false;
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if trimmed == "[transparency]" {
+ in_section = true;
+ continue;
+ }
+ if trimmed.starts_with('[') && in_section {
+ break;
+ }
+ if in_section && trimmed.starts_with("opacity") {
+ if let Some(val) = trimmed.split('=').nth(1) {
+ if let Ok(o) = val.trim().parse::<f32>() {
+ return o.clamp(0.0, 1.0);
+ }
+ }
+ }
+ }
+ 0.9 // default to 0.9
+}
+
+fn write_transparency_config_value(key: &str, value: &str) {
+ let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
+ let new_line = format!("{} = {}", key, value);
+
+ let mut found = false;
+ let mut updated_lines = Vec::new();
+ let mut in_section = false;
+
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if trimmed == "[transparency]" {
+ in_section = true;
+ updated_lines.push(line.to_string());
+ continue;
+ }
+ if trimmed.starts_with('[') && in_section {
+ in_section = false;
+ }
+ if in_section && trimmed.starts_with(key) {
+ found = true;
+ updated_lines.push(new_line.clone());
+ } else {
+ updated_lines.push(line.to_string());
+ }
+ }
+
+ let mut updated = updated_lines.join("\n");
+
+ if !found {
+ let mut result = String::new();
+ let has_section = content.lines().any(|l| l.trim() == "[transparency]");
+ if has_section {
+ let mut in_section = false;
+ let mut inserted = false;
+ for line in updated.lines() {
+ if line.trim() == "[transparency]" {
+ in_section = true;
+ result.push_str(line);
+ result.push('\n');
+ continue;
+ }
+ if line.trim().starts_with('[') && in_section {
+ if !inserted {
+ result.push_str(&new_line);
+ result.push('\n');
+ inserted = true;
+ }
+ in_section = false;
+ }
+ result.push_str(line);
+ result.push('\n');
+ }
+ if !inserted {
+ result.push_str(&new_line);
+ result.push('\n');
+ }
+ updated = result;
+ } else {
+ updated.push_str("\n[transparency]\n");
+ updated.push_str(&new_line);
+ updated.push_str("\n");
+ }
+ }
+ let _ = fs::write(CONFIG_PATH, updated);
+}
+
pub fn view(state: &mut NotificationsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
let mut pc = PageContent::new();
let y = cy + 12.0;
@@ -246,8 +346,14 @@ pub fn view(state: &mut NotificationsState, cx: f32, cy: f32, cw: f32, _ch: f32)
);
});
sec.spacing(12.0);
-
sec.finish(&mut pc);
+
+ let mut sec2 = Section::new(&mut pc, cx, sec.ay() + 24.0, cw, "Transparency");
+ state.opacity_slider.set_value(state.opacity);
+ sec2.widget(&mut pc, &mut state.opacity_slider, 14.0, 300.0, 20.0);
+ sec2.spacing(12.0);
+ sec2.finish(&mut pc);
+
pc
}
@@ -265,8 +371,13 @@ pub fn update(state: &mut NotificationsState, msg: NotificationsMessage) {
state.duration = d;
write_config_value("duration", &state.duration.to_string());
}
+ NotificationsMessage::SetOpacity(o) => {
+ state.opacity = o;
+ write_transparency_config_value("opacity", &format!("{:.2}", o));
+ send_ipc_command("reload");
+ }
NotificationsMessage::SendTestNotification => {
- send_ipc_command("notify \"clearwm\" \"System notifications are working correctly!\"");
+ send_ipc_command("notify \"ccec\" \"System notifications are working correctly!\"");
}
NotificationsMessage::Refreshed(new) => {
*state = new;
diff --git a/src/pages/status.rs b/src/pages/status.rs
index b7f6427..612f461 100644
--- a/src/pages/status.rs
+++ b/src/pages/status.rs
@@ -1,25 +1,37 @@
use crate::app::{AppAction, PageContent};
use clear_ui::layout::Section;
-use clear_ui::widget::{Label, Widget};
-use crate::pages::typeface::{parse_u16_from, write_config_value};
+use clear_ui::widget::{Label, Toggle, Widget, Spinbox};
+use crate::pages::typeface::parse_u16_from;
#[derive(Debug, Clone)]
pub struct StatusState {
pub font_size: u16,
+ pub padding: u16,
+ pub separators: bool,
+ pub underline: bool,
pub running: bool,
pub loaded: bool,
pub status_label: Label,
pub size_label: Label,
+ pub separators_toggle: Toggle,
+ pub underline_toggle: Toggle,
+ pub padding_spinbox: Spinbox,
}
impl Default for StatusState {
fn default() -> Self {
Self {
font_size: 11,
+ padding: 8,
+ separators: true,
+ underline: true,
running: false,
loaded: false,
status_label: Label::new("Status Interface: Stopped").with_font_size(14.0).with_color([170, 51, 51]),
size_label: Label::new("Font size: 11px").with_font_size(13.0).with_color([212, 212, 212]),
+ separators_toggle: Toggle::new().with_label("Show Separators"),
+ underline_toggle: Toggle::new().with_label("Show Underline"),
+ padding_spinbox: Spinbox::new(8, 0, 32, 1).with_label("Side Padding").with_unit("px"),
}
}
}
@@ -29,7 +41,10 @@ pub enum StatusMessage {
Refreshed(StatusState),
FontSizeUp,
FontSizeDown,
+ ToggleSeparators,
+ ToggleUnderline,
ReloadStatus,
+ SetPadding(u16),
}
pub async fn fetch_status_state() -> StatusState {
@@ -39,9 +54,15 @@ pub async fn fetch_status_state() -> StatusState {
.unwrap_or(false);
let font_size = read_status_font_size().unwrap_or(11);
+ let padding = read_status_padding().unwrap_or(8);
+ let separators = read_status_separators().unwrap_or(true);
+ let underline = read_status_underline().unwrap_or(true);
let status_color = if running { [92, 143, 97] } else { [170, 51, 51] };
StatusState {
font_size,
+ padding,
+ separators,
+ underline,
running,
loaded: true,
status_label: Label::new(&format!("Status Interface: {}", if running { "Running" } else { "Stopped" }))
@@ -50,22 +71,111 @@ pub async fn fetch_status_state() -> StatusState {
size_label: Label::new(&format!("Font size: {}px", font_size))
.with_font_size(13.0)
.with_color([212, 212, 212]),
+ separators_toggle: Toggle::new().with_label("Show Separators"),
+ underline_toggle: Toggle::new().with_label("Show Underline"),
+ padding_spinbox: Spinbox::new(padding as i32, 0, 32, 1).with_label("Side Padding").with_unit("px"),
}
}
+#[cfg(test)]
+thread_local! {
+ static TEST_CONFIG_PATH: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
+}
+
+fn get_config_path() -> String {
+ #[cfg(test)]
+ {
+ TEST_CONFIG_PATH.with(|p| {
+ if let Some(path) = p.borrow().as_ref() {
+ return path.clone();
+ }
+ "/home/lsgalante/.config/ccec/config.toml".to_string()
+ })
+ }
+ #[cfg(not(test))]
+ {
+ "/home/lsgalante/.config/ccec/config.toml".to_string()
+ }
+}
+
+fn write_status_value(key: &str, value: &str) {
+ crate::pages::typeface::write_config_value_path(&get_config_path(), key, value);
+}
+
fn read_status_font_size() -> Option<u16> {
- let content = std::fs::read_to_string("/home/lsgalante/.config/clearwm/config.toml").ok()?;
+ let content = std::fs::read_to_string(&get_config_path()).ok()?;
Some(parse_u16_from(&content, "status_font_size", 11))
}
fn write_status_font_size(size: u16) {
- write_config_value("status_font_size", &size.to_string());
+ write_status_value("status_font_size", &size.to_string());
+}
+
+fn read_status_padding() -> Option<u16> {
+ let content = std::fs::read_to_string(&get_config_path()).ok()?;
+ Some(parse_u16_from(&content, "status_padding", 8))
+}
+
+fn write_status_padding(padding: u16) {
+ write_status_value("status_padding", &padding.to_string());
+}
+
+fn read_status_separators() -> Option<bool> {
+ let content = std::fs::read_to_string(&get_config_path()).ok()?;
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if let Some(rest) = trimmed.strip_prefix("status_separators") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ if let Ok(val) = rest.trim_end_matches('"').trim().parse::<bool>() {
+ return Some(val);
+ }
+ }
+ }
+ Some(true)
+}
+
+fn write_status_separators(val: bool) {
+ write_status_value("status_separators", &val.to_string());
+}
+
+fn read_status_underline() -> Option<bool> {
+ let content = std::fs::read_to_string(&get_config_path()).ok()?;
+ for line in content.lines() {
+ let trimmed = line.trim();
+ if let Some(rest) = trimmed.strip_prefix("status_underline") {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ if let Ok(val) = rest.trim_end_matches('"').trim().parse::<bool>() {
+ return Some(val);
+ }
+ }
+ }
+ Some(true)
+}
+
+fn write_status_underline(val: bool) {
+ write_status_value("status_underline", &val.to_string());
+}
+
+fn get_socket_path() -> String {
+ match std::env::var("WAYLAND_DISPLAY") {
+ Ok(display) => format!("/tmp/ccec-{}.sock", display),
+ Err(_) => "/tmp/ccec.sock".to_string(),
+ }
+}
+
+fn send_ipc_command(cmd: &str) {
+ if let Ok(mut stream) = std::os::unix::net::UnixStream::connect(get_socket_path()) {
+ use std::io::Write;
+ let _ = stream.write_all(format!("{}\n", cmd).as_bytes());
+ }
}
fn status_interface_reload() {
let _ = std::process::Command::new("pkill")
.args(["-f", "clear-status-interface"])
- .spawn();
+ .status();
+ std::thread::sleep(std::time::Duration::from_millis(150));
+ send_ipc_command("spawn clear-status-interface");
}
const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
@@ -104,7 +214,22 @@ pub fn view(state: &mut StatusState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Pag
pc.button("+1", sec.ax(12.0 + 36.0 + 8.0), yt, 36.0, btn_h,
BTN_ACTIVE, BTN_HOVER, WHITE,
AppAction::Status(StatusMessage::FontSizeUp));
- sec.content_y += btn_h + 12.0;
+ sec.content_y += btn_h + 16.0;
+
+ // Separators toggle
+ state.separators_toggle.set_toggled(state.separators);
+ sec.widget(&mut pc, &mut state.separators_toggle, 12.0, 48.0, 24.0);
+ sec.spacing(16.0);
+
+ // Underline toggle
+ state.underline_toggle.set_toggled(state.underline);
+ sec.widget(&mut pc, &mut state.underline_toggle, 12.0, 48.0, 24.0);
+ sec.spacing(16.0);
+
+ // Padding spinbox
+ state.padding_spinbox.value = state.padding as i32;
+ sec.widget(&mut pc, &mut state.padding_spinbox, 12.0, 200.0, 26.0);
+ sec.spacing(16.0);
// Reload button
let yt = sec.ay();
@@ -123,9 +248,13 @@ pub fn update(state: &mut StatusState, msg: StatusMessage) {
StatusMessage::Refreshed(new) => {
let was_status_hovered = state.status_label.hovered();
let was_size_hovered = state.size_label.hovered();
+ let was_separators_hovered = state.separators_toggle.hovered();
+ let was_underline_hovered = state.underline_toggle.hovered();
*state = new;
state.status_label.set_hovered(was_status_hovered);
state.size_label.set_hovered(was_size_hovered);
+ state.separators_toggle.set_hovered(was_separators_hovered);
+ state.underline_toggle.set_hovered(was_underline_hovered);
}
StatusMessage::FontSizeUp => {
if state.font_size < 28 {
@@ -141,8 +270,83 @@ pub fn update(state: &mut StatusState, msg: StatusMessage) {
status_interface_reload();
}
}
+ StatusMessage::ToggleSeparators => {
+ state.separators = !state.separators;
+ write_status_separators(state.separators);
+ status_interface_reload();
+ }
+ StatusMessage::ToggleUnderline => {
+ state.underline = !state.underline;
+ write_status_underline(state.underline);
+ status_interface_reload();
+ }
+ StatusMessage::SetPadding(val) => {
+ state.padding = val;
+ write_status_padding(val);
+ status_interface_reload();
+ }
StatusMessage::ReloadStatus => {
status_interface_reload();
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fs;
+
+ #[test]
+ fn test_read_write_separators() {
+ let dir = std::env::temp_dir();
+ let path = dir.join("test_status_separators.toml");
+ let path_str = path.to_str().unwrap().to_string();
+
+ let _ = fs::write(&path_str, "[layout]\nstatus_separators = true\nstatus_padding = 8\n");
+ TEST_CONFIG_PATH.with(|p| *p.borrow_mut() = Some(path_str));
+
+ let original = read_status_separators().unwrap_or(true);
+ write_status_separators(!original);
+ assert_eq!(read_status_separators(), Some(!original));
+ write_status_separators(original);
+ assert_eq!(read_status_separators(), Some(original));
+
+ let _ = fs::remove_file(path);
+ }
+
+ #[test]
+ fn test_read_write_padding() {
+ let dir = std::env::temp_dir();
+ let path = dir.join("test_status_padding.toml");
+ let path_str = path.to_str().unwrap().to_string();
+
+ let _ = fs::write(&path_str, "[layout]\nstatus_separators = true\nstatus_padding = 8\n");
+ TEST_CONFIG_PATH.with(|p| *p.borrow_mut() = Some(path_str));
+
+ let original = read_status_padding().unwrap_or(8);
+ write_status_padding(12);
+ assert_eq!(read_status_padding(), Some(12));
+ write_status_padding(original);
+ assert_eq!(read_status_padding(), Some(original));
+
+ let _ = fs::remove_file(path);
+ }
+
+ #[test]
+ fn test_read_write_underline() {
+ let dir = std::env::temp_dir();
+ let path = dir.join("test_status_underline.toml");
+ let path_str = path.to_str().unwrap().to_string();
+
+ let _ = fs::write(&path_str, "[layout]\nstatus_underline = true\nstatus_padding = 8\n");
+ TEST_CONFIG_PATH.with(|p| *p.borrow_mut() = Some(path_str));
+
+ let original = read_status_underline().unwrap_or(true);
+ write_status_underline(!original);
+ assert_eq!(read_status_underline(), Some(!original));
+ write_status_underline(original);
+ assert_eq!(read_status_underline(), Some(original));
+
+ let _ = fs::remove_file(path);
+ }
+}
diff --git a/src/pages/typeface.rs b/src/pages/typeface.rs
index 2bab21d..9e799f0 100644
--- a/src/pages/typeface.rs
+++ b/src/pages/typeface.rs
@@ -253,7 +253,11 @@ pub fn parse_u16_from(content: &str, key: &str, default: u16) -> u16 {
}
pub fn write_config_value(key: &str, value: &str) -> bool {
- let content = fs::read_to_string("/home/lsgalante/.config/clearwm/config.toml").unwrap_or_default();
+ write_config_value_path("/home/lsgalante/.config/ccec/config.toml", key, value)
+}
+
+pub fn write_config_value_path(path: &str, key: &str, value: &str) -> bool {
+ let content = fs::read_to_string(path).unwrap_or_default();
let new_line = format!("{} = {}", key, value);
let mut found = false;
let updated: String = content.lines()
@@ -274,14 +278,14 @@ pub fn write_config_value(key: &str, value: &str) -> bool {
result.push_str(line); result.push('\n');
}
if in_layout && !inserted { result.push_str(&new_line); result.push('\n'); }
- fs::write("/home/lsgalante/.config/clearwm/config.toml", result).is_ok()
- } else { fs::write("/home/lsgalante/.config/clearwm/config.toml", updated).is_ok() }
+ fs::write(path, result).is_ok()
+ } else { fs::write(path, updated).is_ok() }
}
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
- Ok(display) => format!("/tmp/clearwm-{}.sock", display),
- Err(_) => "/tmp/clearwm.sock".to_string(),
+ Ok(display) => format!("/tmp/ccec-{}.sock", display),
+ Err(_) => "/tmp/ccec.sock".to_string(),
}
}
@@ -293,12 +297,12 @@ fn send_ipc_command(cmd: &str) {
}
fn read_border_font_size() -> Option<u16> {
- let content = fs::read_to_string("/home/lsgalante/.config/clearwm/config.toml").ok()?;
+ let content = fs::read_to_string("/home/lsgalante/.config/ccec/config.toml").ok()?;
Some(parse_u16_from(&content, "border_font_size", 11))
}
fn read_status_size() -> Option<u16> {
- let content = fs::read_to_string("/home/lsgalante/.config/clearwm/config.toml").ok()?;
+ let content = fs::read_to_string("/home/lsgalante/.config/ccec/config.toml").ok()?;
Some(parse_u16_from(&content, "status_font_size", 11))
}