diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d959a5ee..6d93815a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Add support for secondary and middle mouse buttons. * Add `Label` methods for code, strong, strikethrough, underline and italics. -* Add `ui.group(|ui| { … })` to visually group some widgets within a frame +* Add `ui.group(|ui| { … })` to visually group some widgets within a frame. +* Add `ui.set_enabled(false)` to disable all widgets in a `Ui` (grayed out and non-interactive). * Add `TextEdit::hint_text` for showing a weak hint text when empty. * `egui::popup::popup_below_widget`: show a popup area below another widget. * Add `Slider::clamp_to_range(bool)`: if set, clamp the incoming and outgoing values to the slider range. diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index 054d30ff1..f8c7b1bb3 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -45,6 +45,7 @@ pub struct Area { pub(crate) id: Id, movable: bool, interactable: bool, + enabled: bool, order: Order, default_pos: Option, new_pos: Option, @@ -56,6 +57,7 @@ impl Area { id: Id::new(id_source), movable: true, interactable: true, + enabled: true, order: Order::Middle, default_pos: None, new_pos: None, @@ -71,6 +73,15 @@ impl Area { LayerId::new(self.order, self.id) } + /// If false, no content responds to click + /// and widgets will be shown grayed out. + /// You won't be able to move the window. + /// Default: `true`. + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + /// moveable by dragging the area? pub fn movable(mut self, movable: bool) -> Self { self.movable = movable; @@ -78,8 +89,12 @@ impl Area { self } + pub fn is_enabled(&self) -> bool { + self.enabled + } + pub fn is_movable(&self) -> bool { - self.movable + self.movable && self.enabled } /// If false, clicks goes straight through to what is behind us. @@ -121,6 +136,7 @@ pub(crate) struct Prepared { layer_id: LayerId, state: State, movable: bool, + enabled: bool, } impl Area { @@ -130,6 +146,7 @@ impl Area { movable, order, interactable, + enabled, default_pos, new_pos, } = self; @@ -149,6 +166,7 @@ impl Area { layer_id, state, movable, + enabled, } } @@ -214,13 +232,15 @@ impl Prepared { clip_rect = clip_rect.intersect(central_area); } - Ui::new( + let mut ui = Ui::new( ctx.clone(), self.layer_id, self.layer_id.id, max_rect, clip_rect, - ) + ); + ui.set_enabled(self.enabled); + ui } #[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`. @@ -229,6 +249,7 @@ impl Prepared { layer_id, mut state, movable, + enabled, } = self; state.size = content_ui.min_rect().size(); @@ -247,6 +268,7 @@ impl Prepared { interact_id, state.rect(), sense, + enabled, ); if move_response.dragged() && movable { diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index a08338795..9594bf7ca 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -23,7 +23,7 @@ impl Frame { Self { margin: Vec2::new(8.0, 6.0), corner_radius: 4.0, - stroke: style.visuals.widgets.noninteractive.bg_stroke, + stroke: style.visuals.window_stroke(), ..Default::default() } } @@ -32,8 +32,8 @@ impl Frame { Self { margin: Vec2::new(8.0, 2.0), corner_radius: 0.0, - fill: style.visuals.widgets.noninteractive.bg_fill, - stroke: style.visuals.widgets.noninteractive.bg_stroke, + fill: style.visuals.window_fill(), + stroke: style.visuals.window_stroke(), ..Default::default() } } @@ -42,7 +42,7 @@ impl Frame { Self { margin: Vec2::new(8.0, 8.0), corner_radius: 0.0, - fill: style.visuals.widgets.noninteractive.bg_fill, + fill: style.visuals.window_fill(), stroke: Default::default(), ..Default::default() } @@ -53,8 +53,8 @@ impl Frame { margin: style.spacing.window_padding, corner_radius: style.visuals.window_corner_radius, shadow: style.visuals.window_shadow, - fill: style.visuals.widgets.noninteractive.bg_fill, - stroke: style.visuals.widgets.noninteractive.bg_stroke, + fill: style.visuals.window_fill(), + stroke: style.visuals.window_stroke(), } } @@ -63,8 +63,8 @@ impl Frame { margin: Vec2::splat(1.0), corner_radius: 2.0, shadow: Shadow::small(), - fill: style.visuals.widgets.noninteractive.bg_fill, - stroke: style.visuals.widgets.noninteractive.bg_stroke, + fill: style.visuals.window_fill(), + stroke: style.visuals.window_stroke(), } } @@ -73,8 +73,8 @@ impl Frame { margin: style.spacing.window_padding, corner_radius: 5.0, shadow: Shadow::small(), - fill: style.visuals.widgets.noninteractive.bg_fill, - stroke: style.visuals.widgets.noninteractive.bg_stroke, + fill: style.visuals.window_fill(), + stroke: style.visuals.window_stroke(), } } @@ -84,7 +84,7 @@ impl Frame { margin: Vec2::new(10.0, 10.0), corner_radius: 5.0, fill: Color32::from_black_alpha(250), - stroke: style.visuals.widgets.noninteractive.bg_stroke, + stroke: style.visuals.window_stroke(), ..Default::default() } } diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 87e4700e7..fa6e8b32d 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -70,6 +70,12 @@ impl<'open> Window<'open> { self } + /// If `false` the window will be grayed out and non-interactive. + pub fn enabled(mut self, enabled: bool) -> Self { + self.area = self.area.enabled(enabled); + self + } + /// Usage: `Window::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))` /// Not sure this is a good interface for this. pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self { @@ -239,8 +245,8 @@ impl<'open> Window<'open> { let is_maximized = !with_title_bar || collapsing_header::State::is_open(ctx, collapsing_id).unwrap_or_default(); let possible = PossibleInteractions { - movable: area.is_movable(), - resizable: resize.is_resizable() && is_maximized, + movable: area.is_enabled() && area.is_movable(), + resizable: area.is_enabled() && resize.is_resizable() && is_maximized, }; let area = area.movable(false); // We move it manually diff --git a/egui/src/context.rs b/egui/src/context.rs index 0c92625dc..6afa79032 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -218,6 +218,7 @@ impl CtxRef { // --------------------------------------------------------------------- /// Use `ui.interact` instead + #[allow(clippy::too_many_arguments)] pub(crate) fn interact( &self, clip_rect: Rect, @@ -226,6 +227,7 @@ impl CtxRef { id: Id, rect: Rect, sense: Sense, + enabled: bool, ) -> Response { let gap = 0.5; // Just to make sure we don't accidentally hover two things at once (a small eps should be sufficient). let interact_rect = rect.expand2( @@ -234,7 +236,7 @@ impl CtxRef { .at_most(Vec2::splat(5.0)), ); // make it easier to click let hovered = self.rect_contains_pointer(layer_id, clip_rect.intersect(interact_rect)); - self.interact_with_hovered(layer_id, id, rect, sense, hovered) + self.interact_with_hovered(layer_id, id, rect, sense, enabled, hovered) } /// You specify if a thing is hovered, and the function gives a `Response`. @@ -244,8 +246,11 @@ impl CtxRef { id: Id, rect: Rect, sense: Sense, + enabled: bool, hovered: bool, ) -> Response { + let hovered = hovered && enabled; // can't even hover disabled widgets + let has_kb_focus = self.memory().has_kb_focus(id); let lost_kb_focus = self.memory().lost_kb_focus(id); @@ -255,6 +260,7 @@ impl CtxRef { id, rect, sense, + enabled, hovered, clicked: Default::default(), double_clicked: Default::default(), @@ -266,7 +272,7 @@ impl CtxRef { lost_kb_focus, }; - if sense == Sense::hover() || !layer_id.allow_interaction() { + if !enabled || sense == Sense::hover() || !layer_id.allow_interaction() { // Not interested or allowed input: return response; } diff --git a/egui/src/painter.rs b/egui/src/painter.rs index 531837d98..ca0ae33a5 100644 --- a/egui/src/painter.rs +++ b/egui/src/painter.rs @@ -22,6 +22,10 @@ pub struct Painter { /// Everything painted in this `Painter` will be clipped against this. /// This means nothing outside of this rectangle will be visible on screen. clip_rect: Rect, + + /// If set, all shapes will have their colors modified to be closer to this. + /// This is used to implement grayed out interfaces. + fade_to_color: Option, } impl Painter { @@ -30,6 +34,7 @@ impl Painter { ctx, layer_id, clip_rect, + fade_to_color: None, } } @@ -39,6 +44,7 @@ impl Painter { ctx: self.ctx, layer_id, clip_rect: self.clip_rect, + fade_to_color: None, } } @@ -47,6 +53,11 @@ impl Painter { self.layer_id = layer_id; } + /// If set, colors will be modified to look like this + pub(crate) fn set_fade_to_color(&mut self, fade_to_color: Option) { + self.fade_to_color = fade_to_color; + } + /// Create a painter for a sub-region of this `Painter`. /// /// The clip-rect of the returned `Painter` will be the intersection @@ -106,10 +117,17 @@ impl Painter { /// ## Low level impl Painter { + fn transform_shape(&self, shape: &mut Shape) { + if let Some(fade_to_color) = self.fade_to_color { + tint_shape_towards(shape, fade_to_color); + } + } + /// It is up to the caller to make sure there is room for this. /// Can be used for free painting. /// NOTE: all coordinates are screen coordinates! - pub fn add(&self, shape: Shape) -> ShapeIdx { + pub fn add(&self, mut shape: Shape) -> ShapeIdx { + self.transform_shape(&mut shape); self.ctx .graphics() .list(self.layer_id) @@ -119,8 +137,14 @@ impl Painter { /// Add many shapes at once. /// /// Calling this once is generally faster than calling [`Self::add`] multiple times. - pub fn extend(&self, shapes: Vec) { + pub fn extend(&self, mut shapes: Vec) { if !shapes.is_empty() { + if self.fade_to_color.is_some() { + for shape in &mut shapes { + self.transform_shape(shape); + } + } + self.ctx .graphics() .list(self.layer_id) @@ -129,7 +153,8 @@ impl Painter { } /// Modify an existing [`Shape`]. - pub fn set(&self, idx: ShapeIdx, shape: Shape) { + pub fn set(&self, idx: ShapeIdx, mut shape: Shape) { + self.transform_shape(&mut shape); self.ctx .graphics() .list(self.layer_id) @@ -294,3 +319,9 @@ impl Painter { }); } } + +fn tint_shape_towards(shape: &mut Shape, target: Color32) { + epaint::shape_transform::adjust_colors(shape, &|color| { + *color = crate::color::tint_color_towards(*color, target); + }); +} diff --git a/egui/src/response.rs b/egui/src/response.rs index 4316da074..b327f2af1 100644 --- a/egui/src/response.rs +++ b/egui/src/response.rs @@ -29,6 +29,10 @@ pub struct Response { /// The senses (click and/or drag) that the widget was interested in (if any). pub sense: Sense, + /// Was the widget enabled? + /// If `false`, there was no interaction attempted (not even hover). + pub(crate) enabled: bool, + // OUT: /// The pointer is hovering above this widget or the widget was clicked/tapped this frame. pub(crate) hovered: bool, @@ -68,6 +72,7 @@ impl std::fmt::Debug for Response { id, rect, sense, + enabled, hovered, clicked, double_clicked, @@ -83,6 +88,7 @@ impl std::fmt::Debug for Response { .field("id", id) .field("rect", rect) .field("sense", sense) + .field("enabled", enabled) .field("hovered", hovered) .field("clicked", clicked) .field("double_clicked", double_clicked) @@ -117,6 +123,13 @@ impl Response { self.double_clicked[PointerButton::Primary as usize] } + /// Was the widget enabled? + /// If false, there was no interaction attempted + /// and the widget should be drawn in a gray disabled look. + pub fn enabled(&self) -> bool { + self.enabled + } + /// The pointer is hovering above this widget or the widget was clicked/tapped this frame. pub fn hovered(&self) -> bool { self.hovered @@ -208,8 +221,14 @@ impl Response { /// if response.clicked() { /* … */ } /// ``` pub fn interact(&self, sense: Sense) -> Self { - self.ctx - .interact_with_hovered(self.layer_id, self.id, self.rect, sense, self.hovered) + self.ctx.interact_with_hovered( + self.layer_id, + self.id, + self.rect, + sense, + self.enabled, + self.hovered, + ) } /// Move the scroll to this UI with the specified alignment. @@ -247,6 +266,7 @@ impl Response { id: self.id, rect: self.rect.union(other.rect), sense: self.sense.union(other.sense), + enabled: self.enabled || other.enabled, hovered: self.hovered || other.hovered, clicked: [ self.clicked[0] || other.clicked[0], diff --git a/egui/src/style.rs b/egui/src/style.rs index c04b26197..dc07e5224 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -184,12 +184,20 @@ impl Visuals { } pub fn weak_text_color(&self) -> Color32 { - self.widgets.disabled.text_color() + crate::color::tint_color_towards(self.text_color(), self.window_fill()) } pub fn strong_text_color(&self) -> Color32 { self.widgets.active.text_color() } + + pub fn window_fill(&self) -> Color32 { + self.widgets.noninteractive.bg_fill + } + + pub fn window_stroke(&self) -> Stroke { + self.widgets.noninteractive.bg_stroke + } } /// Selected text, selected elements etc @@ -210,8 +218,6 @@ pub struct Widgets { /// * `noninteractive.bg_fill` is the background color of windows. /// * `noninteractive.fg_stroke` is the normal text color. pub noninteractive: WidgetVisuals, - /// The style of a disabled button. - pub disabled: WidgetVisuals, /// The style of an interactive widget, such as a button, at rest. pub inactive: WidgetVisuals, /// The style of an interactive widget while you hover it. @@ -224,8 +230,6 @@ impl Widgets { pub fn style(&self, response: &Response) -> &WidgetVisuals { if response.is_pointer_button_down_on() || response.has_kb_focus { &self.active - } else if response.sense == crate::Sense::hover() { - &self.disabled } else if response.hovered() { &self.hovered } else { @@ -374,19 +378,12 @@ impl Widgets { pub fn dark() -> Self { Self { noninteractive: WidgetVisuals { + bg_fill: Color32::from_gray(30), // window background bg_stroke: Stroke::new(1.0, Color32::from_gray(65)), // window outline - bg_fill: Color32::from_gray(30), // window background fg_stroke: Stroke::new(1.0, Color32::from_gray(160)), // normal text color corner_radius: 4.0, expansion: 0.0, }, - disabled: WidgetVisuals { - bg_fill: Color32::from_gray(40), // Should look grayed out - bg_stroke: Stroke::new(1.0, Color32::from_gray(70)), - fg_stroke: Stroke::new(1.0, Color32::from_gray(110)), // Should look grayed out. Also used for "weak" text color. - corner_radius: 4.0, - expansion: 0.0, - }, inactive: WidgetVisuals { bg_fill: Color32::from_gray(70), bg_stroke: Default::default(), @@ -414,16 +411,9 @@ impl Widgets { pub fn light() -> Self { Self { noninteractive: WidgetVisuals { + bg_fill: Color32::from_gray(220), // window background bg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // window outline - bg_fill: Color32::from_gray(220), // window background - fg_stroke: Stroke::new(1.0, Color32::from_gray(70)), // normal text color - corner_radius: 4.0, - expansion: 0.0, - }, - disabled: WidgetVisuals { - bg_fill: Color32::from_gray(215), // Should look grayed out - bg_stroke: Stroke::new(1.0, Color32::from_gray(185)), - fg_stroke: Stroke::new(1.0, Color32::from_gray(145)), // Should look grayed out. Also used for "weak" text color. + fg_stroke: Stroke::new(1.0, Color32::from_gray(70)), // normal text color corner_radius: 4.0, expansion: 0.0, }, @@ -542,7 +532,6 @@ impl Widgets { active, hovered, inactive, - disabled, noninteractive, } = self; @@ -550,10 +539,6 @@ impl Widgets { ui.label("The style of a widget that you cannot interact with."); noninteractive.ui(ui) }); - ui.collapsing("interactive & disabled", |ui| { - ui.label("The style of a disabled button."); - disabled.ui(ui) - }); ui.collapsing("interactive & inactive", |ui| { ui.label("The style of an interactive widget, such as a button, at rest."); inactive.ui(ui) diff --git a/egui/src/ui.rs b/egui/src/ui.rs index 3d6eacc7e..8e2198f9f 100644 --- a/egui/src/ui.rs +++ b/egui/src/ui.rs @@ -46,6 +46,10 @@ pub struct Ui { /// Handles the `Ui` size and the placement of new widgets. placer: Placer, + + /// If false we are unresponsive to input, + /// and all widgets will assume a gray style. + enabled: bool, } impl Ui { @@ -60,6 +64,7 @@ impl Ui { painter: Painter::new(ctx, layer_id, clip_rect), style, placer: Placer::new(max_rect, Layout::default()), + enabled: true, } } @@ -72,6 +77,7 @@ impl Ui { painter: self.painter.clone(), style: self.style.clone(), placer: Placer::new(max_rect, layout), + enabled: self.enabled, } } @@ -166,6 +172,39 @@ impl Ui { &self.painter } + /// If `false`, the `Ui` does not allow any interaction and + /// the widgets in it will draw with a gray look. + pub fn enabled(&self) -> bool { + self.enabled + } + + /// Calling `set_enabled(false)` will cause the `Ui` to deny all future interaction + /// and all the widgets will draw with a gray look. + /// + /// Calling `set_enabled(true)` has no effect - it will NOT re-enable the `Ui` once disabled. + /// + /// ### Example + /// ``` + /// # let ui = &mut egui::Ui::__test(); + /// # let mut enabled = true; + /// ui.group(|ui|{ + /// ui.checkbox(&mut enabled, "Enable subsection"); + /// ui.set_enabled(enabled); + /// if ui.button("Button that is not always clickable").clicked() { + /// /* … */ + /// } + /// }); + /// ``` + pub fn set_enabled(&mut self, enabled: bool) { + self.enabled &= enabled; + if self.enabled { + self.painter.set_fade_to_color(None); + } else { + self.painter + .set_fade_to_color(Some(self.visuals().window_fill())); + } + } + pub fn layout(&self) -> &Layout { self.placer.layout() } @@ -427,6 +466,7 @@ impl Ui { id, rect, sense, + self.enabled, ) } diff --git a/egui/src/widgets/button.rs b/egui/src/widgets/button.rs index 5713fbce4..b0fd356a1 100644 --- a/egui/src/widgets/button.rs +++ b/egui/src/widgets/button.rs @@ -68,6 +68,8 @@ impl Button { /// If you set this to `false`, the button will be grayed out and un-clickable. /// `enabled(false)` has the same effect as calling `sense(Sense::hover())`. + /// + /// This is a convenience for [`Ui::set_enabled`]. pub fn enabled(mut self, enabled: bool) -> Self { if !enabled { self.sense = Sense::hover(); @@ -76,8 +78,8 @@ impl Button { } } -impl Widget for Button { - fn ui(self, ui: &mut Ui) -> Response { +impl Button { + fn enabled_ui(self, ui: &mut Ui) -> Response { let Button { text, text_color, @@ -136,6 +138,22 @@ impl Widget for Button { } } +impl Widget for Button { + fn ui(self, ui: &mut Ui) -> Response { + let button_enabled = self.sense != Sense::hover(); + if button_enabled || !ui.enabled() { + self.enabled_ui(ui) + } else { + // We need get a temporary disabled `Ui` to get that grayed out look: + ui.wrap(|ui| { + ui.set_enabled(false); + self.enabled_ui(ui) + }) + .0 + } + } +} + // ---------------------------------------------------------------------------- // TODO: allow checkbox without a text label diff --git a/egui/src/widgets/label.rs b/egui/src/widgets/label.rs index 884d98a7c..8ce3341e5 100644 --- a/egui/src/widgets/label.rs +++ b/egui/src/widgets/label.rs @@ -160,15 +160,15 @@ impl Label { .. } = *self; - let text_color = self.text_color.unwrap_or_else(|| { - if strong { - ui.visuals().strong_text_color() - } else if weak { - ui.visuals().weak_text_color() - } else { - ui.visuals().text_color() - } - }); + let text_color = if let Some(text_color) = self.text_color { + text_color + } else if strong { + ui.visuals().strong_text_color() + } else if weak { + ui.visuals().weak_text_color() + } else { + ui.visuals().text_color() + }; if code { background_color = ui.visuals().code_bg_color; diff --git a/egui/src/widgets/selected_label.rs b/egui/src/widgets/selected_label.rs index 40ce0d8bd..fcc0e2fd8 100644 --- a/egui/src/widgets/selected_label.rs +++ b/egui/src/widgets/selected_label.rs @@ -48,16 +48,19 @@ impl Widget for SelectableLabel { if selected || response.hovered() { let rect = rect.expand(visuals.expansion); + let fill = if selected { ui.visuals().selection.bg_fill } else { Default::default() }; + let stroke = if selected { ui.visuals().selection.stroke } else { visuals.bg_stroke }; + let corner_radius = 2.0; ui.painter().rect(rect, corner_radius, fill, stroke); } diff --git a/egui_demo_lib/src/apps/demo/drag_and_drop.rs b/egui_demo_lib/src/apps/demo/drag_and_drop.rs index ffdf60ba0..78b26c72b 100644 --- a/egui_demo_lib/src/apps/demo/drag_and_drop.rs +++ b/egui_demo_lib/src/apps/demo/drag_and_drop.rs @@ -51,20 +51,24 @@ pub fn drop_target( let style = if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() { ui.visuals().widgets.active - } else if is_being_dragged && can_accept_what_is_being_dragged { - ui.visuals().widgets.inactive - } else if is_being_dragged && !can_accept_what_is_being_dragged { - ui.visuals().widgets.disabled } else { ui.visuals().widgets.inactive }; + let mut fill = style.bg_fill; + let mut stroke = style.bg_stroke; + if is_being_dragged && !can_accept_what_is_being_dragged { + // gray out: + fill = color::tint_color_towards(fill, ui.visuals().window_fill()); + stroke.color = color::tint_color_towards(stroke.color, ui.visuals().window_fill()); + } + ui.painter().set( where_to_put_background, Shape::Rect { corner_radius: style.corner_radius, - fill: style.bg_fill, - stroke: style.bg_stroke, + fill, + stroke, rect, }, ); diff --git a/egui_demo_lib/src/apps/demo/toggle_switch.rs b/egui_demo_lib/src/apps/demo/toggle_switch.rs index 756c47322..bf1700686 100644 --- a/egui_demo_lib/src/apps/demo/toggle_switch.rs +++ b/egui_demo_lib/src/apps/demo/toggle_switch.rs @@ -83,9 +83,12 @@ fn toggle_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response { pub fn demo(ui: &mut egui::Ui, on: &mut bool) { ui.horizontal_wrapped_for_text(egui::TextStyle::Button, |ui| { - ui.label("It's easy to create your own widgets!"); - ui.label("This toggle switch is just one function and 15 lines of code:"); toggle(ui, on).on_hover_text("Click to toggle"); ui.add(crate::__egui_github_link_file!()); - }); + }) + .1 + .on_hover_text( + "It's easy to create your own widgets!\n\ + This toggle switch is just one function and 20 lines of code.", + ); } diff --git a/egui_demo_lib/src/apps/demo/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index 9c5ccdcb8..6a6e24d4d 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -8,6 +8,7 @@ enum Enum { #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] pub struct WidgetGallery { + enabled: bool, boolean: bool, radio: Enum, scalar: f32, @@ -18,6 +19,7 @@ pub struct WidgetGallery { impl Default for WidgetGallery { fn default() -> Self { Self { + enabled: true, boolean: false, radio: Enum::First, scalar: 42.0, @@ -46,6 +48,7 @@ impl super::Demo for WidgetGallery { impl super::View for WidgetGallery { fn ui(&mut self, ui: &mut egui::Ui) { let Self { + enabled, boolean, radio, scalar, @@ -53,6 +56,12 @@ impl super::View for WidgetGallery { color, } = self; + ui.horizontal(|ui| { + ui.radio_value(enabled, true, "Enabled"); + ui.radio_value(enabled, false, "Disabled"); + }); + ui.set_enabled(*enabled); + let grid = egui::Grid::new("my_grid") .striped(true) .spacing([40.0, 4.0]); @@ -65,7 +74,7 @@ impl super::View for WidgetGallery { ui.add(egui::Hyperlink::new("https://github.com/emilk/egui").text(" egui home page")); ui.end_row(); - ui.label("Text Input:"); + ui.label("TextEdit:"); ui.add(egui::TextEdit::singleline(string).hint_text("Write something here")); ui.end_row(); @@ -73,7 +82,7 @@ impl super::View for WidgetGallery { ui.checkbox(boolean, "Checkbox"); ui.end_row(); - ui.label("Radio buttons:"); + ui.label("RadioButton:"); ui.horizontal(|ui| { ui.radio_value(radio, Enum::First, "First"); ui.radio_value(radio, Enum::Second, "Second"); @@ -143,6 +152,10 @@ impl super::View for WidgetGallery { }); }); ui.end_row(); + + ui.label("Custom widget"); + super::toggle_switch::demo(ui, boolean); + ui.end_row(); }); ui.separator(); diff --git a/egui_demo_lib/src/apps/demo/widgets.rs b/egui_demo_lib/src/apps/demo/widgets.rs index 2f08ac8aa..4a593f5a4 100644 --- a/egui_demo_lib/src/apps/demo/widgets.rs +++ b/egui_demo_lib/src/apps/demo/widgets.rs @@ -134,8 +134,5 @@ impl Widgets { ui.label("Multiline text input:"); ui.text_edit_multiline(&mut self.multiline_text_input); - - ui.separator(); - super::toggle_switch::demo(ui, &mut self.toggle_switch); } } diff --git a/egui_demo_lib/src/apps/demo/window_options.rs b/egui_demo_lib/src/apps/demo/window_options.rs index 218b9b833..5b089b841 100644 --- a/egui_demo_lib/src/apps/demo/window_options.rs +++ b/egui_demo_lib/src/apps/demo/window_options.rs @@ -7,6 +7,7 @@ pub struct WindowOptions { collapsible: bool, resizable: bool, scroll: bool, + disabled_time: f64, } impl Default for WindowOptions { @@ -18,6 +19,7 @@ impl Default for WindowOptions { collapsible: true, resizable: true, scroll: false, + disabled_time: f64::NEG_INFINITY, } } } @@ -36,15 +38,22 @@ impl super::Demo for WindowOptions { collapsible, resizable, scroll, + disabled_time, } = self.clone(); + let enabled = ctx.input().time - disabled_time > 2.0; + if !enabled { + ctx.request_repaint(); + } + use super::View; let mut window = egui::Window::new(title) .id(egui::Id::new("demo_window_options")) // required since we change the title .resizable(resizable) .collapsible(collapsible) .title_bar(title_bar) - .scroll(scroll); + .scroll(scroll) + .enabled(enabled); if closable { window = window.open(open); } @@ -63,6 +72,7 @@ impl super::View for WindowOptions { collapsible, resizable, scroll, + disabled_time, } = self; ui.horizontal(|ui| { @@ -77,5 +87,9 @@ impl super::View for WindowOptions { ui.vertical_centered(|ui| { ui.add(crate::__egui_github_link_file!()); }); + + if ui.button("Disable for 2 seconds").clicked() { + *disabled_time = ui.input().time; + } } } diff --git a/epaint/src/color.rs b/epaint/src/color.rs index 595785ab5..963a4ca28 100644 --- a/epaint/src/color.rs +++ b/epaint/src/color.rs @@ -682,3 +682,30 @@ impl From for HsvaGamma { } } } + +// ---------------------------------------------------------------------------- + +/// Cheap and ugly. +/// Made for graying out disabled `Ui`:s. +pub fn tint_color_towards(color: Color32, target: Color32) -> Color32 { + let [mut r, mut g, mut b, mut a] = color.to_array(); + + if a == 0 { + r /= 2; + g /= 2; + b /= 2; + } else if a < 170 { + // Cheapish and looks ok. + // Works for e.g. grid stripes. + let div = (2 * 255 / a as i32) as u8; + r = r / 2 + target.r() / div; + g = g / 2 + target.g() / div; + b = b / 2 + target.b() / div; + a /= 2; + } else { + r = r / 2 + target.r() / 2; + g = g / 2 + target.g() / 2; + b = b / 2 + target.b() / 2; + } + Color32::from_rgba_premultiplied(r, g, b, a) +} diff --git a/epaint/src/lib.rs b/epaint/src/lib.rs index b7bdb2311..ef7fb35b5 100644 --- a/epaint/src/lib.rs +++ b/epaint/src/lib.rs @@ -49,6 +49,7 @@ mod mesh; pub mod mutex; mod shadow; mod shape; +pub mod shape_transform; pub mod stats; mod stroke; pub mod tessellator; diff --git a/epaint/src/shape_transform.rs b/epaint/src/shape_transform.rs new file mode 100644 index 000000000..c33b17843 --- /dev/null +++ b/epaint/src/shape_transform.rs @@ -0,0 +1,35 @@ +use crate::*; + +pub fn adjust_colors(shape: &mut Shape, adjust_color: &impl Fn(&mut Color32)) { + match shape { + Shape::Noop => {} + Shape::Vec(shapes) => { + for shape in shapes { + adjust_colors(shape, adjust_color) + } + } + Shape::Circle { fill, stroke, .. } => { + adjust_color(fill); + adjust_color(&mut stroke.color); + } + Shape::LineSegment { stroke, .. } => { + adjust_color(&mut stroke.color); + } + Shape::Path { fill, stroke, .. } => { + adjust_color(fill); + adjust_color(&mut stroke.color); + } + Shape::Rect { fill, stroke, .. } => { + adjust_color(fill); + adjust_color(&mut stroke.color); + } + Shape::Text { color, .. } => { + adjust_color(color); + } + Shape::Mesh(mesh) => { + for v in &mut mesh.vertices { + adjust_color(&mut v.color); + } + } + } +}