diff --git a/crates/egui/src/painter.rs b/crates/egui/src/painter.rs index e318fbcd..8689ec81 100644 --- a/crates/egui/src/painter.rs +++ b/crates/egui/src/painter.rs @@ -28,6 +28,11 @@ pub struct Painter { /// 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, + + /// If set, all shapes will have their colors modified with [`Color32::gamma_multiply`] with + /// this value as the factor. + /// This is used to make interfaces semi-transparent. + opacity_factor: f32, } impl Painter { @@ -38,6 +43,7 @@ impl Painter { layer_id, clip_rect, fade_to_color: None, + opacity_factor: 1.0, } } @@ -49,6 +55,7 @@ impl Painter { layer_id, clip_rect: self.clip_rect, fade_to_color: None, + opacity_factor: 1.0, } } @@ -62,6 +69,7 @@ impl Painter { layer_id: self.layer_id, clip_rect: rect.intersect(self.clip_rect), fade_to_color: self.fade_to_color, + opacity_factor: self.opacity_factor, } } @@ -75,6 +83,12 @@ impl Painter { self.fade_to_color = fade_to_color; } + pub(crate) fn set_opacity(&mut self, opacity: f32) { + if opacity.is_finite() { + self.opacity_factor = opacity.clamp(0.0, 1.0); + } + } + pub(crate) fn is_visible(&self) -> bool { self.fade_to_color != Some(Color32::TRANSPARENT) } @@ -151,13 +165,16 @@ impl Painter { if let Some(fade_to_color) = self.fade_to_color { tint_shape_towards(shape, fade_to_color); } + if self.opacity_factor < 1.0 { + multiply_opacity(shape, self.opacity_factor); + } } /// 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: impl Into) -> ShapeIdx { - if self.fade_to_color == Some(Color32::TRANSPARENT) { + if self.fade_to_color == Some(Color32::TRANSPARENT) || self.opacity_factor == 0.0 { self.paint_list(|l| l.add(self.clip_rect, Shape::Noop)) } else { let mut shape = shape.into(); @@ -170,10 +187,10 @@ impl Painter { /// /// Calling this once is generally faster than calling [`Self::add`] multiple times. pub fn extend>(&self, shapes: I) { - if self.fade_to_color == Some(Color32::TRANSPARENT) { + if self.fade_to_color == Some(Color32::TRANSPARENT) || self.opacity_factor == 0.0 { return; } - if self.fade_to_color.is_some() { + if self.fade_to_color.is_some() || self.opacity_factor < 1.0 { let shapes = shapes.into_iter().map(|mut shape| { self.transform_shape(&mut shape); shape @@ -181,7 +198,7 @@ impl Painter { self.paint_list(|l| l.extend(self.clip_rect, shapes)); } else { self.paint_list(|l| l.extend(self.clip_rect, shapes)); - }; + } } /// Modify an existing [`Shape`]. @@ -496,3 +513,11 @@ fn tint_shape_towards(shape: &mut Shape, target: Color32) { } }); } + +fn multiply_opacity(shape: &mut Shape, opacity: f32) { + epaint::shape_transform::adjust_colors(shape, &|color| { + if *color != Color32::PLACEHOLDER { + *color = color.gamma_multiply(opacity); + } + }); +} diff --git a/crates/egui/src/ui.rs b/crates/egui/src/ui.rs index 1d65253e..087d2dd8 100644 --- a/crates/egui/src/ui.rs +++ b/crates/egui/src/ui.rs @@ -277,6 +277,26 @@ impl Ui { } } + /// Make the widget in this [`Ui`] semi-transparent. + /// + /// `opacity` must be between 0.0 and 1.0, where 0.0 means fully transparent (i.e., invisible) + /// and 1.0 means fully opaque (i.e., the same as not calling the method at all). + /// + /// ### Example + /// ``` + /// # egui::__run_test_ui(|ui| { + /// ui.group(|ui| { + /// ui.set_opacity(0.5); + /// if ui.button("Half-transparent button").clicked() { + /// /* … */ + /// } + /// }); + /// # }); + /// ``` + pub fn set_opacity(&mut self, opacity: f32) { + self.painter.set_opacity(opacity); + } + /// Read the [`Layout`]. #[inline] pub fn layout(&self) -> &Layout { diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 4189e61c..ac55a4d0 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -12,6 +12,7 @@ pub struct WidgetGallery { enabled: bool, visible: bool, boolean: bool, + opacity: f32, radio: Enum, scalar: f32, string: String, @@ -28,6 +29,7 @@ impl Default for WidgetGallery { Self { enabled: true, visible: true, + opacity: 1.0, boolean: false, radio: Enum::First, scalar: 42.0, @@ -61,6 +63,7 @@ impl super::View for WidgetGallery { fn ui(&mut self, ui: &mut egui::Ui) { ui.add_enabled_ui(self.enabled, |ui| { ui.set_visible(self.visible); + ui.set_opacity(self.opacity); egui::Grid::new("my_grid") .num_columns(2) @@ -79,6 +82,12 @@ impl super::View for WidgetGallery { if self.visible { ui.checkbox(&mut self.enabled, "Interactive") .on_hover_text("Uncheck to inspect how the widgets look when disabled."); + (ui.add( + egui::DragValue::new(&mut self.opacity) + .speed(0.01) + .clamp_range(0.0..=1.0), + ) | ui.label("Opacity")) + .on_hover_text("Reduce this value to make widgets semi-transparent"); } }); @@ -99,6 +108,7 @@ impl WidgetGallery { let Self { enabled: _, visible: _, + opacity: _, boolean, radio, scalar,