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

commitd9885280c502a7244e183d81a39fb221d2a6d0bc
parentfcb7ace755
authorLucas Galante <[email protected]>
date2026-07-03 01:15
Moved style.data.textbox.line_wrap to style.data.textbox.multiline.line_wrap and restricted to multiline textboxes

 src/config.rs                | 79 +++++++++++++++++++++++++++-----------------
 src/layout.rs                |  2 +-
 src/widget/input/text_box.rs | 50 ++++++++++++----------------
 3 files changed, 70 insertions(+), 61 deletions(-)

diff --git a/src/config.rs b/src/config.rs
index f24cdbd..f1f88a8 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -95,44 +95,61 @@ fn kdl_to_json(doc: &kdl::KdlDocument) -> serde_json::Value {
             } else {
                 serde_json::Value::Null
             }
-        } else if let Some(children) = node.children() {
-            kdl_to_json(children)
-        } else if has_props {
-            serde_json::Value::Object(node_map)
-        } else if let Some(entry) = node.entries().first() {
-            match entry.value() {
-                kdl::KdlValue::Bool(b) => serde_json::Value::Bool(*b),
-                kdl::KdlValue::Base2(i) |
-                kdl::KdlValue::Base8(i) |
-                kdl::KdlValue::Base10(i) |
-                kdl::KdlValue::Base16(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
-                kdl::KdlValue::Base10Float(f) => {
-                    let mut val_f = *f;
-                    if let Some(ty) = entry.ty() {
-                        let ty_str = ty.value();
-                        if ty_str.starts_with("f64:") {
-                            let range_str = ty_str.trim_start_matches("f64:");
-                            if let Some(dash_idx) = range_str.find('-') {
-                                let min_str = &range_str[..dash_idx].trim();
-                                let max_str = &range_str[dash_idx + 1..].trim();
-                                if let (Ok(min_f), Ok(max_f)) = (min_str.parse::<f64>(), max_str.parse::<f64>()) {
-                                    val_f = val_f.clamp(min_f, max_f);
+        } else {
+            let mut node_val = serde_json::Value::Null;
+            if has_props {
+                node_val = serde_json::Value::Object(node_map);
+            } else if let Some(entry) = node.entries().first() {
+                node_val = match entry.value() {
+                    kdl::KdlValue::Bool(b) => serde_json::Value::Bool(*b),
+                    kdl::KdlValue::Base2(i) |
+                    kdl::KdlValue::Base8(i) |
+                    kdl::KdlValue::Base10(i) |
+                    kdl::KdlValue::Base16(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
+                    kdl::KdlValue::Base10Float(f) => {
+                        let mut val_f = *f;
+                        if let Some(ty) = entry.ty() {
+                            let ty_str = ty.value();
+                            if ty_str.starts_with("f64:") {
+                                let range_str = ty_str.trim_start_matches("f64:");
+                                if let Some(dash_idx) = range_str.find('-') {
+                                    let min_str = &range_str[..dash_idx].trim();
+                                    let max_str = &range_str[dash_idx + 1..].trim();
+                                    if let (Ok(min_f), Ok(max_f)) = (min_str.parse::<f64>(), max_str.parse::<f64>()) {
+                                        val_f = val_f.clamp(min_f, max_f);
+                                    }
                                 }
                             }
                         }
+                        if let Some(num) = serde_json::Number::from_f64(val_f) {
+                            serde_json::Value::Number(num)
+                        } else {
+                            serde_json::Value::Null
+                        }
                     }
-                    if let Some(num) = serde_json::Number::from_f64(val_f) {
-                        serde_json::Value::Number(num)
+                    kdl::KdlValue::String(s) |
+                    kdl::KdlValue::RawString(s) => serde_json::Value::String(s.clone()),
+                    kdl::KdlValue::Null => serde_json::Value::Null,
+                };
+            }
+
+            if let Some(children) = node.children() {
+                let children_val = kdl_to_json(children);
+                if let serde_json::Value::Object(children_map) = children_val {
+                    if let serde_json::Value::Object(mut nm) = node_val {
+                        for (k, v) in children_map {
+                            nm.insert(k, v);
+                        }
+                        serde_json::Value::Object(nm)
                     } else {
-                        serde_json::Value::Null
+                        serde_json::Value::Object(children_map)
                     }
+                } else {
+                    node_val
                 }
-                kdl::KdlValue::String(s) |
-                kdl::KdlValue::RawString(s) => serde_json::Value::String(s.clone()),
-                kdl::KdlValue::Null => serde_json::Value::Null,
+            } else {
+                node_val
             }
-        } else {
-            serde_json::Value::Null
         };
 
         if let Some(existing) = map.remove(&name) {
@@ -235,7 +252,7 @@ pub fn parse_config_path(key: &str, default_section: &str) -> (String, String, O
 const PROP_NODES: &[&str] = &[
     "gestures", "key_bindings", "pointer_bind", "gesture_bind",
     "button", "button_strip", "dropdown", "toggle", "spinbox", "slider", "font_selector",
-    "status", "overlay", "backplate", "desktop", "list", "section", "textbox", "editor", "tree"
+    "status", "overlay", "backplate", "desktop", "list", "section", "textbox", "multiline", "editor", "tree"
 ];
 
 fn get_or_create_node_mut<'a>(doc: &'a mut kdl::KdlDocument, path: &[&str]) -> Option<&'a mut kdl::KdlNode> {
diff --git a/src/layout.rs b/src/layout.rs
index 1ca82a2..2a92e65 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -106,7 +106,7 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 "style.control.spinbox.button_padding" => "spinbox_button_padding",
                 "style.control.spinbox.corner_radius" => "spinbox_corner_radius",
                 "style.textbox.height" | "style.data.textbox.height" => "textbox_height",
-                "style.textbox.line_wrap" | "style.data.textbox.line_wrap" => "textbox_line_wrap",
+                "style.textbox.multiline.line_wrap" | "style.data.textbox.multiline.line_wrap" => "textbox_line_wrap",
                 "style.control.toggle.font" => "toggle_font",
                 "style.control.toggle.height" => "toggle_height",
                 "style.control.toggle.border_width" => "toggle_border_width",
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index deba499..ce479e2 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -153,7 +153,7 @@ impl TextBox {
         let mut current_line = Vec::new();
         let mut index_map = vec![(0, 0); chars.len() + 1];
         
-        if !crate::layout::textbox_line_wrap() {
+        if !self.line_wrap_enabled() {
             let mut i = 0;
             while i < chars.len() {
                 let ch = chars[i];
@@ -291,6 +291,10 @@ impl TextBox {
         changed
     }
 
+    pub fn line_wrap_enabled(&self) -> bool {
+        self.multiline && crate::layout::textbox_line_wrap()
+    }
+
     pub fn with_max_width(mut self, max_w: Option<f32>) -> Self {
         self.max_width = max_w;
         self
@@ -397,12 +401,8 @@ impl TextBox {
     pub fn clamp_scroll(&mut self) {
         let char_width = self.char_width();
         let line_height = self.line_height();
-        let max_chars = if self.multiline {
-            if crate::layout::textbox_line_wrap() {
-                (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
-            } else {
-                999999
-            }
+        let max_chars = if self.line_wrap_enabled() {
+            (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
         } else {
             999999
         };
@@ -421,7 +421,7 @@ impl TextBox {
             self.scroll_y = 0.0;
         }
 
-        if !crate::layout::textbox_line_wrap() {
+        if !self.line_wrap_enabled() {
             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);
@@ -434,12 +434,8 @@ impl TextBox {
     pub fn scroll_to_cursor(&mut self) {
         let char_width = self.char_width();
         let line_height = self.line_height();
-        let max_chars = if self.multiline {
-            if crate::layout::textbox_line_wrap() {
-                (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
-            } else {
-                999999
-            }
+        let max_chars = if self.line_wrap_enabled() {
+            (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
         } else {
             999999
         };
@@ -471,7 +467,7 @@ impl TextBox {
             }
         }
         
-        if !crate::layout::textbox_line_wrap() {
+        if !self.line_wrap_enabled() {
             let cursor_x = col_idx as f32 * char_width;
             if cursor_x < self.scroll_x + 10.0 {
                 self.scroll_x = (cursor_x - 20.0).max(0.0);
@@ -672,7 +668,7 @@ impl Element for TextBox {
             let char_width = self.char_width();
             let drag_idx = if self.multiline {
                 let line_height = self.line_height();
-                let max_chars = if crate::layout::textbox_line_wrap() {
+                let max_chars = if self.line_wrap_enabled() {
                     (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
                 } else {
                     999999
@@ -718,7 +714,7 @@ impl Element for TextBox {
         let top = self.base.label_offset();
         let drag_idx = if self.multiline {
             let line_height = self.line_height();
-            let max_chars = if crate::layout::textbox_line_wrap() {
+            let max_chars = if self.line_wrap_enabled() {
                 (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
             } else {
                 999999
@@ -770,7 +766,7 @@ impl Element for TextBox {
                     let top = self.base.label_offset();
                     let idx = if self.multiline {
                         let line_height = self.line_height();
-                        let max_chars = if crate::layout::textbox_line_wrap() {
+                        let max_chars = if self.line_wrap_enabled() {
                             (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
                         } else {
                             999999
@@ -1030,7 +1026,7 @@ impl Element for TextBox {
             let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
 
             if self.multiline {
-                let max_chars = if crate::layout::textbox_line_wrap() {
+                let max_chars = if self.line_wrap_enabled() {
                     (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
                 } else {
                     999999
@@ -1181,7 +1177,7 @@ impl Element for TextBox {
             let end = self.select_anchor.unwrap_or(self.cursor_idx).max(self.cursor_idx);
 
             if self.multiline {
-                let max_chars = if crate::layout::textbox_line_wrap() {
+                let max_chars = if self.line_wrap_enabled() {
                     (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
                 } else {
                     999999
@@ -1323,7 +1319,7 @@ impl Element for TextBox {
         if self.multiline {
             let char_width = self.char_width();
             let line_height = self.line_height();
-            let max_chars = if crate::layout::textbox_line_wrap() {
+            let max_chars = if self.line_wrap_enabled() {
                 (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
             } else {
                 999999
@@ -1340,7 +1336,7 @@ impl Element for TextBox {
                         current_line.clear();
                     } else {
                         current_line.push(ch);
-                        if crate::layout::textbox_line_wrap() && current_line.len() > max_chars {
+                        if self.line_wrap_enabled() && current_line.len() > max_chars {
                             p_lines.push(current_line.iter().collect::<String>());
                             current_line.clear();
                         }
@@ -1388,12 +1384,8 @@ impl Element for TextBox {
         let char_width = self.char_width();
         let line_height = self.line_height();
         
-        let max_chars = if self.multiline {
-            if crate::layout::textbox_line_wrap() {
-                (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
-            } else {
-                999999
-            }
+        let max_chars = if self.line_wrap_enabled() {
+            (((self.base.w - 16.0) / char_width).floor() as usize).max(1)
         } else {
             999999
         };
@@ -1421,7 +1413,7 @@ impl Element for TextBox {
             }
         }
 
-        if !crate::layout::textbox_line_wrap() {
+        if !self.line_wrap_enabled() {
             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);