system settings
git clone https://git.lucas.co/cce-system-interface.git
Move Window Manager page into Windows section in Interface page, remove Layout page
src/app.rs | 4 -
src/main.rs | 801 +++++++++++++++++++++++--------------------
src/pages/interface.rs | 896 +++++++++++++++++++++++++++++++++++++++++++++++-
src/pages/layout.rs | 910 -------------------------------------------------
src/pages/mod.rs | 6 +-
5 files changed, 1326 insertions(+), 1291 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 3203e93..0478dd9 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -3,7 +3,6 @@ use cce_ui::layout::RenderTarget;
use crate::pages::audio;
use crate::pages::display;
use crate::pages::input;
-use crate::pages::layout;
use crate::pages::network;
use crate::pages::hardware;
use crate::pages::system_info;
@@ -19,7 +18,6 @@ pub struct AppState {
pub audio: audio::AudioState,
pub display: display::DisplayState,
pub network: network::NetworkState,
- pub layout: layout::LayoutState,
pub input: input::InputState,
pub hardware: hardware::HardwareState,
pub system_info: system_info::SystemState,
@@ -37,7 +35,6 @@ impl Default for AppState {
audio: audio::AudioState::default(),
display: display::DisplayState::default(),
network: network::NetworkState::default(),
- layout: layout::LayoutState::default(),
input: input::InputState::default(),
hardware: hardware::HardwareState::default(),
system_info: system_info::SystemState::default(),
@@ -56,7 +53,6 @@ pub enum AppAction {
Audio(audio::AudioMessage),
Display(display::DisplayMessage),
Radios(network::NetworkMessage),
- Layout(layout::LayoutMessage),
Input(input::InputMessage),
Hardware(hardware::HardwareMessage),
SystemInfo(system_info::SystemMessage),
diff --git a/src/main.rs b/src/main.rs
index 744a6a1..7b797c6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -132,7 +132,7 @@ struct SystemInterface {
rx_audio: std::sync::mpsc::Receiver<pages::audio::AudioState>,
rx_display: std::sync::mpsc::Receiver<pages::display::DisplayState>,
rx_network: std::sync::mpsc::Receiver<pages::network::NetworkState>,
- rx_layout: std::sync::mpsc::Receiver<pages::layout::LayoutState>,
+ rx_layout: std::sync::mpsc::Receiver<pages::interface::WindowsState>,
rx_wm_events: std::sync::mpsc::Receiver<()>,
rx_input: std::sync::mpsc::Receiver<pages::input::InputState>,
rx_fingers: std::sync::mpsc::Receiver<Vec<Finger>>,
@@ -180,7 +180,6 @@ impl cce_ui::engine::Application for SystemInterface {
fn new(_qh: &wayland_client::QueueHandle<cce_ui::engine::EngineState<Self>>, sender: calloop::channel::Sender<Self::Message>) -> Self {
cce_ui::scale::set_scale_factor(1.0);
let app = AppState {
- layout: pages::layout::read_layout_config(),
input: pages::input::read_input_config(),
interface: pages::interface::read_interface_config(),
..Default::default()
@@ -226,19 +225,19 @@ impl cce_ui::engine::Application for SystemInterface {
let rx_display = spawn_bg_active(current_page_shared.clone(), 2, 10, || pages::display::fetch_display_state());
let rx_network = spawn_bg_active(current_page_shared.clone(), 7, 5, || pages::network::fetch_network_state());
let rx_layout = {
- let (tx, rx) = std::sync::mpsc::channel::<pages::layout::LayoutState>();
+ let (tx, rx) = std::sync::mpsc::channel::<pages::interface::WindowsState>();
let current_page_shared = current_page_shared.clone();
tokio::spawn(async move {
let mut last_fetch: Option<std::time::Instant> = None;
loop {
let current_page = current_page_shared.load(std::sync::atomic::Ordering::SeqCst);
- if current_page == 11 { // Layout is index 11
+ if current_page == 5 { // Interface is index 5
let should_fetch = match last_fetch {
None => true,
Some(t) => t.elapsed() >= std::time::Duration::from_secs(30),
};
if should_fetch {
- let val = tokio::task::spawn_blocking(|| pages::layout::read_layout_config()).await;
+ let val = tokio::task::spawn_blocking(|| pages::interface::read_windows_config()).await;
if let Ok(val) = val {
if tx.send(val).is_err() { break; }
}
@@ -267,7 +266,7 @@ impl cce_ui::engine::Application for SystemInterface {
loop {
let current_page = current_page_shared.load(std::sync::atomic::Ordering::SeqCst);
- if current_page == 11 { // Layout is index 11
+ if current_page == 5 { // Interface is index 5
let mut changed = false;
for p in &[&windows_path, &tags_path, &title_path] {
if let Some(mtime) = check_mtime(p) {
@@ -682,16 +681,16 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
self.app.network.wifi_list_box.scroll_box.clear_children(&mut self.ui_context); self.app.network.wifi_list_box.scroll_box.set_parent(None, &mut self.ui_context);
- for sb in &mut self.app.layout.spinboxes {
+ for sb in &mut self.app.interface.windows.spinboxes {
sb.clear_children(&mut self.ui_context);
sb.set_parent(None, &mut self.ui_context);
}
- self.app.layout.cascade_offset_spinbox.clear_children(&mut self.ui_context); self.app.layout.cascade_offset_spinbox.set_parent(None, &mut self.ui_context);
- self.app.layout.edge_gap_spinbox.clear_children(&mut self.ui_context); self.app.layout.edge_gap_spinbox.set_parent(None, &mut self.ui_context);
- self.app.layout.top_gap_spinbox.clear_children(&mut self.ui_context); self.app.layout.top_gap_spinbox.set_parent(None, &mut self.ui_context);
- self.app.layout.grid_gap_spinbox.clear_children(&mut self.ui_context); self.app.layout.grid_gap_spinbox.set_parent(None, &mut self.ui_context);
- self.app.layout.transition_duration_spinbox.clear_children(&mut self.ui_context); self.app.layout.transition_duration_spinbox.set_parent(None, &mut self.ui_context);
- self.app.layout.status_height_spinbox.clear_children(&mut self.ui_context); self.app.layout.status_height_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.cascade_offset_spinbox.clear_children(&mut self.ui_context); self.app.interface.windows.cascade_offset_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.edge_gap_spinbox.clear_children(&mut self.ui_context); self.app.interface.windows.edge_gap_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.top_gap_spinbox.clear_children(&mut self.ui_context); self.app.interface.windows.top_gap_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.grid_gap_spinbox.clear_children(&mut self.ui_context); self.app.interface.windows.grid_gap_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.transition_duration_spinbox.clear_children(&mut self.ui_context); self.app.interface.windows.transition_duration_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.status_height_spinbox.clear_children(&mut self.ui_context); self.app.interface.windows.status_height_spinbox.set_parent(None, &mut self.ui_context);
for cs in &mut self.app.interface.color_selectors {
cs.clear_children(&mut self.ui_context);
@@ -862,20 +861,20 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
self.app.services.status_underline_toggle.clear_children(&mut self.ui_context); self.app.services.status_underline_toggle.set_parent(None, &mut self.ui_context);
self.app.services.status_padding_spinbox.clear_children(&mut self.ui_context); self.app.services.status_padding_spinbox.set_parent(None, &mut self.ui_context);
- for menu in &mut self.app.layout.tag_layout_menus {
+ for menu in &mut self.app.interface.windows.tag_layout_menus {
menu.clear_children(&mut self.ui_context);
menu.set_parent(None, &mut self.ui_context);
}
- self.app.layout.side_panel_behavior_menu.clear_children(&mut self.ui_context);
- self.app.layout.side_panel_behavior_menu.set_parent(None, &mut self.ui_context);
- self.app.layout.side_panel_position_menu.clear_children(&mut self.ui_context);
- self.app.layout.side_panel_position_menu.set_parent(None, &mut self.ui_context);
- self.app.layout.side_panel_width_spinbox.clear_children(&mut self.ui_context);
- self.app.layout.side_panel_width_spinbox.set_parent(None, &mut self.ui_context);
- self.app.layout.side_panel_border_gap_spinbox.clear_children(&mut self.ui_context);
- self.app.layout.side_panel_border_gap_spinbox.set_parent(None, &mut self.ui_context);
- self.app.layout.side_panel_border_opacity_spinbox.clear_children(&mut self.ui_context);
- self.app.layout.side_panel_border_opacity_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.side_panel_behavior_menu.clear_children(&mut self.ui_context);
+ self.app.interface.windows.side_panel_behavior_menu.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.side_panel_position_menu.clear_children(&mut self.ui_context);
+ self.app.interface.windows.side_panel_position_menu.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.side_panel_width_spinbox.clear_children(&mut self.ui_context);
+ self.app.interface.windows.side_panel_width_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.side_panel_border_gap_spinbox.clear_children(&mut self.ui_context);
+ self.app.interface.windows.side_panel_border_gap_spinbox.set_parent(None, &mut self.ui_context);
+ self.app.interface.windows.side_panel_border_opacity_spinbox.clear_children(&mut self.ui_context);
+ self.app.interface.windows.side_panel_border_opacity_spinbox.set_parent(None, &mut self.ui_context);
use cce_ui::widget::focus::link_parent_child;
match self.app.current_page {
@@ -927,47 +926,10 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
}
link_parent_child(&mut self.page_sec_containers[0], &mut self.app.network.wifi_list_box.scroll_box, &mut self.ui_context);
}
- Page::Layout => {
- self.page_sec_containers.resize_with(7, cce_ui::widget::Container::new);
-
- for i in 0..7 {
- link_parent_child(page_root, &mut self.page_sec_containers[i], &mut self.ui_context);
- }
-
- // Fullscreen (Section 0)
- link_parent_child(&mut self.page_sec_containers[0], &mut self.app.layout.spinboxes[0], &mut self.ui_context);
-
- // Cascade (Section 1)
- link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.spinboxes[1], &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.cascade_offset_spinbox, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.edge_gap_spinbox, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.top_gap_spinbox, &mut self.ui_context);
-
- // Grid (Section 2)
- link_parent_child(&mut self.page_sec_containers[2], &mut self.app.layout.grid_gap_spinbox, &mut self.ui_context);
-
- // Floating (Section 3)
- link_parent_child(&mut self.page_sec_containers[3], &mut self.app.layout.spinboxes[3], &mut self.ui_context);
-
- // Movement (Section 4)
- link_parent_child(&mut self.page_sec_containers[4], &mut self.app.layout.transition_duration_spinbox, &mut self.ui_context);
-
- // Default Layouts (Section 5)
- for menu in &mut self.app.layout.tag_layout_menus {
- link_parent_child(&mut self.page_sec_containers[5], menu, &mut self.ui_context);
- }
-
- // Side Panel (Section 6)
- link_parent_child(&mut self.page_sec_containers[6], &mut self.app.layout.side_panel_behavior_menu, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[6], &mut self.app.layout.side_panel_position_menu, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[6], &mut self.app.layout.side_panel_width_spinbox, &mut self.ui_context);
- link_parent_child(&mut self.page_sec_containers[6], &mut self.app.layout.side_panel_border_gap_spinbox, &mut self.ui_context);
- 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(9, cce_ui::widget::Container::new);
+ self.page_sec_containers.resize_with(10, cce_ui::widget::Container::new);
- for i in 0..9 {
+ for i in 0..10 {
link_parent_child(page_root, &mut self.page_sec_containers[i], &mut self.ui_context);
}
@@ -1099,6 +1061,41 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
link_parent_child(&mut self.page_sec_containers[8], &mut self.app.interface.graph_cell_opacity_spinbox, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[8], &mut self.app.interface.graph_gap_opacity_spinbox, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[8], &mut self.app.interface.graph_gap_width_spinbox, &mut self.ui_context);
+
+ // Section 9: Windows (parent of Fullscreen, Cascade, Grid, Floating, Movement, Default Layouts, Side Panel, Effects)
+ // Fullscreen
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.spinboxes[0], &mut self.ui_context);
+
+ // Cascade
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.spinboxes[1], &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.cascade_offset_spinbox, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.edge_gap_spinbox, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.top_gap_spinbox, &mut self.ui_context);
+
+ // Grid
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.grid_gap_spinbox, &mut self.ui_context);
+
+ // Floating
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.spinboxes[3], &mut self.ui_context);
+
+ // Movement
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.transition_duration_spinbox, &mut self.ui_context);
+
+ // Default Layouts
+ for menu in &mut self.app.interface.windows.tag_layout_menus {
+ link_parent_child(&mut self.page_sec_containers[9], menu, &mut self.ui_context);
+ }
+
+ // Side Panel
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.side_panel_behavior_menu, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.side_panel_position_menu, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.side_panel_width_spinbox, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.side_panel_border_gap_spinbox, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.side_panel_border_opacity_spinbox, &mut self.ui_context);
+
+ // Effects
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.transparency_toggle, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[9], &mut self.app.interface.windows.blur_toggle, &mut self.ui_context);
}
@@ -1479,7 +1476,6 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
Page::Audio => audio::view(&mut self.app.audio, cx, cy, cw, ch, &sec_focused, &mut layout, &mut self.ui_context),
Page::Display => display::view(&mut self.app.display, cx, cy, cw, ch, &mut layout, &mut self.ui_context),
Page::Radios => network::view(&mut self.app.network, cx, cy, cw, ch, root_focused, &mut layout, &mut self.ui_context),
- Page::Layout => layout::view(&mut self.app.layout, cx, cy, cw, ch, &sec_focused, &mut layout, &mut self.ui_context),
Page::Hardware => hardware::view(&mut self.app.hardware, cx, cy, cw, ch, root_focused, &mut layout, &mut self.ui_context),
Page::Input => input::view(&mut self.app.input, cx, cy, cw, ch, &sec_focused, &mut layout, &mut self.ui_context),
Page::System => system_info::view(&self.app.system_info, cx, cy, cw, ch, &mut layout, &mut self.ui_context),
@@ -1604,13 +1600,13 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
}
}
while let Ok(s) = self.rx_layout.try_recv() {
- layout::update(&mut self.app.layout, layout::LayoutMessage::Refreshed(s));
- if self.app.current_page == Page::Layout {
+ interface::update_windows(&mut self.app.interface.windows, interface::WindowsMessage::Refreshed(s));
+ if self.app.current_page == Page::Interface {
self.needs_rebuild = true;
}
}
while let Ok(_) = self.rx_wm_events.try_recv() {
- if self.app.current_page == Page::Layout {
+ if self.app.current_page == Page::Interface {
self.needs_rebuild = true;
}
}
@@ -1714,7 +1710,6 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
AppAction::Audio(m) => audio::update(&mut self.app.audio, m.clone()),
AppAction::Display(m) => display::update(&mut self.app.display, m.clone()),
AppAction::Radios(m) => network::update(&mut self.app.network, m.clone()),
- AppAction::Layout(m) => layout::update(&mut self.app.layout, m.clone()),
AppAction::Input(m) => input::update(&mut self.app.input, m.clone()),
AppAction::SystemInfo(m) => system_info::update(&mut self.app.system_info, m.clone()),
AppAction::Hardware(m) => hardware::update(&mut self.app.hardware, m.clone()),
@@ -1845,57 +1840,7 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
changed = true;
}
}
- if self.app.current_page == Page::Layout {
- for sb in &mut self.app.layout.spinboxes {
- if sb.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- }
- if self.app.layout.cascade_offset_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.edge_gap_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.grid_gap_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.transition_duration_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.status_height_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- for menu in &mut self.app.layout.tag_layout_menus {
- if menu.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- }
- if self.app.layout.side_panel_behavior_menu.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.side_panel_position_menu.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.side_panel_width_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.side_panel_border_gap_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.side_panel_border_opacity_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.transparency_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- if self.app.layout.blur_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
- changed = true;
- }
- }
+
if self.app.current_page == Page::Interface {
for cp in &mut self.app.interface.color_selectors {
if cp.cursor_moved(lx, ly, &mut self.ui_context) {
@@ -2087,6 +2032,55 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if self.app.interface.custom_multicontrol.cursor_moved(lx, ly, &mut self.ui_context) {
changed = true;
}
+ for sb in &mut self.app.interface.windows.spinboxes {
+ if sb.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ }
+ if self.app.interface.windows.cascade_offset_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.edge_gap_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.top_gap_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.grid_gap_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.transition_duration_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.status_height_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ for menu in &mut self.app.interface.windows.tag_layout_menus {
+ if menu.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ }
+ if self.app.interface.windows.side_panel_behavior_menu.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.side_panel_position_menu.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.side_panel_width_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.side_panel_border_gap_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.side_panel_border_opacity_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.transparency_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
+ if self.app.interface.windows.blur_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
}
if self.app.current_page == Page::Input {
if self.app.input.rate_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
@@ -2492,27 +2486,7 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if accs.smtp_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
}
}
- Page::Layout => {
- for sb in &mut self.app.layout.spinboxes {
- if sb.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- }
- if self.app.layout.cascade_offset_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.edge_gap_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.top_gap_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.grid_gap_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.transition_duration_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.status_height_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- for menu in &mut self.app.layout.tag_layout_menus {
- if menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- }
- if self.app.layout.side_panel_behavior_menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.side_panel_position_menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.side_panel_width_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.side_panel_border_gap_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.side_panel_border_opacity_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.transparency_toggle.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- if self.app.layout.blur_toggle.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
- }
+
Page::Interface => {
if self.app.interface.custom_multicontrol.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
for cp in &mut self.app.interface.color_selectors {
@@ -2577,6 +2551,25 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if tf.fuzzel_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if tf.terminal_menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if tf.terminal_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ for sb in &mut tf.windows.spinboxes {
+ if sb.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ }
+ if tf.windows.cascade_offset_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.edge_gap_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.top_gap_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.grid_gap_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.transition_duration_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.status_height_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ for menu in &mut tf.windows.tag_layout_menus {
+ if menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ }
+ if tf.windows.side_panel_behavior_menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.side_panel_position_menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.side_panel_width_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.side_panel_border_gap_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.side_panel_border_opacity_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.transparency_toggle.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if tf.windows.blur_toggle.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
}
Page::Input => {
if self.app.input.rate_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
@@ -2657,127 +2650,169 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
}
}
- if state == cce_ui::widget::ElementState::Pressed && self.app.current_page == Page::Layout {
- for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
+ if state == cce_ui::widget::ElementState::Pressed && self.app.current_page == Page::Interface {
+ let tf = &mut self.app.interface.windows;
+ for (i, sb) in tf.spinboxes.iter_mut().enumerate() {
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::Layout(
- pages::layout::LayoutMessage::SetWidth(
- pages::layout::WidthParam::ALL[i],
- sb.value as u16,
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetWidth(
+ pages::interface::WidthParam::ALL[i],
+ sb.value as u16,
+ )
)
));
}
}
- let sb = &mut self.app.layout.cascade_offset_spinbox;
+ let sb = &mut tf.cascade_offset_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::Layout(
- pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetCascadeOffset(sb.value as u16)
+ )
));
}
- let sb = &mut self.app.layout.edge_gap_spinbox;
+ let sb = &mut tf.edge_gap_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::Layout(
- pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetEdgeGap(sb.value as u16)
+ )
));
}
- let sb = &mut self.app.layout.top_gap_spinbox;
+ let sb = &mut tf.top_gap_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::Layout(
- pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetTopGap(sb.value as u16)
+ )
));
}
- let sb = &mut self.app.layout.grid_gap_spinbox;
+ let sb = &mut tf.grid_gap_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::Layout(
- pages::layout::LayoutMessage::SetGridGap(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetGridGap(sb.value as u16)
+ )
));
}
- let sb = &mut self.app.layout.transition_duration_spinbox;
+ let sb = &mut tf.transition_duration_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::Layout(
- pages::layout::LayoutMessage::SetTransitionDuration(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetTransitionDuration(sb.value as u16)
+ )
));
}
- let sb = &mut self.app.layout.status_height_spinbox;
+ let sb = &mut tf.status_height_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::Layout(
- pages::layout::LayoutMessage::SetStatusHeight(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetStatusHeight(sb.value as u16)
+ )
));
}
}
- if self.app.current_page == Page::Layout {
- for (idx, menu) in self.app.layout.tag_layout_menus.iter_mut().enumerate() {
+ if self.app.current_page == Page::Interface {
+ let tf = &mut self.app.interface.windows;
+ for (idx, menu) in tf.tag_layout_menus.iter_mut().enumerate() {
if state == cce_ui::widget::ElementState::Pressed && !menu.hit_test(lx, ly, &self.ui_context) { menu.unfocus(); }
if menu.mouse_input(button, state, lx, ly, &mut self.ui_context) {
self.needs_rebuild = true;
}
if state == cce_ui::widget::ElementState::Pressed && menu.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetTagLayout(idx + 1, menu.selected)));
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetTagLayout(idx + 1, menu.selected)
+ )
+ ));
}
}
- let menu = &mut self.app.layout.side_panel_behavior_menu;
+ let menu = &mut tf.side_panel_behavior_menu;
if state == cce_ui::widget::ElementState::Pressed && !menu.hit_test(lx, ly, &self.ui_context) { menu.unfocus(); }
if menu.mouse_input(button, state, lx, ly, &mut self.ui_context) {
self.needs_rebuild = true;
}
if state == cce_ui::widget::ElementState::Pressed && menu.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelBehavior(menu.selected)));
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelBehavior(menu.selected)
+ )
+ ));
}
- let menu = &mut self.app.layout.side_panel_position_menu;
+ let menu = &mut tf.side_panel_position_menu;
if state == cce_ui::widget::ElementState::Pressed && !menu.hit_test(lx, ly, &self.ui_context) { menu.unfocus(); }
if menu.mouse_input(button, state, lx, ly, &mut self.ui_context) {
self.needs_rebuild = true;
}
if state == cce_ui::widget::ElementState::Pressed && menu.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelPosition(menu.selected)));
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelPosition(menu.selected)
+ )
+ ));
}
- let sb = &mut self.app.layout.side_panel_width_spinbox;
+ let sb = &mut tf.side_panel_width_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::Layout(
- pages::layout::LayoutMessage::SetSidePanelWidth(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelWidth(sb.value as u16)
+ )
));
}
- let sb = &mut self.app.layout.side_panel_border_gap_spinbox;
+ let sb = &mut tf.side_panel_border_gap_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::Layout(
- pages::layout::LayoutMessage::SetSidePanelBorderGap(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelBorderGap(sb.value as u16)
+ )
));
}
- let sb = &mut self.app.layout.side_panel_border_opacity_spinbox;
+ let sb = &mut tf.side_panel_border_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::Layout(
- pages::layout::LayoutMessage::SetSidePanelBorderOpacity(sb.value as u16)
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelBorderOpacity(sb.value as u16)
+ )
));
}
- let toggle = &mut self.app.layout.transparency_toggle;
+ let toggle = &mut tf.transparency_toggle;
toggle.mouse_input(button, state, lx, ly, &mut self.ui_context);
if toggle.take_click() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::ToggleTransparency));
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::ToggleTransparency
+ )
+ ));
}
- let toggle2 = &mut self.app.layout.blur_toggle;
+ let toggle2 = &mut tf.blur_toggle;
toggle2.mouse_input(button, state, lx, ly, &mut self.ui_context);
if toggle2.take_click() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::ToggleBlur));
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::ToggleBlur
+ )
+ ));
}
}
if state == cce_ui::widget::ElementState::Pressed && self.app.current_page == Page::Interface {
@@ -3752,51 +3787,6 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
fn propagate_widget_changes(&mut self, actions: &mut Vec<AppAction>) {
match self.app.current_page {
- Page::Layout => {
- for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
- if sb.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetWidth(pages::layout::WidthParam::ALL[i], sb.value as u16)));
- }
- }
- if self.app.layout.cascade_offset_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetCascadeOffset(self.app.layout.cascade_offset_spinbox.value as u16)));
- }
- if self.app.layout.edge_gap_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetEdgeGap(self.app.layout.edge_gap_spinbox.value as u16)));
- }
- if self.app.layout.top_gap_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetTopGap(self.app.layout.top_gap_spinbox.value as u16)));
- }
- if self.app.layout.grid_gap_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetGridGap(self.app.layout.grid_gap_spinbox.value as u16)));
- }
- if self.app.layout.transition_duration_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetTransitionDuration(self.app.layout.transition_duration_spinbox.value as u16)));
- }
- if self.app.layout.status_height_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetStatusHeight(self.app.layout.status_height_spinbox.value as u16)));
- }
- for (idx, menu) in self.app.layout.tag_layout_menus.iter_mut().enumerate() {
- if menu.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetTagLayout(idx + 1, menu.selected)));
- }
- }
- if self.app.layout.side_panel_behavior_menu.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelBehavior(self.app.layout.side_panel_behavior_menu.selected)));
- }
- if self.app.layout.side_panel_position_menu.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelPosition(self.app.layout.side_panel_position_menu.selected)));
- }
- if self.app.layout.side_panel_width_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelWidth(self.app.layout.side_panel_width_spinbox.value as u16)));
- }
- if self.app.layout.side_panel_border_gap_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelBorderGap(self.app.layout.side_panel_border_gap_spinbox.value as u16)));
- }
- if self.app.layout.side_panel_border_opacity_spinbox.take_change() {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelBorderOpacity(self.app.layout.side_panel_border_opacity_spinbox.value as u16)));
- }
- }
Page::Interface => {
for (i, cp) in self.app.interface.color_selectors.iter_mut().enumerate() {
if cp.take_change() {
@@ -3990,6 +3980,51 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if self.app.interface.status_size_box.take_change() {
actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetStatusSize(self.app.interface.status_size_box.value)));
}
+
+ let tf = &mut self.app.interface.windows;
+ for (i, sb) in tf.spinboxes.iter_mut().enumerate() {
+ if sb.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetWidth(pages::interface::WidthParam::ALL[i], sb.value as u16))));
+ }
+ }
+ if tf.cascade_offset_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetCascadeOffset(tf.cascade_offset_spinbox.value as u16))));
+ }
+ if tf.edge_gap_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetEdgeGap(tf.edge_gap_spinbox.value as u16))));
+ }
+ if tf.top_gap_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetTopGap(tf.top_gap_spinbox.value as u16))));
+ }
+ if tf.grid_gap_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetGridGap(tf.grid_gap_spinbox.value as u16))));
+ }
+ if tf.transition_duration_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetTransitionDuration(tf.transition_duration_spinbox.value as u16))));
+ }
+ if tf.status_height_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetStatusHeight(tf.status_height_spinbox.value as u16))));
+ }
+ for (idx, menu) in tf.tag_layout_menus.iter_mut().enumerate() {
+ if menu.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetTagLayout(idx + 1, menu.selected))));
+ }
+ }
+ if tf.side_panel_behavior_menu.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetSidePanelBehavior(tf.side_panel_behavior_menu.selected))));
+ }
+ if tf.side_panel_position_menu.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetSidePanelPosition(tf.side_panel_position_menu.selected))));
+ }
+ if tf.side_panel_width_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetSidePanelWidth(tf.side_panel_width_spinbox.value as u16))));
+ }
+ if tf.side_panel_border_gap_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetSidePanelBorderGap(tf.side_panel_border_gap_spinbox.value as u16))));
+ }
+ if tf.side_panel_border_opacity_spinbox.take_change() {
+ actions.push(AppAction::Interface(pages::interface::InterfaceMessage::Windows(pages::interface::WindowsMessage::SetSidePanelBorderOpacity(tf.side_panel_border_opacity_spinbox.value as u16))));
+ }
}
Page::Input => {
if self.app.input.rate_spinbox.take_change() {
@@ -4446,145 +4481,6 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
}
}
- if self.app.current_page == Page::Layout {
- let mut changed = false;
- let mut actions = Vec::new();
- for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetWidth(
- pages::layout::WidthParam::ALL[i],
- sb.value as u16,
- )
- ));
- }
- changed = true;
- }
- }
- let sb = &mut self.app.layout.cascade_offset_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetCascadeOffset(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.edge_gap_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetEdgeGap(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.top_gap_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.grid_gap_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetGridGap(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.transition_duration_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetTransitionDuration(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.status_height_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetStatusHeight(sb.value as u16)
- ));
- }
- changed = true;
- }
- let (menu_changed, old_selected, new_selected) = {
- let menu = &mut self.app.layout.side_panel_behavior_menu;
- let old = menu.selected;
- let changed = menu.keyboard_input(event, &mut self.ui_context);
- (changed, old, menu.selected)
- };
- if menu_changed {
- if new_selected != old_selected {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelBehavior(new_selected)));
- }
- changed = true;
- }
- let (menu_changed, old_selected, new_selected) = {
- let menu = &mut self.app.layout.side_panel_position_menu;
- let old = menu.selected;
- let changed = menu.keyboard_input(event, &mut self.ui_context);
- (changed, old, menu.selected)
- };
- if menu_changed {
- if new_selected != old_selected {
- actions.push(AppAction::Layout(pages::layout::LayoutMessage::SetSidePanelPosition(new_selected)));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.side_panel_width_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetSidePanelWidth(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.side_panel_border_gap_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetSidePanelBorderGap(sb.value as u16)
- ));
- }
- changed = true;
- }
- let sb = &mut self.app.layout.side_panel_border_opacity_spinbox;
- let old = sb.value;
- if sb.keyboard_input(event, &mut self.ui_context) {
- if sb.value != old {
- actions.push(AppAction::Layout(
- pages::layout::LayoutMessage::SetSidePanelBorderOpacity(sb.value as u16)
- ));
- }
- changed = true;
- }
- for a in &actions {
- self.handle_action(a);
- }
- if changed {
- self.needs_rebuild = true;
- return true;
- }
- }
if self.app.current_page == Page::Interface {
let mc = &mut self.app.interface.custom_multicontrol;
if mc.keyboard_input(event, &mut self.ui_context) {
@@ -5087,6 +4983,175 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
self.needs_rebuild = true;
return true;
}
+
+ let mut changed = false;
+ let mut actions = Vec::new();
+ {
+ let tf = &mut self.app.interface.windows;
+ for (i, sb) in tf.spinboxes.iter_mut().enumerate() {
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetWidth(
+ pages::interface::WidthParam::ALL[i],
+ sb.value as u16,
+ )
+ )
+ ));
+ }
+ changed = true;
+ }
+ }
+ let sb = &mut tf.cascade_offset_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetCascadeOffset(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let sb = &mut tf.edge_gap_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetEdgeGap(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let sb = &mut tf.top_gap_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetTopGap(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let sb = &mut tf.grid_gap_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetGridGap(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let sb = &mut tf.transition_duration_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetTransitionDuration(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let sb = &mut tf.status_height_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetStatusHeight(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let (menu_changed, old_selected, new_selected) = {
+ let menu = &mut tf.side_panel_behavior_menu;
+ let old = menu.selected;
+ let changed = menu.keyboard_input(event, &mut self.ui_context);
+ (changed, old, menu.selected)
+ };
+ if menu_changed {
+ if new_selected != old_selected {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelBehavior(new_selected)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let (menu_changed, old_selected, new_selected) = {
+ let menu = &mut tf.side_panel_position_menu;
+ let old = menu.selected;
+ let changed = menu.keyboard_input(event, &mut self.ui_context);
+ (changed, old, menu.selected)
+ };
+ if menu_changed {
+ if new_selected != old_selected {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelPosition(new_selected)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let sb = &mut tf.side_panel_width_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelWidth(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let sb = &mut tf.side_panel_border_gap_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelBorderGap(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ let sb = &mut tf.side_panel_border_opacity_spinbox;
+ let old = sb.value;
+ if sb.keyboard_input(event, &mut self.ui_context) {
+ if sb.value != old {
+ actions.push(AppAction::Interface(
+ pages::interface::InterfaceMessage::Windows(
+ pages::interface::WindowsMessage::SetSidePanelBorderOpacity(sb.value as u16)
+ )
+ ));
+ }
+ changed = true;
+ }
+ }
+ for a in &actions {
+ self.handle_action(a);
+ }
+ if changed {
+ self.needs_rebuild = true;
+ return true;
+ }
}
if self.app.current_page == Page::Services {
let sb = &mut self.app.services.notifications_duration_spinbox;
diff --git a/src/pages/interface.rs b/src/pages/interface.rs
index 60c0f5a..51409df 100644
--- a/src/pages/interface.rs
+++ b/src/pages/interface.rs
@@ -1,9 +1,10 @@
use std::fs;
use std::io::Write;
use crate::app::PageContent;
-use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy};
+use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
use cce_ui::widget::{
- ColorSelector, Spinbox, Element, Dropdown, TextBox, FontSelector, Toggle, MultiControl
+ ColorSelector, Spinbox, Element, Dropdown, TextBox, FontSelector, Toggle, MultiControl,
+ LayoutPreview, PreviewLayoutMode
};
const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.json";
@@ -16,8 +17,154 @@ fn get_socket_path() -> String {
}
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum WidthParam {
+ Fullscreen, Cascade, Grid, Floating,
+}
+
+impl WidthParam {
+ pub const ALL: [WidthParam; 4] = [
+ WidthParam::Fullscreen, WidthParam::Cascade, WidthParam::Grid,
+ 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::Floating => "floating_border_width",
+ }
+ }
+ pub fn label(self) -> &'static str {
+ match self {
+ WidthParam::Fullscreen => "Fullscreen",
+ WidthParam::Cascade => "Cascade",
+ WidthParam::Grid => "Grid",
+ WidthParam::Floating => "Floating",
+ }
+ }
+}
+
+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(fl as i32, 0, 100, 1),
+ ]
+}
+
+#[derive(Debug, Clone)]
+pub struct WindowsState {
+ pub fullscreen_border_width: u16,
+ pub cascade_border_width: u16,
+ pub grid_border_width: u16,
+ pub floating_border_width: u16,
+ pub cascade_offset: u16,
+ pub edge_gap: u16,
+ pub top_gap: u16,
+ pub grid_gap: u16,
+ pub status_height: 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 grid_gap_spinbox: Spinbox,
+ pub status_height_spinbox: Spinbox,
+ pub transition_duration_spinbox: Spinbox,
+ pub tag_layout_menus: Vec<Dropdown>,
+ pub side_panel_behavior_menu: Dropdown,
+ pub side_panel_position_menu: Dropdown,
+ pub side_panel_width: u16,
+ pub side_panel_width_spinbox: Spinbox,
+ pub side_panel_border_gap: u16,
+ pub side_panel_border_gap_spinbox: Spinbox,
+ pub side_panel_border_opacity: u16,
+ pub side_panel_border_opacity_spinbox: Spinbox,
+ pub transparency_enabled: bool,
+ pub transparency_toggle: Toggle,
+ pub blur_enabled: bool,
+ pub blur_toggle: Toggle,
+}
+
+impl Default for WindowsState {
+ fn default() -> Self {
+ Self {
+ fullscreen_border_width: 0,
+ cascade_border_width: 6,
+ grid_border_width: 6,
+ floating_border_width: 6,
+ cascade_offset: 20,
+ edge_gap: 48,
+ top_gap: 48,
+ grid_gap: 6,
+ status_height: 24,
+ 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),
+ grid_gap_spinbox: Spinbox::new(6, 0, 200, 1),
+ status_height_spinbox: Spinbox::new(24, 0, 100, 1),
+ transition_duration_spinbox: Spinbox::new(300, 0, 2000, 50),
+ tag_layout_menus: (1..=4).map(|i| {
+ Dropdown::new(
+ vec![
+ "Cascade".to_string(),
+ "Grid".to_string(),
+ "Fullscreen".to_string(),
+ "Floating".to_string(),
+ "Popup".to_string(),
+ ],
+ 0
+ ).with_label(&format!("Tag {}", i))
+ }).collect(),
+ side_panel_behavior_menu: Dropdown::new(
+ vec!["Above".to_string(), "Inline".to_string()],
+ 1,
+ ).with_label("Behavior"),
+ side_panel_position_menu: Dropdown::new(
+ vec!["Left".to_string(), "Right".to_string()],
+ 0,
+ ).with_label("Position"),
+ side_panel_width: 360,
+ side_panel_width_spinbox: Spinbox::new(360, 0, 2000, 10),
+ side_panel_border_gap: 0,
+ side_panel_border_gap_spinbox: Spinbox::new(0, 0, 500, 1),
+ side_panel_border_opacity: 100,
+ side_panel_border_opacity_spinbox: Spinbox::new(100, 0, 100, 5),
+ transparency_enabled: true,
+ transparency_toggle: Toggle::new().with_label("Transparency"),
+ blur_enabled: true,
+ blur_toggle: Toggle::new().with_label("Blur"),
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum WindowsMessage {
+ SetWidth(WidthParam, u16),
+ SetCascadeOffset(u16),
+ SetEdgeGap(u16),
+ SetTopGap(u16),
+ SetGridGap(u16),
+ SetTransitionDuration(u16),
+ SetStatusHeight(u16),
+ SetTagLayout(usize, usize),
+ SetSidePanelBehavior(usize),
+ SetSidePanelPosition(usize),
+ SetSidePanelWidth(u16),
+ SetSidePanelBorderGap(u16),
+ SetSidePanelBorderOpacity(u16),
+ ToggleTransparency,
+ ToggleBlur,
+ Refreshed(WindowsState),
+}
+
#[derive(Debug, Clone)]
pub struct InterfaceState {
+ pub windows: WindowsState,
pub low_color: [u8; 3],
pub high_color: [u8; 3],
pub disabled_color: [u8; 3],
@@ -168,6 +315,7 @@ pub struct InterfaceState {
impl Default for InterfaceState {
fn default() -> Self {
Self {
+ windows: WindowsState::default(),
low_color: [0x0a, 0x1a, 0x0e],
high_color: [0x3e, 0x3e, 0x3e],
disabled_color: [0x55, 0x55, 0x55],
@@ -342,6 +490,7 @@ impl Default for InterfaceState {
#[derive(Debug, Clone)]
pub enum InterfaceMessage {
+ Windows(WindowsMessage),
SetLowColor([u8; 3]),
SetHighColor([u8; 3]),
SetDisabledColor([u8; 3]),
@@ -555,6 +704,7 @@ pub fn read_interface_config() -> InterfaceState {
let window_corner_radius = parse_surfaces_u16(&content, "window_corner_radius", 12);
InterfaceState {
+ windows: read_windows_config(),
low_color: bg,
high_color: border,
disabled_color: disabled,
@@ -726,6 +876,339 @@ pub fn read_interface_config() -> InterfaceState {
}
}
+pub fn read_windows_config() -> WindowsState {
+ let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
+ 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 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 gg = parse_u16_from(&content, "grid_gap", 6);
+ let sh = parse_u16_from(&content, "bar_height", 24);
+ let td = parse_u16_from(&content, "transition_duration", 300);
+
+ let tag_modes = parse_tag_layouts_from_config(&content);
+ let dropdown_options = vec![
+ "Cascade".to_string(),
+ "Grid".to_string(),
+ "Fullscreen".to_string(),
+ "Floating".to_string(),
+ "Popup".to_string(),
+ ];
+ let tag_layout_menus = (1..=4).map(|i| {
+ let mode_str = &tag_modes[i - 1];
+ let idx = dropdown_options.iter().position(|opt| opt.to_lowercase() == mode_str.to_lowercase()).unwrap_or(0);
+ Dropdown::new(dropdown_options.clone(), idx).with_label(&format!("Tag {}", i))
+ }).collect();
+
+ let side_panel_behavior = parse_string_from(&content, "side_panel_behavior", "inline");
+ let side_panel_behavior_idx = if side_panel_behavior == "above" { 0 } else { 1 };
+ let side_panel_behavior_menu = Dropdown::new(
+ vec!["Above".to_string(), "Inline".to_string()],
+ side_panel_behavior_idx,
+ ).with_label("Behavior");
+
+ let side_panel_position = parse_string_from(&content, "side_panel_position", "left");
+ let side_panel_position_idx = if side_panel_position == "right" { 1 } else { 0 };
+ let side_panel_position_menu = Dropdown::new(
+ vec!["Left".to_string(), "Right".to_string()],
+ side_panel_position_idx,
+ ).with_label("Position");
+
+ let spw = parse_u16_from(&content, "side_panel_width", 360);
+ let spbg = parse_u16_from(&content, "side_panel_border_gap", 0);
+ let spbo = parse_u16_from(&content, "side_panel_border_opacity", 100);
+
+ let transparency_opacity = parse_transparency_opacity(&content);
+ let transparency_enabled = transparency_opacity < 1.0;
+
+ let window_blur = parse_bool_from(&content, "window_blur", false);
+ let border_blur = parse_bool_from(&content, "border_blur", false);
+ let blur_enabled = window_blur || border_blur;
+
+ WindowsState {
+ fullscreen_border_width: fs,
+ cascade_border_width: ca,
+ grid_border_width: g,
+ floating_border_width: fl,
+ cascade_offset: co,
+ edge_gap: gl,
+ top_gap: gt,
+ grid_gap: gg,
+ status_height: sh,
+ 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),
+ grid_gap_spinbox: Spinbox::new(gg as i32, 0, 200, 1),
+ status_height_spinbox: Spinbox::new(sh as i32, 0, 100, 1),
+ transition_duration_spinbox: Spinbox::new(td as i32, 0, 2000, 50),
+ tag_layout_menus,
+ side_panel_behavior_menu,
+ side_panel_position_menu,
+ side_panel_width: spw,
+ side_panel_width_spinbox: Spinbox::new(spw as i32, 0, 2000, 10),
+ side_panel_border_gap: spbg,
+ side_panel_border_gap_spinbox: Spinbox::new(spbg as i32, 0, 500, 1),
+ side_panel_border_opacity: spbo,
+ side_panel_border_opacity_spinbox: Spinbox::new(spbo as i32, 0, 100, 5),
+ transparency_enabled,
+ transparency_toggle: Toggle::new().with_label("Transparency"),
+ blur_enabled,
+ blur_toggle: Toggle::new().with_label("Blur"),
+ }
+}
+
+fn parse_tag_layouts_from_config(content: &str) -> Vec<String> {
+ let val = parse_json(content);
+ let mut modes = vec!["cascade".to_string(); 4];
+ if let Some(arr) = val.get("tag_layout").and_then(|t| t.as_array()) {
+ for item in arr {
+ if let (Some(tag), Some(mode)) = (
+ item.get("tag").and_then(|t| t.as_u64()),
+ item.get("mode").and_then(|m| m.as_str())
+ ) {
+ if tag >= 1 && tag <= 4 {
+ modes[tag as usize - 1] = mode.to_string();
+ }
+ }
+ }
+ }
+ modes
+}
+
+fn write_tag_layout(tag_num: usize, mode_str: &str) -> bool {
+ let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
+ let mut val = parse_json(&content);
+
+ let mut found = false;
+ if let Some(arr) = val.get_mut("tag_layout").and_then(|t| t.as_array_mut()) {
+ for item in arr.iter_mut() {
+ if item.get("tag").and_then(|t| t.as_u64()) == Some(tag_num as u64) {
+ if let Some(obj) = item.as_object_mut() {
+ obj.insert("mode".to_string(), serde_json::json!(mode_str.to_lowercase()));
+ found = true;
+ break;
+ }
+ }
+ }
+ if !found {
+ arr.push(serde_json::json!({
+ "tag": tag_num,
+ "mode": mode_str.to_lowercase()
+ }));
+ }
+ } else {
+ let arr = vec![serde_json::json!({
+ "tag": tag_num,
+ "mode": mode_str.to_lowercase()
+ })];
+ if let Some(obj) = val.as_object_mut() {
+ obj.insert("tag_layout".to_string(), serde_json::Value::Array(arr));
+ }
+ }
+
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ return fs::write(CONFIG_PATH, updated_str).is_ok();
+ }
+ false
+}
+
+fn apply_all_widths(s: &WindowsState) {
+ let w = |k: &str, v: u16| { write_config_value(k, &v.to_string()); send_ipc_command(&format!("layout {} {}", k, v)); };
+ 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("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);
+}
+
+fn apply_single_layout_param(key: &str, val: u16) {
+ write_config_value(key, &val.to_string());
+ send_ipc_command(&format!("layout {} {}", key, val));
+}
+
+fn apply_edge_gap(val: u16) {
+ let val_str = val.to_string();
+ write_config_value("gap_left", &val_str);
+ write_config_value("gap_right", &val_str);
+ write_config_value("gap_bottom", &val_str);
+ send_ipc_command(&format!("layout gap_left {}", val));
+ send_ipc_command(&format!("layout gap_right {}", val));
+ send_ipc_command(&format!("layout gap_bottom {}", val));
+}
+
+#[derive(Debug, Clone)]
+#[allow(dead_code)]
+struct PreviewWindow {
+ app_id: String,
+ title: String,
+ x: f32,
+ y: f32,
+ w: f32,
+ h: f32,
+ tags: u32,
+ _minimized: bool,
+ has_parent: bool,
+ layout_mode: String,
+}
+
+#[allow(dead_code)]
+struct LayoutStatusInfo {
+ active_tags: u32,
+ focused_tags: u32,
+ _num_tags: u32,
+ windows: Vec<PreviewWindow>,
+ focused_title: String,
+ focused_layout_mode: String,
+}
+
+fn read_current_layout_status() -> LayoutStatusInfo {
+ let mut active_tags = 1;
+ let mut focused_tags = 1;
+ let mut _num_tags = 4;
+
+ let display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
+
+ let tags_path = format!("/tmp/cce-tags-{}", display);
+ let tags_fallback = "/tmp/cce-tags".to_string();
+ let tags_content = fs::read_to_string(&tags_path)
+ .or_else(|_| fs::read_to_string(&tags_fallback))
+ .unwrap_or_default();
+
+ if let Some(line) = tags_content.lines().next() {
+ let parts: Vec<&str> = line.split_whitespace().collect();
+ if parts.len() >= 3 {
+ active_tags = parts[0].parse().unwrap_or(1);
+ focused_tags = parts[1].parse().unwrap_or(1);
+ _num_tags = parts[2].parse().unwrap_or(4);
+ }
+ }
+
+ let title_path = format!("/tmp/cce-title-{}", display);
+ let title_fallback = "/tmp/cce-title".to_string();
+ let focused_title = fs::read_to_string(&title_path)
+ .or_else(|_| fs::read_to_string(&title_fallback))
+ .unwrap_or_default()
+ .trim()
+ .to_string();
+
+ let layout_path = format!("/tmp/cce-layout-{}", display);
+ let layout_fallback = "/tmp/cce-layout".to_string();
+ let focused_layout_mode = fs::read_to_string(&layout_path)
+ .or_else(|_| fs::read_to_string(&layout_fallback))
+ .unwrap_or_else(|_| "Cascade".to_string())
+ .trim()
+ .to_string();
+
+ let windows_path = format!("/tmp/cce-windows-{}", display);
+ let windows_fallback = "/tmp/cce-windows".to_string();
+ let windows_content = fs::read_to_string(&windows_path)
+ .or_else(|_| fs::read_to_string(&windows_fallback))
+ .unwrap_or_default();
+
+ let mut windows = Vec::new();
+ for line in windows_content.lines() {
+ if !line.starts_with("window ") { continue; }
+
+ let mut app_id = String::new();
+ let mut title = String::new();
+ let mut x = 0.0;
+ let mut y = 0.0;
+ let mut w = 0.0;
+ let mut h = 0.0;
+ let mut tags = 0;
+ let mut minimized = false;
+ let mut has_parent = false;
+ let mut layout_mode = "Cascade".to_string();
+
+ let parts = line.strip_prefix("window ").unwrap_or(line);
+
+ let get_val = |p: &str, k: &str| -> Option<String> {
+ if let Some(idx) = p.find(k) {
+ let start = idx + k.len();
+ let mut end = p.len();
+ let next_keys = [
+ " app_id=", " title=", " mode=", " decoration=", " presentation=",
+ " tags=", " x=", " y=", " w=", " h=", " has_parent=", " minimized="
+ ];
+ for nk in next_keys {
+ if nk != k {
+ if let Some(nidx) = p[start..].find(nk) {
+ end = end.min(start + nidx);
+ }
+ }
+ }
+ Some(p[start..end].trim().to_string())
+ } else {
+ None
+ }
+ };
+
+ if let Some(val) = get_val(parts, "app_id=") { app_id = val; }
+ if let Some(val) = get_val(parts, "title=") { title = val; }
+ if let Some(val) = get_val(parts, "x=") { x = val.parse().unwrap_or(0.0); }
+ if let Some(val) = get_val(parts, "y=") { y = val.parse().unwrap_or(0.0); }
+ if let Some(val) = get_val(parts, "w=") { w = val.parse().unwrap_or(0.0); }
+ if let Some(val) = get_val(parts, "h=") { h = val.parse().unwrap_or(0.0); }
+ if let Some(val) = get_val(parts, "tags=") { tags = val.parse().unwrap_or(0); }
+ if let Some(val) = get_val(parts, "minimized=") { minimized = val == "true"; }
+ if let Some(val) = get_val(parts, "has_parent=") { has_parent = val == "true"; }
+ if let Some(val) = get_val(parts, "mode=") { layout_mode = val; }
+
+ windows.push(PreviewWindow {
+ app_id,
+ title,
+ x,
+ y,
+ w,
+ h,
+ tags,
+ _minimized: minimized,
+ has_parent,
+ layout_mode,
+ });
+ }
+
+ LayoutStatusInfo {
+ active_tags,
+ focused_tags,
+ _num_tags,
+ windows,
+ focused_title,
+ focused_layout_mode,
+ }
+}
+
+fn set_width(state: &mut WindowsState, param: WidthParam, val: u16) {
+ let val = val.min(100);
+ match param {
+ WidthParam::Fullscreen => state.fullscreen_border_width = val,
+ WidthParam::Cascade => state.cascade_border_width = val,
+ WidthParam::Grid => state.grid_border_width = val,
+ WidthParam::Floating => state.floating_border_width = val,
+ }
+ state.spinboxes[param_idx(param)].value = val as i32;
+ apply_all_widths(state);
+}
+
+fn param_idx(p: WidthParam) -> usize {
+ match p {
+ WidthParam::Fullscreen => 0,
+ WidthParam::Cascade => 1,
+ WidthParam::Grid => 2,
+ WidthParam::Floating => 3,
+ }
+}
+
fn parse_json(content: &str) -> serde_json::Value {
serde_json::from_str(content).unwrap_or_default()
}
@@ -1922,7 +2405,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 cce_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(9);
+ let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(10);
// 1. Custom Parameters Section
@@ -2399,8 +2882,143 @@ pub fn view(state: &mut InterfaceState, cx: f32, cy: f32, cw: f32, ch: f32, sec_
sec.spacing(12.0);
});
- final_pc
+ // 10. Windows Section
+ builder.add_section(&mut final_pc, "Windows", sec_focused.get(9).copied().unwrap_or(false), |sec| {
+ sec.spacing(8.0);
+
+ // 1. Current Layout Preview
+ sec.add_section("Current Layout", false, |sec_cl| {
+ sec_cl.spacing(8.0);
+
+ let info = read_current_layout_status();
+
+ let card_w = (sec_w - 24.0) / 2.0;
+ let card_h = 135.0;
+ let rx = sec_cl.left;
+
+ for tag_idx in 0..4 {
+ let col = tag_idx % 2;
+ let row = tag_idx / 2;
+ let tx = rx + 8.0 + col as f32 * (card_w + 8.0);
+ let ty = sec_cl.ay() + row as f32 * (card_h + 8.0);
+
+ let is_active = (info.active_tags & (1 << tag_idx)) != 0;
+ let layout_idx = state.windows.tag_layout_menus.get(tag_idx).map(|m| m.selected).unwrap_or(0);
+ let mode = match layout_idx {
+ 1 => PreviewLayoutMode::Cascade,
+ 2 => PreviewLayoutMode::Stack,
+ 3 => PreviewLayoutMode::Grid,
+ 4 => PreviewLayoutMode::LeftTiled,
+ 5 => PreviewLayoutMode::RightTiled,
+ 6 => PreviewLayoutMode::Equal,
+ 7 => PreviewLayoutMode::Spiral,
+ 8 => PreviewLayoutMode::Floating,
+ _ => PreviewLayoutMode::Fullscreen,
+ };
+
+ let mut preview = LayoutPreview::new(mode)
+ .with_active(is_active)
+ .with_label(&format!("TAG {}", tag_idx + 1));
+ render_widget(sec_cl.pc, &mut preview, tx, ty, card_w, card_h, ctx);
+ }
+
+ sec_cl.content_y += 2.0 * (card_h + 8.0) + 4.0;
+ });
+
+ // 2. Fullscreen Section
+ sec.add_section("Fullscreen", false, |subsec| {
+ subsec.spacing(8.0);
+ state.windows.spinboxes[0].set_label("Border Width");
+ subsec.widget_full(&mut state.windows.spinboxes[0], 44.0, ctx);
+ subsec.spacing(8.0);
+ });
+
+ // 3. Cascade Section
+ sec.add_section("Cascade", false, |subsec| {
+ subsec.spacing(8.0);
+ state.windows.spinboxes[1].set_label("Border Width");
+ subsec.widget_full(&mut state.windows.spinboxes[1], 44.0, ctx);
+ subsec.spacing(8.0);
+ state.windows.cascade_offset_spinbox.set_label("Offset");
+ subsec.widget_full(&mut state.windows.cascade_offset_spinbox, 44.0, ctx);
+ subsec.spacing(8.0);
+ state.windows.edge_gap_spinbox.set_label("Edge Gap");
+ subsec.widget_full(&mut state.windows.edge_gap_spinbox, 44.0, ctx);
+ subsec.spacing(8.0);
+ state.windows.top_gap_spinbox.set_label("Top Gap");
+ subsec.widget_full(&mut state.windows.top_gap_spinbox, 44.0, ctx);
+ subsec.spacing(8.0);
+ });
+
+ // 4. Grid Section
+ sec.add_section("Grid", false, |subsec| {
+ subsec.spacing(8.0);
+ state.windows.spinboxes[2].set_label("Border Width");
+ subsec.widget_full(&mut state.windows.spinboxes[2], 44.0, ctx);
+ subsec.spacing(8.0);
+ state.windows.grid_gap_spinbox.set_label("Gap");
+ subsec.widget_full(&mut state.windows.grid_gap_spinbox, 44.0, ctx);
+ subsec.spacing(8.0);
+ });
+
+ // 5. Floating Section
+ sec.add_section("Floating", false, |subsec| {
+ subsec.spacing(8.0);
+ state.windows.spinboxes[3].set_label("Border Width");
+ subsec.widget_full(&mut state.windows.spinboxes[3], 44.0, ctx);
+ subsec.spacing(8.0);
+ });
+
+ // 6. Movement Section
+ sec.add_section("Movement", false, |subsec| {
+ subsec.spacing(8.0);
+ state.windows.transition_duration_spinbox.set_label("Duration (ms)");
+ subsec.widget_full(&mut state.windows.transition_duration_spinbox, 44.0, ctx);
+ subsec.spacing(8.0);
+ });
+
+ // 7. Default Layouts Section
+ sec.add_section("Default Layouts", false, |subsec| {
+ subsec.spacing(8.0);
+ for i in 0..4 {
+ subsec.widget_full(&mut state.windows.tag_layout_menus[i], 44.0, ctx);
+ subsec.spacing(8.0);
+ }
+ });
+
+ // 8. Side Panel Section
+ sec.add_section("Side Panel", false, |subsec| {
+ subsec.spacing(8.0);
+ subsec.widget_full(&mut state.windows.side_panel_behavior_menu, 44.0, ctx);
+ subsec.spacing(8.0);
+ subsec.widget_full(&mut state.windows.side_panel_position_menu, 44.0, ctx);
+ subsec.spacing(8.0);
+ state.windows.side_panel_width_spinbox.set_label("Default Width");
+ subsec.widget_full(&mut state.windows.side_panel_width_spinbox, 44.0, ctx);
+ subsec.spacing(8.0);
+ state.windows.side_panel_border_gap_spinbox.set_label("Border Gap");
+ subsec.widget_full(&mut state.windows.side_panel_border_gap_spinbox, 44.0, ctx);
+ subsec.spacing(8.0);
+ state.windows.side_panel_border_opacity_spinbox.set_label("Border Opacity");
+ subsec.widget_full(&mut state.windows.side_panel_border_opacity_spinbox, 44.0, ctx);
+ subsec.spacing(8.0);
+ });
+ // 9. Effects Section
+ sec.add_section("Effects", false, |subsec| {
+ subsec.spacing(8.0);
+ state.windows.transparency_toggle.set_toggled(state.windows.transparency_enabled);
+ subsec.widget_full(&mut state.windows.transparency_toggle, cce_ui::layout::toggle_height(), ctx);
+ subsec.spacing(8.0);
+ state.windows.blur_toggle.set_toggled(state.windows.blur_enabled);
+ subsec.widget_full(&mut state.windows.blur_toggle, cce_ui::layout::toggle_height(), ctx);
+ subsec.spacing(8.0);
+ });
+
+ sec.spacing(8.0);
+ });
+
+ final_pc
}
@@ -3177,6 +3795,154 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
state.terminal_size_box.value = val;
write_terminal_size(val as u16);
}
+ InterfaceMessage::Windows(sub_msg) => {
+ update_windows(&mut state.windows, sub_msg);
+ }
+ }
+}
+
+pub fn update_windows(state: &mut WindowsState, msg: WindowsMessage) {
+ match msg {
+ WindowsMessage::SetWidth(p, v) => set_width(state, p, v),
+ WindowsMessage::SetCascadeOffset(v) => {
+ let val = v.min(200);
+ state.cascade_offset = val;
+ state.cascade_offset_spinbox.value = val as i32;
+ apply_single_layout_param("cascade_offset", val);
+ }
+ WindowsMessage::SetEdgeGap(v) => {
+ let val = v.min(200);
+ state.edge_gap = val;
+ state.edge_gap_spinbox.value = val as i32;
+ apply_edge_gap(val);
+ }
+ WindowsMessage::SetTopGap(v) => {
+ let val = v.min(200);
+ state.top_gap = val;
+ state.top_gap_spinbox.value = val as i32;
+ apply_single_layout_param("gap_top", val);
+ }
+ WindowsMessage::SetGridGap(v) => {
+ let val = v.min(200);
+ state.grid_gap = val;
+ state.grid_gap_spinbox.value = val as i32;
+ apply_single_layout_param("grid_gap", val);
+ }
+ WindowsMessage::SetTransitionDuration(v) => {
+ let val = v.min(2000);
+ state.transition_duration = val;
+ state.transition_duration_spinbox.value = val as i32;
+ apply_single_layout_param("transition_duration", val);
+ }
+ WindowsMessage::SetStatusHeight(v) => {
+ let val = v.min(100);
+ state.status_height = val;
+ state.status_height_spinbox.value = val as i32;
+ apply_single_layout_param("bar_height", val);
+ }
+ WindowsMessage::SetTagLayout(tag, idx) => {
+ if tag >= 1 && tag <= 4 && idx < 5 {
+ state.tag_layout_menus[tag - 1].selected = idx;
+ let modes = vec!["cascade", "grid", "fullscreen", "floating", "popup"];
+ let mode_str = modes[idx];
+ write_tag_layout(tag, mode_str);
+ send_ipc_command(&format!("tag-layout {} {}", tag, mode_str));
+ }
+ }
+ WindowsMessage::SetSidePanelBehavior(idx) => {
+ if idx < 2 {
+ state.side_panel_behavior_menu.selected = idx;
+ let val = if idx == 0 { "above" } else { "inline" };
+ write_config_value("side_panel_behavior", &format!("\"{}\"", val));
+ send_ipc_command(&format!("layout side_panel_behavior {}", val));
+ }
+ }
+ WindowsMessage::SetSidePanelPosition(idx) => {
+ if idx < 2 {
+ state.side_panel_position_menu.selected = idx;
+ let val = if idx == 1 { "right" } else { "left" };
+ write_config_value("side_panel_position", &format!("\"{}\"", val));
+ send_ipc_command(&format!("layout side_panel_position {}", val));
+ }
+ }
+ WindowsMessage::SetSidePanelWidth(v) => {
+ let val = v.min(2000);
+ state.side_panel_width = val;
+ state.side_panel_width_spinbox.value = val as i32;
+ write_config_value("side_panel_width", &val.to_string());
+ send_ipc_command(&format!("layout side_panel_width {}", val));
+ }
+ WindowsMessage::SetSidePanelBorderGap(v) => {
+ let val = v.min(500);
+ state.side_panel_border_gap = val;
+ state.side_panel_border_gap_spinbox.value = val as i32;
+ write_config_value("side_panel_border_gap", &val.to_string());
+ send_ipc_command(&format!("layout side_panel_border_gap {}", val));
+ }
+ WindowsMessage::SetSidePanelBorderOpacity(v) => {
+ let val = v.min(100);
+ state.side_panel_border_opacity = val;
+ state.side_panel_border_opacity_spinbox.value = val as i32;
+ write_config_value("side_panel_border_opacity", &val.to_string());
+ send_ipc_command(&format!("layout side_panel_border_opacity {}", val));
+ }
+ WindowsMessage::ToggleTransparency => {
+ state.transparency_enabled = !state.transparency_enabled;
+ if state.transparency_enabled {
+ write_transparency_config_value("opacity", "0.85");
+ } else {
+ write_transparency_config_value("opacity", "1.00");
+ }
+ send_ipc_command("reload");
+ status_interface_reload();
+ }
+ WindowsMessage::ToggleBlur => {
+ state.blur_enabled = !state.blur_enabled;
+ let val = state.blur_enabled.to_string();
+ write_config_value("window_blur", &val);
+ write_config_value("border_blur", &val);
+ send_ipc_command("reload");
+ }
+ WindowsMessage::Refreshed(new) => {
+ // Restore hover states
+ let transparency_hover = state.transparency_toggle.hovered();
+ let blur_hover = state.blur_toggle.hovered();
+ let tag_layout_menus_hover: Vec<bool> = state.tag_layout_menus.iter().map(|m| m.hovered()).collect();
+ let side_panel_behavior_hover = state.side_panel_behavior_menu.hovered();
+ let side_panel_position_hover = state.side_panel_position_menu.hovered();
+ let spinboxes_hover: Vec<bool> = state.spinboxes.iter().map(|sb| sb.hovered()).collect();
+ let cascade_offset_hover = state.cascade_offset_spinbox.hovered();
+ let edge_gap_hover = state.edge_gap_spinbox.hovered();
+ let top_gap_hover = state.top_gap_spinbox.hovered();
+ let grid_gap_hover = state.grid_gap_spinbox.hovered();
+ let status_height_hover = state.status_height_spinbox.hovered();
+ let transition_duration_hover = state.transition_duration_spinbox.hovered();
+ let side_panel_width_hover = state.side_panel_width_spinbox.hovered();
+ let side_panel_border_gap_hover = state.side_panel_border_gap_spinbox.hovered();
+ let side_panel_border_opacity_hover = state.side_panel_border_opacity_spinbox.hovered();
+
+ *state = new;
+
+ state.transparency_toggle.set_hovered(transparency_hover);
+ state.blur_toggle.set_hovered(blur_hover);
+ for (menu, hover) in state.tag_layout_menus.iter_mut().zip(tag_layout_menus_hover) {
+ menu.set_hovered(hover);
+ }
+ state.side_panel_behavior_menu.set_hovered(side_panel_behavior_hover);
+ state.side_panel_position_menu.set_hovered(side_panel_position_hover);
+ for (sb, hover) in state.spinboxes.iter_mut().zip(spinboxes_hover) {
+ sb.set_hovered(hover);
+ }
+ state.cascade_offset_spinbox.set_hovered(cascade_offset_hover);
+ state.edge_gap_spinbox.set_hovered(edge_gap_hover);
+ state.top_gap_spinbox.set_hovered(top_gap_hover);
+ state.grid_gap_spinbox.set_hovered(grid_gap_hover);
+ state.status_height_spinbox.set_hovered(status_height_hover);
+ state.transition_duration_spinbox.set_hovered(transition_duration_hover);
+ state.side_panel_width_spinbox.set_hovered(side_panel_width_hover);
+ state.side_panel_border_gap_spinbox.set_hovered(side_panel_border_gap_hover);
+ state.side_panel_border_opacity_spinbox.set_hovered(side_panel_border_opacity_hover);
+ }
}
}
@@ -4557,6 +5323,128 @@ mod tests {
let _ = fs::remove_file(path_str);
}
+
+ #[test]
+ fn test_parse_tag_layouts_empty() {
+ let content = "";
+ let modes = parse_tag_layouts_from_config(content);
+ assert_eq!(modes, vec!["cascade", "cascade", "cascade", "cascade"]);
+ }
+
+ #[test]
+ fn test_parse_side_panel_width_default() {
+ let content = "";
+ let width = parse_u16_from(content, "side_panel_width", 360);
+ assert_eq!(width, 360);
+ }
+
+ #[test]
+ fn test_parse_side_panel_width_explicit() {
+ let content = "{\"layout\": {\"side_panel_width\": 450}}";
+ let width = parse_u16_from(content, "side_panel_width", 360);
+ assert_eq!(width, 450);
+ }
+
+ #[test]
+ fn test_parse_side_panel_border_opacity_default() {
+ let content = "";
+ let opacity = parse_u16_from(content, "side_panel_border_opacity", 100);
+ assert_eq!(opacity, 100);
+ }
+
+ #[test]
+ fn test_parse_side_panel_border_opacity_explicit() {
+ let content = "{\"layout\": {\"side_panel_border_opacity\": 75}}";
+ let opacity = parse_u16_from(content, "side_panel_border_opacity", 100);
+ assert_eq!(opacity, 75);
+ }
+
+ #[test]
+ fn test_parse_tag_layouts_single() {
+ let content = r#"{
+ "layout": {"gap": 10},
+ "tag_layout": [
+ {"tag": 2, "mode": "grid"}
+ ]
+ }"#;
+ let modes = parse_tag_layouts_from_config(content);
+ assert_eq!(modes, vec!["cascade", "grid", "cascade", "cascade"]);
+ }
+
+ #[test]
+ fn test_parse_tag_layouts_multiple() {
+ let content = r#"{
+ "tag_layout": [
+ {"tag": 1, "mode": "fullscreen"},
+ {"tag": 4, "mode": "floating"}
+ ]
+ }"#;
+ let modes = parse_tag_layouts_from_config(content);
+ assert_eq!(modes, vec!["fullscreen", "cascade", "cascade", "floating"]);
+ }
+
+ #[test]
+ fn test_parse_tag_layouts_out_of_bounds() {
+ let content = r#"{
+ "tag_layout": [
+ {"tag": 5, "mode": "grid"},
+ {"tag": 0, "mode": "popup"}
+ ]
+ }"#;
+ let modes = parse_tag_layouts_from_config(content);
+ assert_eq!(modes, vec!["cascade", "cascade", "cascade", "cascade"]);
+ }
+
+ #[test]
+ fn test_view_layout_grid() {
+ let mut state = InterfaceState::default();
+ let mut layout = cce_ui::layout::ColumnLayout::new(20.0);
+ let pc = view(&mut state, 10.0, 20.0, 800.0, 600.0, &[false, false, false, false, false, false, false, false, false, false], &mut layout, &mut cce_ui::context::UiContext::new());
+ assert!(!pc.rects.is_empty() || !pc.texts.is_empty());
+ }
+
+ #[test]
+ fn test_spinbox_right_click() {
+ use cce_ui::widget::Element;
+ let mut state = InterfaceState::default();
+ let mut ctx = cce_ui::context::UiContext::new();
+ let sb = &mut state.windows.spinboxes[0];
+ sb.set_rect(0.0, 0.0, 100.0, 44.0);
+ let res = sb.mouse_input(
+ cce_ui::widget::MouseButton::Right,
+ cce_ui::widget::ElementState::Pressed,
+ 50.0,
+ 20.0,
+ &mut ctx,
+ );
+ assert!(res);
+ }
+
+ #[test]
+ #[allow(unused_assignments)]
+ fn test_spinbox_right_click_crash() {
+ use cce_ui::widget::Element;
+ let mut state = InterfaceState::default();
+ let mut ctx = cce_ui::context::UiContext::new();
+ let sb = &mut state.windows.spinboxes[0];
+ sb.set_rect(0.0, 0.0, 100.0, 44.0);
+ let res = sb.mouse_input(
+ cce_ui::widget::MouseButton::Right,
+ cce_ui::widget::ElementState::Pressed,
+ 50.0,
+ 20.0,
+ &mut ctx,
+ );
+ assert!(res);
+ assert!(cce_ui::widget::context_menu::is_visible());
+
+ // Now replace the state simulating config reload/refresh
+ let new_state = InterfaceState::default();
+ state = new_state;
+
+ // Assert that the context menu is hidden (cleared)
+ assert!(!cce_ui::widget::context_menu::is_visible());
+ }
}
diff --git a/src/pages/layout.rs b/src/pages/layout.rs
deleted file mode 100644
index 1e25e76..0000000
--- a/src/pages/layout.rs
+++ /dev/null
@@ -1,910 +0,0 @@
-use std::fs;
-use std::io::Write;
-
-use crate::app::PageContent;
-use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
-use cce_ui::widget::{Spinbox, Dropdown, LayoutPreview, PreviewLayoutMode, Toggle, Element};
-use crate::pages::interface::{parse_bool_from, parse_transparency_opacity, write_transparency_config_value, status_interface_reload};
-
-
-const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.json";
-
-fn get_socket_path() -> String {
- match std::env::var("WAYLAND_DISPLAY") {
- Ok(display) => format!("/tmp/cce-{}.sock", display),
- Err(_) => "/tmp/cce.sock".to_string(),
- }
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum WidthParam {
- Fullscreen, Cascade, Grid, Floating,
-}
-
-impl WidthParam {
- pub const ALL: [WidthParam; 4] = [
- WidthParam::Fullscreen, WidthParam::Cascade, WidthParam::Grid,
- 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::Floating => "floating_border_width",
- }
- }
- pub fn label(self) -> &'static str {
- match self {
- WidthParam::Fullscreen => "Fullscreen",
- WidthParam::Cascade => "Cascade",
- WidthParam::Grid => "Grid",
- WidthParam::Floating => "Floating",
- }
- }
-}
-
-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(fl as i32, 0, 100, 1),
- ]
-}
-
-#[derive(Debug, Clone)]
-pub struct LayoutState {
- pub fullscreen_border_width: u16,
- pub cascade_border_width: u16,
- pub grid_border_width: u16,
- pub floating_border_width: u16,
- pub cascade_offset: u16,
- pub edge_gap: u16,
- pub top_gap: u16,
- pub grid_gap: u16,
- pub status_height: 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 grid_gap_spinbox: Spinbox,
- pub status_height_spinbox: Spinbox,
- pub transition_duration_spinbox: Spinbox,
- pub tag_layout_menus: Vec<Dropdown>,
- pub side_panel_behavior_menu: Dropdown,
- pub side_panel_position_menu: Dropdown,
- pub side_panel_width: u16,
- pub side_panel_width_spinbox: Spinbox,
- pub side_panel_border_gap: u16,
- pub side_panel_border_gap_spinbox: Spinbox,
- pub side_panel_border_opacity: u16,
- pub side_panel_border_opacity_spinbox: Spinbox,
- pub transparency_enabled: bool,
- pub transparency_toggle: Toggle,
- pub blur_enabled: bool,
- pub blur_toggle: Toggle,
-}
-
-impl Default for LayoutState {
- fn default() -> Self {
- Self {
- fullscreen_border_width: 0,
- cascade_border_width: 6,
- grid_border_width: 6,
- floating_border_width: 6,
- cascade_offset: 20,
- edge_gap: 48,
- top_gap: 48,
- grid_gap: 6,
- status_height: 24,
- 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),
- grid_gap_spinbox: Spinbox::new(6, 0, 200, 1),
- status_height_spinbox: Spinbox::new(24, 0, 100, 1),
- transition_duration_spinbox: Spinbox::new(300, 0, 2000, 50),
- tag_layout_menus: (1..=4).map(|i| {
- Dropdown::new(
- vec![
- "Cascade".to_string(),
- "Grid".to_string(),
- "Fullscreen".to_string(),
- "Floating".to_string(),
- "Popup".to_string(),
- ],
- 0
- ).with_label(&format!("Tag {}", i))
- }).collect(),
- side_panel_behavior_menu: Dropdown::new(
- vec!["Above".to_string(), "Inline".to_string()],
- 1,
- ).with_label("Behavior"),
- side_panel_position_menu: Dropdown::new(
- vec!["Left".to_string(), "Right".to_string()],
- 0,
- ).with_label("Position"),
- side_panel_width: 360,
- side_panel_width_spinbox: Spinbox::new(360, 0, 2000, 10),
- side_panel_border_gap: 0,
- side_panel_border_gap_spinbox: Spinbox::new(0, 0, 500, 1),
- side_panel_border_opacity: 100,
- side_panel_border_opacity_spinbox: Spinbox::new(100, 0, 100, 5),
- transparency_enabled: true,
- transparency_toggle: Toggle::new().with_label("Transparency"),
- blur_enabled: true,
- blur_toggle: Toggle::new().with_label("Blur"),
- }
- }
-}
-
-#[derive(Debug, Clone)]
-pub enum LayoutMessage {
- SetWidth(WidthParam, u16),
- SetCascadeOffset(u16),
- SetEdgeGap(u16),
- SetTopGap(u16),
- SetGridGap(u16),
- SetTransitionDuration(u16),
- SetStatusHeight(u16),
- SetTagLayout(usize, usize),
- SetSidePanelBehavior(usize),
- SetSidePanelPosition(usize),
- SetSidePanelWidth(u16),
- SetSidePanelBorderGap(u16),
- SetSidePanelBorderOpacity(u16),
- ToggleTransparency,
- ToggleBlur,
- Refreshed(LayoutState),
-}
-
-pub fn read_layout_config() -> LayoutState {
- let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
- 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 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 gg = parse_u16_from(&content, "grid_gap", 6);
- let sh = parse_u16_from(&content, "bar_height", 24);
- let td = parse_u16_from(&content, "transition_duration", 300);
-
- let tag_modes = parse_tag_layouts_from_config(&content);
- let dropdown_options = vec![
- "Cascade".to_string(),
- "Grid".to_string(),
- "Fullscreen".to_string(),
- "Floating".to_string(),
- "Popup".to_string(),
- ];
- let tag_layout_menus = (1..=4).map(|i| {
- let mode_str = &tag_modes[i - 1];
- let idx = dropdown_options.iter().position(|opt| opt.to_lowercase() == mode_str.to_lowercase()).unwrap_or(0);
- Dropdown::new(dropdown_options.clone(), idx).with_label(&format!("Tag {}", i))
- }).collect();
-
- let side_panel_behavior = parse_string_from(&content, "side_panel_behavior", "inline");
- let side_panel_behavior_idx = if side_panel_behavior == "above" { 0 } else { 1 };
- let side_panel_behavior_menu = Dropdown::new(
- vec!["Above".to_string(), "Inline".to_string()],
- side_panel_behavior_idx,
- ).with_label("Behavior");
-
- let side_panel_position = parse_string_from(&content, "side_panel_position", "left");
- let side_panel_position_idx = if side_panel_position == "right" { 1 } else { 0 };
- let side_panel_position_menu = Dropdown::new(
- vec!["Left".to_string(), "Right".to_string()],
- side_panel_position_idx,
- ).with_label("Position");
-
- let spw = parse_u16_from(&content, "side_panel_width", 360);
- let spbg = parse_u16_from(&content, "side_panel_border_gap", 0);
- let spbo = parse_u16_from(&content, "side_panel_border_opacity", 100);
-
- let transparency_opacity = parse_transparency_opacity(&content);
- let transparency_enabled = transparency_opacity < 1.0;
-
- let window_blur = parse_bool_from(&content, "window_blur", false);
- let border_blur = parse_bool_from(&content, "border_blur", false);
- let blur_enabled = window_blur || border_blur;
-
- LayoutState {
- fullscreen_border_width: fs,
- cascade_border_width: ca,
- grid_border_width: g,
- floating_border_width: fl,
- cascade_offset: co,
- edge_gap: gl,
- top_gap: gt,
- grid_gap: gg,
- status_height: sh,
- 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),
- grid_gap_spinbox: Spinbox::new(gg as i32, 0, 200, 1),
- status_height_spinbox: Spinbox::new(sh as i32, 0, 100, 1),
- transition_duration_spinbox: Spinbox::new(td as i32, 0, 2000, 50),
- tag_layout_menus,
- side_panel_behavior_menu,
- side_panel_position_menu,
- side_panel_width: spw,
- side_panel_width_spinbox: Spinbox::new(spw as i32, 0, 2000, 10),
- side_panel_border_gap: spbg,
- side_panel_border_gap_spinbox: Spinbox::new(spbg as i32, 0, 500, 1),
- side_panel_border_opacity: spbo,
- side_panel_border_opacity_spinbox: Spinbox::new(spbo as i32, 0, 100, 5),
- transparency_enabled,
- transparency_toggle: Toggle::new().with_label("Transparency"),
- blur_enabled,
- blur_toggle: Toggle::new().with_label("Blur"),
- }
-}
-
-fn parse_json(content: &str) -> serde_json::Value {
- serde_json::from_str(content).unwrap_or_default()
-}
-
-fn json_find_key<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
- if let Some(obj) = val.as_object() {
- for (_, sec_val) in obj.iter() {
- if let Some(sec_obj) = sec_val.as_object() {
- if let Some(v) = sec_obj.get(key) {
- return Some(v);
- }
- }
- }
- }
- None
-}
-
-fn parse_tag_layouts_from_config(content: &str) -> Vec<String> {
- let val = parse_json(content);
- let mut modes = vec!["cascade".to_string(); 4];
- if let Some(arr) = val.get("tag_layout").and_then(|t| t.as_array()) {
- for item in arr {
- if let (Some(tag), Some(mode)) = (
- item.get("tag").and_then(|t| t.as_u64()),
- item.get("mode").and_then(|m| m.as_str())
- ) {
- if tag >= 1 && tag <= 4 {
- modes[tag as usize - 1] = mode.to_string();
- }
- }
- }
- }
- modes
-}
-
-fn write_tag_layout(tag_num: usize, mode_str: &str) -> bool {
- let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
- let mut val = parse_json(&content);
-
- let mut found = false;
- if let Some(arr) = val.get_mut("tag_layout").and_then(|t| t.as_array_mut()) {
- for item in arr.iter_mut() {
- if item.get("tag").and_then(|t| t.as_u64()) == Some(tag_num as u64) {
- if let Some(obj) = item.as_object_mut() {
- obj.insert("mode".to_string(), serde_json::json!(mode_str.to_lowercase()));
- found = true;
- break;
- }
- }
- }
- if !found {
- arr.push(serde_json::json!({
- "tag": tag_num,
- "mode": mode_str.to_lowercase()
- }));
- }
- } else {
- let arr = vec![serde_json::json!({
- "tag": tag_num,
- "mode": mode_str.to_lowercase()
- })];
- if let Some(obj) = val.as_object_mut() {
- obj.insert("tag_layout".to_string(), serde_json::Value::Array(arr));
- }
- }
-
- if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
- return fs::write(CONFIG_PATH, updated_str).is_ok();
- }
- false
-}
-
-fn parse_u16_from(content: &str, key: &str, default: u16) -> u16 {
- let val = parse_json(content);
- json_find_key(&val, key).and_then(|v| v.as_u64()).map(|n| n as u16).unwrap_or(default)
-}
-
-fn parse_string_from(content: &str, key: &str, default: &str) -> String {
- let val = parse_json(content);
- json_find_key(&val, key).and_then(|v| v.as_str()).map(|s| s.to_string()).unwrap_or_else(|| default.to_string())
-}
-
-fn write_config_value(key: &str, value: &str) -> bool {
- super::interface::write_config_value_path(CONFIG_PATH, key, value)
-}
-
-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());
- }
-}
-
-fn apply_all_widths(s: &LayoutState) {
- let w = |k: &str, v: u16| { write_config_value(k, &v.to_string()); send_ipc_command(&format!("layout {} {}", k, v)); };
- 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("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);
-}
-
-fn apply_single_layout_param(key: &str, val: u16) {
- write_config_value(key, &val.to_string());
- send_ipc_command(&format!("layout {} {}", key, val));
-}
-
-fn apply_edge_gap(val: u16) {
- let val_str = val.to_string();
- write_config_value("gap_left", &val_str);
- write_config_value("gap_right", &val_str);
- write_config_value("gap_bottom", &val_str);
- send_ipc_command(&format!("layout gap_left {}", val));
- send_ipc_command(&format!("layout gap_right {}", val));
- send_ipc_command(&format!("layout gap_bottom {}", val));
-}
-
-#[derive(Debug, Clone)]
-#[allow(dead_code)]
-struct PreviewWindow {
- app_id: String,
- title: String,
- x: f32,
- y: f32,
- w: f32,
- h: f32,
- tags: u32,
- _minimized: bool,
- has_parent: bool,
- layout_mode: String,
-}
-
-#[allow(dead_code)]
-struct LayoutStatusInfo {
- active_tags: u32,
- focused_tags: u32,
- _num_tags: u32,
- windows: Vec<PreviewWindow>,
- focused_title: String,
- focused_layout_mode: String,
-}
-
-fn read_current_layout_status() -> LayoutStatusInfo {
- let mut active_tags = 1;
- let mut focused_tags = 1;
- let mut _num_tags = 4;
-
- let display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
-
- let tags_path = format!("/tmp/cce-tags-{}", display);
- let tags_fallback = "/tmp/cce-tags".to_string();
- let tags_content = fs::read_to_string(&tags_path)
- .or_else(|_| fs::read_to_string(&tags_fallback))
- .unwrap_or_default();
-
- if let Some(line) = tags_content.lines().next() {
- let parts: Vec<&str> = line.split_whitespace().collect();
- if parts.len() >= 3 {
- active_tags = parts[0].parse().unwrap_or(1);
- focused_tags = parts[1].parse().unwrap_or(1);
- _num_tags = parts[2].parse().unwrap_or(4);
- }
- }
-
- let title_path = format!("/tmp/cce-title-{}", display);
- let title_fallback = "/tmp/cce-title".to_string();
- let focused_title = fs::read_to_string(&title_path)
- .or_else(|_| fs::read_to_string(&title_fallback))
- .unwrap_or_default()
- .trim()
- .to_string();
-
- let layout_path = format!("/tmp/cce-layout-{}", display);
- let layout_fallback = "/tmp/cce-layout".to_string();
- let focused_layout_mode = fs::read_to_string(&layout_path)
- .or_else(|_| fs::read_to_string(&layout_fallback))
- .unwrap_or_else(|_| "Cascade".to_string())
- .trim()
- .to_string();
-
- let windows_path = format!("/tmp/cce-windows-{}", display);
- let windows_fallback = "/tmp/cce-windows".to_string();
- let windows_content = fs::read_to_string(&windows_path)
- .or_else(|_| fs::read_to_string(&windows_fallback))
- .unwrap_or_default();
-
- let mut windows = Vec::new();
- for line in windows_content.lines() {
- if !line.starts_with("window ") { continue; }
-
- let mut app_id = String::new();
- let mut title = String::new();
- let mut x = 0.0;
- let mut y = 0.0;
- let mut w = 0.0;
- let mut h = 0.0;
- let mut tags = 0;
- let mut minimized = false;
- let mut has_parent = false;
- let mut layout_mode = "Cascade".to_string();
-
- let parts = line.strip_prefix("window ").unwrap_or(line);
-
- let get_val = |p: &str, k: &str| -> Option<String> {
- if let Some(idx) = p.find(k) {
- let start = idx + k.len();
- let mut end = p.len();
- let next_keys = [
- " app_id=", " title=", " mode=", " decoration=", " presentation=",
- " tags=", " x=", " y=", " w=", " h=", " has_parent=", " minimized="
- ];
- for nk in next_keys {
- if nk != k {
- if let Some(nidx) = p[start..].find(nk) {
- end = end.min(start + nidx);
- }
- }
- }
- Some(p[start..end].trim().to_string())
- } else {
- None
- }
- };
-
- if let Some(val) = get_val(parts, "app_id=") { app_id = val; }
- if let Some(val) = get_val(parts, "title=") { title = val; }
- if let Some(val) = get_val(parts, "x=") { x = val.parse().unwrap_or(0.0); }
- if let Some(val) = get_val(parts, "y=") { y = val.parse().unwrap_or(0.0); }
- if let Some(val) = get_val(parts, "w=") { w = val.parse().unwrap_or(0.0); }
- if let Some(val) = get_val(parts, "h=") { h = val.parse().unwrap_or(0.0); }
- if let Some(val) = get_val(parts, "tags=") { tags = val.parse().unwrap_or(0); }
- if let Some(val) = get_val(parts, "minimized=") { minimized = val == "true"; }
- if let Some(val) = get_val(parts, "has_parent=") { has_parent = val == "true"; }
- if let Some(val) = get_val(parts, "mode=") { layout_mode = val; }
-
- windows.push(PreviewWindow {
- app_id,
- title,
- x,
- y,
- w,
- h,
- tags,
- _minimized: minimized,
- has_parent,
- layout_mode,
- });
- }
-
- LayoutStatusInfo {
- active_tags,
- focused_tags,
- _num_tags,
- windows,
- focused_title,
- focused_layout_mode,
- }
-}
-
-pub fn view(state: &mut LayoutState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut cce_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(9);
-
- // Current Layout Section (Read-only visual preview)
- builder.add_section(&mut final_pc, "Current Layout", false, |sec_cl| {
- sec_cl.spacing(8.0);
-
- let info = read_current_layout_status();
-
- let card_w = (sec_w - 24.0) / 2.0;
- let card_h = 135.0;
- let rx = sec_cl.left;
-
- for tag_idx in 0..4 {
- let col = tag_idx % 2;
- let row = tag_idx / 2;
- let tx = rx + 8.0 + col as f32 * (card_w + 8.0);
- let ty = sec_cl.ay() + row as f32 * (card_h + 8.0);
-
- let is_active = (info.active_tags & (1 << tag_idx)) != 0;
- let layout_idx = state.tag_layout_menus.get(tag_idx).map(|m| m.selected).unwrap_or(0);
- let mode = match layout_idx {
- 1 => PreviewLayoutMode::Cascade,
- 2 => PreviewLayoutMode::Stack,
- 3 => PreviewLayoutMode::Grid,
- 4 => PreviewLayoutMode::LeftTiled,
- 5 => PreviewLayoutMode::RightTiled,
- 6 => PreviewLayoutMode::Equal,
- 7 => PreviewLayoutMode::Spiral,
- 8 => PreviewLayoutMode::Floating,
- _ => PreviewLayoutMode::Fullscreen,
- };
-
- let mut preview = LayoutPreview::new(mode)
- .with_active(is_active)
- .with_label(&format!("TAG {}", tag_idx + 1));
- render_widget(sec_cl.pc, &mut preview, tx, ty, card_w, card_h, ctx);
- }
-
- sec_cl.content_y += 2.0 * (card_h + 8.0) + 4.0;
- });
-
- // 1. Fullscreen Section
- builder.add_section(&mut final_pc, "Fullscreen", sec_focused.get(0).copied().unwrap_or(false), |sec_fs| {
- sec_fs.spacing(8.0);
- state.spinboxes[0].set_label("Border Width");
- sec_fs.widget_full(&mut state.spinboxes[0], 44.0, ctx);
- sec_fs.spacing(8.0);
- });
-
- // 2. Cascade Section
- builder.add_section(&mut final_pc, "Cascade", sec_focused.get(1).copied().unwrap_or(false), |sec_cascade| {
- sec_cascade.spacing(8.0);
- state.spinboxes[1].set_label("Border Width");
- sec_cascade.widget_full(&mut state.spinboxes[1], 44.0, ctx);
- sec_cascade.spacing(8.0);
- state.cascade_offset_spinbox.set_label("Offset");
- sec_cascade.widget_full(&mut state.cascade_offset_spinbox, 44.0, ctx);
- sec_cascade.spacing(8.0);
- state.edge_gap_spinbox.set_label("Edge Gap");
- sec_cascade.widget_full(&mut state.edge_gap_spinbox, 44.0, ctx);
- sec_cascade.spacing(8.0);
- state.top_gap_spinbox.set_label("Top Gap");
- sec_cascade.widget_full(&mut state.top_gap_spinbox, 44.0, ctx);
- sec_cascade.spacing(8.0);
- });
-
- // 3. Grid Section
- builder.add_section(&mut final_pc, "Grid", sec_focused.get(2).copied().unwrap_or(false), |sec_grid| {
- sec_grid.spacing(8.0);
- state.spinboxes[2].set_label("Border Width");
- sec_grid.widget_full(&mut state.spinboxes[2], 44.0, ctx);
- sec_grid.spacing(8.0);
- state.grid_gap_spinbox.set_label("Gap");
- sec_grid.widget_full(&mut state.grid_gap_spinbox, 44.0, ctx);
- sec_grid.spacing(8.0);
- });
-
- // 4. Floating Section
- builder.add_section(&mut final_pc, "Floating", sec_focused.get(3).copied().unwrap_or(false), |sec_float| {
- sec_float.spacing(8.0);
- state.spinboxes[3].set_label("Border Width");
- sec_float.widget_full(&mut state.spinboxes[3], 44.0, ctx);
- sec_float.spacing(8.0);
- });
-
- // 5. Movement Section
- builder.add_section(&mut final_pc, "Movement", sec_focused.get(4).copied().unwrap_or(false), |movement_sec| {
- movement_sec.spacing(8.0);
- state.transition_duration_spinbox.set_label("Duration (ms)");
- movement_sec.widget_full(&mut state.transition_duration_spinbox, 44.0, ctx);
- movement_sec.spacing(8.0);
- });
-
- // 6. Default Layouts Section
- builder.add_section(&mut final_pc, "Default Layouts", true, |default_layouts_sec| {
- default_layouts_sec.spacing(8.0);
- for i in 0..4 {
- default_layouts_sec.widget_full(&mut state.tag_layout_menus[i], 44.0, ctx);
- default_layouts_sec.spacing(8.0);
- }
- });
-
- // 7. Side Panel Section
- builder.add_section(&mut final_pc, "Side Panel", true, |side_panel_sec| {
- side_panel_sec.spacing(8.0);
- side_panel_sec.widget_full(&mut state.side_panel_behavior_menu, 44.0, ctx);
- side_panel_sec.spacing(8.0);
- side_panel_sec.widget_full(&mut state.side_panel_position_menu, 44.0, ctx);
- side_panel_sec.spacing(8.0);
- state.side_panel_width_spinbox.set_label("Default Width");
- side_panel_sec.widget_full(&mut state.side_panel_width_spinbox, 44.0, ctx);
- side_panel_sec.spacing(8.0);
- state.side_panel_border_gap_spinbox.set_label("Border Gap");
- side_panel_sec.widget_full(&mut state.side_panel_border_gap_spinbox, 44.0, ctx);
- side_panel_sec.spacing(8.0);
- state.side_panel_border_opacity_spinbox.set_label("Border Opacity");
- side_panel_sec.widget_full(&mut state.side_panel_border_opacity_spinbox, 44.0, ctx);
- side_panel_sec.spacing(8.0);
- });
-
- // 8. Effects Section
- builder.add_section(&mut final_pc, "Effects", true, |effects_sec| {
- effects_sec.spacing(8.0);
- state.transparency_toggle.set_toggled(state.transparency_enabled);
- effects_sec.widget_full(&mut state.transparency_toggle, cce_ui::layout::toggle_height(), ctx);
- effects_sec.spacing(8.0);
- state.blur_toggle.set_toggled(state.blur_enabled);
- effects_sec.widget_full(&mut state.blur_toggle, cce_ui::layout::toggle_height(), ctx);
- effects_sec.spacing(8.0);
- });
-
-
- final_pc
-}
-
-fn set_width(state: &mut LayoutState, param: WidthParam, val: u16) {
- let val = val.min(100);
- match param {
- WidthParam::Fullscreen => state.fullscreen_border_width = val,
- WidthParam::Cascade => state.cascade_border_width = val,
- WidthParam::Grid => state.grid_border_width = val,
- WidthParam::Floating => state.floating_border_width = val,
- }
- state.spinboxes[param_idx(param)].value = val as i32;
- apply_all_widths(state);
-}
-
-fn param_idx(p: WidthParam) -> usize {
- match p {
- WidthParam::Fullscreen => 0,
- WidthParam::Cascade => 1,
- WidthParam::Grid => 2,
- WidthParam::Floating => 3,
- }
-}
-
-pub fn update(state: &mut LayoutState, msg: LayoutMessage) {
- match msg {
- LayoutMessage::SetWidth(p, v) => set_width(state, p, v),
- LayoutMessage::SetCascadeOffset(v) => {
- let val = v.min(200);
- state.cascade_offset = val;
- state.cascade_offset_spinbox.value = val as i32;
- apply_single_layout_param("cascade_offset", val);
- }
- LayoutMessage::SetEdgeGap(v) => {
- let val = v.min(200);
- state.edge_gap = val;
- state.edge_gap_spinbox.value = val as i32;
- apply_edge_gap(val);
- }
- LayoutMessage::SetTopGap(v) => {
- let val = v.min(200);
- state.top_gap = val;
- state.top_gap_spinbox.value = val as i32;
- apply_single_layout_param("gap_top", val);
- }
- LayoutMessage::SetGridGap(v) => {
- let val = v.min(200);
- state.grid_gap = val;
- state.grid_gap_spinbox.value = val as i32;
- apply_single_layout_param("grid_gap", val);
- }
- LayoutMessage::SetTransitionDuration(v) => {
- let val = v.min(2000);
- state.transition_duration = val;
- state.transition_duration_spinbox.value = val as i32;
- apply_single_layout_param("transition_duration", val);
- }
- LayoutMessage::SetStatusHeight(v) => {
- let val = v.min(100);
- state.status_height = val;
- state.status_height_spinbox.value = val as i32;
- apply_single_layout_param("bar_height", val);
- }
- LayoutMessage::SetTagLayout(tag, idx) => {
- if tag >= 1 && tag <= 4 && idx < 5 {
- state.tag_layout_menus[tag - 1].selected = idx;
- let modes = vec!["cascade", "grid", "fullscreen", "floating", "popup"];
- let mode_str = modes[idx];
- write_tag_layout(tag, mode_str);
- send_ipc_command(&format!("tag-layout {} {}", tag, mode_str));
- }
- }
- LayoutMessage::SetSidePanelBehavior(idx) => {
- if idx < 2 {
- state.side_panel_behavior_menu.selected = idx;
- let val = if idx == 0 { "above" } else { "inline" };
- write_config_value("side_panel_behavior", &format!("\"{}\"", val));
- send_ipc_command(&format!("layout side_panel_behavior {}", val));
- }
- }
- LayoutMessage::SetSidePanelPosition(idx) => {
- if idx < 2 {
- state.side_panel_position_menu.selected = idx;
- let val = if idx == 1 { "right" } else { "left" };
- write_config_value("side_panel_position", &format!("\"{}\"", val));
- send_ipc_command(&format!("layout side_panel_position {}", val));
- }
- }
- LayoutMessage::SetSidePanelWidth(v) => {
- let val = v.min(2000);
- state.side_panel_width = val;
- state.side_panel_width_spinbox.value = val as i32;
- write_config_value("side_panel_width", &val.to_string());
- send_ipc_command(&format!("layout side_panel_width {}", val));
- }
- LayoutMessage::SetSidePanelBorderGap(v) => {
- let val = v.min(500);
- state.side_panel_border_gap = val;
- state.side_panel_border_gap_spinbox.value = val as i32;
- write_config_value("side_panel_border_gap", &val.to_string());
- send_ipc_command(&format!("layout side_panel_border_gap {}", val));
- }
- LayoutMessage::SetSidePanelBorderOpacity(v) => {
- let val = v.min(100);
- state.side_panel_border_opacity = val;
- state.side_panel_border_opacity_spinbox.value = val as i32;
- write_config_value("side_panel_border_opacity", &val.to_string());
- send_ipc_command(&format!("layout side_panel_border_opacity {}", val));
- }
- LayoutMessage::ToggleTransparency => {
- state.transparency_enabled = !state.transparency_enabled;
- if state.transparency_enabled {
- write_transparency_config_value("opacity", "0.85");
- } else {
- write_transparency_config_value("opacity", "1.00");
- }
- send_ipc_command("reload");
- status_interface_reload();
- }
- LayoutMessage::ToggleBlur => {
- state.blur_enabled = !state.blur_enabled;
- let val = state.blur_enabled.to_string();
- write_config_value("window_blur", &val);
- write_config_value("border_blur", &val);
- send_ipc_command("reload");
- }
- LayoutMessage::Refreshed(new) => {
- let transparency_hover = state.transparency_toggle.hovered();
- let blur_hover = state.blur_toggle.hovered();
- *state = new;
- state.transparency_toggle.set_hovered(transparency_hover);
- state.blur_toggle.set_hovered(blur_hover);
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_tag_layouts_empty() {
- let content = "";
- let modes = parse_tag_layouts_from_config(content);
- assert_eq!(modes, vec!["cascade", "cascade", "cascade", "cascade"]);
- }
-
- #[test]
- fn test_parse_side_panel_width_default() {
- let content = "";
- let width = parse_u16_from(content, "side_panel_width", 360);
- assert_eq!(width, 360);
- }
-
- #[test]
- fn test_parse_side_panel_width_explicit() {
- let content = "{\"layout\": {\"side_panel_width\": 450}}";
- let width = parse_u16_from(content, "side_panel_width", 360);
- assert_eq!(width, 450);
- }
-
- #[test]
- fn test_parse_side_panel_border_opacity_default() {
- let content = "";
- let opacity = parse_u16_from(content, "side_panel_border_opacity", 100);
- assert_eq!(opacity, 100);
- }
-
- #[test]
- fn test_parse_side_panel_border_opacity_explicit() {
- let content = "{\"layout\": {\"side_panel_border_opacity\": 75}}";
- let opacity = parse_u16_from(content, "side_panel_border_opacity", 100);
- assert_eq!(opacity, 75);
- }
-
- #[test]
- fn test_parse_tag_layouts_single() {
- let content = r#"{
- "layout": {"gap": 10},
- "tag_layout": [
- {"tag": 2, "mode": "grid"}
- ]
- }"#;
- let modes = parse_tag_layouts_from_config(content);
- assert_eq!(modes, vec!["cascade", "grid", "cascade", "cascade"]);
- }
-
- #[test]
- fn test_parse_tag_layouts_multiple() {
- let content = r#"{
- "tag_layout": [
- {"tag": 1, "mode": "fullscreen"},
- {"tag": 4, "mode": "floating"}
- ]
- }"#;
- let modes = parse_tag_layouts_from_config(content);
- assert_eq!(modes, vec!["fullscreen", "cascade", "cascade", "floating"]);
- }
-
- #[test]
- fn test_parse_tag_layouts_out_of_bounds() {
- let content = r#"{
- "tag_layout": [
- {"tag": 5, "mode": "grid"},
- {"tag": 0, "mode": "popup"}
- ]
- }"#;
- let modes = parse_tag_layouts_from_config(content);
- assert_eq!(modes, vec!["cascade", "cascade", "cascade", "cascade"]);
- }
-
- #[test]
- fn test_view_layout_grid() {
- let mut state = LayoutState::default();
- let mut layout = cce_ui::layout::ColumnLayout::new(20.0);
- let pc = view(&mut state, 10.0, 20.0, 800.0, 600.0, &[false, false, false, false], &mut layout, &mut cce_ui::context::UiContext::new());
- assert!(!pc.rects.is_empty() || !pc.texts.is_empty());
- }
-
- #[test]
- fn test_spinbox_right_click() {
- use cce_ui::widget::Element;
- let mut state = LayoutState::default();
- let mut ctx = cce_ui::context::UiContext::new();
- let sb = &mut state.spinboxes[0];
- sb.set_rect(0.0, 0.0, 100.0, 44.0);
- let res = sb.mouse_input(
- cce_ui::widget::MouseButton::Right,
- cce_ui::widget::ElementState::Pressed,
- 50.0,
- 20.0,
- &mut ctx,
- );
- assert!(res);
- }
-
- #[test]
- #[allow(unused_assignments)]
- fn test_spinbox_right_click_crash() {
- use cce_ui::widget::Element;
- let mut state = LayoutState::default();
- let mut ctx = cce_ui::context::UiContext::new();
- let sb = &mut state.spinboxes[0];
- sb.set_rect(0.0, 0.0, 100.0, 44.0);
- let res = sb.mouse_input(
- cce_ui::widget::MouseButton::Right,
- cce_ui::widget::ElementState::Pressed,
- 50.0,
- 20.0,
- &mut ctx,
- );
- assert!(res);
- assert!(cce_ui::widget::context_menu::is_visible());
-
- // Now replace the state simulating config reload/refresh
- let new_state = LayoutState::default();
- state = new_state;
-
- // Assert that the context menu is hidden (cleared)
- assert!(!cce_ui::widget::context_menu::is_visible());
- }
-}
-
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index d96c261..ffecbab 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -1,7 +1,6 @@
pub mod audio;
pub mod network;
pub mod display;
-pub mod layout;
pub mod storage;
pub mod system_info;
pub mod keybindings;
@@ -20,7 +19,6 @@ pub enum Page {
Services,
Storage,
Display,
- Layout,
System,
Hardware,
Input,
@@ -29,7 +27,7 @@ pub enum Page {
}
impl Page {
- pub const ALL: [Page; 12] = [
+ pub const ALL: [Page; 11] = [
Page::Accounts,
Page::Audio,
Page::Display,
@@ -41,7 +39,6 @@ impl Page {
Page::Services,
Page::Storage,
Page::System,
- Page::Layout,
];
pub fn label(self) -> &'static str {
@@ -52,7 +49,6 @@ impl Page {
Page::Services => "Services",
Page::Storage => "Storage",
Page::Display => "Display",
- Page::Layout => "Window Manager",
Page::System => "System",
Page::Hardware => "Hardware",
Page::Input => "Input",