system settings
git clone https://git.lucas.co/cce-system-interface.git
Support configurable background color and opacity for notifications on the Interface page
src/main.rs | 227 +++++++++++++++++++++++++++-------------------
src/pages/input.rs | 45 ++++++++-
src/pages/interface.rs | 241 +++++++++++++++++++++++++++++++++++++++++++++----
src/pages/services.rs | 108 ----------------------
4 files changed, 398 insertions(+), 223 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 40b2147..045e601 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -157,7 +157,6 @@ struct SystemInterface {
needs_rebuild: bool,
scroll_y: f32,
max_scroll_y: f32,
- opacity_dragging: bool,
audio_sink_dragging: Option<usize>,
audio_source_dragging: Option<usize>,
display_brightness_dragging: bool,
@@ -465,7 +464,6 @@ impl clear_ui::engine::Application for SystemInterface {
needs_rebuild: true,
scroll_y: 4000.0,
max_scroll_y: 0.0,
- opacity_dragging: false,
audio_sink_dragging: None,
audio_source_dragging: None,
display_brightness_dragging: false,
@@ -718,6 +716,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
self.app.interface.dropdown_height_spinbox.set_parent(None, &mut self.ui_context);
self.app.interface.button_corner_radius_spinbox.clear_children(&mut self.ui_context);
self.app.interface.button_corner_radius_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.notification_opacity_spinbox.clear_children(&mut self.ui_context);
+ self.app.interface.notification_opacity_spinbox.set_parent(None, &mut self.ui_context);
self.app.interface.sans_box.clear_children(&mut self.ui_context); self.app.interface.sans_box.set_parent(None, &mut self.ui_context);
self.app.interface.serif_box.clear_children(&mut self.ui_context); self.app.interface.serif_box.set_parent(None, &mut self.ui_context);
@@ -734,7 +734,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
self.app.services.notifications_enable_toggle.clear_children(&mut self.ui_context); self.app.services.notifications_enable_toggle.set_parent(None, &mut self.ui_context);
self.app.services.notifications_bell_toggle.clear_children(&mut self.ui_context); self.app.services.notifications_bell_toggle.set_parent(None, &mut self.ui_context);
self.app.services.notifications_duration_spinbox.clear_children(&mut self.ui_context); self.app.services.notifications_duration_spinbox.set_parent(None, &mut self.ui_context);
- self.app.services.notifications_opacity_slider.clear_children(&mut self.ui_context); self.app.services.notifications_opacity_slider.set_parent(None, &mut self.ui_context);
self.app.input.rate_spinbox.clear_children(&mut self.ui_context); self.app.input.rate_spinbox.set_parent(None, &mut self.ui_context);
self.app.input.delay_spinbox.clear_children(&mut self.ui_context); self.app.input.delay_spinbox.set_parent(None, &mut self.ui_context);
@@ -751,6 +750,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
self.app.input.trackpoint_accel_profile_menu.clear_children(&mut self.ui_context); self.app.input.trackpoint_accel_profile_menu.set_parent(None, &mut self.ui_context);
self.app.input.cursor_theme_menu.clear_children(&mut self.ui_context); self.app.input.cursor_theme_menu.set_parent(None, &mut self.ui_context);
self.app.input.cursor_size_spinbox.clear_children(&mut self.ui_context); self.app.input.cursor_size_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.input.zoom_in_box.clear_children(&mut self.ui_context); self.app.input.zoom_in_box.set_parent(None, &mut self.ui_context);
+ self.app.input.zoom_out_box.clear_children(&mut self.ui_context); self.app.input.zoom_out_box.set_parent(None, &mut self.ui_context);
for sb in &mut self.app.audio.sink_spinboxes {
sb.clear_children(&mut self.ui_context);
@@ -831,7 +832,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
link_parent_child(&mut self.page_sec_containers[1], &mut self.app.services.notifications_enable_toggle, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[1], &mut self.app.services.notifications_bell_toggle, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[1], &mut self.app.services.notifications_duration_spinbox, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[1], &mut self.app.services.notifications_opacity_slider, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[2], &mut self.app.services.status_separators_toggle, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[2], &mut self.app.services.status_underline_toggle, &mut self.ui_context);
@@ -891,9 +891,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
link_parent_child(&mut self.page_sec_containers[6], &mut self.app.layout.side_panel_border_opacity_spinbox, &mut self.ui_context);
}
Page::Interface => {
- self.page_sec_containers.resize_with(5, clear_ui::widget::Container::new);
+ self.page_sec_containers.resize_with(6, clear_ui::widget::Container::new);
- for i in 0..5 {
+ for i in 0..6 {
link_parent_child(page_root, &mut self.page_sec_containers[i], &mut self.ui_context);
}
@@ -948,13 +948,13 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
// (ScrollingList child widgets)
link_parent_child(&mut self.page_sec_containers[2], &mut self.app.interface.color_selectors[14], &mut self.ui_context);
-
+
// (Breadcrumb child widgets)
link_parent_child(&mut self.page_sec_containers[2], &mut self.app.interface.color_selectors[15], &mut self.ui_context);
-
+
// (Popover child widgets)
link_parent_child(&mut self.page_sec_containers[2], &mut self.app.interface.color_selectors[16], &mut self.ui_context);
-
+
// (Spinbox child widgets)
link_parent_child(&mut self.page_sec_containers[2], &mut self.app.interface.spinbox_height_spinbox, &mut self.ui_context);
@@ -983,30 +983,35 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
// Section 3: Indicators (parent of Primary Highlight)
link_parent_child(&mut self.page_sec_containers[3], &mut self.app.interface.color_selectors[10], &mut self.ui_context);
- // Section 4: Fonts (parent of System Fonts and Program Fonts)
+ // Section 4: Notification
+ link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.color_selectors[17], &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.notification_opacity_spinbox, &mut self.ui_context);
+
+ // Section 5: Fonts (parent of System Fonts and Program Fonts)
// (System Fonts)
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.sans_box, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.serif_box, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.mono_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.sans_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.serif_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.mono_box, &mut self.ui_context);
// (Program Fonts)
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.borders_menu, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.borders_box, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.status_menu, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.status_box, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.fuzzel_menu, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.fuzzel_box, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.terminal_menu, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.terminal_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.borders_menu, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.borders_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.status_menu, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.status_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.fuzzel_menu, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.fuzzel_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.terminal_menu, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.terminal_box, &mut self.ui_context);
}
Page::Input => {
- self.page_sec_containers.resize_with(5, clear_ui::widget::Container::new);
+ self.page_sec_containers.resize_with(6, clear_ui::widget::Container::new);
link_parent_child(page_root, &mut self.page_sec_containers[0], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[1], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[2], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[3], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[4], &mut self.ui_context);
+ link_parent_child(page_root, &mut self.page_sec_containers[5], &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.dwtp_toggle, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_speed_spinbox, &mut self.ui_context);
@@ -1027,6 +1032,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.pointer_friction_spinbox, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.trackpad_toggle, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.trackpad_friction_spinbox, &mut self.ui_context);
+
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.input.zoom_in_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[5], &mut self.app.input.zoom_out_box, &mut self.ui_context);
}
Page::Audio => {
self.page_sec_containers.resize_with(2, clear_ui::widget::Container::new);
@@ -1940,6 +1948,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if self.app.interface.button_corner_radius_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
changed = true;
}
+ if self.app.interface.notification_opacity_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
if self.app.interface.sans_box.cursor_moved(lx, ly, &mut self.ui_context) {
changed = true;
}
@@ -2023,6 +2034,12 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if self.app.input.trackpoint_accel_profile_menu.cursor_moved(lx, ly, &mut self.ui_context) {
changed = true;
}
+ if self.app.input.zoom_in_box.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.input.zoom_out_box.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
}
if self.app.current_page == Page::Audio {
if let Some(idx) = self.audio_sink_dragging {
@@ -2197,56 +2214,47 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if self.app.current_page == Page::Services {
- if self.opacity_dragging {
- if self.app.services.notifications_opacity_slider.drag_update(lx, ly) {
- changed = true;
- }
+ if self.app.services.search_box.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.services.list_box.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ let query = if self.app.services.search_box.editing {
+ self.app.services.search_box.edit_buffer.to_lowercase()
} else {
- if self.app.services.search_box.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.services.list_box.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- let query = if self.app.services.search_box.editing {
- self.app.services.search_box.edit_buffer.to_lowercase()
- } else {
- self.app.services.search_box.text.to_lowercase()
- };
- let matching_count = self.app.services.services.iter()
- .filter(|s| s.is_system == (self.app.services.active_tab == pages::services::ServiceTab::System))
- .filter(|s| s.name.to_lowercase().contains(&query) || s.description.to_lowercase().contains(&query))
- .count();
- for i in 0..matching_count.min(self.app.services.service_items.len()) {
- if self.app.services.service_items[i].cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- }
- if self.app.services.notifications_enable_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.services.notifications_bell_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.services.notifications_duration_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.services.notifications_opacity_slider.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.services.status_label.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.services.status_separators_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.services.status_underline_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.services.status_padding_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ self.app.services.search_box.text.to_lowercase()
+ };
+ let matching_count = self.app.services.services.iter()
+ .filter(|s| s.is_system == (self.app.services.active_tab == pages::services::ServiceTab::System))
+ .filter(|s| s.name.to_lowercase().contains(&query) || s.description.to_lowercase().contains(&query))
+ .count();
+ for i in 0..matching_count.min(self.app.services.service_items.len()) {
+ if self.app.services.service_items[i].cursor_moved(lx, ly, &mut self.ui_context) {
changed = true;
}
}
+ if self.app.services.notifications_enable_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.services.notifications_bell_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.services.notifications_duration_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.services.status_label.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.services.status_separators_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.services.status_underline_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.services.status_padding_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
}
if self.app.current_page == Page::Accounts {
if self.app.accounts.editing_oauth_creds {
@@ -2419,6 +2427,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if self.app.interface.font_selector_height_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if self.app.interface.dropdown_height_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if self.app.interface.button_corner_radius_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if self.app.interface.notification_opacity_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
let tf = &mut self.app.interface;
if tf.sans_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if tf.serif_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
@@ -2440,6 +2449,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if self.app.input.trackpad_friction_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if self.app.input.trackpoint_accel_speed_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if self.app.input.trackpoint_accel_profile_menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if self.app.input.zoom_in_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if self.app.input.zoom_out_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
}
Page::Audio => {
@@ -2475,7 +2486,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if srv.search_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if srv.list_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if srv.notifications_duration_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if srv.notifications_opacity_slider.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if srv.status_label.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if srv.status_padding_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
}
@@ -2646,6 +2656,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
14 => pages::interface::InterfaceMessage::PickScrollingListBgColor,
15 => pages::interface::InterfaceMessage::PickBreadcrumbBgColor,
16 => pages::interface::InterfaceMessage::PickPopoverBgColor,
+ 17 => pages::interface::InterfaceMessage::PickNotificationBgColor,
_ => pages::interface::InterfaceMessage::PickLowColor,
}));
}
@@ -2668,6 +2679,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
14 => pages::interface::InterfaceMessage::SetScrollingListBgColor(cp.color),
15 => pages::interface::InterfaceMessage::SetBreadcrumbBgColor(cp.color),
16 => pages::interface::InterfaceMessage::SetPopoverBgColor(cp.color),
+ 17 => pages::interface::InterfaceMessage::SetNotificationBgColor(cp.color),
_ => pages::interface::InterfaceMessage::SetLowColor(cp.color),
}));
}
@@ -2678,6 +2690,12 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if sb.mouse_input(button, state, lx, ly, &mut self.ui_context) && sb.value != old {
actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetMenubarOpacity(sb.value as f32 / 100.0)));
}
+ let sb = &mut self.app.interface.notification_opacity_spinbox;
+ if !sb.hit_test(lx, ly, &self.ui_context) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly, &mut self.ui_context) && sb.value != old {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetNotificationOpacity(sb.value as f32 / 100.0)));
+ }
let sb = &mut self.app.interface.tab_margin_spinbox_x;
if !sb.hit_test(lx, ly, &self.ui_context) { sb.unfocus(); }
let old = sb.value;
@@ -2951,6 +2969,24 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if state == clear_ui::widget::ElementState::Pressed && menu.take_change() {
actions.push(AppAction::Input(pages::input::InputMessage::ApplyCursorTheme(menu.selected)));
}
+
+ let tb = &mut self.app.input.zoom_in_box;
+ if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly, &self.ui_context) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly, &mut self.ui_context) {
+ self.needs_rebuild = true;
+ }
+ if state == clear_ui::widget::ElementState::Pressed && tb.take_change() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomIn));
+ }
+
+ let tb = &mut self.app.input.zoom_out_box;
+ if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly, &self.ui_context) { tb.unfocus(); }
+ if tb.mouse_input(button, state, lx, ly, &mut self.ui_context) {
+ self.needs_rebuild = true;
+ }
+ if state == clear_ui::widget::ElementState::Pressed && tb.take_change() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomOut));
+ }
}
if self.app.current_page == Page::Services {
let toggle = &mut self.app.services.notifications_enable_toggle;
@@ -2963,24 +2999,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if toggle.take_click() {
actions.push(AppAction::Services(pages::services::ServicesMessage::ToggleNotificationsBell));
}
- let slider = &mut self.app.services.notifications_opacity_slider;
- if button == clear_ui::widget::MouseButton::Left {
- if state == clear_ui::widget::ElementState::Pressed {
- if slider.hit_test(lx, ly, &self.ui_context) {
- 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::Services(pages::services::ServicesMessage::SetNotificationsOpacity(val)));
- self.needs_rebuild = true;
- }
- }
- }
if state == clear_ui::widget::ElementState::Pressed {
let lbl1 = &mut self.app.services.status_label;
if !lbl1.hit_test(lx, ly, &self.ui_context) { lbl1.unfocus(); }
@@ -3547,10 +3565,14 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
14 => pages::interface::InterfaceMessage::SetScrollingListBgColor(cp.color),
15 => pages::interface::InterfaceMessage::SetBreadcrumbBgColor(cp.color),
16 => pages::interface::InterfaceMessage::SetPopoverBgColor(cp.color),
+ 17 => pages::interface::InterfaceMessage::SetNotificationBgColor(cp.color),
_ => pages::interface::InterfaceMessage::SetLowColor(cp.color),
}));
}
}
+ if self.app.interface.notification_opacity_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetNotificationOpacity(self.app.interface.notification_opacity_spinbox.value as f32 / 100.0)));
+ }
if self.app.interface.menubar_opacity_spinbox.take_change() {
actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetMenubarOpacity(self.app.interface.menubar_opacity_spinbox.value as f32 / 100.0)));
}
@@ -3733,6 +3755,12 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if self.app.input.cursor_theme_menu.take_change() {
actions.push(AppAction::Input(pages::input::InputMessage::ApplyCursorTheme(self.app.input.cursor_theme_menu.selected)));
}
+ if self.app.input.zoom_in_box.take_change() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomIn));
+ }
+ if self.app.input.zoom_out_box.take_change() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomOut));
+ }
}
Page::Services => {
if self.app.services.notifications_duration_spinbox.take_change() {
@@ -3747,10 +3775,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
if self.app.services.notifications_bell_toggle.take_change() {
actions.push(AppAction::Services(pages::services::ServicesMessage::ToggleNotificationsBell));
}
- if self.app.services.notifications_opacity_slider.take_change() {
- let val = self.app.services.notifications_opacity_slider.value() as f32 / 100.0;
- actions.push(AppAction::Services(pages::services::ServicesMessage::SetNotificationsOpacity(val)));
- }
if self.app.services.status_separators_toggle.take_change() {
actions.push(AppAction::Services(pages::services::ServicesMessage::StatusToggleSeparators));
}
@@ -4246,6 +4270,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
14 => pages::interface::InterfaceMessage::SetScrollingListBgColor(cp.color),
15 => pages::interface::InterfaceMessage::SetBreadcrumbBgColor(cp.color),
16 => pages::interface::InterfaceMessage::SetPopoverBgColor(cp.color),
+ 17 => pages::interface::InterfaceMessage::SetNotificationBgColor(cp.color),
_ => pages::interface::InterfaceMessage::SetLowColor(cp.color),
}));
}
@@ -4259,6 +4284,16 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
self.needs_rebuild = true;
return true;
}
+ let sb = &mut self.app.interface.notification_opacity_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ let new_val = sb.value;
+ if new_val != old {
+ self.handle_action(&AppAction::Interface(pages::interface::InterfaceMessage::SetNotificationOpacity(new_val as f32 / 100.0)));
+ }
+ self.needs_rebuild = true;
+ return true;
+ }
let sb = &mut self.app.interface.menubar_opacity_spinbox;
let old = sb.value;
if sb.keyboard_input(event, &mut self.ui_context) {
@@ -4656,6 +4691,16 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
self.needs_rebuild = true;
return true;
}
+ if self.app.input.zoom_in_box.keyboard_input(event, &mut self.ui_context) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyZoomIn));
+ self.needs_rebuild = true;
+ return true;
+ }
+ if self.app.input.zoom_out_box.keyboard_input(event, &mut self.ui_context) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyZoomOut));
+ self.needs_rebuild = true;
+ return true;
+ }
}
if self.app.current_page == Page::Accounts {
let mut consumed = false;
diff --git a/src/pages/input.rs b/src/pages/input.rs
index 16855fe..88b5f3a 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -3,7 +3,7 @@ use std::io::Write;
use crate::app::PageContent;
use clear_ui::layout::{PageLayoutBuilder, LayoutStrategy};
-use clear_ui::widget::{Spinbox, Toggle, Trackpad, Dropdown, Finger, Element};
+use clear_ui::widget::{Spinbox, Toggle, Trackpad, Dropdown, Finger, Element, TextBox};
const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.toml";
@@ -70,6 +70,10 @@ pub struct InputState {
pub cursor_theme_menu: Dropdown,
pub cursor_size_spinbox: Spinbox,
pub cursor_themes: Vec<String>,
+
+ // Graph settings
+ pub zoom_in_box: TextBox,
+ pub zoom_out_box: TextBox,
}
fn scan_cursor_themes() -> Vec<String> {
@@ -149,6 +153,10 @@ impl Default for InputState {
cursor_theme_menu: Dropdown::new(cursor_themes.clone(), 0).with_label("Cursor Theme"),
cursor_size_spinbox: Spinbox::new(24, 16, 64, 4).with_label("Cursor Size").with_unit("px"),
cursor_themes,
+
+ // Graph defaults
+ zoom_in_box: TextBox::new("=".to_string()).with_label("Zoom In"),
+ zoom_out_box: TextBox::new("-".to_string()).with_label("Zoom Out"),
}
}
}
@@ -181,6 +189,9 @@ pub enum InputMessage {
ApplyCursorTheme(usize),
ApplyCursorSize,
+
+ ApplyZoomIn,
+ ApplyZoomOut,
}
pub fn read_input_config() -> InputState {
@@ -212,6 +223,9 @@ pub fn read_input_config() -> InputState {
let cursor_themes = scan_cursor_themes();
let theme_idx = cursor_themes.iter().position(|t| t == &cursor_theme).unwrap_or(0);
+ let zoom_in = parse_string_key(&content, "zoom_in", "=");
+ let zoom_out = parse_string_key(&content, "zoom_out", "-");
+
InputState {
tap_to_click: tap,
repeat_rate: rate,
@@ -256,6 +270,10 @@ pub fn read_input_config() -> InputState {
cursor_theme_menu: Dropdown::new(cursor_themes.clone(), theme_idx).with_label("Cursor Theme"),
cursor_size_spinbox: Spinbox::new(cursor_size as i32, 16, 64, 4).with_label("Cursor Size").with_unit("px"),
cursor_themes,
+
+ // Graph settings
+ zoom_in_box: TextBox::new(zoom_in).with_label("Zoom In"),
+ zoom_out_box: TextBox::new(zoom_out).with_label("Zoom Out"),
}
}
@@ -340,7 +358,9 @@ fn write_config_value(key: &str, value: &str) {
.join("\n");
if !found {
- let section = if key == "tap_to_click" || key == "dwtp"
+ let section = if key == "zoom_in" || key == "zoom_out" {
+ "[graph]"
+ } else if key == "tap_to_click" || key == "dwtp"
|| key == "trackpoint_accel_speed" || key == "trackpoint_accel_profile"
|| key == "cursor_theme" || key == "cursor_size" || key == "natural_scroll" {
"[input]"
@@ -392,7 +412,7 @@ const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut clear_ui::context::UiContext) -> PageContent {
let mut final_pc = PageContent::new();
let sec_w = 320.0f32;
- let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(7);
+ let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(8);
// ── Touchpad ──
builder.add_section(&mut final_pc, "Touchpad", false, |sec| {
@@ -473,7 +493,16 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focu
sec.spacing(8.0);
});
- // ── Keybindings ──
+ // ── Graph ──
+ builder.add_section(&mut final_pc, "Graph", sec_focused.get(5).copied().unwrap_or(false), |sec| {
+ sec.widget_full(&mut state.zoom_in_box, 44.0, ctx);
+ sec.spacing(12.0);
+
+ sec.widget_full(&mut state.zoom_out_box, 44.0, ctx);
+ sec.spacing(8.0);
+ });
+
+ // ── Keyboard Bindings ──
builder.add_section(&mut final_pc, "Keyboard Bindings", false, |sec| {
for kb in &state.keybinds {
let binding = if kb.mods.is_empty() {
@@ -583,6 +612,14 @@ pub fn update(state: &mut InputState, msg: InputMessage) {
write_config_value("cursor_size", &size.to_string());
send_ipc_command(&format!("input cursor-size {}", size));
}
+ InputMessage::ApplyZoomIn => {
+ let val = state.zoom_in_box.text.clone();
+ write_config_value("zoom_in", &format!("\"{}\"", val));
+ }
+ InputMessage::ApplyZoomOut => {
+ let val = state.zoom_out_box.text.clone();
+ write_config_value("zoom_out", &format!("\"{}\"", val));
+ }
InputMessage::Refreshed(new) => {
let fingers = state.fingers.clone();
*state = new;
diff --git a/src/pages/interface.rs b/src/pages/interface.rs
index 8a60f22..a3196ad 100644
--- a/src/pages/interface.rs
+++ b/src/pages/interface.rs
@@ -37,6 +37,9 @@ pub struct InterfaceState {
pub color_selectors: Vec<ColorSelector>,
pub menubar_opacity: f32,
pub menubar_opacity_spinbox: Spinbox,
+ pub notification_bg_color: [u8; 3],
+ pub notification_opacity: f32,
+ pub notification_opacity_spinbox: Spinbox,
pub paginator_tab_margin_x: u16,
pub paginator_tab_margin_y: u16,
pub tab_margin_spinbox_x: Spinbox,
@@ -167,6 +170,7 @@ impl Default for InterfaceState {
ColorSelector::new([81, 81, 97]).with_label("Background"), // 14: ScrollingList - Background
ColorSelector::new([81, 81, 97]).with_label("Background"), // 15: Breadcrumb - Background
ColorSelector::new([81, 81, 97]).with_label("Background"), // 16: Popover - Background
+ ColorSelector::new([0x08, 0x08, 0x0c]).with_label("Background"), // 17: Notification - Background
],
paginator_tab_margin_x: 5,
paginator_tab_margin_y: 10,
@@ -224,25 +228,25 @@ impl Default for InterfaceState {
paginator: String::new(),
all_fonts: Vec::new(),
mono_fonts: Vec::new(),
- sans_box: TextBox::default(),
- serif_box: TextBox::default(),
- mono_box: TextBox::default(),
- borders_box: TextBox::default(),
- status_box: TextBox::default(),
- fuzzel_box: TextBox::default(),
- terminal_box: TextBox::default(),
- borders_menu: Dropdown::default(),
- status_menu: Dropdown::default(),
- fuzzel_menu: Dropdown::default(),
- terminal_menu: Dropdown::default(),
- borders_size_box: Spinbox::new(11, 6, 72, 1),
- status_size_box: Spinbox::new(11, 6, 72, 1),
+ sans_box: TextBox::new(String::new()).with_label("Sans-Serif"),
+ serif_box: TextBox::new(String::new()).with_label("Serif"),
+ mono_box: TextBox::new(String::new()).with_label("Monospace"),
+ borders_box: TextBox::new(String::new()).with_label("Active Font"),
+ status_box: TextBox::new(String::new()).with_label("Active Font"),
+ fuzzel_box: TextBox::new(String::new()).with_label("Active Font"),
+ terminal_box: TextBox::new(String::new()).with_label("Active Font"),
+ borders_menu: Dropdown::new(Vec::new(), 0),
+ status_menu: Dropdown::new(Vec::new(), 0),
+ fuzzel_menu: Dropdown::new(Vec::new(), 0),
+ terminal_menu: Dropdown::new(Vec::new(), 0),
+ borders_size_box: Spinbox::new(14, 6, 72, 1),
+ status_size_box: Spinbox::new(14, 6, 72, 1),
fuzzel_size_box: Spinbox::new(14, 6, 72, 1),
- terminal_size_box: Spinbox::new(12, 6, 72, 1),
+ terminal_size_box: Spinbox::new(14, 6, 72, 1),
color_selector_font: "monospace".to_string(),
- color_selector_font_selector: FontSelector::new("monospace".to_string()).with_label("Value"),
+ color_selector_font_selector: FontSelector::new("monospace".to_string()).with_label("Color Label"),
menubar_font: "Outfit".to_string(),
- menubar_font_selector: FontSelector::new("Outfit".to_string()).with_label("Font"),
+ menubar_font_selector: FontSelector::new("Outfit".to_string()).with_label("Menu Label"),
section_label_font: "Outfit".to_string(),
section_label_font_selector: FontSelector::new("Outfit".to_string()).with_label("Label"),
nested_section_label_font: "Outfit".to_string(),
@@ -261,6 +265,9 @@ impl Default for InterfaceState {
graph_gap_width_spinbox: Spinbox::new(35, 0, 100, 1).with_label("Gap Width").with_unit("px"),
menubar_opacity: 0.9,
menubar_opacity_spinbox: Spinbox::new(90, 0, 100, 5).with_label("Opacity").with_unit("%"),
+ notification_bg_color: [0x08, 0x08, 0x0c],
+ notification_opacity: 0.9,
+ notification_opacity_spinbox: Spinbox::new(90, 0, 100, 5).with_label("Opacity").with_unit("%"),
}
}
}
@@ -284,6 +291,8 @@ pub enum InterfaceMessage {
SetScrollingListBgColor([u8; 3]),
SetBreadcrumbBgColor([u8; 3]),
SetPopoverBgColor([u8; 3]),
+ SetNotificationBgColor([u8; 3]),
+ SetNotificationOpacity(f32),
SetTabMarginX(u16),
SetTabMarginY(u16),
SetTabPaddingX(u16),
@@ -333,6 +342,7 @@ pub enum InterfaceMessage {
PickScrollingListBgColor,
PickBreadcrumbBgColor,
PickPopoverBgColor,
+ PickNotificationBgColor,
Refreshed(InterfaceState),
TypefaceRefreshed(InterfaceState),
SetSans(String),
@@ -434,6 +444,8 @@ pub fn read_interface_config() -> InterfaceState {
let graph_gap_opacity = parse_f32_from(&content, "graph_gap_opacity", legacy_opacity);
let graph_gap_width = parse_u16_from(&content, "graph_gap_width", 35);
let opacity = parse_transparency_opacity(&content);
+ let notification_bg_color = parse_notifications_color(&content, "bg_color", [0x08, 0x08, 0x0c]);
+ let notification_opacity = parse_notifications_opacity(&content);
InterfaceState {
low_color: bg,
@@ -471,6 +483,7 @@ pub fn read_interface_config() -> InterfaceState {
ColorSelector::new(scrollinglist_bg).with_label("Background").with_font_family(&color_selector_font), // 14: ScrollingList - Background
ColorSelector::new(breadcrumb_bg).with_label("Background").with_font_family(&color_selector_font), // 15: Breadcrumb - Background
ColorSelector::new(popover_bg).with_label("Background").with_font_family(&color_selector_font), // 16: Popover - Background
+ ColorSelector::new(notification_bg_color).with_label("Background").with_font_family(&color_selector_font), // 17: Notification - Background
],
paginator_tab_margin_x,
paginator_tab_margin_y,
@@ -565,6 +578,9 @@ pub fn read_interface_config() -> InterfaceState {
graph_gap_width_spinbox: Spinbox::new(graph_gap_width as i32, 0, 100, 1).with_label("Gap Width").with_unit("px"),
menubar_opacity: opacity,
menubar_opacity_spinbox: Spinbox::new((opacity * 100.0).round() as i32, 0, 100, 5).with_label("Opacity").with_unit("%"),
+ notification_bg_color,
+ notification_opacity,
+ notification_opacity_spinbox: Spinbox::new((notification_opacity * 100.0).round() as i32, 0, 100, 5).with_label("Opacity").with_unit("%"),
}
}
@@ -817,6 +833,17 @@ fn apply_popover_bg_color(rgb: [u8; 3]) {
clear_ui::color::set_popover_bg_color([r, g, b, 1.0]);
}
+fn apply_notifications_bg_color(rgb: [u8; 3]) {
+ let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+ write_notifications_config_value("bg_color", &hex);
+ send_ipc_command("reload");
+}
+
+fn apply_notifications_opacity(opacity: f32) {
+ write_notifications_config_value("opacity", &format!("{:.2}", opacity));
+ send_ipc_command("reload");
+}
+
fn apply_paginator_tab_margin_x(margin: u16) {
write_config_value("paginator_tab_margin_x", &margin.to_string());
send_ipc_command(&format!("layout paginator_tab_margin_x {}", margin));
@@ -1351,7 +1378,7 @@ pub async fn fetch_typeface_state() -> InterfaceState {
pub fn view(state: &mut InterfaceState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut clear_ui::context::UiContext) -> PageContent {
let mut final_pc = PageContent::new();
let sec_w = 260.0f32;
- let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(5);
+ let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(6);
// 1. Layout Section
builder.add_section(&mut final_pc, "Layout", false, |sec| {
@@ -1634,12 +1661,22 @@ pub fn view(state: &mut InterfaceState, cx: f32, cy: f32, cw: f32, ch: f32, sec_
sec.spacing(8.0);
});
+ // 5. Notification Section
+ builder.add_section(&mut final_pc, "Notification", false, |sec| {
+ sec.spacing(8.0);
+ state.color_selectors[17].color = state.notification_bg_color;
+ sec.widget_full(&mut state.color_selectors[17], 40.0, ctx);
+ sec.spacing(8.0);
+ state.notification_opacity_spinbox.value = (state.notification_opacity * 100.0).round() as i32;
+ sec.widget_full(&mut state.notification_opacity_spinbox, 44.0, ctx);
+ sec.spacing(8.0);
+ });
let widget_h = 26.0;
const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
- // 5. Fonts Section
- builder.add_section(&mut final_pc, "Fonts", sec_focused.get(4).copied().unwrap_or(false), |sec| {
+ // 6. Fonts Section
+ builder.add_section(&mut final_pc, "Fonts", sec_focused.get(5).copied().unwrap_or(false), |sec| {
sec.spacing(8.0);
// System Fonts Section
@@ -1803,6 +1840,15 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
state.popover_bg_color = rgb;
apply_popover_bg_color(rgb);
}
+ InterfaceMessage::SetNotificationBgColor(rgb) => {
+ state.notification_bg_color = rgb;
+ apply_notifications_bg_color(rgb);
+ }
+ InterfaceMessage::SetNotificationOpacity(opacity) => {
+ state.notification_opacity = opacity;
+ state.notification_opacity_spinbox.value = (opacity * 100.0).round() as i32;
+ apply_notifications_opacity(opacity);
+ }
InterfaceMessage::SetTabMarginX(margin) => {
state.paginator_tab_margin_x = margin;
apply_paginator_tab_margin_x(margin);
@@ -1956,7 +2002,7 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
send_ipc_command("reload");
status_interface_reload();
}
- InterfaceMessage::PickLowColor | InterfaceMessage::PickHighColor | InterfaceMessage::PickDisabledColor | InterfaceMessage::PickSeparatorColor | InterfaceMessage::PickVisualGuides | InterfaceMessage::PickSliderTrackColor | InterfaceMessage::PickPageLowColor | InterfaceMessage::PickColorBordersColor | InterfaceMessage::PickNormalColor | InterfaceMessage::PickPaginatorSidebarColor | InterfaceMessage::PickPrimaryHighlightColor | InterfaceMessage::PickMenubarTabLabelColor | InterfaceMessage::PickToggleEnabledColor | InterfaceMessage::PickToggleDisabledColor | InterfaceMessage::PickScrollingListBgColor | InterfaceMessage::PickBreadcrumbBgColor | InterfaceMessage::PickPopoverBgColor => {}
+ InterfaceMessage::PickLowColor | InterfaceMessage::PickHighColor | InterfaceMessage::PickDisabledColor | InterfaceMessage::PickSeparatorColor | InterfaceMessage::PickVisualGuides | InterfaceMessage::PickSliderTrackColor | InterfaceMessage::PickPageLowColor | InterfaceMessage::PickColorBordersColor | InterfaceMessage::PickNormalColor | InterfaceMessage::PickPaginatorSidebarColor | InterfaceMessage::PickPrimaryHighlightColor | InterfaceMessage::PickMenubarTabLabelColor | InterfaceMessage::PickToggleEnabledColor | InterfaceMessage::PickToggleDisabledColor | InterfaceMessage::PickScrollingListBgColor | InterfaceMessage::PickBreadcrumbBgColor | InterfaceMessage::PickPopoverBgColor | InterfaceMessage::PickNotificationBgColor => {}
InterfaceMessage::Refreshed(new) => {
let was_mx_hovered = state.tab_margin_spinbox_x.hovered();
let was_my_hovered = state.tab_margin_spinbox_y.hovered();
@@ -1979,6 +2025,7 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
let was_fsh_hovered = state.font_selector_height_spinbox.hovered();
let was_lm_hovered = state.label_margin_spinbox.hovered();
let was_mo_hovered = state.menubar_opacity_spinbox.hovered();
+ let was_no_hovered = state.notification_opacity_spinbox.hovered();
// Preserve typeface fields
let typeface_loaded = state.typeface_loaded;
let sans_serif = state.sans_serif.clone();
@@ -2030,6 +2077,7 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
state.font_selector_height_spinbox.set_hovered(was_fsh_hovered);
state.label_margin_spinbox.set_hovered(was_lm_hovered);
state.menubar_opacity_spinbox.set_hovered(was_mo_hovered);
+ state.notification_opacity_spinbox.set_hovered(was_no_hovered);
if typeface_loaded {
state.typeface_loaded = typeface_loaded;
@@ -2452,6 +2500,122 @@ fn write_transparency_config_value(key: &str, value: &str) {
let _ = fs::write(CONFIG_PATH, updated);
}
+fn parse_notifications_color(content: &str, key: &str, default: [u8; 3]) -> [u8; 3] {
+ 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(key) {
+ if let Some(rest) = trimmed.strip_prefix(key) {
+ let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+ let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
+ return parse_hex(hex);
+ }
+ }
+ }
+ default
+}
+
+fn parse_notifications_opacity(content: &str) -> f32 {
+ 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("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_notifications_config_value(key: &str, value: &str) {
+ write_notifications_config_value_path(CONFIG_PATH, key, value);
+}
+
+fn write_notifications_config_value_path(path: &str, key: &str, value: &str) {
+ let content = fs::read_to_string(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 == "[notifications]" {
+ 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() == "[notifications]");
+ if has_section {
+ let mut in_section = false;
+ let mut inserted = false;
+ for line in updated.lines() {
+ if line.trim() == "[notifications]" {
+ 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[notifications]\n");
+ updated.push_str(&new_line);
+ updated.push_str("\n");
+ }
+ }
+ let _ = fs::write(path, updated);
+}
+
+
+
#[cfg(test)]
mod tests {
use super::*;
@@ -3191,6 +3355,43 @@ mod tests {
// Clean up
let _ = fs::remove_file(path_str);
}
+
+ #[test]
+ fn test_read_write_notifications_config() {
+ let dir = std::env::temp_dir();
+ let path = dir.join("test_notifications_config.toml");
+ let path_str = path.to_str().unwrap();
+
+ // 1. Initial configuration
+ let initial_content = "[notifications]\nenable = true\n\n[layout]\ngap = 18\n";
+ fs::write(path_str, initial_content).unwrap();
+
+ // 2. Parse opacity when missing (should return default 0.9)
+ let content = fs::read_to_string(path_str).unwrap();
+ let opacity = parse_notifications_opacity(&content);
+ assert_eq!(opacity, 0.9);
+
+ // 3. Write opacity config
+ write_notifications_config_value_path(path_str, "opacity", "0.85");
+ let updated = fs::read_to_string(path_str).unwrap();
+ assert!(updated.contains("opacity = 0.85"));
+
+ // 4. Parse opacity when present (should return 0.85)
+ let opacity2 = parse_notifications_opacity(&updated);
+ assert_eq!(opacity2, 0.85);
+
+ // 5. Write bg_color config
+ write_notifications_config_value_path(path_str, "bg_color", "\"#112233\"");
+ let updated2 = fs::read_to_string(path_str).unwrap();
+ assert!(updated2.contains("bg_color = \"#112233\""));
+
+ // 6. Parse bg_color when present
+ let bg_color = parse_notifications_color(&updated2, "bg_color", [0, 0, 0]);
+ assert_eq!(bg_color, [17, 34, 51]);
+
+ // Clean up
+ let _ = fs::remove_file(path_str);
+ }
}
diff --git a/src/pages/services.rs b/src/pages/services.rs
index 6804308..f255e9b 100644
--- a/src/pages/services.rs
+++ b/src/pages/services.rs
@@ -12,7 +12,6 @@ pub struct NotificationsConfig {
pub enable: bool,
pub bell: bool,
pub duration: i32,
- pub opacity: f32,
}
// ── Status Interface Data ──
@@ -64,8 +63,6 @@ pub struct ServicesState {
pub notifications_bell_toggle: Toggle,
pub notifications_duration: i32,
pub notifications_duration_spinbox: Spinbox,
- pub notifications_opacity: f32,
- pub notifications_opacity_slider: Slider,
// Status Interface fields
pub status_loaded: bool,
@@ -98,10 +95,6 @@ impl Default for ServicesState {
notifications_duration_spinbox: Spinbox::new(5, 1, 60, 1)
.with_label("Notification Duration")
.with_unit("s"),
- notifications_opacity: 0.9,
- notifications_opacity_slider: Slider::new()
- .with_label("Transparency")
- .with_value(0.9),
// Status Interface default initialization
status_loaded: false,
@@ -128,7 +121,6 @@ pub enum ServicesMessage {
ToggleNotificationsEnable,
ToggleNotificationsBell,
SetNotificationsDuration(i32),
- SetNotificationsOpacity(f32),
SendTestNotification,
NotificationsRefreshed(NotificationsConfig),
@@ -435,11 +427,6 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
sec2.widget(&mut state.notifications_duration_spinbox, 14.0, sec_w - 28.0, 44.0, ctx);
sec2.spacing(16.0);
- // Opacity Slider (Transparency, moved here)
- state.notifications_opacity_slider.set_value(state.notifications_opacity);
- sec2.widget(&mut state.notifications_opacity_slider, 14.0, sec_w - 28.0, 38.0, ctx);
- sec2.spacing(16.0);
-
let btn_h = 32.0;
let btn_y = sec2.ay();
let white_color = [1.0, 1.0, 1.0, 1.0];
@@ -562,11 +549,6 @@ pub fn update(state: &mut ServicesState, msg: ServicesMessage) {
state.notifications_duration = d;
write_config_value("duration", &state.notifications_duration.to_string());
}
- ServicesMessage::SetNotificationsOpacity(o) => {
- state.notifications_opacity = o;
- write_transparency_config_value("opacity", &format!("{:.2}", o));
- send_ipc_command("reload");
- }
ServicesMessage::SendTestNotification => {
send_ipc_command("notify \"cce-client\" \"System notifications are working correctly!\"");
}
@@ -575,7 +557,6 @@ pub fn update(state: &mut ServicesState, msg: ServicesMessage) {
state.notifications_enable = new.enable;
state.notifications_bell = new.bell;
state.notifications_duration = new.duration;
- state.notifications_opacity = new.opacity;
}
ServicesMessage::StatusRefreshed(new) => {
let was_status_hovered = state.status_label.hovered();
@@ -631,12 +612,10 @@ pub fn read_notifications_config() -> NotificationsConfig {
let enable = parse_notifications_enable(&content);
let bell = parse_notifications_bell(&content);
let duration = parse_notifications_duration(&content);
- let opacity = parse_transparency_opacity(&content);
NotificationsConfig {
enable,
bell,
duration,
- opacity,
}
}
@@ -702,28 +681,6 @@ fn parse_notifications_duration(content: &str) -> i32 {
5 // default to 5 seconds
}
-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 send_ipc_command(cmd: &str) {
if let Ok(mut stream) = std::os::unix::net::UnixStream::connect(get_socket_path()) {
let _ = stream.write_all(format!("{}\n", cmd).as_bytes());
@@ -801,71 +758,6 @@ fn write_enable_notifications(enabled: bool) {
send_ipc_command("reload");
}
-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);
-}
thread_local! {
static TEST_CONFIG_PATH: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);