From 4dcbb57c19c8e894ab151ff0788aa1be139abae8 Mon Sep 17 00:00:00 2001 From: Fini Date: Thu, 2 Jul 2026 21:46:33 +0800 Subject: [PATCH] fix(editor-ui): make text_input honor its authored box style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit paint_text_field used to paint an unconditional white fill, a default grey border and a 6px radius floor, with near-black value text — a model embedding an input in its own styled wrapper zeroes all of that out (fill:[], stroke thickness 0, cornerRadius 0) and got a glaring white pill with a duplicate icon on a dark themed search bar. No authored fill now paints no box, no authored stroke draws no border, radius is taken as-is, and the value text color adapts to the authored fill's luminance. --- .../src/widgets/canvas_viewport_widget.rs | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/crates/op-editor-ui/src/widgets/canvas_viewport_widget.rs b/crates/op-editor-ui/src/widgets/canvas_viewport_widget.rs index 2b02b1005..91c1f9bcf 100644 --- a/crates/op-editor-ui/src/widgets/canvas_viewport_widget.rs +++ b/crates/op-editor-ui/src/widgets/canvas_viewport_widget.rs @@ -50,6 +50,11 @@ pub fn widget_text_inset_left(w: &SceneWidget) -> f32 { } } +/// Perceived luminance (0..1) of an opaque scene color. +fn color_luminance(c: Color) -> f32 { + 0.299 * c.r + 0.587 * c.g + 0.114 * c.b +} + /// Paint the static visual for a widget scene node, in world coords. /// /// `world_rect` is the node's already-zoom-scaled screen rect; `zoom` @@ -249,14 +254,22 @@ fn paint_radio_group(cx: &mut PaintCx<'_>, w: &SceneWidget, r: Rect, zoom: f32) /// (near-black) or, when empty, the placeholder (muted). fn paint_text_field(cx: &mut PaintCx<'_>, node: &SceneNode, w: &SceneWidget, r: Rect, zoom: f32) { let (x, y, ww, h) = rect_parts(r); - let radius = (node.corner_radius * zoom).max(6.0 * zoom); + // Respect the AUTHORED box style. A model embedding an input into its own + // styled wrapper zeroes everything out (`fill: []`, `stroke.thickness: 0`, + // `cornerRadius: 0`) — the old unconditional white fill + grey border + + // 6px-radius floor painted a glaring white pill on top of a dark themed + // wrapper (measured on a dark dashboard's search bar). No fill → paint no + // box; no stroke → draw no border; radius as authored. + let radius = node.corner_radius * zoom; if let Some(fill) = node.fill { cx.backend.fill_round_rect(r, radius, fill); - } else { - cx.backend.fill_round_rect(r, radius, KNOB); } - let stroke_w = node.stroke.map(|s| s.width).unwrap_or(1.0) * zoom; - cx.backend.stroke_round_rect(r, radius, TRACK_OFF, stroke_w); + if let Some(stroke) = node.stroke { + if stroke.width > 0.0 { + cx.backend + .stroke_round_rect(r, radius, stroke.color, stroke.width * zoom); + } + } // Leading / trailing lucide glyphs at the content edges, vertically // centred. The text inset (`widget_text_inset_left`) reserves room @@ -288,6 +301,19 @@ fn paint_text_field(cx: &mut PaintCx<'_>, node: &SceneNode, w: &SceneWidget, r: } if let Some((text, color)) = text_field_display_text(w) { + // The default value color (#111) assumed the old white box. On an + // authored DARK fill — or no box at all (transparent, blending into a + // dark wrapper) — flip it light; the muted placeholder grey reads on + // both. Only the VALUE color adapts. + let color = if color == TEXT_VALUE { + match node.fill { + Some(bg) if color_luminance(bg) >= 0.5 => TEXT_VALUE, + Some(_) => Color::rgb_u8(0xF5, 0xF5, 0xF5), + None => Color::rgb_u8(0x9C, 0xA3, 0xAF), + } + } else { + color + }; let fs = 14.0 * zoom; // text_area top-aligns; single-line inputs vertically centre. let ty = if w.kind == "text_area" {