system settings
git clone https://git.lucas.co/cce-system-interface.git
Update imports and view fn for origin-relative Column from clear-ui
- Import Column from clear_ui::layout (remove local column.rs)
- Column::new now takes (pc, ox, oy, cx, cy, cw) for parent-relative coords
- Use cy=28 (was cy+12) to fix bg label / header overlap
Cargo.lock | 20 -----
Cargo.toml | 3 +-
src/app.rs | 12 +++
src/main.rs | 174 ++++++++++++++++++++++++++++++++++---
src/pages/layout.rs | 243 +++++++++++++++++++++++++---------------------------
5 files changed, 290 insertions(+), 162 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 3ef71a9..37ca0db 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -391,7 +391,6 @@ dependencies = [
"glyphon",
"pollster",
"serde",
- "taffy",
"tokio",
"wgpu",
"winit",
@@ -405,7 +404,6 @@ dependencies = [
"bytemuck",
"glyphon",
"pollster",
- "taffy",
"tokio",
"wgpu",
"winit",
@@ -968,12 +966,6 @@ dependencies = [
"bitflags 2.11.1",
]
-[[package]]
-name = "grid"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
-
[[package]]
name = "hashbrown"
version = "0.15.5"
@@ -2244,18 +2236,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "taffy"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aea22054047c16c3f34d3ac473a2170be1424b1115b2a3adcf28cfb067c88859"
-dependencies = [
- "arrayvec",
- "grid",
- "serde",
- "slotmap",
-]
-
[[package]]
name = "tempfile"
version = "3.27.0"
diff --git a/Cargo.toml b/Cargo.toml
index 54b66da..f996744 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,12 +4,11 @@ version = "0.1.0"
edition = "2021"
[dependencies]
-clear-ui = { path = "../ui-framework" }
+clear-ui = { path = "../clear-ui" }
winit = "0.30"
wgpu = "24"
bytemuck = { version = "1", features = ["derive"] }
pollster = "0.4"
-taffy = "0.10.1"
glyphon = "0.8"
tokio = { version = "1", features = ["full"] }
zbus = "5"
diff --git a/src/app.rs b/src/app.rs
index b9b0107..9c3774d 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,3 +1,5 @@
+use clear_ui::layout::RenderTarget;
+
use crate::pages::audio;
use crate::pages::display;
use crate::pages::input;
@@ -92,3 +94,13 @@ impl PageContent {
});
}
}
+
+impl RenderTarget for PageContent {
+ fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
+ self.rects.push((color, x, y, w, h));
+ }
+
+ fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
+ self.texts.push((content.to_string(), size, x, y, color));
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 0512950..47c2239 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,6 +6,7 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowAttributes};
use clear_ui::color;
+use clear_ui::widget::Widget;
use glyphon::{
Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas,
TextBounds, TextRenderer, Viewport,
@@ -80,6 +81,11 @@ struct TextItem {
color: glyphon::Color,
}
+enum ColorPickerAction {
+ Background([u8; 3]),
+ Border([u8; 3]),
+}
+
struct SystemInterface {
window: Arc<Window>,
surface: wgpu::Surface<'static>,
@@ -118,6 +124,8 @@ struct SystemInterface {
rx_system: std::sync::mpsc::Receiver<pages::system_info::SystemState>,
rx_status: std::sync::mpsc::Receiver<pages::status::StatusState>,
rx_storage: std::sync::mpsc::Receiver<pages::storage::StorageState>,
+ tx_color_picker: std::sync::mpsc::Sender<ColorPickerAction>,
+ rx_color_picker: std::sync::mpsc::Receiver<ColorPickerAction>,
scale_factor: f64,
width: u32,
@@ -209,17 +217,26 @@ impl SystemInterface {
mapped_at_creation: false,
});
- // ── Initial state ──
+ // ── Initial state (fetch all concurrently) ──
+ let (power, audio, display, network, system_info, status, storage) = tokio::join!(
+ pages::power::fetch_power_state(),
+ pages::audio::fetch_audio_state(),
+ pages::display::fetch_display_state(),
+ pages::network::fetch_network_state(),
+ pages::system_info::fetch_system_state(),
+ pages::status::fetch_status_state(),
+ pages::storage::fetch_storage_state(),
+ );
let app = AppState {
- power: pages::power::fetch_power_state().await,
- audio: pages::audio::fetch_audio_state().await,
- display: pages::display::fetch_display_state().await,
- network: pages::network::fetch_network_state().await,
+ power,
+ audio,
+ display,
+ network,
layout: pages::layout::read_layout_config(),
input: pages::input::read_input_config(),
- system_info: pages::system_info::fetch_system_state().await,
- status: pages::status::fetch_status_state().await,
- storage: pages::storage::fetch_storage_state().await,
+ system_info,
+ status,
+ storage,
current_page: Page::ALL[0],
};
@@ -270,6 +287,7 @@ impl SystemInterface {
let rx_status = spawn_bg(10, || pages::status::fetch_status_state());
let rx_storage = spawn_bg(10, || pages::storage::fetch_storage_state());
+ let (tx_color_picker, rx_color_picker) = std::sync::mpsc::channel();
let scale_factor = (window.scale_factor() as f32).max(2.0) as f64;
let mut this = Self {
window, surface, device, queue, config, render_pipeline,
@@ -282,6 +300,7 @@ impl SystemInterface {
scale_factor,
rx_power, rx_audio, rx_display, rx_network, rx_layout, rx_input,
rx_system, rx_status, rx_storage,
+ tx_color_picker, rx_color_picker,
width: size.width, height: size.height,
needs_rebuild: true,
};
@@ -341,7 +360,7 @@ impl SystemInterface {
text_items.push(TextItem {
buffer: make_text_buffer(&mut self.font_system, page.label(), 12.0 * s),
x: 16.0 * s, y: y + 9.0 * s,
- color: glyphon::Color::rgb(0x99, 0x99, 0xaa),
+ color: glyphon::Color::rgb(0x55, 0x55, 0x77),
});
}
@@ -413,14 +432,14 @@ impl SystemInterface {
self.needs_rebuild = false;
}
- fn render_page_content(&self, cx: f32, cy: f32, cw: f32, ch: f32) -> PageContent {
+ fn render_page_content(&mut self, cx: f32, cy: f32, cw: f32, ch: f32) -> PageContent {
use pages::*;
match self.app.current_page {
Page::Power => power::view(&self.app.power, cx, cy, cw, ch),
Page::Audio => audio::view(&self.app.audio, cx, cy, cw, ch),
Page::Display => display::view(&self.app.display, cx, cy, cw, ch),
Page::Radios => network::view(&self.app.network, cx, cy, cw, ch),
- Page::Layout => layout::view(&self.app.layout, cx, cy, cw, ch),
+ Page::Layout => layout::view(&mut self.app.layout, cx, cy, cw, ch),
Page::Input => input::view(&self.app.input, cx, cy, cw, ch),
Page::System => system_info::view(&self.app.system_info, cx, cy, cw, ch),
Page::Status => status::view(&self.app.status, cx, cy, cw, ch),
@@ -523,16 +542,61 @@ impl SystemInterface {
storage::update(&mut self.app.storage, storage::StorageMessage::Refreshed(s));
self.needs_rebuild = true;
}
+ while let Ok(action) = self.rx_color_picker.try_recv() {
+ match action {
+ ColorPickerAction::Background(rgb) => {
+ layout::update(&mut self.app.layout, layout::LayoutMessage::SetBackground(rgb));
+ }
+ ColorPickerAction::Border(rgb) => {
+ layout::update(&mut self.app.layout, layout::LayoutMessage::SetBorderColor(rgb));
+ }
+ }
+ self.needs_rebuild = true;
+ }
}
fn handle_action(&mut self, action: &AppAction) {
use pages::*;
match action {
+ AppAction::Layout(m) => match m {
+ layout::LayoutMessage::PickBackgroundColor => {
+ let color = self.app.layout.background_color;
+ let tx = self.tx_color_picker.clone();
+ tokio::spawn(async move {
+ let hex = format!("#{:02X}{:02X}{:02X}", color[0], color[1], color[2]);
+ if let Ok(output) = tokio::process::Command::new("clear-colors").arg(&hex).output().await {
+ let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
+ if s.starts_with('#') && s.len() >= 7 {
+ let r = u8::from_str_radix(&s[1..3], 16).unwrap_or(color[0]);
+ let g = u8::from_str_radix(&s[3..5], 16).unwrap_or(color[1]);
+ let b = u8::from_str_radix(&s[5..7], 16).unwrap_or(color[2]);
+ let _ = tx.send(ColorPickerAction::Background([r, g, b]));
+ }
+ }
+ });
+ }
+ layout::LayoutMessage::PickBorderColor => {
+ let color = self.app.layout.border_color;
+ let tx = self.tx_color_picker.clone();
+ tokio::spawn(async move {
+ let hex = format!("#{:02X}{:02X}{:02X}", color[0], color[1], color[2]);
+ if let Ok(output) = tokio::process::Command::new("clear-colors").arg(&hex).output().await {
+ let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
+ if s.starts_with('#') && s.len() >= 7 {
+ let r = u8::from_str_radix(&s[1..3], 16).unwrap_or(color[0]);
+ let g = u8::from_str_radix(&s[3..5], 16).unwrap_or(color[1]);
+ let b = u8::from_str_radix(&s[5..7], 16).unwrap_or(color[2]);
+ let _ = tx.send(ColorPickerAction::Border([r, g, b]));
+ }
+ }
+ });
+ }
+ _ => layout::update(&mut self.app.layout, m.clone()),
+ },
AppAction::Power(m) => power::update(&mut self.app.power, m.clone()),
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::Status(m) => status::update(&mut self.app.status, m.clone()),
@@ -555,9 +619,53 @@ impl SystemInterface {
changed = true;
}
}
+ if self.app.current_page == Page::Layout {
+ let s = self.scale_factor as f32;
+ for sb in &mut self.app.layout.spinboxes {
+ if sb.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ changed = true;
+ }
+ }
+ for cp in &mut self.app.layout.color_pickers {
+ if cp.cursor_moved(self.cursor_x / s, self.cursor_y / s) {
+ changed = true;
+ }
+ }
+ }
if changed { self.needs_rebuild = true; }
changed
}
+ WindowEvent::KeyboardInput { event, .. } => {
+ if self.app.current_page == Page::Layout {
+ let mut changed = false;
+ let mut actions = Vec::new();
+ for sb in &mut self.app.layout.spinboxes {
+ if sb.keyboard_input(event) {
+ changed = true;
+ }
+ }
+ for (i, cp) in self.app.layout.color_pickers.iter_mut().enumerate() {
+ let old = cp.color;
+ if cp.keyboard_input(event) {
+ if cp.color != old {
+ actions.push(AppAction::Layout(match i {
+ 0 => pages::layout::LayoutMessage::SetBackground(cp.color),
+ _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
+ }));
+ }
+ changed = true;
+ }
+ }
+ for a in &actions {
+ self.handle_action(a);
+ }
+ if changed {
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
+ false
+ }
WindowEvent::MouseInput { state, button, .. } => {
if *button != MouseButton::Left { return false; }
if *state == ElementState::Released {
@@ -581,6 +689,48 @@ impl SystemInterface {
}
}
}
+ if *state == ElementState::Pressed && self.app.current_page == Page::Layout {
+ let s = self.scale_factor as f32;
+ let lx = self.cursor_x / s;
+ let ly = self.cursor_y / s;
+ let mut actions = Vec::new();
+ for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(*button, *state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Layout(
+ pages::layout::LayoutMessage::SetWidth(
+ pages::layout::WidthParam::ALL[i],
+ sb.value as u16,
+ )
+ ));
+ }
+ }
+ for (i, cp) in self.app.layout.color_pickers.iter_mut().enumerate() {
+ let old = cp.color;
+ if !cp.hit_test(lx, ly) { cp.unfocus(); }
+ cp.mouse_input(*button, *state, lx, ly);
+ if cp.take_click() {
+ actions.push(AppAction::Layout(match i {
+ 0 => pages::layout::LayoutMessage::PickBackgroundColor,
+ _ => pages::layout::LayoutMessage::PickBorderColor,
+ }));
+ }
+ if cp.color != old {
+ actions.push(AppAction::Layout(match i {
+ 0 => pages::layout::LayoutMessage::SetBackground(cp.color),
+ _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
+ }));
+ }
+ }
+ for a in &actions {
+ self.handle_action(a);
+ }
+ if !actions.is_empty() {
+ self.needs_rebuild = true;
+ return true;
+ }
+ }
self.needs_rebuild = true;
true
}
diff --git a/src/pages/layout.rs b/src/pages/layout.rs
index 560e6a5..e940f50 100644
--- a/src/pages/layout.rs
+++ b/src/pages/layout.rs
@@ -1,11 +1,56 @@
use std::fs;
use std::io::Write;
-use crate::app::{AppAction, PageContent};
+use crate::app::PageContent;
+use clear_ui::layout::Column;
+use clear_ui::widget::{ColorPicker, Spinbox};
const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
const CLEARWM_SOCK: &str = "/tmp/clearwm.sock";
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum WidthParam {
+ Fullscreen, Cascade, Grid, Vsplit, Hsplit, Floating,
+}
+
+impl WidthParam {
+ pub const ALL: [WidthParam; 6] = [
+ WidthParam::Fullscreen, WidthParam::Cascade, WidthParam::Grid,
+ WidthParam::Vsplit, WidthParam::Hsplit, WidthParam::Floating,
+ ];
+ pub fn key(self) -> &'static str {
+ match self {
+ WidthParam::Fullscreen => "fullscreen_border_width",
+ WidthParam::Cascade => "cascade_border_width",
+ WidthParam::Grid => "grid_border_width",
+ WidthParam::Vsplit => "vsplit_border_width",
+ WidthParam::Hsplit => "hsplit_border_width",
+ WidthParam::Floating => "floating_border_width",
+ }
+ }
+ pub fn label(self) -> &'static str {
+ match self {
+ WidthParam::Fullscreen => "Fullscreen",
+ WidthParam::Cascade => "Cascade",
+ WidthParam::Grid => "Grid",
+ WidthParam::Vsplit => "Vsplit",
+ WidthParam::Hsplit => "Hsplit",
+ WidthParam::Floating => "Floating",
+ }
+ }
+}
+
+fn make_spinboxes(fs: u16, ca: u16, g: u16, v: u16, h: u16, fl: u16) -> Vec<Spinbox> {
+ vec![
+ Spinbox::new(fs as i32, 0, 100, 1),
+ Spinbox::new(ca as i32, 0, 100, 1),
+ Spinbox::new(g as i32, 0, 100, 1),
+ Spinbox::new(v as i32, 0, 100, 1),
+ Spinbox::new(h as i32, 0, 100, 1),
+ Spinbox::new(fl as i32, 0, 100, 1),
+ ]
+}
+
#[derive(Debug, Clone)]
pub struct LayoutState {
pub background_color: [u8; 3],
@@ -17,6 +62,8 @@ pub struct LayoutState {
pub hsplit_border_width: u16,
pub floating_border_width: u16,
pub color_options: Vec<(&'static str, [u8; 3])>,
+ pub spinboxes: Vec<Spinbox>,
+ pub color_pickers: Vec<ColorPicker>,
}
impl Default for LayoutState {
@@ -31,6 +78,11 @@ impl Default for LayoutState {
hsplit_border_width: 6,
floating_border_width: 6,
color_options: preset_colors(),
+ spinboxes: make_spinboxes(0, 6, 6, 6, 6, 6),
+ color_pickers: vec![
+ ColorPicker::new([0x0a, 0x1a, 0x0e]).with_label("Desktop Background"),
+ ColorPicker::new([0x3e, 0x3e, 0x3e]).with_label("Border Color"),
+ ],
}
}
}
@@ -39,12 +91,9 @@ impl Default for LayoutState {
pub enum LayoutMessage {
SetBackground([u8; 3]),
SetBorderColor([u8; 3]),
- FullscreenDown, FullscreenUp,
- CascadeDown, CascadeUp,
- GridDown, GridUp,
- VsplitDown, VsplitUp,
- HsplitDown, HsplitUp,
- FloatingDown, FloatingUp,
+ PickBackgroundColor,
+ PickBorderColor,
+ SetWidth(WidthParam, u16),
Refreshed(LayoutState),
}
@@ -67,16 +116,29 @@ fn preset_colors() -> Vec<(&'static str, [u8; 3])> {
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 v = parse_u16_from(&content, "vsplit_border_width", 6);
+ let h = parse_u16_from(&content, "hsplit_border_width", 6);
+ let fl = parse_u16_from(&content, "floating_border_width", 6);
LayoutState {
background_color: parse_color_from_key(&content, "background_color", [0x0a, 0x1a, 0x0e]),
border_color: parse_color_from_key(&content, "border_color", [0x3e, 0x3e, 0x3e]),
- fullscreen_border_width: parse_u16_from(&content, "fullscreen_border_width", 0),
- cascade_border_width: parse_u16_from(&content, "cascade_border_width", 6),
- grid_border_width: parse_u16_from(&content, "grid_border_width", 6),
- vsplit_border_width: parse_u16_from(&content, "vsplit_border_width", 6),
- hsplit_border_width: parse_u16_from(&content, "hsplit_border_width", 6),
- floating_border_width: parse_u16_from(&content, "floating_border_width", 6),
+ fullscreen_border_width: fs,
+ cascade_border_width: ca,
+ grid_border_width: g,
+ vsplit_border_width: v,
+ hsplit_border_width: h,
+ floating_border_width: fl,
color_options: preset_colors(),
+ spinboxes: make_spinboxes(fs, ca, g, v, h, fl),
+ color_pickers: vec![
+ ColorPicker::new(parse_color_from_key(&content, "background_color", [0x0a, 0x1a, 0x0e]))
+ .with_label("Desktop Background"),
+ ColorPicker::new(parse_color_from_key(&content, "border_color", [0x3e, 0x3e, 0x3e]))
+ .with_label("Border Color"),
+ ],
}
}
@@ -170,122 +232,57 @@ fn apply_all_widths(s: &LayoutState) {
w("floating_border_width", s.floating_border_width);
}
-fn rgb_float(c: [u8; 3]) -> [f32; 4] {
- [c[0] as f32 / 255.0, c[1] as f32 / 255.0, c[2] as f32 / 255.0, 1.0]
-}
-
-const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
-const ACCENT: [f32; 4] = [0.36, 0.56, 0.38, 1.0];
-const BTN_ACTIVE: [f32; 4] = [0.20, 0.40, 0.22, 1.0];
-const BTN_INACTIVE: [f32; 4] = [0.13, 0.18, 0.14, 1.0];
-const BTN_HOVER: [f32; 4] = [0.25, 0.30, 0.26, 1.0];
-const SECTION_BORDER: [f32; 4] = [0.18, 0.18, 0.27, 1.0];
-const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
-pub fn view(state: &LayoutState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut LayoutState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
let mut pc = PageContent::new();
- let mut y = cy + 12.0;
+ let mut col = Column::new(&mut pc, cx, cy, 0.0, 28.0, cw);
- // ── Background Color ──
- pc.text("Desktop Background", cx + 12.0, y, 14.0, TEXT_FG);
- y += 22.0;
-
- let hex = format!("#{:02x}{:02x}{:02x}", state.background_color[0], state.background_color[1], state.background_color[2]);
- pc.text(&format!("Current: {}", hex), cx + 14.0, y, 12.0, ACCENT);
- y += 18.0;
-
- // Color swatch grid
- let swatch_w = 48.0;
- let swatch_h = 24.0;
- let gap = 6.0;
- let cols = 4usize;
- let total_w = cols as f32 * (swatch_w + gap);
- let start_x = cx + (cw - total_w) / 2.0;
-
- for (i, (name, rgb)) in state.color_options.iter().enumerate() {
- let col = i % cols;
- let row = i / cols;
- let sx = start_x + col as f32 * (swatch_w + gap);
- let sy = y + row as f32 * (swatch_h + 16.0);
- let is_selected = *rgb == state.background_color;
- let border = if is_selected { ACCENT } else { [0.24, 0.24, 0.24, 1.0] };
-
- pc.rect(rgb_float(*rgb), sx, sy, swatch_w, swatch_h);
- if is_selected {
- pc.rect(border, sx - 1.0, sy - 1.0, swatch_w + 2.0, swatch_h + 2.0);
- }
- pc.text(name, sx + swatch_w / 2.0 - name.len() as f32 * 2.5, sy + swatch_h + 2.0, 8.0, TEXT_DIM);
- // Clickable over the swatch
- let action = AppAction::Layout(LayoutMessage::SetBackground(*rgb));
- pc.button("", sx, sy, swatch_w, swatch_h,
- [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], action);
+ state.color_pickers[0].color = state.background_color;
+ col.widget(&mut state.color_pickers[0], 12.0, 220.0, 22.0);
+ col.spacing(16.0);
+ col.separator();
+ col.spacing(12.0);
+ state.color_pickers[1].color = state.border_color;
+ col.widget(&mut state.color_pickers[1], 12.0, 220.0, 22.0);
+ col.separator();
+ col.header("Border Width", 12.0);
+ for (i, param) in WidthParam::ALL.iter().enumerate() {
+ col.row(30.0, |row| {
+ row.text(param.label(), 14.0, 6.0, 12.0, TEXT_DIM);
+ row.widget(&mut state.spinboxes[i], 110.0, 90.0, 26.0);
+ });
}
- let swatch_rows = (state.color_options.len() + cols - 1) / cols;
- y += swatch_rows as f32 * (swatch_h + 16.0) + 8.0;
-
- // ── Border Color ──
- pc.rect(SECTION_BORDER, cx + 8.0, y, cw - 16.0, 1.0);
- y += 8.0;
- pc.text("Border Color", cx + 12.0, y, 14.0, TEXT_FG);
- y += 22.0;
- for (i, (name, rgb)) in state.color_options.iter().enumerate() {
- let col = i % cols;
- let row = i / cols;
- let sx = start_x + col as f32 * (swatch_w + gap);
- let sy = y + row as f32 * (swatch_h + 16.0);
- let is_selected = *rgb == state.border_color;
- let border = if is_selected { ACCENT } else { [0.24, 0.24, 0.24, 1.0] };
+ pc
+}
- pc.rect(rgb_float(*rgb), sx, sy, swatch_w, swatch_h);
- if is_selected {
- pc.rect(border, sx - 1.0, sy - 1.0, swatch_w + 2.0, swatch_h + 2.0);
- }
- pc.text(name, sx + swatch_w / 2.0 - name.len() as f32 * 2.5, sy + swatch_h + 2.0, 8.0, TEXT_DIM);
- let action = AppAction::Layout(LayoutMessage::SetBorderColor(*rgb));
- pc.button("", sx, sy, swatch_w, swatch_h,
- [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], action);
+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::Vsplit => state.vsplit_border_width = val,
+ WidthParam::Hsplit => state.hsplit_border_width = val,
+ WidthParam::Floating => state.floating_border_width = val,
}
- y += swatch_rows as f32 * (swatch_h + 16.0) + 8.0;
-
- // ── Border Widths ──
- pc.rect(SECTION_BORDER, cx + 8.0, y, cw - 16.0, 1.0);
- y += 8.0;
- pc.text("Border Width", cx + 12.0, y, 14.0, TEXT_FG);
- y += 22.0;
-
- let widths: [(&str, u16, AppAction, AppAction); 6] = [
- ("Fullscreen", state.fullscreen_border_width,
- AppAction::Layout(LayoutMessage::FullscreenDown), AppAction::Layout(LayoutMessage::FullscreenUp)),
- ("Cascade", state.cascade_border_width,
- AppAction::Layout(LayoutMessage::CascadeDown), AppAction::Layout(LayoutMessage::CascadeUp)),
- ("Grid", state.grid_border_width,
- AppAction::Layout(LayoutMessage::GridDown), AppAction::Layout(LayoutMessage::GridUp)),
- ("Vsplit", state.vsplit_border_width,
- AppAction::Layout(LayoutMessage::VsplitDown), AppAction::Layout(LayoutMessage::VsplitUp)),
- ("Hsplit", state.hsplit_border_width,
- AppAction::Layout(LayoutMessage::HsplitDown), AppAction::Layout(LayoutMessage::HsplitUp)),
- ("Floating", state.floating_border_width,
- AppAction::Layout(LayoutMessage::FloatingDown), AppAction::Layout(LayoutMessage::FloatingUp)),
- ];
+ state.spinboxes[param_idx(param)].value = val as i32;
+ apply_all_widths(state);
+}
- for (name, val, down, up) in &widths {
- pc.text(name, cx + 14.0, y + 6.0, 12.0, TEXT_DIM);
- pc.button("-1", cx + cw - 100.0, y, 30.0, 26.0,
- BTN_INACTIVE, BTN_HOVER, WHITE, down.clone());
- pc.text(&format!(" {}px ", val), cx + cw - 66.0, y + 6.0, 13.0, TEXT_FG);
- pc.button("+1", cx + cw - 38.0, y, 30.0, 26.0,
- BTN_ACTIVE, BTN_HOVER, WHITE, up.clone());
- y += 30.0;
+fn param_idx(p: WidthParam) -> usize {
+ match p {
+ WidthParam::Fullscreen => 0,
+ WidthParam::Cascade => 1,
+ WidthParam::Grid => 2,
+ WidthParam::Vsplit => 3,
+ WidthParam::Hsplit => 4,
+ WidthParam::Floating => 5,
}
-
- pc
}
pub fn update(state: &mut LayoutState, msg: LayoutMessage) {
- let dec = |v: &mut u16| { if *v > 0 { *v -= 1; } };
- let inc = |v: &mut u16| { if *v < 100 { *v += 1; } };
match msg {
LayoutMessage::SetBackground(rgb) => {
state.background_color = rgb;
@@ -295,18 +292,8 @@ pub fn update(state: &mut LayoutState, msg: LayoutMessage) {
state.border_color = rgb;
apply_border_color(rgb);
}
- LayoutMessage::FullscreenDown => { dec(&mut state.fullscreen_border_width); apply_all_widths(state); }
- LayoutMessage::FullscreenUp => { inc(&mut state.fullscreen_border_width); apply_all_widths(state); }
- LayoutMessage::CascadeDown => { dec(&mut state.cascade_border_width); apply_all_widths(state); }
- LayoutMessage::CascadeUp => { inc(&mut state.cascade_border_width); apply_all_widths(state); }
- LayoutMessage::GridDown => { dec(&mut state.grid_border_width); apply_all_widths(state); }
- LayoutMessage::GridUp => { inc(&mut state.grid_border_width); apply_all_widths(state); }
- LayoutMessage::VsplitDown => { dec(&mut state.vsplit_border_width); apply_all_widths(state); }
- LayoutMessage::VsplitUp => { inc(&mut state.vsplit_border_width); apply_all_widths(state); }
- LayoutMessage::HsplitDown => { dec(&mut state.hsplit_border_width); apply_all_widths(state); }
- LayoutMessage::HsplitUp => { inc(&mut state.hsplit_border_width); apply_all_widths(state); }
- LayoutMessage::FloatingDown => { dec(&mut state.floating_border_width); apply_all_widths(state); }
- LayoutMessage::FloatingUp => { inc(&mut state.floating_border_width); apply_all_widths(state); }
+ LayoutMessage::PickBackgroundColor | LayoutMessage::PickBorderColor => {}
+ LayoutMessage::SetWidth(p, v) => set_width(state, p, v),
LayoutMessage::Refreshed(new) => { *state = new; }
}
}