Skip to content

Commit

Permalink
Add indeterminate state to checkbox (#3605)
Browse files Browse the repository at this point in the history
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to add commits to your PR.
* Remember to run `cargo fmt` and `cargo cranky`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->
  • Loading branch information
YgorSouza authored Jan 6, 2024
1 parent 932fdae commit 797406d
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 7 deletions.
3 changes: 3 additions & 0 deletions crates/egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,9 @@ impl Response {
} else {
Checked::False
});
} else if matches!(info.typ, WidgetType::Checkbox) {
// Indeterminate state
builder.set_checked(Checked::Mixed);
}
}

Expand Down
44 changes: 37 additions & 7 deletions crates/egui/src/widgets/button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,24 +356,40 @@ impl Widget for Button<'_> {
pub struct Checkbox<'a> {
checked: &'a mut bool,
text: WidgetText,
indeterminate: bool,
}

impl<'a> Checkbox<'a> {
pub fn new(checked: &'a mut bool, text: impl Into<WidgetText>) -> Self {
Checkbox {
checked,
text: text.into(),
indeterminate: false,
}
}

pub fn without_text(checked: &'a mut bool) -> Self {
Self::new(checked, WidgetText::default())
}

/// Display an indeterminate state (neither checked nor unchecked)
///
/// This only affects the checkbox's appearance. It will still toggle its boolean value when
/// clicked.
#[inline]
pub fn indeterminate(mut self, indeterminate: bool) -> Self {
self.indeterminate = indeterminate;
self
}
}

impl<'a> Widget for Checkbox<'a> {
fn ui(self, ui: &mut Ui) -> Response {
let Checkbox { checked, text } = self;
let Checkbox {
checked,
text,
indeterminate,
} = self;

let spacing = &ui.spacing();
let icon_width = spacing.icon_width;
Expand Down Expand Up @@ -402,11 +418,18 @@ impl<'a> Widget for Checkbox<'a> {
response.mark_changed();
}
response.widget_info(|| {
WidgetInfo::selected(
WidgetType::Checkbox,
*checked,
galley.as_ref().map_or("", |x| x.text()),
)
if indeterminate {
WidgetInfo::labeled(
WidgetType::Checkbox,
galley.as_ref().map_or("", |x| x.text()),
)
} else {
WidgetInfo::selected(
WidgetType::Checkbox,
*checked,
galley.as_ref().map_or("", |x| x.text()),
)
}
});

if ui.is_rect_visible(rect) {
Expand All @@ -420,7 +443,14 @@ impl<'a> Widget for Checkbox<'a> {
visuals.bg_stroke,
));

if *checked {
if indeterminate {
// Horizontal line:
ui.painter().add(Shape::hline(
small_icon_rect.x_range(),
small_icon_rect.center().y,
visuals.fg_stroke,
));
} else if *checked {
// Check mark:
ui.painter().add(Shape::line(
vec![
Expand Down
20 changes: 20 additions & 0 deletions crates/egui_demo_lib/src/demo/misc_demo_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub struct MiscDemoWindow {

dummy_bool: bool,
dummy_usize: usize,
checklist: [bool; 3],
}

impl Default for MiscDemoWindow {
Expand All @@ -30,6 +31,7 @@ impl Default for MiscDemoWindow {

dummy_bool: false,
dummy_usize: 0,
checklist: std::array::from_fn(|i| i == 0),
}
}
}
Expand Down Expand Up @@ -107,6 +109,24 @@ impl View for MiscDemoWindow {
}
});
ui.radio_value(&mut self.dummy_usize, 64, "radio_value");
ui.label("Checkboxes can be in an indeterminate state:");
let mut all_checked = self.checklist.iter().all(|item| *item);
let any_checked = self.checklist.iter().any(|item| *item);
let indeterminate = any_checked && !all_checked;
if ui
.add(
Checkbox::new(&mut all_checked, "Check/uncheck all")
.indeterminate(indeterminate),
)
.changed()
{
self.checklist
.iter_mut()
.for_each(|checked| *checked = all_checked);
}
for (i, checked) in self.checklist.iter_mut().enumerate() {
ui.checkbox(checked, format!("Item {}", i + 1));
}
});

ui.collapsing("Columns", |ui| {
Expand Down

0 comments on commit 797406d

Please sign in to comment.