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

commit014305b13fb006ed629083bf74794a7652ce33e7
parentfed3d6769e
authorLucas Galante <[email protected]>
date2026-07-01 13:00
Implement StyleRegistry, CornerRadii struct, and standardized rounded border rendering across all widgets

 src/backend/window_runner.rs | 418 +++++++++++++++++++++++++++++++------------
 src/layout.rs                | 131 +++++++++-----
 src/widget/mod.rs            |  29 +++
 3 files changed, 420 insertions(+), 158 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index c231b11..8dc46c9 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -327,21 +327,55 @@ pub fn rounded_rect_vertices_corners(
     clip_rect: Option<(f32, f32, f32, f32)>,
 ) -> Vec<Vertex> {
     let mut verts = Vec::new();
-    push_rounded_rect_vertices_corners(x, y, ww, h, r, sw, sh, color, clip_circle, corners, clip_rect, &mut verts);
+    let radii = crate::widget::CornerRadii::new(
+        if corners.0 { r } else { 0.0 },
+        if corners.1 { r } else { 0.0 },
+        if corners.2 { r } else { 0.0 },
+        if corners.3 { r } else { 0.0 },
+    );
+    push_rounded_rect_vertices_corners(x, y, ww, h, radii, sw, sh, color, clip_circle, clip_rect, &mut verts);
     verts
 }
 
 pub fn push_rounded_rect_vertices_corners(
     x: f32, y: f32, ww: f32, h: f32,
-    r: f32,
+    radii: crate::widget::CornerRadii,
     sw: f32, sh: f32,
     color: [f32; 4],
     clip_circle: [f32; 3],
-    corners: (bool, bool, bool, bool),
     clip_rect: Option<(f32, f32, f32, f32)>,
     out: &mut Vec<Vertex>,
 ) {
-    let r = r.min(ww * 0.5).min(h * 0.5);
+    let mut r_tl = radii.top_left.max(0.0);
+    let mut r_tr = radii.top_right.max(0.0);
+    let mut r_br = radii.bottom_right.max(0.0);
+    let mut r_bl = radii.bottom_left.max(0.0);
+
+    // Simple scale clamping
+    let sum_top = r_tl + r_tr;
+    if sum_top > ww {
+        let f = ww / sum_top;
+        r_tl *= f;
+        r_tr *= f;
+    }
+    let sum_bottom = r_bl + r_br;
+    if sum_bottom > ww {
+        let f = ww / sum_bottom;
+        r_bl *= f;
+        r_br *= f;
+    }
+    let sum_left = r_tl + r_bl;
+    if sum_left > h {
+        let f = h / sum_left;
+        r_tl *= f;
+        r_bl *= f;
+    }
+    let sum_right = r_tr + r_br;
+    if sum_right > h {
+        let f = h / sum_right;
+        r_tr *= f;
+        r_br *= f;
+    }
 
     let clamp_x = |val: f32| -> f32 {
         if let Some((cx0, _, cx1, _)) = clip_rect {
@@ -381,60 +415,166 @@ pub fn push_rounded_rect_vertices_corners(
         verts.push(Vertex { position: [ndc_x0, ndc_y1], color, clip_circle });
     };
 
-    if r <= 0.1 || (!corners.0 && !corners.1 && !corners.2 && !corners.3) {
+    let has_corners = r_tl > 0.1 || r_tr > 0.1 || r_br > 0.1 || r_bl > 0.1;
+    if !has_corners {
         push_quad(out, x, y, ww, h);
         return;
     }
 
-    // 1. Center rectangle
-    push_quad(out, x + r, y, ww - 2.0 * r, h);
-    
-    // 2. Left rectangle
-    push_quad(out, x, y + r, r, h - 2.0 * r);
-    
-    // 3. Right rectangle
-    push_quad(out, x + ww - r, y + r, r, h - 2.0 * r);
-
-    // 4. Four corners
-    let corner_configs = [
-        // Top-left
-        (corners.0, x, y, x + r, y + r, std::f32::consts::PI, 1.5 * std::f32::consts::PI),
-        // Top-right
-        (corners.1, x + ww - r, y, x + ww - r, y + r, 1.5 * std::f32::consts::PI, 2.0 * std::f32::consts::PI),
-        // Bottom-right
-        (corners.2, x + ww - r, y + h - r, x + ww - r, y + h - r, 0.0, 0.5 * std::f32::consts::PI),
-        // Bottom-left
-        (corners.3, x, y + h - r, x + r, y + h - r, 0.5 * std::f32::consts::PI, std::f32::consts::PI),
-    ];
+    // Body rectangles
+    let mid_x0 = r_tl.max(r_bl);
+    let mid_x1 = ww - r_tr.max(r_br);
+    if mid_x1 > mid_x0 {
+        push_quad(out, x + mid_x0, y, mid_x1 - mid_x0, h);
+    }
+    if h > r_tl + r_bl {
+        push_quad(out, x, y + r_tl, mid_x0, h - r_tl - r_bl);
+    }
+    if h > r_tr + r_br {
+        push_quad(out, x + mid_x1, y + r_tr, ww - mid_x1, h - r_tr - r_br);
+    }
 
+    // Corner rendering
     let segments = 16;
-    for &(is_rounded, sqx, sqy, cx, cy, start, end) in &corner_configs {
-        if is_rounded {
-            for i in 0..segments {
-                let theta1 = start + (i as f32) * (end - start) / (segments as f32);
-                let theta2 = start + ((i + 1) as f32) * (end - start) / (segments as f32);
-                
-                let x0 = clamp_x(cx);
-                let y0 = clamp_y(cy);
-                let x1 = clamp_x(cx + r * theta1.cos());
-                let y1 = clamp_y(cy + r * theta1.sin());
-                let x2 = clamp_x(cx + r * theta2.cos());
-                let y2 = clamp_y(cy + r * theta2.sin());
-                
-                let ndc_x0 = (x0 / sw) * 2.0 - 1.0;
-                let ndc_y0 = 1.0 - (y0 / sh) * 2.0;
-                let ndc_x1 = (x1 / sw) * 2.0 - 1.0;
-                let ndc_y1 = 1.0 - (y1 / sh) * 2.0;
-                let ndc_x2 = (x2 / sw) * 2.0 - 1.0;
-                let ndc_y2 = 1.0 - (y2 / sh) * 2.0;
-                
-                out.push(Vertex { position: [ndc_x0, ndc_y0], color, clip_circle });
-                out.push(Vertex { position: [ndc_x1, ndc_y1], color, clip_circle });
-                out.push(Vertex { position: [ndc_x2, ndc_y2], color, clip_circle });
-            }
-        } else {
-            push_quad(out, sqx, sqy, r, r);
+
+    // Top-Left
+    if r_tl > 0.1 {
+        let cx = x + r_tl;
+        let cy = y + r_tl;
+        let start = std::f32::consts::PI;
+        let end = 1.5 * std::f32::consts::PI;
+        for i in 0..segments {
+            let theta1 = start + (i as f32) * (end - start) / (segments as f32);
+            let theta2 = start + ((i + 1) as f32) * (end - start) / (segments as f32);
+            
+            let x0 = clamp_x(cx);
+            let y0 = clamp_y(cy);
+            let x1 = clamp_x(cx + r_tl * theta1.cos());
+            let y1 = clamp_y(cy + r_tl * theta1.sin());
+            let x2 = clamp_x(cx + r_tl * theta2.cos());
+            let y2 = clamp_y(cy + r_tl * theta2.sin());
+            
+            let ndc_x0 = (x0 / sw) * 2.0 - 1.0;
+            let ndc_y0 = 1.0 - (y0 / sh) * 2.0;
+            let ndc_x1 = (x1 / sw) * 2.0 - 1.0;
+            let ndc_y1 = 1.0 - (y1 / sh) * 2.0;
+            let ndc_x2 = (x2 / sw) * 2.0 - 1.0;
+            let ndc_y2 = 1.0 - (y2 / sh) * 2.0;
+            
+            out.push(Vertex { position: [ndc_x0, ndc_y0], color, clip_circle });
+            out.push(Vertex { position: [ndc_x1, ndc_y1], color, clip_circle });
+            out.push(Vertex { position: [ndc_x2, ndc_y2], color, clip_circle });
+        }
+        if mid_x0 > r_tl {
+            push_quad(out, x + r_tl, y, mid_x0 - r_tl, r_tl);
+        }
+    } else {
+        push_quad(out, x, y, mid_x0, 0.0);
+    }
+
+    // Top-Right
+    if r_tr > 0.1 {
+        let cx = x + ww - r_tr;
+        let cy = y + r_tr;
+        let start = 1.5 * std::f32::consts::PI;
+        let end = 2.0 * std::f32::consts::PI;
+        for i in 0..segments {
+            let theta1 = start + (i as f32) * (end - start) / (segments as f32);
+            let theta2 = start + ((i + 1) as f32) * (end - start) / (segments as f32);
+            
+            let x0 = clamp_x(cx);
+            let y0 = clamp_y(cy);
+            let x1 = clamp_x(cx + r_tr * theta1.cos());
+            let y1 = clamp_y(cy + r_tr * theta1.sin());
+            let x2 = clamp_x(cx + r_tr * theta2.cos());
+            let y2 = clamp_y(cy + r_tr * theta2.sin());
+            
+            let ndc_x0 = (x0 / sw) * 2.0 - 1.0;
+            let ndc_y0 = 1.0 - (y0 / sh) * 2.0;
+            let ndc_x1 = (x1 / sw) * 2.0 - 1.0;
+            let ndc_y1 = 1.0 - (y1 / sh) * 2.0;
+            let ndc_x2 = (x2 / sw) * 2.0 - 1.0;
+            let ndc_y2 = 1.0 - (y2 / sh) * 2.0;
+            
+            out.push(Vertex { position: [ndc_x0, ndc_y0], color, clip_circle });
+            out.push(Vertex { position: [ndc_x1, ndc_y1], color, clip_circle });
+            out.push(Vertex { position: [ndc_x2, ndc_y2], color, clip_circle });
+        }
+        if ww - mid_x1 > r_tr {
+            push_quad(out, x + mid_x1, y, ww - mid_x1 - r_tr, r_tr);
+        }
+    } else {
+        push_quad(out, x + mid_x1, y, ww - mid_x1, 0.0);
+    }
+
+    // Bottom-Right
+    if r_br > 0.1 {
+        let cx = x + ww - r_br;
+        let cy = y + h - r_br;
+        let start = 0.0;
+        let end = 0.5 * std::f32::consts::PI;
+        for i in 0..segments {
+            let theta1 = start + (i as f32) * (end - start) / (segments as f32);
+            let theta2 = start + ((i + 1) as f32) * (end - start) / (segments as f32);
+            
+            let x0 = clamp_x(cx);
+            let y0 = clamp_y(cy);
+            let x1 = clamp_x(cx + r_br * theta1.cos());
+            let y1 = clamp_y(cy + r_br * theta1.sin());
+            let x2 = clamp_x(cx + r_br * theta2.cos());
+            let y2 = clamp_y(cy + r_br * theta2.sin());
+            
+            let ndc_x0 = (x0 / sw) * 2.0 - 1.0;
+            let ndc_y0 = 1.0 - (y0 / sh) * 2.0;
+            let ndc_x1 = (x1 / sw) * 2.0 - 1.0;
+            let ndc_y1 = 1.0 - (y1 / sh) * 2.0;
+            let ndc_x2 = (x2 / sw) * 2.0 - 1.0;
+            let ndc_y2 = 1.0 - (y2 / sh) * 2.0;
+            
+            out.push(Vertex { position: [ndc_x0, ndc_y0], color, clip_circle });
+            out.push(Vertex { position: [ndc_x1, ndc_y1], color, clip_circle });
+            out.push(Vertex { position: [ndc_x2, ndc_y2], color, clip_circle });
+        }
+        if ww - mid_x1 > r_br {
+            push_quad(out, x + mid_x1, y + h - r_br, ww - mid_x1 - r_br, r_br);
+        }
+    } else {
+        push_quad(out, x + mid_x1, y + h, ww - mid_x1, 0.0);
+    }
+
+    // Bottom-Left
+    if r_bl > 0.1 {
+        let cx = x + r_bl;
+        let cy = y + h - r_bl;
+        let start = 0.5 * std::f32::consts::PI;
+        let end = std::f32::consts::PI;
+        for i in 0..segments {
+            let theta1 = start + (i as f32) * (end - start) / (segments as f32);
+            let theta2 = start + ((i + 1) as f32) * (end - start) / (segments as f32);
+            
+            let x0 = clamp_x(cx);
+            let y0 = clamp_y(cy);
+            let x1 = clamp_x(cx + r_bl * theta1.cos());
+            let y1 = clamp_y(cy + r_bl * theta1.sin());
+            let x2 = clamp_x(cx + r_bl * theta2.cos());
+            let y2 = clamp_y(cy + r_bl * theta2.sin());
+            
+            let ndc_x0 = (x0 / sw) * 2.0 - 1.0;
+            let ndc_y0 = 1.0 - (y0 / sh) * 2.0;
+            let ndc_x1 = (x1 / sw) * 2.0 - 1.0;
+            let ndc_y1 = 1.0 - (y1 / sh) * 2.0;
+            let ndc_x2 = (x2 / sw) * 2.0 - 1.0;
+            let ndc_y2 = 1.0 - (y2 / sh) * 2.0;
+            
+            out.push(Vertex { position: [ndc_x0, ndc_y0], color, clip_circle });
+            out.push(Vertex { position: [ndc_x1, ndc_y1], color, clip_circle });
+            out.push(Vertex { position: [ndc_x2, ndc_y2], color, clip_circle });
         }
+        if mid_x0 > r_bl {
+            push_quad(out, x + r_bl, y + h - r_bl, mid_x0 - r_bl, r_bl);
+        }
+    } else {
+        push_quad(out, x, y + h, mid_x0, 0.0);
     }
 }
 
@@ -445,7 +585,9 @@ pub fn rounded_rect_vertices(
     color: [f32; 4],
     clip_circle: [f32; 3],
 ) -> Vec<Vertex> {
-    rounded_rect_vertices_corners(x, y, ww, h, r, sw, sh, color, clip_circle, (true, true, true, true), None)
+    let mut verts = Vec::new();
+    push_rounded_rect_vertices_corners(x, y, ww, h, crate::widget::CornerRadii::uniform(r), sw, sh, color, clip_circle, None, &mut verts);
+    verts
 }
 
 pub fn push_rounded_rect_vertices(
@@ -456,7 +598,7 @@ pub fn push_rounded_rect_vertices(
     clip_circle: [f32; 3],
     out: &mut Vec<Vertex>,
 ) {
-    push_rounded_rect_vertices_corners(x, y, ww, h, r, sw, sh, color, clip_circle, (true, true, true, true), None, out);
+    push_rounded_rect_vertices_corners(x, y, ww, h, crate::widget::CornerRadii::uniform(r), sw, sh, color, clip_circle, None, out);
 }
 
 pub fn plate_bevel_vertices(
@@ -533,48 +675,96 @@ pub fn push_plate_bevel_vertices(
 
 pub fn push_plate_solid_border_vertices(
     x: f32, y: f32, ww: f32, h: f32,
-    r: f32,
+    radii: crate::widget::CornerRadii,
     t: f32,
     sw: f32, sh: f32,
     color: [f32; 4],
     clip_circle: [f32; 3],
     out: &mut Vec<Vertex>,
 ) {
-    let r = r.min(ww * 0.5).min(h * 0.5);
-    out.extend_from_slice(&quad_vertices_with_clip(x + r, y, ww - 2.0 * r, t, sw, sh, color, clip_circle));
-    out.extend_from_slice(&quad_vertices_with_clip(x, y + r, t, h - 2.0 * r, sw, sh, color, clip_circle));
-    out.extend_from_slice(&quad_vertices_with_clip(x + r, y + h - t, ww - 2.0 * r, t, sw, sh, color, clip_circle));
-    out.extend_from_slice(&quad_vertices_with_clip(x + ww - t, y + r, t, h - 2.0 * r, sw, sh, color, clip_circle));
-
-    let segments = 16;
+    let mut r_tl = radii.top_left.max(0.0);
+    let mut r_tr = radii.top_right.max(0.0);
+    let mut r_br = radii.bottom_right.max(0.0);
+    let mut r_bl = radii.bottom_left.max(0.0);
+
+    // Simple scale clamping
+    let sum_top = r_tl + r_tr;
+    if sum_top > ww {
+        let f = ww / sum_top;
+        r_tl *= f;
+        r_tr *= f;
+    }
+    let sum_bottom = r_bl + r_br;
+    if sum_bottom > ww {
+        let f = ww / sum_bottom;
+        r_bl *= f;
+        r_br *= f;
+    }
+    let sum_left = r_tl + r_bl;
+    if sum_left > h {
+        let f = h / sum_left;
+        r_tl *= f;
+        r_bl *= f;
+    }
+    let sum_right = r_tr + r_br;
+    if sum_right > h {
+        let f = h / sum_right;
+        r_tr *= f;
+        r_br *= f;
+    }
 
-    push_arc_background_vertices(
-        x + r, y + r, r, t,
-        std::f32::consts::PI, 1.5 * std::f32::consts::PI,
-        sw, sh, color, segments, clip_circle,
-        out,
-    );
+    out.extend_from_slice(&quad_vertices_with_clip(x + r_tl, y, ww - r_tl - r_tr, t, sw, sh, color, clip_circle));
+    out.extend_from_slice(&quad_vertices_with_clip(x, y + r_tl, t, h - r_tl - r_bl, sw, sh, color, clip_circle));
+    out.extend_from_slice(&quad_vertices_with_clip(x + r_bl, y + h - t, ww - r_bl - r_br, t, sw, sh, color, clip_circle));
+    out.extend_from_slice(&quad_vertices_with_clip(x + ww - t, y + r_tr, t, h - r_tr - r_br, sw, sh, color, clip_circle));
 
-    push_arc_background_vertices(
-        x + ww - r, y + r, r, t,
-        1.5 * std::f32::consts::PI, 2.0 * std::f32::consts::PI,
-        sw, sh, color, segments, clip_circle,
-        out,
-    );
+    let segments = 16;
 
-    push_arc_background_vertices(
-        x + ww - r, y + h - r, r, t,
-        0.0, 0.5 * std::f32::consts::PI,
-        sw, sh, color, segments, clip_circle,
-        out,
-    );
+    if r_tl > 0.1 {
+        push_arc_background_vertices(
+            x + r_tl, y + r_tl, r_tl, t,
+            std::f32::consts::PI, 1.5 * std::f32::consts::PI,
+            sw, sh, color, segments, clip_circle,
+            out,
+        );
+    }
+    if r_tr > 0.1 {
+        push_arc_background_vertices(
+            x + ww - r_tr, y + r_tr, r_tr, t,
+            1.5 * std::f32::consts::PI, 2.0 * std::f32::consts::PI,
+            sw, sh, color, segments, clip_circle,
+            out,
+        );
+    }
+    if r_br > 0.1 {
+        push_arc_background_vertices(
+            x + ww - r_br, y + h - r_br, r_br, t,
+            0.0, 0.5 * std::f32::consts::PI,
+            sw, sh, color, segments, clip_circle,
+            out,
+        );
+    }
+    if r_bl > 0.1 {
+        push_arc_background_vertices(
+            x + r_bl, y + h - r_bl, r_bl, t,
+            0.5 * std::f32::consts::PI, std::f32::consts::PI,
+            sw, sh, color, segments, clip_circle,
+            out,
+        );
+    }
+}
 
-    push_arc_background_vertices(
-        x + r, y + h - r, r, t,
-        0.5 * std::f32::consts::PI, std::f32::consts::PI,
-        sw, sh, color, segments, clip_circle,
-        out,
-    );
+pub fn push_plate_solid_border_vertices_legacy(
+    x: f32, y: f32, ww: f32, h: f32,
+    r: f32,
+    t: f32,
+    sw: f32, sh: f32,
+    color: [f32; 4],
+    clip_circle: [f32; 3],
+    out: &mut Vec<Vertex>,
+) {
+    let radii = crate::widget::CornerRadii::uniform(r);
+    push_plate_solid_border_vertices(x, y, ww, h, radii, t, sw, sh, color, clip_circle, out);
 }
 
 pub fn widget_vertices(w: &dyn crate::widget::Element, sw: f32, sh: f32, clip_circle: [f32; 3]) -> Vec<Vertex> {
@@ -585,15 +775,11 @@ pub fn widget_vertices(w: &dyn crate::widget::Element, sw: f32, sh: f32, clip_ci
 
 pub fn push_widget_vertices(w: &dyn crate::widget::Element, sw: f32, sh: f32, clip_circle: [f32; 3], out: &mut Vec<Vertex>) {
     let (x, y, ww, h) = w.rect();
-    let corners = w.rounded_corners();
-    if corners != (false, false, false, false) {
-        push_rounded_rect_vertices_corners(x, y, ww, h, w.corner_radius(), sw, sh, w.color(), clip_circle, corners, None, out);
-    } else {
-        out.extend_from_slice(&quad_vertices_with_clip(x, y, ww, h, sw, sh, w.color(), clip_circle));
-    }
+    let radii = w.corner_radii();
+    push_rounded_rect_vertices_corners(x, y, ww, h, radii, sw, sh, w.color(), clip_circle, None, out);
 
     if let Some((color, thickness)) = w.solid_border() {
-        push_plate_solid_border_vertices(x, y, ww, h, w.corner_radius(), thickness, sw, sh, color, clip_circle, out);
+        push_plate_solid_border_vertices(x, y, ww, h, radii, thickness, sw, sh, color, clip_circle, out);
     }
 }
 
@@ -747,13 +933,13 @@ pub fn push_extra_quad_vertices(
     out: &mut Vec<Vertex>,
 ) {
     let target_w = get_child_widget_for_quad(w, qx, qy, qw, qh);
-    let corners = target_w.rounded_corners();
-    if corners == (false, false, false, false) {
+    let radii = target_w.corner_radii();
+    if radii.top_left <= 0.1 && radii.top_right <= 0.1 && radii.bottom_right <= 0.1 && radii.bottom_left <= 0.1 {
         out.extend_from_slice(&quad_vertices_with_clip(qx, qy, qw, qh, sw, sh, qc, clip_circle));
         if let Some((color, thickness)) = target_w.solid_border() {
             let (wx, wy, ww, wh) = target_w.rect();
             if (qx - wx).abs() < 0.1 && (qy - wy).abs() < 0.1 && (qw - ww).abs() < 0.1 && (qh - wh).abs() < 0.1 {
-                push_plate_solid_border_vertices(qx, qy, qw, qh, target_w.corner_radius(), thickness, sw, sh, color, clip_circle, out);
+                push_plate_solid_border_vertices(qx, qy, qw, qh, radii, thickness, sw, sh, color, clip_circle, out);
             }
         }
         return;
@@ -763,14 +949,14 @@ pub fn push_extra_quad_vertices(
     let top_room = crate::widget::label_offset(target_w);
     wy += top_room;
     wh -= top_room;
-    let extra_corners = (
-        corners.0 && qx <= wx + 1.5 && qy <= wy + 1.5,
-        corners.1 && qx + qw >= wx + ww - 1.5 && qy <= wy + 1.5,
-        corners.2 && qx + qw >= wx + ww - 1.5 && qy + qh >= wy + wh - 1.5,
-        corners.3 && qx <= wx + 1.5 && qy + qh >= wy + wh - 1.5,
+    let extra_radii = crate::widget::CornerRadii::new(
+        if qx <= wx + 1.5 && qy <= wy + 1.5 { radii.top_left } else { 0.0 },
+        if qx + qw >= wx + ww - 1.5 && qy <= wy + 1.5 { radii.top_right } else { 0.0 },
+        if qx + qw >= wx + ww - 1.5 && qy + qh >= wy + wh - 1.5 { radii.bottom_right } else { 0.0 },
+        if qx <= wx + 1.5 && qy + qh >= wy + wh - 1.5 { radii.bottom_left } else { 0.0 },
     );
 
-    push_rounded_rect_vertices_corners(qx, qy, qw, qh, target_w.corner_radius(), sw, sh, qc, clip_circle, extra_corners, None, out);
+    push_rounded_rect_vertices_corners(qx, qy, qw, qh, extra_radii, sw, sh, qc, clip_circle, None, out);
 
     if let Some((color, thickness)) = target_w.solid_border() {
         let (rx, mut ry, rw, mut rh) = target_w.rect();
@@ -778,7 +964,7 @@ pub fn push_extra_quad_vertices(
         ry += top;
         rh -= top;
         if (qx - rx).abs() < 0.1 && (qy - ry).abs() < 0.1 && (qw - rw).abs() < 0.1 && (qh - rh).abs() < 0.1 {
-            push_plate_solid_border_vertices(qx, qy, qw, qh, target_w.corner_radius(), thickness, sw, sh, color, clip_circle, out);
+            push_plate_solid_border_vertices(qx, qy, qw, qh, radii, thickness, sw, sh, color, clip_circle, out);
         }
     }
 }
@@ -806,8 +992,8 @@ pub fn push_extra_quad_vertices_clipped(
     out: &mut Vec<Vertex>,
 ) {
     let target_w = get_child_widget_for_quad(w, qx, qy, qw, qh);
-    let corners = target_w.rounded_corners();
-    if corners == (false, false, false, false) {
+    let radii = target_w.corner_radii();
+    if radii.top_left <= 0.1 && radii.top_right <= 0.1 && radii.bottom_right <= 0.1 && radii.bottom_left <= 0.1 {
         let (cx0, cy0, cx1, cy1) = clip;
         let ix0 = qx.max(cx0);
         let iy0 = qy.max(cy0);
@@ -820,7 +1006,7 @@ pub fn push_extra_quad_vertices_clipped(
         if let Some((color, thickness)) = target_w.solid_border() {
             let (wx, wy, ww, wh) = target_w.rect();
             if (qx - wx).abs() < 0.1 && (qy - wy).abs() < 0.1 && (qw - ww).abs() < 0.1 && (qh - wh).abs() < 0.1 {
-                push_plate_solid_border_vertices(qx, qy, qw, qh, target_w.corner_radius(), thickness, sw, sh, color, clip_circle, out);
+                push_plate_solid_border_vertices(qx, qy, qw, qh, radii, thickness, sw, sh, color, clip_circle, out);
             }
         }
         return;
@@ -830,14 +1016,14 @@ pub fn push_extra_quad_vertices_clipped(
     let top_room = crate::widget::label_offset(target_w);
     wy += top_room;
     wh -= top_room;
-    let extra_corners = (
-        corners.0 && qx <= wx + 1.5 && qy <= wy + 1.5,
-        corners.1 && qx + qw >= wx + ww - 1.5 && qy <= wy + 1.5,
-        corners.2 && qx + qw >= wx + ww - 1.5 && qy + qh >= wy + wh - 1.5,
-        corners.3 && qx <= wx + 1.5 && qy + qh >= wy + wh - 1.5,
+    let extra_radii = crate::widget::CornerRadii::new(
+        if qx <= wx + 1.5 && qy <= wy + 1.5 { radii.top_left } else { 0.0 },
+        if qx + qw >= wx + ww - 1.5 && qy <= wy + 1.5 { radii.top_right } else { 0.0 },
+        if qx + qw >= wx + ww - 1.5 && qy + qh >= wy + wh - 1.5 { radii.bottom_right } else { 0.0 },
+        if qx <= wx + 1.5 && qy + qh >= wy + wh - 1.5 { radii.bottom_left } else { 0.0 },
     );
 
-    push_rounded_rect_vertices_corners(qx, qy, qw, qh, target_w.corner_radius(), sw, sh, qc, clip_circle, extra_corners, Some(clip), out);
+    push_rounded_rect_vertices_corners(qx, qy, qw, qh, extra_radii, sw, sh, qc, clip_circle, Some(clip), out);
 
     if let Some((color, thickness)) = target_w.solid_border() {
         let (rx, mut ry, rw, mut rh) = target_w.rect();
@@ -845,7 +1031,7 @@ pub fn push_extra_quad_vertices_clipped(
         ry += top;
         rh -= top;
         if (qx - rx).abs() < 0.1 && (qy - ry).abs() < 0.1 && (qw - rw).abs() < 0.1 && (qh - rh).abs() < 0.1 {
-            push_plate_solid_border_vertices(qx, qy, qw, qh, target_w.corner_radius(), thickness, sw, sh, color, clip_circle, out);
+            push_plate_solid_border_vertices(qx, qy, qw, qh, radii, thickness, sw, sh, color, clip_circle, out);
         }
     }
 }
@@ -1316,7 +1502,13 @@ impl<A: Application> EngineState<A> {
         }
         for &(qx, qy, qw, qh, qr, qc, qcorners) in &rounded_quads {
             if qr > 0.1 {
-                push_rounded_rect_vertices_corners(qx, qy, qw, qh, qr, logical_w, logical_h, qc, [0.0, 0.0, 0.0], qcorners, None, &mut verts);
+                let radii = crate::widget::CornerRadii::new(
+                    if qcorners.0 { qr } else { 0.0 },
+                    if qcorners.1 { qr } else { 0.0 },
+                    if qcorners.2 { qr } else { 0.0 },
+                    if qcorners.3 { qr } else { 0.0 },
+                );
+                push_rounded_rect_vertices_corners(qx, qy, qw, qh, radii, logical_w, logical_h, qc, [0.0, 0.0, 0.0], None, &mut verts);
             } else {
                 verts.extend(quad_vertices(qx, qy, qw, qh, logical_w, logical_h, qc));
             }
diff --git a/src/layout.rs b/src/layout.rs
index ec15fc2..d40dfd2 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -1,6 +1,68 @@
 use crate::widget::Element;
 use crate::context::UiContext;
 use std::sync::RwLock;
+use std::collections::HashMap;
+use std::sync::OnceLock;
+
+#[derive(Debug)]
+pub struct StyleRegistry {
+    pub floats: HashMap<String, f32>,
+    pub strings: HashMap<String, String>,
+}
+
+impl StyleRegistry {
+    pub fn new() -> Self {
+        Self {
+            floats: HashMap::new(),
+            strings: HashMap::new(),
+        }
+    }
+
+    pub fn get_float(&self, key: &str) -> Option<f32> {
+        self.floats.get(key).copied()
+    }
+
+    pub fn get_string(&self, key: &str) -> Option<String> {
+        self.strings.get(key).cloned()
+    }
+
+    pub fn set_float(&mut self, key: &str, val: f32) {
+        self.floats.insert(key.to_string(), val);
+    }
+
+    pub fn set_string(&mut self, key: &str, val: String) {
+        self.strings.insert(key.to_string(), val);
+    }
+}
+
+pub static STYLE_REGISTRY: OnceLock<RwLock<StyleRegistry>> = OnceLock::new();
+
+pub fn get_style_registry() -> &'static RwLock<StyleRegistry> {
+    STYLE_REGISTRY.get_or_init(|| RwLock::new(StyleRegistry::new()))
+}
+
+pub fn lazy_init_style_registry() {
+    use std::sync::Once;
+    static INIT: Once = Once::new();
+    INIT.call_once(|| {
+        if let Some(content) = read_config() {
+            for line in content.lines() {
+                let trimmed = line.trim();
+                if let Some(eq_idx) = trimmed.find('=') {
+                    let key = trimmed[..eq_idx].trim().to_string();
+                    let val_str = trimmed[eq_idx + 1..].trim().trim_matches('"').trim();
+                    if let Ok(mut registry) = get_style_registry().write() {
+                        if let Ok(f_val) = val_str.parse::<f32>() {
+                            registry.set_float(&key, f_val);
+                        } else {
+                            registry.set_string(&key, val_str.to_string());
+                        }
+                    }
+                }
+            }
+        }
+    });
+}
 
 fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props: &mut String) {
     match val {
@@ -228,6 +290,17 @@ pub fn reload_config() {
         let mut list_font_changed = false;
         for line in content.lines() {
             let trimmed = line.trim();
+            if let Some(eq_idx) = trimmed.find('=') {
+                let key = trimmed[..eq_idx].trim().to_string();
+                let val_str = trimmed[eq_idx + 1..].trim().trim_matches('"').trim();
+                if let Ok(mut registry) = get_style_registry().write() {
+                    if let Ok(f_val) = val_str.parse::<f32>() {
+                        registry.set_float(&key, f_val);
+                    } else {
+                        registry.set_string(&key, val_str.to_string());
+                    }
+                }
+            }
             if let Some(rest) = trimmed.strip_prefix("label_margin") {
                 let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
                 let val_str = rest.trim_end_matches('"').trim();
@@ -2359,64 +2432,33 @@ pub fn breadcrumb_corner_radius() -> f32 {
 }
 
 pub fn set_breadcrumb_corner_radius(radius: f32) {
-    if let Ok(mut lock) = BREADCRUMB_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("breadcrumb_corner_radius", radius);
     }
 }
 
 pub fn list_corner_radius() -> f32 {
-    use std::sync::Once;
-    static INIT: Once = Once::new();
-    INIT.call_once(|| {
-        if let Some(content) = read_config() {
-            for line in content.lines() {
-                let trimmed = line.trim();
-                if let Some(rest) = trimmed.strip_prefix("list_corner_radius") {
-                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
-                    let val_str = rest.trim_end_matches('"').trim();
-                    if let Ok(val) = val_str.parse::<f32>() {
-                        if let Ok(mut lock) = LIST_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *LIST_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("list_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_list_corner_radius(radius: f32) {
-    if let Ok(mut lock) = LIST_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("list_corner_radius", radius);
     }
 }
 
 pub fn tree_corner_radius() -> f32 {
-    use std::sync::Once;
-    static INIT: Once = Once::new();
-    INIT.call_once(|| {
-        if let Some(content) = read_config() {
-            for line in content.lines() {
-                let trimmed = line.trim();
-                if let Some(rest) = trimmed.strip_prefix("tree_corner_radius") {
-                    let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
-                    let val_str = rest.trim_end_matches('"').trim();
-                    if let Ok(val) = val_str.parse::<f32>() {
-                        if let Ok(mut lock) = TREE_CORNER_RADIUS.write() {
-                            *lock = val;
-                        }
-                    }
-                }
-            }
-        }
-    });
-    *TREE_CORNER_RADIUS.read().unwrap()
+    lazy_init_style_registry();
+    get_style_registry().read().unwrap().get_float("tree_corner_radius").unwrap_or(4.0)
 }
 
 pub fn set_tree_corner_radius(radius: f32) {
-    if let Ok(mut lock) = TREE_CORNER_RADIUS.write() {
-        *lock = radius;
+    lazy_init_style_registry();
+    if let Ok(mut registry) = get_style_registry().write() {
+        registry.set_float("tree_corner_radius", radius);
     }
 }
 
@@ -3670,7 +3712,6 @@ impl Radial {
 }
 
 use std::cell::RefCell;
-use std::collections::HashMap;
 
 thread_local! {
     pub static GRID_STATES: RefCell<HashMap<usize, Grid>> = RefCell::new(HashMap::new());
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 9c4877d..e2dc580 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -610,6 +610,17 @@ pub trait Element {
     fn rounded_corners(&self) -> (bool, bool, bool, bool) { (false, false, false, false) }
 
     fn corner_radius(&self) -> f32 { 12.0 }
+
+    fn corner_radii(&self) -> CornerRadii {
+        let r = self.corner_radius();
+        let (tl, tr, br, bl) = self.rounded_corners();
+        CornerRadii::new(
+            if tl { r } else { 0.0 },
+            if tr { r } else { 0.0 },
+            if br { r } else { 0.0 },
+            if bl { r } else { 0.0 },
+        )
+    }
     fn layout_ignore(&self) -> bool { false }
     fn color_u8(&self) -> Option<[u8; 4]> { None }
 }
@@ -757,3 +768,21 @@ pub fn label_offset(w: &dyn Element) -> f32 {
     }
     w.base().map_or(0.0, |b| b.label_offset())
 }
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct CornerRadii {
+    pub top_left: f32,
+    pub top_right: f32,
+    pub bottom_right: f32,
+    pub bottom_left: f32,
+}
+
+impl CornerRadii {
+    pub fn new(tl: f32, tr: f32, br: f32, bl: f32) -> Self {
+        Self { top_left: tl, top_right: tr, bottom_right: br, bottom_left: bl }
+    }
+
+    pub fn uniform(radius: f32) -> Self {
+        Self::new(radius, radius, radius, radius)
+    }
+}