GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
style: flat wells share one frame and no floor
With relief off, TextBox, KeybindRecorder, ColorSelector and FontSelector
each framed their field in their own greys and lit it in a different colour
when active (a blue, a grey, a green, a white wash), and two of them laid an
opaque near-black floor under it while the other two let the plate show.
colors::well_frame_color is now the one frame: idle and hover greys, the
highlight accent while active — the colour the relief wells light their rims
with, so both styles share the focus cue — and every flat well draws no
floor of its own, the plate being the floor as under relief. The canvas
wells' flat frame is the same hairline.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
CLAUDE.md | 3 +++
src/color.rs | 24 ++++++++++++++++++++++--
src/scene/paint.rs | 4 ++--
src/widget/input/color_selector.rs | 8 +-------
src/widget/input/font_selector.rs | 17 ++++++-----------
src/widget/input/keybind_recorder.rs | 21 ++++++++-------------
src/widget/input/text_box.rs | 16 ++--------------
7 files changed, 44 insertions(+), 49 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 3c44a5a..33260ef 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -130,6 +130,9 @@ widget does not fit one of them, say so rather than stretching a word.
the plate darkened for its floor (`colors::WELL_FLOOR`) and the recess for
its rim, through `PaintCtx::well_floor` / `well_rim` (`canvas_well`),
rounded like the text wells. The Ramp editor's plot is the reference look.
+ With relief off, a well is its frame: the one hairline
+ `colors::well_frame_color` gives every well (lit in the highlight while it
+ is active, the relief rim's focus cue), and still no floor of its own.
- **A group is a lasso.** `widget::Group` owns nothing: it is a set of member
ids, and its frame is the padded hull of wherever the host's layout put
them, with a title tab flush on the top edge — the section's frame
diff --git a/src/color.rs b/src/color.rs
index 4a45e3a..e848a7f 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -916,8 +916,28 @@ pub const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
pub const WELL_FLOOR: [f32; 4] = [0.0, 0.0, 0.0, 0.18];
/// [`WELL_FLOOR`] risen toward the plate: a clickable canvas's hover cue.
pub const WELL_FLOOR_LIFTED: [f32; 4] = [0.0, 0.0, 0.0, 0.10];
-/// The hairline a canvas well is framed with when relief is off.
-pub const WELL_FRAME: [f32; 4] = [0.28, 0.28, 0.38, 1.0];
+/// The hairline a well is framed with when relief is off, idle; see
+/// [`well_frame_color`].
+pub const WELL_FRAME: [f32; 4] = [0.18, 0.18, 0.24, 1.0];
+/// [`WELL_FRAME`] under the pointer.
+pub const WELL_FRAME_HOVER: [f32; 4] = [0.25, 0.25, 0.35, 1.0];
+
+/// The frame a flat well is drawn in — the one hairline every well (text,
+/// keybind, colour and font fields, the canvases) wears when relief is off:
+/// neutral greys idle and hovered, the highlight accent while the well is
+/// active (editing, recording, pressed) — the colour the relief wells light
+/// their rims with, so the two styles share one focus cue. A flat well has no
+/// floor of its own any more than a relief one: the plate is the floor.
+pub fn well_frame_color(hovered: bool, active: bool) -> [f32; 4] {
+ if active {
+ let c = highlight_primary_color();
+ [c[0], c[1], c[2], 1.0]
+ } else if hovered {
+ WELL_FRAME_HOVER
+ } else {
+ WELL_FRAME
+ }
+}
pub const TEXT_HEADER: [f32; 4] = [0.90, 0.90, 0.95, 1.0];
pub const TEXT_ACCENT: [f32; 4] = [0.56, 0.83, 0.56, 1.0];
diff --git a/src/scene/paint.rs b/src/scene/paint.rs
index e1fe5cb..98e6e27 100644
--- a/src/scene/paint.rs
+++ b/src/scene/paint.rs
@@ -1210,7 +1210,7 @@ impl PaintCtx {
/// over whatever runs to the edge. Under `relief` it is the recess carved
/// inside `rect` ([`crate::layout::carve_inside`], the wall the DE width
/// capped at a fifth of the height — every well's rule); flat, the
- /// hairline frame ([`crate::colors::WELL_FRAME`]).
+ /// hairline frame every well shares ([`crate::colors::well_frame_color`]).
pub fn well_rim(&mut self, rect: Rect, radius: f32, relief: bool) {
let radii = (radius, radius, radius, radius);
if relief {
@@ -1218,7 +1218,7 @@ impl PaintCtx {
let (well, radii) = crate::layout::carve_inside(rect, radii, depth);
self.recess(well, radii, depth);
} else {
- self.border(rect, radii, [0.0; 4], crate::colors::WELL_FRAME, 1.0);
+ self.border(rect, radii, [0.0; 4], crate::colors::well_frame_color(false, false), 1.0);
}
}
diff --git a/src/widget/input/color_selector.rs b/src/widget/input/color_selector.rs
index 5ce9e2b..5f97634 100644
--- a/src/widget/input/color_selector.rs
+++ b/src/widget/input/color_selector.rs
@@ -246,13 +246,7 @@ impl Paint for ColorSelector {
// real text wells draw no fill, so even a neutral one read as "the
// color selector has a background". Neutral greys for the frame; the
// caret is the editing affordance.
- let border_color = if self.editing {
- [0.45, 0.45, 0.52, 1.0]
- } else if self.hovered {
- [0.25, 0.25, 0.35, 1.0]
- } else {
- [0.18, 0.18, 0.24, 1.0]
- };
+ let border_color = crate::colors::well_frame_color(self.hovered, self.editing);
// 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
diff --git a/src/widget/input/font_selector.rs b/src/widget/input/font_selector.rs
index 02b726b..082cc7d 100644
--- a/src/widget/input/font_selector.rs
+++ b/src/widget/input/font_selector.rs
@@ -140,7 +140,9 @@ impl Layout for FontSelector {
impl Paint for FontSelector {
fn color(&self) -> [f32; 4] {
- [0.08, 0.08, 0.12, 1.0]
+ // The plate shows through in both styles: a transparent-faced flush
+ // plate raised, the shared well frame flat.
+ [0.0, 0.0, 0.0, 0.0]
}
fn widget_font(&self) -> Option<String> {
@@ -178,16 +180,9 @@ impl Paint for FontSelector {
self.paint_labels(rect, ctx);
return;
}
- // The flat style: the framed dark field, rounded at the selector's radius.
- let bg_color = [0.08, 0.08, 0.12, 1.0];
- let border_color = if self.pressed {
- [0.30, 0.50, 0.32, 1.0]
- } else if self.hovered {
- [0.25, 0.25, 0.35, 1.0]
- } else {
- [0.18, 0.18, 0.24, 1.0]
- };
- ctx.border(rect, (r, r, r, r), bg_color, border_color, 1.0);
+ // The flat style: the one well frame (`colors::well_frame_color`) over
+ // the plate, lit while pressed, rounded at the selector's radius.
+ ctx.border(rect, (r, r, r, r), [0.0; 4], colors::well_frame_color(self.hovered, self.pressed), 1.0);
self.paint_labels(rect, ctx);
}
diff --git a/src/widget/input/keybind_recorder.rs b/src/widget/input/keybind_recorder.rs
index 6ec55f8..0b65053 100644
--- a/src/widget/input/keybind_recorder.rs
+++ b/src/widget/input/keybind_recorder.rs
@@ -17,7 +17,7 @@ pub struct KeybindRecorder {
/// Recessed style, the TextBox's: the field is a well carved into the
/// 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 keeps the framed dark field.
+ /// `control_relief()`; the flat style is the shared well frame.
recessed: bool,
/// Keyboard focus (FocusIn / FocusOut): Enter / Space arm recording.
focused: bool,
@@ -65,7 +65,8 @@ impl Layout for KeybindRecorder {
impl Paint for KeybindRecorder {
fn color(&self) -> [f32; 4] {
- [0.08, 0.08, 0.12, 1.0]
+ // A well: the plate is its floor, in both styles.
+ [0.0, 0.0, 0.0, 0.0]
}
/// The field text in the TextBox's font: both are wells you type into.
@@ -87,18 +88,12 @@ impl Paint for KeybindRecorder {
self.paint_text(rect, ctx);
return;
}
- let border_color = if self.recording {
- colors::HIGHLIGHT_PRIMARY
- } else if self.pressed {
- [0.30, 0.50, 0.32, 1.0]
- } else if self.hovered {
- [0.25, 0.25, 0.35, 1.0]
- } else {
- [0.18, 0.18, 0.24, 1.0]
- };
- // The framed dark field, rounded like the text wells.
+ // The flat style: the one well frame (`colors::well_frame_color`), lit
+ // while recording, over the plate — no floor of its own, like the
+ // TextBox it stands beside. Rounded like the text wells.
let radius = crate::layout::textbox_corner_radius();
- ctx.border(rect, (radius, radius, radius, radius), [0.08, 0.08, 0.12, 1.0], border_color, 1.0);
+ let frame = colors::well_frame_color(self.hovered, self.recording || self.pressed);
+ ctx.border(rect, (radius, radius, radius, radius), [0.0; 4], frame, 1.0);
self.paint_text(rect, ctx);
}
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index a373fe3..5b98a87 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -1502,13 +1502,7 @@ impl Paint for TextBox {
// the tinted recess rim (rounded path) / editing border, not a
// surface swap.
let bg_color = crate::colors::textbox_background_color();
- let border_color = if self.editing {
- [0.20, 0.50, 0.85, 1.0]
- } else if self.hovered {
- [0.25, 0.25, 0.35, 1.0]
- } else {
- [0.18, 0.18, 0.24, 1.0]
- };
+ let border_color = crate::colors::well_frame_color(self.hovered, self.editing);
quads.push((self.rect.x, self.rect.y + top, self.rect.width, visual_h, border_color));
quads.push((self.rect.x + border_w, self.rect.y + top + border_w, self.rect.width - 2.0 * border_w, visual_h - 2.0 * border_w, bg_color));
}
@@ -1524,13 +1518,7 @@ impl Paint for TextBox {
// One background regardless of focus (see the flat path above).
let bg_color = crate::colors::textbox_background_color();
- let border_color = if self.editing {
- [0.20, 0.50, 0.85, 1.0]
- } else if self.hovered {
- [0.25, 0.25, 0.35, 1.0]
- } else {
- [0.18, 0.18, 0.24, 1.0]
- };
+ let border_color = crate::colors::well_frame_color(self.hovered, self.editing);
if self.draw_bg_border {
let corners = (true, true, true, true);