system settings
git clone https://git.lucas.co/cce-system-interface.git
Update system configuration and interface modules
Cargo.lock | 1 +
src/main.rs | 95 +++++++++++++++++++++-----
src/pages/input.rs | 183 +++++++++++++++++++++++++++-----------------------
src/pages/typeface.rs | 32 +--------
4 files changed, 182 insertions(+), 129 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index b235c80..08b9097 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -355,6 +355,7 @@ dependencies = [
"pollster",
"raw-window-handle",
"resvg",
+ "serde",
"smithay-client-toolkit",
"tokio",
"wayland-client",
diff --git a/src/main.rs b/src/main.rs
index 03c603d..281a0f1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,7 @@
use std::sync::Arc;
use clear_ui::color;
-use clear_ui::widget::{Spinbox, Widget};
+use clear_ui::widget::{Spinbox, Widget, Finger};
use glyphon::{
Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas,
TextBounds, TextRenderer, Viewport,
@@ -18,7 +18,7 @@ use smithay_client_toolkit::{
output::{OutputHandler, OutputState},
seat::{
keyboard::KeyboardHandler,
- pointer::PointerHandler,
+ pointer::{PointerHandler, ThemedPointer, ThemeSpec, CursorIcon},
Capability, SeatHandler, SeatState,
},
shell::{
@@ -216,7 +216,7 @@ struct SystemInterface {
rx_network: std::sync::mpsc::Receiver<pages::network::NetworkState>,
rx_layout: std::sync::mpsc::Receiver<pages::layout::LayoutState>,
rx_input: std::sync::mpsc::Receiver<pages::input::InputState>,
- rx_fingers: std::sync::mpsc::Receiver<Vec<pages::input::Finger>>,
+ rx_fingers: std::sync::mpsc::Receiver<Vec<Finger>>,
rx_hardware: std::sync::mpsc::Receiver<pages::hardware::HardwareState>,
rx_system: std::sync::mpsc::Receiver<pages::system_info::SystemState>,
rx_status: std::sync::mpsc::Receiver<pages::status::StatusState>,
@@ -394,7 +394,7 @@ impl SystemInterface {
rx
};
let rx_fingers = {
- let (tx, rx) = std::sync::mpsc::channel::<Vec<pages::input::Finger>>();
+ let (tx, rx) = std::sync::mpsc::channel::<Vec<Finger>>();
tokio::spawn(async move {
let socket_path = "/tmp/clear-input-coords.sock";
loop {
@@ -403,7 +403,7 @@ impl SystemInterface {
let reader = tokio::io::BufReader::new(stream);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
- if let Ok(fingers) = serde_json::from_str::<Vec<pages::input::Finger>>(&line) {
+ if let Ok(fingers) = serde_json::from_str::<Vec<Finger>>(&line) {
if tx.send(fingers).is_err() {
return;
}
@@ -478,6 +478,19 @@ impl SystemInterface {
this
}
+fn collect_popover_rects(w: &dyn clear_ui::widget::Widget, popovers: &mut Vec<(f32, f32, f32, f32)>) {
+ if let Some(rect) = w.popover_rect() {
+ popovers.push(rect);
+ }
+ for child_ptr in w.children() {
+ unsafe {
+ if let Some(child) = child_ptr.as_ref() {
+ Self::collect_popover_rects(child, popovers);
+ }
+ }
+ }
+}
+
fn rebuild_layout(&mut self, sw: f32, sh: f32) {
let s = self.scale_factor as f32;
let mut widgets = Vec::new();
@@ -538,7 +551,24 @@ impl SystemInterface {
});
// Page content in LOGICAL coordinates, then scale to physical
- let pc = self.render_page_content(lcx, lcy, lcw, lch);
+ let mut pc = self.render_page_content(lcx, lcy, lcw, lch);
+
+ let mut popovers = Vec::new();
+ Self::collect_popover_rects(&self.page_root_container, &mut popovers);
+
+ if !popovers.is_empty() {
+ pc.texts.retain(|(text, size, tx, ty, _, _)| {
+ let text_w = text.chars().count() as f32 * *size * 0.65;
+ for &(px, py, pw, ph) in &popovers {
+ let x_overlap = *tx <= px + pw && (*tx + text_w) >= px;
+ let y_overlap = *ty <= py + ph && (*ty + *size) >= py;
+ if x_overlap && y_overlap {
+ return false;
+ }
+ }
+ true
+ });
+ }
let mut max_y = 0.0f32;
for (_, _, y, _, h) in &pc.rects {
@@ -773,20 +803,24 @@ impl SystemInterface {
link_parent_child(&mut self.page_root_container, &mut self.app.notifications.duration_spinbox);
}
Page::Input => {
- self.page_sec_containers.resize_with(3, clear_ui::widget::Container::new);
+ self.page_sec_containers.resize_with(4, clear_ui::widget::Container::new);
link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[0]);
link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[1]);
link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[2]);
+ link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[3]);
link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_speed_spinbox);
link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_profile_menu);
link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.rate_spinbox);
link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.delay_spinbox);
+
+ link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.cursor_theme_menu);
+ link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.cursor_size_spinbox);
- link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.scroll_friction_spinbox);
- link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.pointer_friction_spinbox);
- link_parent_child(&mut self.page_sec_containers[2], &mut self.app.input.trackpad_friction_spinbox);
+ link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.scroll_friction_spinbox);
+ link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.pointer_friction_spinbox);
+ link_parent_child(&mut self.page_sec_containers[3], &mut self.app.input.trackpad_friction_spinbox);
}
Page::Audio => {
self.page_sec_containers.resize_with(2, clear_ui::widget::Container::new);
@@ -1514,6 +1548,12 @@ impl SystemInterface {
if sb.mouse_input(button, state, lx, ly) && sb.value != old {
actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpointAccelSpeed));
}
+ let sb = &mut self.app.input.cursor_size_spinbox;
+ if !sb.hit_test(lx, ly) { sb.unfocus(); }
+ let old = sb.value;
+ if sb.mouse_input(button, state, lx, ly) && sb.value != old {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyCursorSize));
+ }
}
if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Notifications {
let sb = &mut self.app.notifications.duration_spinbox;
@@ -1557,6 +1597,14 @@ impl SystemInterface {
if state == clear_ui::widget::ElementState::Pressed && menu.take_change() {
actions.push(AppAction::Input(pages::input::InputMessage::ApplyTrackpointAccelProfile(menu.selected)));
}
+ let menu = &mut self.app.input.cursor_theme_menu;
+ if state == clear_ui::widget::ElementState::Pressed && !menu.hit_test(lx, ly) { menu.unfocus(); }
+ if menu.mouse_input(button, state, lx, ly) {
+ self.needs_rebuild = true;
+ }
+ if state == clear_ui::widget::ElementState::Pressed && menu.take_change() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ApplyCursorTheme(menu.selected)));
+ }
}
if self.app.current_page == Page::Notifications {
let toggle = &mut self.app.notifications.enable_toggle;
@@ -1858,9 +1906,7 @@ impl SystemInterface {
if self.app.current_page == Page::Input {
let input = &self.app.input;
- if lx >= input.trackpad_x && lx <= input.trackpad_x + input.trackpad_w
- && ly >= input.trackpad_y && ly <= input.trackpad_y + input.trackpad_h
- {
+ if input.is_over_trackpad(lx, ly) {
return true;
}
}
@@ -2073,6 +2119,11 @@ impl SystemInterface {
self.needs_rebuild = true;
return true;
}
+ if self.app.input.cursor_size_spinbox.keyboard_input(event) {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyCursorSize));
+ self.needs_rebuild = true;
+ return true;
+ }
}
if self.app.current_page == Page::Audio {
let mut actions = Vec::new();
@@ -2430,7 +2481,7 @@ struct App {
output_state: OutputState,
seats: Vec<wl_seat::WlSeat>,
- pointer: Option<wl_pointer::WlPointer>,
+ pointer: Option<ThemedPointer>,
keyboard: Option<wl_keyboard::WlKeyboard>,
window: Option<XdgWindow>,
@@ -2527,8 +2578,15 @@ impl SeatHandler for App {
capability: Capability,
) {
if capability == Capability::Pointer && self.pointer.is_none() {
- let pointer = self.seat_state.get_pointer(qh, &seat).unwrap();
- self.pointer = Some(pointer);
+ let surface = self.compositor_state.create_surface(qh);
+ let themed_pointer = self.seat_state.get_pointer_with_theme(
+ qh,
+ &seat,
+ self.shm_state.wl_shm(),
+ surface,
+ ThemeSpec::System,
+ ).unwrap();
+ self.pointer = Some(themed_pointer);
}
if capability == Capability::Keyboard && self.keyboard.is_none() {
let keyboard = self.seat_state.get_keyboard(qh, &seat, None).unwrap();
@@ -2621,6 +2679,11 @@ impl PointerHandler for App {
}
}
}
+ PointerEventKind::Enter { .. } => {
+ if let Some(ref themed_pointer) = self.pointer {
+ let _ = themed_pointer.set_cursor(_conn, CursorIcon::Default);
+ }
+ }
_ => {}
}
}
diff --git a/src/pages/input.rs b/src/pages/input.rs
index 7d0859b..2aeefb6 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -3,7 +3,7 @@ use std::io::Write;
use crate::app::PageContent;
use clear_ui::layout::Section;
-use clear_ui::widget::{Dropdown, Spinbox, Toggle, Widget};
+use clear_ui::widget::{Dropdown, Spinbox, Toggle, Widget, Finger, Trackpad};
const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
@@ -14,13 +14,6 @@ fn get_socket_path() -> String {
}
}
-#[derive(Debug, Clone, serde::Deserialize)]
-pub struct Finger {
- pub slot: usize,
- pub x: f32,
- pub y: f32,
-}
-
#[derive(Debug, Clone)]
pub struct Keybind {
pub mods: String,
@@ -39,10 +32,7 @@ pub struct InputState {
pub tap_toggle: Toggle,
pub keybinds: Vec<Keybind>,
pub fingers: Vec<Finger>,
- pub trackpad_x: f32,
- pub trackpad_y: f32,
- pub trackpad_w: f32,
- pub trackpad_h: f32,
+ pub trackpad: Trackpad,
// Inertial settings
pub inertial_scroll: bool,
@@ -67,10 +57,48 @@ pub struct InputState {
pub dwtp_toggle: Toggle,
pub trackpoint_accel_speed_spinbox: Spinbox,
pub trackpoint_accel_profile_menu: Dropdown,
+
+ // Cursor settings
+ pub cursor_theme: String,
+ pub cursor_size: u32,
+ pub cursor_theme_menu: Dropdown,
+ pub cursor_size_spinbox: Spinbox,
+ pub cursor_themes: Vec<String>,
+}
+
+fn scan_cursor_themes() -> Vec<String> {
+ let mut themes = vec!["default".to_string()];
+ let paths = [
+ "/usr/share/icons",
+ "/home/lsgalante/.icons",
+ "/home/lsgalante/.local/share/icons",
+ ];
+ for base_path in &paths {
+ if let Ok(entries) = std::fs::read_dir(base_path) {
+ for entry in entries.flatten() {
+ if let Ok(file_type) = entry.file_type() {
+ if file_type.is_dir() {
+ let path = entry.path();
+ if path.join("cursors").is_dir() {
+ if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
+ let name_str = name.to_string();
+ if !themes.contains(&name_str) {
+ themes.push(name_str);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ themes.sort();
+ themes
}
impl Default for InputState {
fn default() -> Self {
+ let cursor_themes = vec!["default".to_string()];
Self {
tap_to_click: false,
repeat_rate: 50,
@@ -80,10 +108,7 @@ impl Default for InputState {
tap_toggle: Toggle::new().with_label("Tap to Click"),
keybinds: Vec::new(),
fingers: Vec::new(),
- trackpad_x: 0.0,
- trackpad_y: 0.0,
- trackpad_w: 0.0,
- trackpad_h: 0.0,
+ trackpad: Trackpad::new(),
inertial_scroll: true,
scroll_friction: 90,
@@ -106,14 +131,20 @@ impl Default for InputState {
dwtp_toggle: Toggle::new().with_label("Disable While Trackpointing"),
trackpoint_accel_speed_spinbox: Spinbox::new(5, -10, 10, 1).with_label("Acceleration Speed").with_decimals(1),
trackpoint_accel_profile_menu: Dropdown::new(vec!["flat".to_string(), "adaptive".to_string()], 0).with_label("Acceleration Profile"),
+
+ // Cursor settings defaults
+ cursor_theme: "default".to_string(),
+ cursor_size: 24,
+ cursor_theme_menu: Dropdown::new(cursor_themes.clone(), 0).with_label("Cursor Theme"),
+ cursor_size_spinbox: Spinbox::new(24, 16, 64, 4).with_label("Cursor Size").with_unit("px"),
+ cursor_themes,
}
}
}
impl InputState {
pub fn is_over_trackpad(&self, lx: f32, ly: f32) -> bool {
- lx >= self.trackpad_x && lx <= self.trackpad_x + self.trackpad_w
- && ly >= self.trackpad_y && ly <= self.trackpad_y + self.trackpad_h
+ self.trackpad.hit_test(lx, ly)
}
}
@@ -134,6 +165,9 @@ pub enum InputMessage {
ToggleDwtp,
ApplyTrackpointAccelSpeed,
ApplyTrackpointAccelProfile(usize),
+
+ ApplyCursorTheme(usize),
+ ApplyCursorSize,
}
pub fn read_input_config() -> InputState {
@@ -156,6 +190,11 @@ pub fn read_input_config() -> InputState {
let speed_val = (trackpoint_accel_speed * 10.0).round() as i32;
let profile_idx = if trackpoint_accel_profile == "adaptive" { 1 } else { 0 };
+ let cursor_theme = parse_string_key(&content, "cursor_theme", "default");
+ let cursor_size = parse_u16_key(&content, "cursor_size", 24);
+ let cursor_themes = scan_cursor_themes();
+ let theme_idx = cursor_themes.iter().position(|t| t == &cursor_theme).unwrap_or(0);
+
InputState {
tap_to_click: tap,
repeat_rate: rate,
@@ -165,10 +204,7 @@ pub fn read_input_config() -> InputState {
tap_toggle: Toggle::new().with_label("Tap to Click"),
keybinds: parse_keybinds(&content),
fingers: Vec::new(),
- trackpad_x: 0.0,
- trackpad_y: 0.0,
- trackpad_w: 0.0,
- trackpad_h: 0.0,
+ trackpad: Trackpad::new(),
inertial_scroll,
scroll_friction,
@@ -191,6 +227,13 @@ pub fn read_input_config() -> InputState {
dwtp_toggle: Toggle::new().with_label("Disable While Trackpointing"),
trackpoint_accel_speed_spinbox: Spinbox::new(speed_val, -10, 10, 1).with_label("Acceleration Speed").with_decimals(1),
trackpoint_accel_profile_menu: Dropdown::new(vec!["flat".to_string(), "adaptive".to_string()], profile_idx).with_label("Acceleration Profile"),
+
+ // Cursor settings
+ cursor_theme: cursor_theme.clone(),
+ cursor_size: cursor_size as u32,
+ cursor_theme_menu: Dropdown::new(cursor_themes.clone(), theme_idx).with_label("Cursor Theme"),
+ cursor_size_spinbox: Spinbox::new(cursor_size as i32, 16, 64, 4).with_label("Cursor Size").with_unit("px"),
+ cursor_themes,
}
}
@@ -276,7 +319,8 @@ fn write_config_value(key: &str, value: &str) {
if !found {
let section = if key == "tap_to_click" || key == "dwtp"
- || key == "trackpoint_accel_speed" || key == "trackpoint_accel_profile" {
+ || key == "trackpoint_accel_speed" || key == "trackpoint_accel_profile"
+ || key == "cursor_theme" || key == "cursor_size" {
"[input]"
} else if key == "inertial_scroll" || key == "scroll_friction"
|| key == "inertial_pointer" || key == "pointer_friction"
@@ -336,45 +380,12 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
sec.widget(&mut pc, &mut state.tap_toggle, 14.0, toggle_w, toggle_h);
sec.spacing(8.0);
- // Left-aligned trackpad visualizer box
+ // Built-in trackpad visualizer widget
let pad_w = 280.0;
let pad_h = 140.0;
- let pad_x = sec.ax(14.0);
- let pad_y = sec.ay();
-
- state.trackpad_x = pad_x;
- state.trackpad_y = pad_y;
- state.trackpad_w = pad_w;
- state.trackpad_h = pad_h;
-
- // Background of trackpad: sleek dark translucent blue/grey
- pc.rect([0.11, 0.11, 0.16, 0.85], pad_x, pad_y, pad_w, pad_h);
-
- // Border: clean border
- let border_color = [0.28, 0.28, 0.38, 1.0];
- pc.rect(border_color, pad_x, pad_y, pad_w, 1.0);
- pc.rect(border_color, pad_x, pad_y + pad_h - 1.0, pad_w, 1.0);
- pc.rect(border_color, pad_x, pad_y, 1.0, pad_h);
- pc.rect(border_color, pad_x + pad_w - 1.0, pad_y, 1.0, pad_h);
-
- // Sleek label in the touchpad area
- pc.text("Touchpad Area", pad_x + 12.0, pad_y + pad_h - 22.0, 11.0, [0.45, 0.45, 0.55, 1.0]);
-
- // Active fingers visualizer
- for finger in &state.fingers {
- let rx = finger.x.clamp(0.0, 1.0);
- let ry = finger.y.clamp(0.0, 1.0);
- let fx = pad_x + rx * pad_w;
- let fy = pad_y + ry * pad_h;
- let dot_size = 12.0;
-
- // Render glow (outer light blue rectangle)
- pc.rect([0.35, 0.55, 0.95, 0.4], fx - (dot_size + 6.0) / 2.0, fy - (dot_size + 6.0) / 2.0, dot_size + 6.0, dot_size + 6.0);
- // Render core (solid blue/purple rectangle)
- pc.rect([0.45, 0.65, 1.0, 1.0], fx - dot_size / 2.0, fy - dot_size / 2.0, dot_size, dot_size);
- }
-
- sec.spacing(pad_h + 12.0);
+ state.trackpad.set_fingers(state.fingers.clone());
+ sec.widget(&mut pc, &mut state.trackpad, 14.0, pad_w, pad_h);
+ sec.spacing(12.0);
y = sec.finish(&mut pc);
// ── Trackpoint ──
@@ -401,6 +412,17 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
sec.spacing(8.0);
y = sec.finish_focused(&mut pc, sec_focused.get(1).copied().unwrap_or(false));
+ // ── Cursor ──
+ let mut sec = Section::new(&mut pc, cx, y, cw, "Cursor");
+
+ sec.widget(&mut pc, &mut state.cursor_theme_menu, 14.0, 200.0, 26.0);
+ sec.spacing(12.0);
+
+ sec.widget(&mut pc, &mut state.cursor_size_spinbox, 14.0, 200.0, 26.0);
+ sec.spacing(8.0);
+
+ y = sec.finish_focused(&mut pc, sec_focused.get(2).copied().unwrap_or(false));
+
// ── Inertial Input ──
let mut sec = Section::new(&mut pc, cx, y, cw, "Inertial Input");
@@ -425,7 +447,7 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
sec.widget(&mut pc, &mut state.trackpad_friction_spinbox, 14.0, 200.0, 26.0);
sec.spacing(8.0);
- y = sec.finish_focused(&mut pc, sec_focused.get(2).copied().unwrap_or(false));
+ y = sec.finish_focused(&mut pc, sec_focused.get(3).copied().unwrap_or(false));
// ── Keybindings ──
let mut sec = Section::new(&mut pc, cx, y, cw, "Keyboard Bindings");
@@ -448,25 +470,8 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_foc
}
sec.finish(&mut pc);
- // Filter out base text items covered by any open popover to prevent showing through
- let mut popovers = Vec::new();
- if state.trackpoint_accel_profile_menu.open {
- let (x, y, w, h) = state.trackpoint_accel_profile_menu.rect();
- popovers.push((x, y + h, w, state.trackpoint_accel_profile_menu.options.len() as f32 * 24.0));
- }
-
- if !popovers.is_empty() {
- pc.texts.retain(|(_, _, tx, ty, _, _)| {
- for &(px, py, pw, ph) in &popovers {
- if *tx >= px && *tx <= px + pw && *ty >= py && *ty <= py + ph {
- return false;
- }
- }
- true
- });
- }
-
state.trackpoint_accel_profile_menu.render_popover(&mut pc);
+ state.cursor_theme_menu.render_popover(&mut pc);
pc
}
@@ -529,6 +534,21 @@ pub fn update(state: &mut InputState, msg: InputMessage) {
write_config_value("trackpoint_accel_profile", &format!("\"{}\"", profile));
send_ipc_command(&format!("input trackpoint-accel-profile {}", profile));
}
+ InputMessage::ApplyCursorTheme(idx) => {
+ if idx < state.cursor_themes.len() {
+ let theme = state.cursor_themes[idx].clone();
+ state.cursor_theme = theme.clone();
+ state.cursor_theme_menu.selected = idx;
+ write_config_value("cursor_theme", &format!("\"{}\"", theme));
+ send_ipc_command(&format!("input cursor-theme {}", theme));
+ }
+ }
+ InputMessage::ApplyCursorSize => {
+ let size = state.cursor_size_spinbox.value.max(16).min(64) as u32;
+ state.cursor_size = size;
+ write_config_value("cursor_size", &size.to_string());
+ send_ipc_command(&format!("input cursor-size {}", size));
+ }
InputMessage::Refreshed(new) => {
let fingers = state.fingers.clone();
*state = new;
@@ -547,10 +567,7 @@ mod tests {
#[test]
fn test_is_over_trackpad() {
let mut state = InputState::default();
- state.trackpad_x = 100.0;
- state.trackpad_y = 200.0;
- state.trackpad_w = 300.0;
- state.trackpad_h = 150.0;
+ state.trackpad.set_rect(100.0, 200.0, 300.0, 150.0);
// Inside
assert!(state.is_over_trackpad(150.0, 250.0));
diff --git a/src/pages/typeface.rs b/src/pages/typeface.rs
index 1112d1c..2bab21d 100644
--- a/src/pages/typeface.rs
+++ b/src/pages/typeface.rs
@@ -663,12 +663,12 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_
let is_selected = state.selected_font.as_ref() == Some(*font_name);
let font_btn = &mut state.font_buttons[idx];
- font_btn.label = Some((*font_name).clone());
+ font_btn.set_text(font_name);
font_btn.selected = is_selected;
clear_ui::layout::render_widget(&mut pc, font_btn, inner_x, draw_y, inner_w - 44.0, btn_h);
let copy_btn = &mut state.copy_buttons[idx];
- copy_btn.label = Some("📋".to_string());
+ copy_btn.set_text("📋");
copy_btn.selected = is_selected;
clear_ui::layout::render_widget(&mut pc, copy_btn, inner_x + inner_w - 40.0, draw_y, 40.0, btn_h);
}
@@ -787,35 +787,7 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_
let list_focused = sec_focused.get(2).copied().unwrap_or(false);
sec.finish_focused(&mut pc, list_focused);
- // Filter out base text items covered by any open popover to prevent showing through
- let mut popovers = Vec::new();
- if state.borders_menu.open {
- let (x, y, w, h) = state.borders_menu.rect();
- popovers.push((x, y + h, w, state.borders_menu.options.len() as f32 * 24.0));
- }
- if state.status_menu.open {
- let (x, y, w, h) = state.status_menu.rect();
- popovers.push((x, y + h, w, state.status_menu.options.len() as f32 * 24.0));
- }
- if state.fuzzel_menu.open {
- let (x, y, w, h) = state.fuzzel_menu.rect();
- popovers.push((x, y + h, w, state.fuzzel_menu.options.len() as f32 * 24.0));
- }
- if state.terminal_menu.open {
- let (x, y, w, h) = state.terminal_menu.rect();
- popovers.push((x, y + h, w, state.terminal_menu.options.len() as f32 * 24.0));
- }
- if !popovers.is_empty() {
- pc.texts.retain(|(_, _, tx, ty, _, _)| {
- for &(px, py, pw, ph) in &popovers {
- if *tx >= px && *tx <= px + pw && *ty >= py && *ty <= py + ph {
- return false;
- }
- }
- true
- });
- }
// Render dropdown popovers on top of all other widgets
state.borders_menu.render_popover(&mut pc);