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

commitd67a1493c41038538034e6cd68d889d4d54f5e61
parentafd1288d72
authorLucas Galante <[email protected]>
date2026-09-09 11:42
feat(layout): control_relief is read live; set_control_relief switches it

Every control captured `control_relief()` once at construction into a
`raised` / `recessed` bool, so the DE's relief setting could only change
between runs, and a gallery had to build a second copy of each widget to show
its flat look. The flag is now `Option<bool>` on all fourteen widgets that
carry it — Button, Toggle, Dropdown, FontSelector, ButtonStrip,
KeybindRecorder, ColorSelector, TextBox, Trackpad, ProgressBar, UsageBar,
MenuBar, StatusBar — read through a `raised()` / `recessed()` accessor that
falls back to `control_relief()` at paint and layout; `with_raised` /
`with_recessed` / `with_recess` pin one widget either way, as before.
`layout::set_control_relief` writes the registry value so a process can
switch every unpinned control at once (the gallery's Style dropdown). Not
persisted; a config reload restores the configured value.

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/layout.rs                        | 17 ++++++++++++++---
 src/widget/container/menu.rs         | 15 +++++++++++----
 src/widget/display/progress_bar.rs   | 15 +++++++++++----
 src/widget/display/status_bar.rs     | 15 +++++++++++----
 src/widget/display/usage_bar.rs      | 15 +++++++++++----
 src/widget/input/button.rs           | 15 +++++++++++----
 src/widget/input/button_strip.rs     | 21 ++++++++++++++-------
 src/widget/input/checkbox.rs         | 15 +++++++++++----
 src/widget/input/color_selector.rs   | 21 ++++++++++++++-------
 src/widget/input/dropdown.rs         | 17 ++++++++++++-----
 src/widget/input/font_selector.rs    | 15 +++++++++++----
 src/widget/input/keybind_recorder.rs | 15 +++++++++++----
 src/widget/input/text_box.rs         | 17 ++++++++++++-----
 src/widget/input/trackpad.rs         | 15 +++++++++++----
 14 files changed, 165 insertions(+), 63 deletions(-)

diff --git a/src/layout.rs b/src/layout.rs
index 3b82d03..a686bc9 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -1847,14 +1847,25 @@ pub fn bar_wall_width() -> f32 {
 
 /// DE-wide default for the controls' relief styling (`window_manager.control_relief`
 /// in config.kdl, default on): raised Button/Toggle/Dropdown plates, recessed
-/// TextBox/Slider wells, recessed MenuBar/StatusBar bands. Widgets read this at
-/// construction; the per-widget `with_raised` / `with_recessed` / `with_recess`
-/// builders override it either way. `0` reverts the whole DE to the flat look.
+/// TextBox/Slider wells, recessed MenuBar/StatusBar bands. Widgets read this
+/// LIVE, at paint and layout, so [`set_control_relief`] restyles every control
+/// in the process at once; the per-widget `with_raised` / `with_recessed` /
+/// `with_recess` builders pin one widget either way. `0` reverts the whole DE
+/// to the flat look.
 pub fn control_relief() -> bool {
     lazy_init_style_registry();
     get_style_registry().read().unwrap().get_float("control_relief").map(|v| v != 0.0).unwrap_or(true)
 }
 
+/// Switch the controls' relief styling at runtime — the gallery's Style
+/// dropdown. Every widget without a per-widget override follows on its next
+/// paint; the caller asks for a rebuild. Not persisted: a config reload puts
+/// the configured value back.
+pub fn set_control_relief(relief: bool) {
+    lazy_init_style_registry();
+    get_style_registry().write().unwrap().set_float("control_relief", if relief { 1.0 } else { 0.0 });
+}
+
 pub fn toggle_corner_radius() -> f32 {
     lazy_init_style_registry();
     get_style_registry().read().unwrap().get_float("toggle_corner_radius").unwrap_or_else(control_corner_radius)
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index 63a101e..f161b97 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -36,7 +36,7 @@ pub struct MenuBar {
     /// Draw as a recess carved into the window root plate instead of as an opaque bar:
     /// no background fill of its own, just shaded edges, so the plate shows through.
     /// `color` is ignored while this is set — see [`Adapted::<MenuBar>::with_recess`].
-    pub recessed: bool,
+    pub recessed: Option<bool>,
     pub title: String,
     pub menus: Adapted<ButtonStrip>,
     pub menu_items: Vec<String>,
@@ -65,6 +65,13 @@ pub struct MenuBar {
 }
 
 impl MenuBar {
+    /// The style in force: the per-widget override (`with_recess`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(x: f32, y: f32, w: f32, h: f32) -> Adapted<MenuBar> {
         let mut bar = Adapted::new(MenuBar {
             visible: true,
@@ -72,7 +79,7 @@ impl MenuBar {
             curved_circle: None,
             blur: false,
             color: None,
-            recessed: crate::layout::control_relief(),
+            recessed: None,
             title: String::new(),
             menus: Adapted::new(ButtonStrip::new(x, y, w, h).with_inherit_menubar_font(true)),
             menu_items: Vec::new(),
@@ -359,7 +366,7 @@ impl Adapted<MenuBar> {
     /// than a slab sitting on it. Shading follows the DE-wide `light_source_position` /
     /// `bevel_depth` config, inverted so the light-facing edges are the shadowed ones.
     pub fn with_recess(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 
@@ -464,7 +471,7 @@ impl Paint for MenuBar {
     }
 
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
-        if self.recessed {
+        if self.recessed() {
             // No background of our own: carve the root plate instead. The recess shading is
             // a light/shadow overlay, so whatever the plate painted here (fill, rim
             // gradient, blur) shows through modulated.
diff --git a/src/widget/display/progress_bar.rs b/src/widget/display/progress_bar.rs
index 9bee403..dcac390 100644
--- a/src/widget/display/progress_bar.rs
+++ b/src/widget/display/progress_bar.rs
@@ -14,19 +14,26 @@ pub struct ProgressBar {
     /// plate below — no track fill, the plate is the floor — with the progress
     /// fill inset onto that floor. Defaults to `control_relief()`; the flat
     /// style keeps the filled, rounded track.
-    recessed: bool,
+    recessed: Option<bool>,
 }
 
 impl ProgressBar {
+    /// The style in force: the per-widget override (`with_recessed`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(value: f32) -> Adapted<ProgressBar> {
-        Adapted::new(ProgressBar { value, recessed: crate::layout::control_relief() })
+        Adapted::new(ProgressBar { value, recessed: None })
     }
 }
 
 impl Adapted<ProgressBar> {
     /// Recessed style: see the `recessed` field.
     pub fn with_recessed(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 }
@@ -49,7 +56,7 @@ impl Paint for ProgressBar {
 
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
         let radius = crate::layout::slider_corner_radius();
-        if self.recessed {
+        if self.recessed() {
             // The Slider's recessed composition: the fill sits on the well's flat
             // floor (past the wall's inner half-span), the carve comes after it so
             // the walls' shading modulates what they cross.
diff --git a/src/widget/display/status_bar.rs b/src/widget/display/status_bar.rs
index a3f8b10..36beb0b 100644
--- a/src/widget/display/status_bar.rs
+++ b/src/widget/display/status_bar.rs
@@ -24,10 +24,17 @@ pub struct StatusBar {
     /// background fill of its own, just the shaded wall facing the content, so the plate
     /// shows through. `bg_color` is ignored while this is set — see
     /// [`Adapted::<StatusBar>::with_recess`].
-    pub recessed: bool,
+    pub recessed: Option<bool>,
 }
 
 impl StatusBar {
+    /// The style in force: the per-widget override (`with_recess`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new() -> Adapted<StatusBar> {
         Adapted::new(StatusBar {
             rect: Rect { x: 0.0, y: 0.0, width: 0.0, height: 0.0 },
@@ -36,7 +43,7 @@ impl StatusBar {
             text_offset_x: None,
             text_color: None,
             bg_color: None,
-            recessed: crate::layout::control_relief(),
+            recessed: None,
         })
     }
 
@@ -92,7 +99,7 @@ impl Adapted<StatusBar> {
     /// (facing the content) — the other three sides are the plate's outer edge, which
     /// carries its own roll.
     pub fn with_recess(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 }
@@ -137,7 +144,7 @@ impl Paint for StatusBar {
     /// default `all_rounded_quads` path) — plus the text label (the legacy `text_labels`
     /// body; deliberately no `widget_font`, see module docs).
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
-        if self.recessed {
+        if self.recessed() {
             // The recess shading is a light/shadow overlay — whatever the plate painted
             // here shows through modulated, so no surface color is needed.
             // Capped against the bar's own height so a deep DE-wide roll can't swallow it
diff --git a/src/widget/display/usage_bar.rs b/src/widget/display/usage_bar.rs
index c554c9d..46ab580 100644
--- a/src/widget/display/usage_bar.rs
+++ b/src/widget/display/usage_bar.rs
@@ -14,16 +14,23 @@ pub struct UsageBar {
     /// Recessed-track style, the ProgressBar's: a well carved into the plate,
     /// `bg_color` unused (the plate is the floor), the fill inset onto it.
     /// Defaults to `control_relief()`.
-    recessed: bool,
+    recessed: Option<bool>,
 }
 
 impl UsageBar {
+    /// The style in force: the per-widget override (`with_recessed`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(value: f32) -> Adapted<UsageBar> {
         Adapted::new(UsageBar {
             value: value.clamp(0.0, 1.0),
             fill_color: [0.30, 0.50, 0.32, 1.0], // green-ish
             bg_color: [0.15, 0.15, 0.24, 1.0],   // dark-ish
-            recessed: crate::layout::control_relief(),
+            recessed: None,
         })
     }
 
@@ -43,7 +50,7 @@ impl Adapted<UsageBar> {
 
     /// Recessed style: see the `recessed` field.
     pub fn with_recessed(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 }
@@ -61,7 +68,7 @@ impl Paint for UsageBar {
     }
 
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
-        if self.recessed {
+        if self.recessed() {
             // The ProgressBar's recessed composition: fill on the well floor, then
             // the carve, rounded like the sliders' tracks.
             let radius = crate::layout::slider_corner_radius();
diff --git a/src/widget/input/button.rs b/src/widget/input/button.rs
index c765ee3..04c3a4d 100644
--- a/src/widget/input/button.rs
+++ b/src/widget/input/button.rs
@@ -52,7 +52,7 @@ pub struct Button {
     focused: bool,
     /// Raised style: the background is an SDF-lit `Bevel` plate — fill plus a
     /// rolled, lit edge — instead of a flat fill + border stroke.
-    raised: bool,
+    raised: Option<bool>,
 }
 
 impl std::fmt::Debug for Button {
@@ -70,6 +70,13 @@ impl std::fmt::Debug for Button {
 }
 
 impl Button {
+    /// The style in force: the per-widget override (`with_raised`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn raised(&self) -> bool {
+        self.raised.unwrap_or_else(crate::layout::control_relief)
+    }
+
     fn model(kind: ButtonKind) -> Button {
         Button {
             pressed: false,
@@ -86,7 +93,7 @@ impl Button {
             icon_alpha: 1.0,
             hovered: false,
             focused: false,
-            raised: crate::layout::control_relief(),
+            raised: None,
         }
     }
 
@@ -190,7 +197,7 @@ impl Button {
     /// (flat styling, or a ListRow / MenuItem, transparent-until-hover
     /// surfaces that would wear a permanent carved ring on every idle row).
     pub fn plate(&self, rect: Rect) -> Option<crate::widget::ControlPlate> {
-        if !self.raised
+        if !self.raised()
             || self.kind == ButtonKind::ListRow
             || self.kind == ButtonKind::MenuItem
         {
@@ -232,7 +239,7 @@ impl Adapted<Button> {
 
     /// Raised style: see the `raised` field.
     pub fn with_raised(mut self, raised: bool) -> Self {
-        self.raised = raised;
+        self.raised = Some(raised);
         self
     }
 
diff --git a/src/widget/input/button_strip.rs b/src/widget/input/button_strip.rs
index 1e9e45a..80459ad 100644
--- a/src/widget/input/button_strip.rs
+++ b/src/widget/input/button_strip.rs
@@ -28,13 +28,20 @@ pub struct ButtonStrip {
     /// floor; the selected segment is a plateau raised back out of it, hover
     /// and press a wash. Defaults to `control_relief()`; the flat style keeps
     /// the plain state quads.
-    pub recessed: bool,
+    pub recessed: Option<bool>,
     /// Keyboard focus (FocusIn / FocusOut): the selected segment's plate wears
     /// the ring; arrows move the selection, Enter / Space press it.
     focused: bool,
 }
 
 impl ButtonStrip {
+    /// The style in force: the per-widget override (`with_recessed`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
         Self {
             x,
@@ -54,14 +61,14 @@ impl ButtonStrip {
             last_scale: None,
             inherit_menubar_font: false,
             label: None,
-            recessed: crate::layout::control_relief(),
+            recessed: None,
             focused: false,
         }
     }
 
     /// Recessed style: see the `recessed` field.
     pub fn with_recessed(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 
@@ -352,7 +359,7 @@ impl crate::widget::Paint for ButtonStrip {
         // through `all_rounded_quads`, which the Paginator aggregates).
         let (sx, sy, sw, sh) = self.rect();
         let radius = crate::layout::button_corner_radius();
-        let depth = if self.recessed {
+        let depth = if self.recessed() {
             let short = if self.vertical { sw } else { sh };
             let depth = crate::layout::bevel_width().min(short * 0.2);
             let (well, radii) = crate::layout::carve_inside(Rect { x: sx, y: sy, width: sw, height: sh }, (radius, radius, radius, radius), depth);
@@ -376,12 +383,12 @@ impl crate::widget::Paint for ButtonStrip {
             // The segment's footprint: in the well, inset by the wall's inner
             // half-span so it stands on the floor (the selected plateau's rect);
             // flat, the item rect itself. The state fill and the plateau share it.
-            let inset = if self.recessed { depth * 0.5 } else { 0.0 };
+            let inset = if self.recessed() { depth * 0.5 } else { 0.0 };
             let seg = Rect { x: r.0 + inset, y: r.1 + inset, width: (r.2 - 2.0 * inset).max(0.0), height: (r.3 - 2.0 * inset).max(0.0) };
             let seg_r = (radius - inset).max(0.0);
             let focus_ring = self.focused && Some(i) == self.selected;
             if bg_color != [0.0, 0.0, 0.0, 0.0] {
-                if focus_ring && !self.recessed {
+                if focus_ring && !self.recessed() {
                     // Flat: no rim to light, so the selected fill wears a hairline
                     // ring in the highlight.
                     let t = crate::widget::ControlPlate::focus_tint();
@@ -390,7 +397,7 @@ impl crate::widget::Paint for ButtonStrip {
                     pc.rounded_rect(seg, seg_r, (true, true, true, true), bg_color);
                 }
             }
-            if self.recessed && Some(i) == self.selected {
+            if self.recessed() && Some(i) == self.selected {
                 // The selected segment: a raised control plate standing on the
                 // well floor, faceless (the floor shows through), at the well's
                 // depth — its rim lit while the strip holds keyboard focus.
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index 0d2bb99..9ce7f85 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -233,7 +233,7 @@ pub struct Toggle {
     /// beveled edges, the state half a raised plateau, the other recessed
     /// (see `rocker_reliefs` and the paint impl) — and the flat style's
     /// state gradient is dropped.
-    raised: bool,
+    raised: Option<bool>,
     /// The slide style's animated button position, 0 (left/off) → 1 (right/on).
     /// Chases `toggled` in `tick` after a click; programmatic state syncs
     /// (`set_toggled`, `set_value_string`) snap it, so only user interaction
@@ -245,6 +245,13 @@ pub struct Toggle {
 }
 
 impl Toggle {
+    /// The style in force: the per-widget override (`with_raised`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn raised(&self) -> bool {
+        self.raised.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new() -> Adapted<Toggle> {
         Adapted::new(Toggle {
             toggled: false,
@@ -253,7 +260,7 @@ impl Toggle {
             hovered: false,
             focused: false,
             justify: Justification::Center,
-            raised: crate::layout::control_relief(),
+            raised: None,
             slide_t: 0.0,
             slide_override: None,
         })
@@ -369,7 +376,7 @@ impl Toggle {
                 edges: (true, true, true, true),
             }];
         }
-        if !self.raised {
+        if !self.raised() {
             return Vec::new();
         }
         // The carves stay inside the pill (`layout::carve_inside`): the halves are
@@ -442,7 +449,7 @@ impl Adapted<Toggle> {
 
     /// Raised style: see the `raised` field.
     pub fn with_raised(mut self, raised: bool) -> Self {
-        self.raised = raised;
+        self.raised = Some(raised);
         self
     }
 
diff --git a/src/widget/input/color_selector.rs b/src/widget/input/color_selector.rs
index 5f97634..8fc9bcb 100644
--- a/src/widget/input/color_selector.rs
+++ b/src/widget/input/color_selector.rs
@@ -35,7 +35,7 @@ pub struct ColorSelector {
     /// (the TextBox's, rim lit while editing) and the swatch a raised bevel
     /// plate of its colour, instead of the hairline frame and the flat swatch
     /// with its glow. Defaults to `control_relief()`.
-    recessed: bool,
+    recessed: Option<bool>,
 }
 
 impl Clone for ColorSelector {
@@ -63,6 +63,13 @@ impl Clone for ColorSelector {
 }
 
 impl ColorSelector {
+    /// The style in force: the per-widget override (`with_recessed`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(color: [u8; 3]) -> Adapted<ColorSelector> {
         Adapted::new(ColorSelector {
             color,
@@ -81,7 +88,7 @@ impl ColorSelector {
             live_rx: None,
             revert_hex: None,
             glyph_offsets: Vec::new(),
-            recessed: crate::layout::control_relief(),
+            recessed: None,
         })
     }
 
@@ -103,7 +110,7 @@ impl ColorSelector {
             live_rx: None,
             revert_hex: None,
             glyph_offsets: Vec::new(),
-            recessed: crate::layout::control_relief(),
+            recessed: None,
         })
     }
 
@@ -112,7 +119,7 @@ impl ColorSelector {
     /// widget's assigned content `rect`, or None when the style is off. The same
     /// geometry `paint` carves (untinted).
     pub fn field_relief(&self, rect: Rect) -> Option<(f32, f32, f32, f32, f32, f32)> {
-        if !self.recessed {
+        if !self.recessed() {
             return None;
         }
         let well_h = crate::layout::color_selector_height().min(rect.height);
@@ -133,7 +140,7 @@ impl ColorSelector {
 impl Adapted<ColorSelector> {
     /// Recessed style: see the `recessed` field.
     pub fn with_recessed(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 
@@ -251,7 +258,7 @@ impl Paint for ColorSelector {
         // A real frame, not a border-quad-under-fill-quad: with no fill, the
         // old full-rect border quad would read as a solid slab. Rounded at the
         // selector's radius like the well it stands in for.
-        if !self.recessed {
+        if !self.recessed() {
             let fr = crate::layout::color_selector_corner_radius();
             ctx.border(rect, (fr, fr, fr, fr), [0.0; 4], border_color, 1.0);
         }
@@ -321,7 +328,7 @@ impl Paint for ColorSelector {
             }
         };
 
-        if self.recessed {
+        if self.recessed() {
             // The Breadcrumb composition: ONE well across the whole control,
             // the hex text on its floor at the left and the swatch as the
             // colour laid flush on the floor's right segment (the well's
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 601342a..01d1465 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -106,7 +106,7 @@ pub struct Dropdown {
     corner_frame: Option<((f32, f32, f32, f32), f32, (bool, bool, bool, bool))>,
     /// Raised style: the closed control's background is an SDF-lit `Bevel`
     /// plate (fill + rolled lit edge) instead of a flat fill + border stroke.
-    raised: bool,
+    raised: Option<bool>,
     /// Keyboard focus (FocusIn / FocusOut): lights the trigger plate's rim.
     focused: bool,
     /// The open menu REPLACES the trigger instead of growing out of it: no
@@ -138,6 +138,13 @@ pub struct Dropdown {
 }
 
 impl Dropdown {
+    /// The style in force: the per-widget override (`with_raised`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn raised(&self) -> bool {
+        self.raised.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(options: Vec<String>, selected: usize) -> Adapted<Dropdown> {
         Adapted::new(Dropdown {
             options,
@@ -154,7 +161,7 @@ impl Dropdown {
             label: None,
             hovered: false,
             corner_frame: None,
-            raised: crate::layout::control_relief(),
+            raised: None,
             focused: false,
             menu_replaces_trigger: false,
             anim_from: 0.0,
@@ -425,7 +432,7 @@ impl Dropdown {
         // flat outlines, which a rolled edge replaces). A transparent
         // configured fill degrades to a Boss: edges only, plate as the face —
         // judged on the RAW alpha, before the opacity force above.
-        if self.raised {
+        if self.raised() {
             let depth = crate::layout::bevel_width().min(visual_h * 0.2);
             // Concentric corner_frame adjustment applies to the relief too: a
             // corner nested at equal gaps into the frame follows its curve.
@@ -737,7 +744,7 @@ impl Dropdown {
 impl Adapted<Dropdown> {
     /// Raised style: see the `raised` field.
     pub fn with_raised(mut self, raised: bool) -> Self {
-        self.raised = raised;
+        self.raised = Some(raised);
         self
     }
 
@@ -906,7 +913,7 @@ impl Paint for Dropdown {
             c[3] = -crate::color::menu_opacity();
             c
         };
-        if self.raised {
+        if self.raised() {
             let depth = crate::layout::bevel_width().min(rect.height * 0.2);
             let (t, tr) = crate::layout::carve_inside(
                 crate::scene::layout::Rect { x: ux, y: uy, width: uw, height: uh },
diff --git a/src/widget/input/font_selector.rs b/src/widget/input/font_selector.rs
index 082cc7d..27f432d 100644
--- a/src/widget/input/font_selector.rs
+++ b/src/widget/input/font_selector.rs
@@ -20,13 +20,20 @@ pub struct FontSelector {
     /// with a transparent face (the plate shows through), the hover and press
     /// states a wash inside it. Defaults to `control_relief()`; the flat style
     /// keeps the framed dark field.
-    raised: bool,
+    raised: Option<bool>,
     /// Keyboard focus (FocusIn / FocusOut): lights the plate's rim and arms
     /// Enter / Space to open the picker.
     focused: bool,
 }
 
 impl FontSelector {
+    /// The style in force: the per-widget override (`with_raised`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn raised(&self) -> bool {
+        self.raised.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(font_family: String) -> Adapted<FontSelector> {
         Adapted::new(FontSelector {
             font_family,
@@ -34,7 +41,7 @@ impl FontSelector {
             pressed: false,
             hovered: false,
             child: Arc::new(Mutex::new(None)),
-            raised: crate::layout::control_relief(),
+            raised: None,
             focused: false,
         })
     }
@@ -127,7 +134,7 @@ impl FontSelector {
 impl Adapted<FontSelector> {
     /// Raised style: see the `raised` field.
     pub fn with_raised(mut self, raised: bool) -> Self {
-        self.raised = raised;
+        self.raised = Some(raised);
         self
     }
 }
@@ -160,7 +167,7 @@ impl Paint for FontSelector {
 
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
         let r = crate::layout::font_selector_corner_radius();
-        if self.raised {
+        if self.raised() {
             // The closed-dropdown chrome: a flush control plate with a
             // transparent face, the state fill rounded to sit inside it.
             ctx.control_plate(
diff --git a/src/widget/input/keybind_recorder.rs b/src/widget/input/keybind_recorder.rs
index 0b65053..e30eebd 100644
--- a/src/widget/input/keybind_recorder.rs
+++ b/src/widget/input/keybind_recorder.rs
@@ -18,12 +18,19 @@ pub struct KeybindRecorder {
     /// plate below with no fill of its own, its rim lit in the highlight
     /// accent while recording (the TextBox's editing treatment). Defaults to
     /// `control_relief()`; the flat style is the shared well frame.
-    recessed: bool,
+    recessed: Option<bool>,
     /// Keyboard focus (FocusIn / FocusOut): Enter / Space arm recording.
     focused: bool,
 }
 
 impl KeybindRecorder {
+    /// The style in force: the per-widget override (`with_recessed`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(value: String) -> Adapted<KeybindRecorder> {
         Adapted::new(KeybindRecorder {
             value,
@@ -31,7 +38,7 @@ impl KeybindRecorder {
             just_changed: false,
             pressed: false,
             hovered: false,
-            recessed: crate::layout::control_relief(),
+            recessed: None,
             focused: false,
         })
     }
@@ -52,7 +59,7 @@ impl KeybindRecorder {
 impl Adapted<KeybindRecorder> {
     /// Recessed style: see the `recessed` field.
     pub fn with_recessed(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 }
@@ -75,7 +82,7 @@ impl Paint for KeybindRecorder {
     }
 
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
-        if self.recessed {
+        if self.recessed() {
             let radius = crate::layout::textbox_corner_radius();
             let depth = crate::layout::bevel_width().min(rect.height * 0.2);
             let (well, radii) = crate::layout::carve_inside(rect, (radius, radius, radius, radius), depth);
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index 5b98a87..a49d265 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -105,10 +105,17 @@ pub struct TextBox {
     rect: Rect,
     /// Recessed style: a `Recess` overlay is carved over the box's own fill —
     /// an inset well, the input-direction counterpart of the raised controls.
-    recessed: bool,
+    recessed: Option<bool>,
 }
 
 impl TextBox {
+    /// The style in force: the per-widget override (`with_recessed`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new(text: String) -> Adapted<TextBox> {
         let (style_family, style_size) = crate::layout::control_label_font_detached_parsed();
         let editor_state = TextEditorState::new(text.clone());
@@ -151,7 +158,7 @@ impl TextBox {
             label: None,
             hovered: false,
             rect: Rect { x: 0.0, y: 0.0, width: 0.0, height: 0.0 },
-            recessed: crate::layout::control_relief(),
+            recessed: None,
         })
     }
 
@@ -1186,7 +1193,7 @@ impl TextBox {
     /// the bridge is exactly how the two would drift apart.
     pub fn well(&self) -> Option<(Rect, f32, f32, Option<[f32; 3]>)> {
         let radius = crate::layout::textbox_corner_radius();
-        if radius <= 0.0 || !self.recessed || !self.draw_bg_border {
+        if radius <= 0.0 || !self.recessed() || !self.draw_bg_border {
             return None;
         }
         let top = self.label_top();
@@ -1209,7 +1216,7 @@ impl TextBox {
 impl Adapted<TextBox> {
     /// Recessed style: see the `recessed` field.
     pub fn with_recessed(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 
@@ -1526,7 +1533,7 @@ impl Paint for TextBox {
                 // well — the plate below is its floor, so the flat border and
                 // bg rects are skipped entirely. An opaque fill (e.g. the edit
                 // color while editing) draws as usual and gets carved.
-                let bare = self.recessed && bg_color[3] <= 0.001;
+                let bare = self.recessed() && bg_color[3] <= 0.001;
                 if !bare {
                     ctx.rounded_rect(Rect { x, y: self.rect.y + top, width: w, height: visual_h }, radius, corners, border_color);
                     ctx.rounded_rect(
diff --git a/src/widget/input/trackpad.rs b/src/widget/input/trackpad.rs
index be16a53..08ff2a7 100644
--- a/src/widget/input/trackpad.rs
+++ b/src/widget/input/trackpad.rs
@@ -23,17 +23,24 @@ pub struct Trackpad {
     /// Recessed style: the touch area is a well carved into the plate below,
     /// a faint dark wash for its floor, instead of the framed dark pane.
     /// Defaults to `control_relief()`.
-    recessed: bool,
+    recessed: Option<bool>,
 }
 
 impl Trackpad {
+    /// The style in force: the per-widget override (`with_recessed`) when set, else
+    /// the DE's `control_relief`, read live so a runtime switch
+    /// (`layout::set_control_relief`) restyles every control at once.
+    fn recessed(&self) -> bool {
+        self.recessed.unwrap_or_else(crate::layout::control_relief)
+    }
+
     pub fn new() -> Adapted<Trackpad> {
         Adapted::new(Trackpad {
             rect: Rect { x: 0.0, y: 0.0, width: 0.0, height: 0.0 },
             label: None,
             hovered: false,
             fingers: Vec::new(),
-            recessed: crate::layout::control_relief(),
+            recessed: None,
         })
     }
 
@@ -45,7 +52,7 @@ impl Trackpad {
 impl Adapted<Trackpad> {
     /// Recessed style: see the `recessed` field.
     pub fn with_recessed(mut self, recessed: bool) -> Self {
-        self.recessed = recessed;
+        self.recessed = Some(recessed);
         self
     }
 }
@@ -96,7 +103,7 @@ impl Paint for Trackpad {
         // opening shares (`PaintCtx::canvas_well`), rounded like the text wells;
         // the fingers draw over the rim.
         let radius = crate::layout::textbox_corner_radius();
-        ctx.canvas_well(area, radius, self.recessed, false);
+        ctx.canvas_well(area, radius, self.recessed(), false);
 
         // 3. Fingers
         for finger in &self.fingers {