GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat(checkbox): a round style — the ring-and-dot mark cce-list drew by hand
`style.control.checkbox.style = "round"` (or `set_round(true)` on the model)
swaps the square box for a 14px ring in TEXT_DIM with a TOGGLE_ON dot when
checked, drawn on the LEFT of the label the way a list row reads; standalone,
the mark fills the rect. The drawing is `Checkbox::paint_round_mark`, a
public associated fn, so a host that paints its own rows (cce-list) draws the
identical mark instead of a private copy. The square style is unchanged and
stays the default.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
src/layout.rs | 14 +++++++++++
src/widget/input/checkbox.rs | 55 +++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 68 insertions(+), 1 deletion(-)
diff --git a/src/layout.rs b/src/layout.rs
index 29ded73..954759c 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -108,6 +108,7 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
"style.control.textbox.multiline.line_wrap" | "style.textbox.multiline.line_wrap" | "style.data.textbox.multiline.line_wrap" => "textbox_line_wrap",
"style.control.textbox.multiline.border_width" | "style.textbox.multiline.border_width" | "style.data.textbox.multiline.border_width" => "textbox_multiline_border_width",
"style.control.toggle.style" => "toggle_style",
+ "style.control.checkbox.style" => "checkbox_style",
"style.control.toggle.height" => "toggle_height",
"style.control.toggle.border_width" => "toggle_border_width",
"style.control.toggle.disabled_color" => "toggle_disabled_color",
@@ -1867,6 +1868,19 @@ pub fn slider_corner_radius() -> f32 {
get_style_registry().read().unwrap().get_float("slider_corner_radius").unwrap_or(4.0)
}
+/// The checkbox's render style: `style.control.checkbox.style = "round"` swaps
+/// the square box for the ring-and-dot mark (see `Checkbox::paint_round_mark`),
+/// with the mark on the LEFT of the label the way a list row reads. Anything
+/// else is the square style.
+pub fn checkbox_round() -> bool {
+ lazy_init_style_registry();
+ get_style_registry()
+ .read()
+ .unwrap()
+ .get_string("checkbox_style")
+ .is_some_and(|s| s == "round")
+}
+
/// The toggle's render style: `style.control.toggle.style = "slide"` swaps the
/// rocker/gradient pill for a half-width button that slides between the left
/// (off) and right (on) ends of the widget (see `Toggle::paint`). Anything
diff --git a/src/widget/input/checkbox.rs b/src/widget/input/checkbox.rs
index a8ca78c..04bd68c 100644
--- a/src/widget/input/checkbox.rs
+++ b/src/widget/input/checkbox.rs
@@ -31,9 +31,15 @@ pub struct Checkbox {
label: Option<String>,
hovered: bool,
focused: bool,
+ /// Round style: a ring-and-dot mark on the left of the label instead of the
+ /// square box on the right. Seeded from `style.control.checkbox.style`.
+ round: bool,
}
impl Checkbox {
+ /// The round mark's radius: a 14px disc, the size cce-list's rows always drew.
+ pub const ROUND_RADIUS: f32 = 7.0;
+
pub fn new() -> Adapted<Checkbox> {
Adapted::new(Checkbox {
checked: false,
@@ -42,6 +48,7 @@ impl Checkbox {
label: None,
hovered: false,
focused: false,
+ round: crate::layout::checkbox_round(),
})
}
@@ -53,6 +60,33 @@ impl Checkbox {
self.checked
}
+ /// Select the round style regardless of config (a list row wants it whatever
+ /// the DE-wide setting says).
+ pub fn set_round(&mut self, round: bool) {
+ self.round = round;
+ }
+
+ pub fn round(&self) -> bool {
+ self.round
+ }
+
+ /// The round style's mark, centred on (`cx`, `cy`): a dim ring, and a solid
+ /// dot in the toggle-on colour when checked. Shared with hosts that draw
+ /// their own rows (cce-list) so a list's marks and a `Checkbox` agree pixel
+ /// for pixel.
+ pub fn paint_round_mark(ctx: &mut PaintCtx, cx: f32, cy: f32, radius: f32, checked: bool) {
+ ctx.border(
+ Rect { x: cx - radius, y: cy - radius, width: 2.0 * radius, height: 2.0 * radius },
+ (radius, radius, radius, radius),
+ [0.0, 0.0, 0.0, 0.0],
+ colors::TEXT_DIM,
+ 1.5,
+ );
+ if checked {
+ ctx.circle(cx, cy, radius - 3.0, colors::TOGGLE_ON);
+ }
+ }
+
/// Hover state, also settable directly for immediate-mode hosts that do their own
/// hit-testing instead of routing `MouseEnter`/`MouseLeave` (json_layout).
pub fn hovered(&self) -> bool {
@@ -86,7 +120,7 @@ impl Paint for Checkbox {
// is the colored box itself. Style-property paths (demo `widget_vertices`) read this.
// Width isn't known here, so mirror the legacy intent via the label: labeled checkboxes
// are the wide rows.
- if self.label.is_some() {
+ if self.label.is_some() || self.round {
[0.0, 0.0, 0.0, 0.0]
} else {
self.box_color()
@@ -99,6 +133,25 @@ impl Paint for Checkbox {
fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
+ if self.round {
+ // Round style: the mark leads and the label follows, a list row's
+ // reading order. Standalone, the mark fills the rect.
+ let r = if self.label.is_some() { Self::ROUND_RADIUS } else { (w.min(h) / 2.0).max(1.0) };
+ let (cx, cy) = if self.label.is_some() { (x + r, y + h / 2.0) } else { (x + w / 2.0, y + h / 2.0) };
+ Self::paint_round_mark(ctx, cx, cy, r, self.checked);
+ if let Some(ref label) = self.label {
+ let (_, font_size) = crate::layout::control_label_font_parsed();
+ let ty = crate::layout::align_text_y(y, h, font_size, 0.0);
+ ctx.text(
+ label.clone(),
+ cx + r + 8.0,
+ ty,
+ font_size,
+ colors::control_label_color_for_state(self.hovered, self.focused),
+ );
+ }
+ return;
+ }
if w > 30.0 {
// Wide mode: label on the left, 18px box on the right — the legacy `extra_quads`
// geometry verbatim.