From 785e19f999a514e2b7b0336b141f403efef8fedb Mon Sep 17 00:00:00 2001 From: Adrien Zianne Date: Thu, 16 Oct 2025 17:11:51 +0200 Subject: [PATCH] CheckboxStyle --- crates/egui/src/style_trait.rs | 25 +++++++++++++-- crates/egui/src/widgets/checkbox.rs | 29 ++++++++++------- examples/hello_world_simple/questions.md | 10 ++++++ examples/hello_world_simple/src/main.rs | 40 +++++++++++++++--------- 4 files changed, 76 insertions(+), 28 deletions(-) diff --git a/crates/egui/src/style_trait.rs b/crates/egui/src/style_trait.rs index 0dfa3d020..cacf5ebe3 100644 --- a/crates/egui/src/style_trait.rs +++ b/crates/egui/src/style_trait.rs @@ -36,8 +36,10 @@ pub struct CheckboxStyle { pub frame: Frame, /// Text next to it pub text: TextVisuals, - /// Size - pub size: Vec2, + /// Box size + pub size: f32, + /// Check size + pub check_size: f32, /// Frame of the checkbox itself pub checkbox_frame: Frame, /// Checkmark stroke @@ -149,6 +151,7 @@ impl Style { fill: visuals.bg_fill, stroke: visuals.bg_stroke, corner_radius: visuals.corner_radius, + inner_margin: self.spacing.button_padding.into(), ..Default::default() }, stroke: visuals.fg_stroke, @@ -169,7 +172,23 @@ impl Style { } } - // pub fn checkbox_style(&self, state: WidgetState) -> CheckboxStylee {} + pub fn checkbox_style(&self, state: WidgetState) -> CheckboxStyle { + let visuals = self.visuals.widgets.state(state); + let ws = self.widget_style(state); + CheckboxStyle { + frame: ws.frame.fill(Color32::TRANSPARENT), + size: self.spacing.icon_width, + check_size: self.spacing.icon_width_inner, + checkbox_frame: Frame { + fill: visuals.weak_bg_fill, + corner_radius: visuals.corner_radius, + stroke: visuals.bg_stroke, + ..Default::default() + }, + text: ws.text, + stroke: ws.stroke, + } + } pub fn label_style(&self, state: WidgetState) -> LabelStyle { let ws = self.widget_style(state); diff --git a/crates/egui/src/widgets/checkbox.rs b/crates/egui/src/widgets/checkbox.rs index c90cca292..598fc15e0 100644 --- a/crates/egui/src/widgets/checkbox.rs +++ b/crates/egui/src/widgets/checkbox.rs @@ -55,10 +55,17 @@ impl Widget for Checkbox<'_> { indeterminate, } = self; - let spacing = &ui.spacing(); - let icon_width = spacing.icon_width; + // Get the widget style by reading the rect from the previous pass + let id = ui.next_auto_id(); + let response: Option = ui.ctx().read_response(id); + let state = response.map(|r| r.widget_state()).unwrap_or_default(); + let style = ui.style().checkbox_style(state); - let mut min_size = Vec2::splat(spacing.interact_size.y); + let icon_width = style.size; + + // interact_size or size ? + // let mut min_size = Vec2::splat(ui.spacing().interact_size.y); + let mut min_size = Vec2::splat(style.size); min_size.y = min_size.y.at_least(icon_width); // In order to center the checkbox based on min_size we set the icon height to at least min_size.y @@ -72,6 +79,7 @@ impl Widget for Checkbox<'_> { let mut prepared = AtomLayout::new(atoms) .sense(Sense::click()) .min_size(min_size) + .frame(style.frame) .allocate(ui); if prepared.response.clicked() { @@ -97,17 +105,16 @@ impl Widget for Checkbox<'_> { if ui.is_rect_visible(prepared.response.rect) { // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful - let visuals = *ui.style().interact(&prepared.response); - prepared.fallback_text_color = visuals.text_color(); + prepared.fallback_text_color = style.text.color; let response = prepared.paint(ui); if let Some(rect) = response.rect(rect_id) { let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); ui.painter().add(epaint::RectShape::new( - big_icon_rect.expand(visuals.expansion), - visuals.corner_radius, - visuals.bg_fill, - visuals.bg_stroke, + big_icon_rect, + style.checkbox_frame.corner_radius, + style.checkbox_frame.fill, + style.checkbox_frame.stroke, epaint::StrokeKind::Inside, )); @@ -116,7 +123,7 @@ impl Widget for Checkbox<'_> { ui.painter().add(Shape::hline( small_icon_rect.x_range(), small_icon_rect.center().y, - visuals.fg_stroke, + style.stroke, )); } else if *checked { // Check mark: @@ -126,7 +133,7 @@ impl Widget for Checkbox<'_> { pos2(small_icon_rect.center().x, small_icon_rect.bottom()), pos2(small_icon_rect.right(), small_icon_rect.top()), ], - visuals.fg_stroke, + style.stroke, )); } } diff --git a/examples/hello_world_simple/questions.md b/examples/hello_world_simple/questions.md index a2afff1cf..85e5d713f 100644 --- a/examples/hello_world_simple/questions.md +++ b/examples/hello_world_simple/questions.md @@ -1,3 +1,7 @@ +## General + +- Interact size is great, but it SHOULD or MUST be at least this size ? Maybe add a parameter for this choice ? + ## Button - If hovering a button add a stroke, the ui shift. Maybe add the option to avoid this resize by making the frame smaller ? @@ -7,3 +11,9 @@ - I understand checking if a sense has been set by the user, but why check if it's different than hover ? Is it for technical purpose or purely to avoid confusion with a link ? - Selecting the text while being underlined move the underline + +## Checkbox + +- Checkbox are in fact a label and a check box. To propagate the CheckBoxStyle to the label we need to use a scope or something similar and change the global style. Why bind label to the check box, the user can add it himself (To keep the great prototyping speed we could keep this behavior in a Ui method, that would just merge) + +- Propose different type of checkmark ? (eg. small square, dot, filled, custom, etc) diff --git a/examples/hello_world_simple/src/main.rs b/examples/hello_world_simple/src/main.rs index 6798fada6..8f3da8a44 100644 --- a/examples/hello_world_simple/src/main.rs +++ b/examples/hello_world_simple/src/main.rs @@ -1,9 +1,10 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![allow(rustdoc::missing_crate_level_docs)] // it's an example -use std::process::exit; - -use eframe::egui::{self, Color32, FontId, Label, RichText, Sense, Stroke, style::WidgetVisuals}; +use eframe::egui::{ + self, Atom, Checkbox, Color32, FontId, Label, RichText, Sense, Stroke, style::WidgetVisuals, + vec2, +}; fn main() -> eframe::Result { env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). @@ -16,12 +17,18 @@ fn main() -> eframe::Result { // Our application state: let mut name = "Arthur".to_owned(); let mut age = 42; + let mut is_adult = age >= 18; eframe::run_simple_native("My egui App", options, move |ctx, _frame| { egui::CentralPanel::default().show(ctx, |ui| { ui.ctx().style_mut(|s| { + s.spacing.icon_width_inner = 8.0; + s.spacing.icon_width = 15.0; + s.spacing.button_padding = vec2(5.0, 5.0); + // s.spacing.interact_size.y = 30.0; s.visuals.widgets.inactive = WidgetVisuals { fg_stroke: Stroke::new(1.0, Color32::LIGHT_GRAY), + bg_stroke: Stroke::new(1.0, Color32::LIGHT_GRAY), ..s.visuals.widgets.inactive }; s.visuals.widgets.active = WidgetVisuals { @@ -29,7 +36,7 @@ fn main() -> eframe::Result { ..s.visuals.widgets.inactive }; s.visuals.widgets.hovered = WidgetVisuals { - fg_stroke: Stroke::new(1.0, Color32::YELLOW), + fg_stroke: Stroke::new(3.0, Color32::LIGHT_YELLOW), ..s.visuals.widgets.inactive }; s.visuals.widgets.noninteractive = WidgetVisuals { @@ -55,17 +62,22 @@ fn main() -> eframe::Result { // ui.add_enabled(false, Button::new("disabled")); // ui.add(Button::new("no frame inactive").frame_when_inactive(false)); - ui.label("Normal text"); - // Should not be affected by WidgetStyle - ui.label( - RichText::new("Unaffected by style") - .font(FontId::monospace(15.0)) - .color(Color32::KHAKI), - ); + // ui.label("Normal text"); + // // Should not be affected by WidgetStyle + // ui.label( + // RichText::new("Unaffected by style") + // .font(FontId::monospace(15.0)) + // .color(Color32::KHAKI), + // ); - ui.add(Label::new("interaction click").sense(Sense::click())); - ui.add(Label::new("focusable").sense(Sense::focusable_noninteractive())) - .request_focus(); + // ui.add(Label::new("interaction click").sense(Sense::click())); + // ui.add(Label::new("focusable").sense(Sense::focusable_noninteractive())) + // .request_focus(); + + ui.add(Checkbox::new(&mut is_adult, "test")); + ui.add(Checkbox::new(&mut is_adult, "test")); + ui.add(Checkbox::new(&mut is_adult, Atom::default())); + ui.add(Checkbox::new(&mut is_adult, Atom::default()).indeterminate(true)); }); }) }