git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commite10563aa58df115b9d5c117034b31cfa4a886c09
parentae44e622bb
authorLucas Galante <[email protected]>
date2026-07-06 15:02
Support input.touchpad.natural_scroll mapping and horizontal panning direction inversion

 src/layout.rs                | 22 ++++++++++++++++++++++
 src/widget/input/text_box.rs | 10 ++++++----
 2 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/src/layout.rs b/src/layout.rs
index c6e6081..0139076 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -177,6 +177,7 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 "style.surface.plate.border_color" => "plate_border_color",
                 "style.surface.plate.border_thickness" => "plate_border_thickness",
                 "style.surface.plate.blur" => "plate_blur",
+                "input.touchpad.natural_scroll" => "touchpad_natural_scroll",
                 
                 other => {
                     if let Some(rest) = other.strip_prefix("layout.") {
@@ -267,6 +268,7 @@ static TREE_BLUR: RwLock<f32> = RwLock::new(0.0);
 static PLATE_PADDING: RwLock<f32> = RwLock::new(20.0);
 static DROPDOWN_HEIGHT: RwLock<f32> = RwLock::new(44.0);
 static NESTED_SECTION_LABEL_ALIGNMENT: RwLock<u8> = RwLock::new(0);
+static TOUCHPAD_NATURAL_SCROLL: RwLock<bool> = RwLock::new(false);
 
 
 static BUTTON_CORNER_RADIUS: RwLock<f32> = RwLock::new(4.0);
@@ -531,6 +533,14 @@ pub fn reload_config() {
                     *lock = wrap;
                 }
             }
+            if let Some(rest) = trimmed.strip_prefix("touchpad_natural_scroll") {
+                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+                let val_str = rest.trim_end_matches('"').trim();
+                let enabled = val_str == "true" || val_str == "1" || val_str == "1.0";
+                if let Ok(mut lock) = TOUCHPAD_NATURAL_SCROLL.write() {
+                    *lock = enabled;
+                }
+            }
             if let Some(rest) = trimmed.strip_prefix("textbox_multiline_border_width") {
                 let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
                 let val_str = rest.trim_end_matches('"').trim();
@@ -2639,6 +2649,18 @@ pub fn set_textbox_line_wrap(wrap: bool) {
     }
 }
 
+pub fn touchpad_natural_scroll() -> bool {
+    lazy_init_style_registry();
+    *TOUCHPAD_NATURAL_SCROLL.read().unwrap()
+}
+
+pub fn set_touchpad_natural_scroll(enabled: bool) {
+    lazy_init_style_registry();
+    if let Ok(mut lock) = TOUCHPAD_NATURAL_SCROLL.write() {
+        *lock = enabled;
+    }
+}
+
 pub fn textbox_multiline_border_width() -> f32 {
     lazy_init_style_registry();
     *TEXTBOX_MULTILINE_BORDER_WIDTH.read().unwrap()
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 4c32a8b..fb48c3f 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -1451,21 +1451,23 @@ impl Element for TextBox {
             let max_line_len = lines.iter().map(|l| l.chars().count()).max().unwrap_or(0);
             let content_w = max_line_len as f32 * char_width;
             let max_scroll_x = (content_w - (self.base.w - 16.0)).max(0.0);
+            let natural = crate::layout::touchpad_natural_scroll();
             let scroll_amt_x = match *delta {
                 MouseScrollDelta::LineDelta(dx, dy) => {
                     if !self.multiline {
-                        let scroll_val = if dy != 0.0 { -dy } else { dx };
+                        let scroll_val = if dy != 0.0 { -dy } else { if natural { -dx } else { dx } };
                         scroll_val * char_width * 3.0
                     } else {
-                        dx * char_width * 3.0
+                        let scroll_val = if natural { -dx } else { dx };
+                        scroll_val * char_width * 3.0
                     }
                 }
                 MouseScrollDelta::PixelDelta(pos) => {
                     if !self.multiline {
-                        let scroll_val = if pos.y != 0.0 { -pos.y as f32 } else { pos.x as f32 };
+                        let scroll_val = if pos.y != 0.0 { -pos.y as f32 } else { if natural { -pos.x as f32 } else { pos.x as f32 } };
                         scroll_val
                     } else {
-                        pos.x as f32
+                        if natural { -pos.x as f32 } else { pos.x as f32 }
                     }
                 }
             };