system settings
git clone https://git.lucas.co/cce-system-interface.git
Integrate editable KeybindsControl widget on input page
src/main.rs | 31 ++++++++++++++++++++++++++++++-
src/pages/input.rs | 39 ++++++++++++++++++---------------------
2 files changed, 48 insertions(+), 22 deletions(-)
diff --git a/src/main.rs b/src/main.rs
index 2a5d5a2..45a4c21 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -823,6 +823,7 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
self.app.input.cursor_size_spinbox.clear_children(&mut self.ui_context); self.app.input.cursor_size_spinbox.set_parent(None, &mut self.ui_context);
self.app.input.zoom_in_box.clear_children(&mut self.ui_context); self.app.input.zoom_in_box.set_parent(None, &mut self.ui_context);
self.app.input.zoom_out_box.clear_children(&mut self.ui_context); self.app.input.zoom_out_box.set_parent(None, &mut self.ui_context);
+ self.app.input.keybinds_control.clear_children(&mut self.ui_context); self.app.input.keybinds_control.set_parent(None, &mut self.ui_context);
for sb in &mut self.app.audio.sink_spinboxes {
sb.clear_children(&mut self.ui_context);
@@ -1099,13 +1100,14 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
Page::Input => {
- self.page_sec_containers.resize_with(6, cce_ui::widget::Container::new);
+ self.page_sec_containers.resize_with(7, cce_ui::widget::Container::new);
link_parent_child(page_root, &mut self.page_sec_containers[0], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[1], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[2], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[3], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[4], &mut self.ui_context);
link_parent_child(page_root, &mut self.page_sec_containers[5], &mut self.ui_context);
+ link_parent_child(page_root, &mut self.page_sec_containers[6], &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.dwtp_toggle, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_speed_spinbox, &mut self.ui_context);
@@ -1129,6 +1131,7 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
link_parent_child(&mut self.page_sec_containers[5], &mut self.app.input.zoom_in_box, &mut self.ui_context);
link_parent_child(&mut self.page_sec_containers[5], &mut self.app.input.zoom_out_box, &mut self.ui_context);
+ link_parent_child(&mut self.page_sec_containers[6], &mut self.app.input.keybinds_control, &mut self.ui_context);
}
Page::Audio => {
self.page_sec_containers.resize_with(2, cce_ui::widget::Container::new);
@@ -2118,6 +2121,9 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if self.app.input.zoom_out_box.cursor_moved(lx, ly, &mut self.ui_context) {
changed = true;
}
+ if self.app.input.keybinds_control.cursor_moved(lx, ly, &mut self.ui_context) {
+ changed = true;
+ }
}
if self.app.current_page == Page::Audio {
if let Some(idx) = self.audio_sink_dragging {
@@ -2571,6 +2577,7 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if self.app.input.trackpoint_accel_profile_menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if self.app.input.zoom_in_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
if self.app.input.zoom_out_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+ if self.app.input.keybinds_control.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
}
Page::Audio => {
@@ -3121,6 +3128,10 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if sb.mouse_input(button, state, lx, ly, &mut self.ui_context) && sb.value != old {
actions.push(AppAction::Input(pages::input::InputMessage::ApplyCursorSize));
}
+ let kc = &mut self.app.input.keybinds_control;
+ if state == cce_ui::widget::ElementState::Pressed && !kc.hit_test(lx, ly, &self.ui_context) {
+ kc.unfocus();
+ }
}
if state == cce_ui::widget::ElementState::Pressed && self.app.current_page == Page::Services {
let sb = &mut self.app.services.notifications_duration_spinbox;
@@ -3201,6 +3212,14 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if state == cce_ui::widget::ElementState::Pressed && tb.take_change() {
actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomOut));
}
+ let kc = &mut self.app.input.keybinds_control;
+ if kc.mouse_input(button, state, lx, ly, &mut self.ui_context) {
+ self.needs_rebuild = true;
+ }
+ if kc.take_change() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ReloadKeybinds));
+ self.needs_rebuild = true;
+ }
}
if self.app.current_page == Page::Services {
let toggle = &mut self.app.services.notifications_enable_toggle;
@@ -4004,6 +4023,9 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
if self.app.input.zoom_out_box.take_change() {
actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomOut));
}
+ if self.app.input.keybinds_control.take_change() {
+ actions.push(AppAction::Input(pages::input::InputMessage::ReloadKeybinds));
+ }
}
Page::Services => {
if self.app.services.notifications_duration_spinbox.take_change() {
@@ -5114,6 +5136,13 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
self.needs_rebuild = true;
return true;
}
+ if self.app.input.keybinds_control.keyboard_input(event, &mut self.ui_context) {
+ if self.app.input.keybinds_control.take_change() {
+ self.handle_action(&AppAction::Input(pages::input::InputMessage::ReloadKeybinds));
+ }
+ self.needs_rebuild = true;
+ return true;
+ }
}
if self.app.current_page == Page::Accounts {
let mut consumed = false;
diff --git a/src/pages/input.rs b/src/pages/input.rs
index 762ebc1..6994779 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -3,7 +3,7 @@ use std::io::Write;
use crate::app::PageContent;
use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy};
-use cce_ui::widget::{Spinbox, Toggle, Trackpad, Dropdown, Finger, Element, TextBox};
+use cce_ui::widget::{Spinbox, Toggle, Trackpad, Dropdown, Finger, Element, TextBox, KeybindsControl};
const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.json";
@@ -74,6 +74,9 @@ pub struct InputState {
// Graph settings
pub zoom_in_box: TextBox,
pub zoom_out_box: TextBox,
+
+ // Keyboard bindings widget
+ pub keybinds_control: KeybindsControl,
}
fn scan_cursor_themes() -> Vec<String> {
@@ -157,6 +160,9 @@ impl Default for InputState {
// Graph defaults
zoom_in_box: TextBox::new("=".to_string()).with_label("Zoom In"),
zoom_out_box: TextBox::new("-".to_string()).with_label("Zoom Out"),
+
+ // Keyboard bindings widget
+ keybinds_control: KeybindsControl::new(),
}
}
}
@@ -192,6 +198,7 @@ pub enum InputMessage {
ApplyZoomIn,
ApplyZoomOut,
+ ReloadKeybinds,
}
pub fn read_input_config() -> InputState {
@@ -274,6 +281,9 @@ pub fn read_input_config() -> InputState {
// Graph settings
zoom_in_box: TextBox::new(zoom_in).with_label("Zoom In"),
zoom_out_box: TextBox::new(zoom_out).with_label("Zoom Out"),
+
+ // Keyboard bindings widget
+ keybinds_control: KeybindsControl::new(),
}
}
@@ -394,8 +404,6 @@ fn apply_repeat_config(rate: u16, delay: u16) {
send_ipc_command(&format!("repeat delay {}", delay));
}
-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];
pub fn view(state: &mut InputState, 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();
@@ -491,22 +499,8 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focu
// ── Keyboard Bindings ──
builder.add_section(&mut final_pc, "Keyboard Bindings", false, |sec| {
- for kb in &state.keybinds {
- let binding = if kb.mods.is_empty() {
- kb.key.clone()
- } else {
- format!("{}+{}", kb.mods, kb.key)
- };
- let action_label = if kb.command.is_empty() {
- kb.action.clone()
- } else {
- format!("{}: {}", kb.action, kb.command)
- };
- sec.text(&binding, 14.0, 0.0, 12.0, TEXT_FG);
- let label_w = sec_w - 200.0;
- sec.text(&action_label, 14.0 + label_w.min(180.0), 0.0, 12.0, TEXT_DIM);
- sec.spacing(18.0);
- }
+ let height = state.keybinds_control.preferred_height().unwrap_or(200.0);
+ sec.widget_full(&mut state.keybinds_control, height, ctx);
});
final_pc
@@ -607,6 +601,9 @@ pub fn update(state: &mut InputState, msg: InputMessage) {
let val = state.zoom_out_box.text.clone();
write_config_value("zoom_out", &format!("\"{}\"", val));
}
+ InputMessage::ReloadKeybinds => {
+ send_ipc_command("reload");
+ }
InputMessage::Refreshed(new) => {
let fingers = state.fingers.clone();
*state = new;
@@ -644,11 +641,11 @@ mod tests {
#[test]
fn test_parse_scrolling_params() {
- let content = "[input]\nnatural_scroll = true\n[inertial]\nscroll_speed = 2.5\n";
+ let content = r#"{"input": {"natural_scroll": true}, "inertial": {"scroll_speed": 2.5}}"#;
assert_eq!(parse_bool_from_default(content, "natural_scroll", false), true);
assert_eq!(parse_f32_key(content, "scroll_speed", 1.0), 2.5);
- let empty_content = "";
+ let empty_content = "{}";
assert_eq!(parse_bool_from_default(empty_content, "natural_scroll", false), false);
assert_eq!(parse_f32_key(empty_content, "scroll_speed", 1.0), 1.0);
}