diff --git a/egui/src/containers/area.rs b/egui/src/containers/area.rs index d463254b..03a3a229 100644 --- a/egui/src/containers/area.rs +++ b/egui/src/containers/area.rs @@ -158,6 +158,34 @@ impl Area { add_contents(&mut content_ui); prepared.end(ctx, content_ui) } + + pub fn show_open_close_animation(&self, ctx: &CtxRef, frame: &Frame, is_open: bool) { + // must be called first so animation managers know the latest state + let visibility_factor = ctx.animate_bool(self.id.with("close_animation"), is_open); + + if is_open { + // we actually only show close animations. + // when opening a window we show it right away. + return; + } + if visibility_factor <= 0.0 { + return; + } + + let layer_id = LayerId::new(self.order, self.id); + let area_rect = ctx.memory().areas.get(self.id).map(|area| area.rect()); + if let Some(area_rect) = area_rect { + let clip_rect = ctx.available_rect(); + let painter = Painter::new(ctx.clone(), layer_id, clip_rect); + + // shrinkage: looks kinda a bad on its own + // let area_rect = + // Rect::from_center_size(area_rect.center(), visibility_factor * area_rect.size()); + + let frame = frame.multiply_with_opacity(visibility_factor); + painter.add(frame.paint(area_rect)); + } + } } impl Prepared { diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index 03101459..4a19634d 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -90,6 +90,13 @@ impl Frame { self.stroke = stroke; self } + + pub fn multiply_with_opacity(mut self, opacity: f32) -> Self { + self.fill = self.fill.linear_multiply(opacity); + self.stroke.color = self.stroke.color.linear_multiply(opacity); + self.shadow.color = self.shadow.color.linear_multiply(opacity); + self + } } pub struct Prepared { @@ -122,6 +129,31 @@ impl Frame { prepared.end(ui); ret } + + pub fn paint(&self, outer_rect: Rect) -> Shape { + let Self { + margin: _, + corner_radius, + shadow, + fill, + stroke, + } = *self; + + let frame_shape = Shape::Rect { + rect: outer_rect, + corner_radius, + fill, + stroke, + }; + + if shadow == Default::default() { + frame_shape + } else { + let shadow = shadow.tessellate(outer_rect, corner_radius); + let shadow = Shape::Triangles(shadow); + Shape::Vec(vec![shadow, frame_shape]) + } + } } impl Prepared { @@ -141,26 +173,9 @@ impl Prepared { .. } = self; - let frame_shape = Shape::Rect { - rect: outer_rect, - corner_radius: frame.corner_radius, - fill: frame.fill, - stroke: frame.stroke, - }; - - if frame.shadow == Default::default() { - ui.painter().set(where_to_put_background, frame_shape); - } else { - let shadow = frame.shadow.tessellate(outer_rect, frame.corner_radius); - let shadow = Shape::Triangles(shadow); - ui.painter().set( - where_to_put_background, - Shape::Vec(vec![shadow, frame_shape]), - ) - }; - + let shape = frame.paint(outer_rect); + ui.painter().set(where_to_put_background, shape); ui.advance_cursor_after_rect(outer_rect); - outer_rect } } diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index 927d0a75..32038c36 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -224,7 +224,12 @@ impl<'open> Window<'open> { with_title_bar, } = self; - if matches!(open, Some(false)) && !ctx.memory().everything_is_visible() { + let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); + + let is_open = !matches!(open, Some(false)) || ctx.memory().everything_is_visible(); + area.show_open_close_animation(ctx, &frame, is_open); + + if !is_open { return None; } @@ -244,8 +249,6 @@ impl<'open> Window<'open> { let resize = resize.resizable(false); // We move it manually let mut resize = resize.id(resize_id); - let frame = frame.unwrap_or_else(|| Frame::window(&ctx.style())); - let mut area = area.begin(ctx); let title_content_spacing = 2.0 * ctx.style().spacing.item_spacing.y; diff --git a/egui_demo_lib/src/apps/demo/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index 9dfb4d41..c14dcb01 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -22,7 +22,7 @@ impl Default for WidgetGallery { radio: Enum::First, scalar: 42.0, string: "Hello World!".to_owned(), - color: (egui::Rgba::from(egui::Color32::LIGHT_BLUE) * 0.5).into(), + color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5), } } } diff --git a/epaint/src/color.rs b/epaint/src/color.rs index 3b4474b0..fdba57e0 100644 --- a/epaint/src/color.rs +++ b/epaint/src/color.rs @@ -129,6 +129,14 @@ impl Color32 { pub fn to_tuple(&self) -> (u8, u8, u8, u8) { (self.r(), self.g(), self.b(), self.a()) } + + /// Multiply with 0.5 to make color half as opaque. + pub fn linear_multiply(self, factor: f32) -> Color32 { + debug_assert!(0.0 <= factor && factor <= 1.0); + // As an unfortunate side-effect of using premultiplied alpha + // we need a somewhat expensive conversion to linear space and back. + Rgba::from(self).multiply(factor).into() + } } // ---------------------------------------------------------------------------- diff --git a/epaint/src/shadow.rs b/epaint/src/shadow.rs index 477d1e0f..df963495 100644 --- a/epaint/src/shadow.rs +++ b/epaint/src/shadow.rs @@ -1,11 +1,15 @@ use super::*; -/// A rectangular shadow with a soft penumbra. +/// The color and fuzziness of a fuzzy shape. +/// Can be used for a rectangular shadow with a soft penumbra. #[derive(Clone, Copy, Debug, Default, PartialEq)] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] pub struct Shadow { - // The shadow extends this much outside the rect. + /// The shadow extends this much outside the rect. + /// The size of the fuzzy penumbra. pub extrusion: f32, + + /// Color of the opaque center of the shadow. pub color: Color32, } diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 3ec7aded..b656814f 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -420,8 +420,9 @@ fn stroke_path( fn mul_color(color: Color32, factor: f32) -> Color32 { debug_assert!(0.0 <= factor && factor <= 1.0); - // sRGBA correct fading requires conversion to linear space and back again because of premultiplied alpha - Rgba::from(color).multiply(factor).into() + // As an unfortunate side-effect of using premultiplied alpha + // we need a somewhat expensive conversion to linear space and back. + color.linear_multiply(factor) } // ----------------------------------------------------------------------------