Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fade in windows, tooltips, popups, etc #4587

Merged
merged 3 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 41 additions & 33 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ pub struct AreaState {

/// If false, clicks goes straight through to what is behind us. Useful for tooltips etc.
pub interactable: bool,

/// At what time was this area first shown?
///
/// Used to fade in the area.
pub last_became_visible_at: f64,
}

impl AreaState {
Expand Down Expand Up @@ -88,6 +93,7 @@ pub struct Area {
pivot: Align2,
anchor: Option<(Align2, Vec2)>,
new_pos: Option<Pos2>,
fade_in: bool,
}

impl WidgetWithState for Area {
Expand All @@ -111,6 +117,7 @@ impl Area {
new_pos: None,
pivot: Align2::LEFT_TOP,
anchor: None,
fade_in: true,
}
}

Expand Down Expand Up @@ -282,6 +289,15 @@ impl Area {
Align2::LEFT_TOP
}
}

/// If `true`, quickly fade in the area.
///
/// Default: `true`.
#[inline]
pub fn fade_in(mut self, fade_in: bool) -> Self {
self.fade_in = fade_in;
self
}
}

pub(crate) struct Prepared {
Expand All @@ -298,6 +314,8 @@ pub(crate) struct Prepared {
/// and then can correctly position the window and its contents the next frame,
/// without having one frame where the window is wrongly positioned or sized.
sizing_pass: bool,

fade_in: bool,
}

impl Area {
Expand Down Expand Up @@ -328,6 +346,7 @@ impl Area {
anchor,
constrain,
constrain_rect,
fade_in,
} = self;

let layer_id = LayerId::new(order, id);
Expand Down Expand Up @@ -363,11 +382,19 @@ impl Area {
pivot,
size,
interactable,
last_became_visible_at: ctx.input(|i| i.time),
}
});
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
state.interactable = interactable;

// TODO(emilk): if last frame was sizing pass, it should be considered invisible for smmother fade-in
let visible_last_frame = ctx.memory(|mem| mem.areas().visible_last_frame(&layer_id));

if !visible_last_frame {
state.last_became_visible_at = ctx.input(|i| i.time);
}

if let Some((anchor, offset)) = anchor {
let screen = ctx.available_rect();
state.set_left_top_pos(
Expand Down Expand Up @@ -421,7 +448,7 @@ impl Area {

state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos()));

// Update responsbe with posisbly moved/constrained rect:
// Update response with possibly moved/constrained rect:
move_response.rect = state.rect();
move_response.interact_rect = state.rect();

Expand All @@ -433,34 +460,7 @@ impl Area {
constrain,
constrain_rect,
sizing_pass: is_new,
}
}

pub fn show_open_close_animation(&self, ctx: &Context, 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 = AreaState::load(ctx, self.id).map(|state| state.rect());
if let Some(area_rect) = area_rect {
let clip_rect = Rect::EVERYTHING;
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));
fade_in,
}
}
}
Expand Down Expand Up @@ -509,6 +509,17 @@ impl Prepared {
max_rect,
clip_rect,
);

if self.fade_in {
let age =
ctx.input(|i| (i.time - self.state.last_became_visible_at) as f32 + i.predicted_dt);
let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0);
ui.multiply_opacity(opacity);
if opacity < 1.0 {
ctx.request_repaint();
}
}

ui.set_enabled(self.enabled);
if self.sizing_pass {
ui.set_sizing_pass();
Expand All @@ -522,10 +533,7 @@ impl Prepared {
layer_id,
mut state,
move_response,
enabled: _,
constrain: _,
constrain_rect: _,
sizing_pass: _,
..
} = self;

state.size = content_ui.min_size();
Expand Down
34 changes: 31 additions & 3 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub struct Window<'open> {
collapsible: bool,
default_open: bool,
with_title_bar: bool,
fade_out: bool,
}

impl<'open> Window<'open> {
Expand All @@ -62,6 +63,7 @@ impl<'open> Window<'open> {
collapsible: true,
default_open: true,
with_title_bar: true,
fade_out: true,
}
}

Expand Down Expand Up @@ -111,6 +113,26 @@ impl<'open> Window<'open> {
self
}

/// If `true`, quickly fade in the `Window` when it first appears.
///
/// Default: `true`.
#[inline]
pub fn fade_in(mut self, fade_in: bool) -> Self {
self.area = self.area.fade_in(fade_in);
self
}

/// If `true`, quickly fade out the `Window` when it closes.
///
/// This only works if you use [`Self::open`] to close the window.
///
/// Default: `true`.
#[inline]
pub fn fade_out(mut self, fade_out: bool) -> Self {
self.fade_out = fade_out;
self
}

/// Usage: `Window::new(…).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
// TODO(emilk): I'm not sure this is a good interface for this.
#[inline]
Expand Down Expand Up @@ -402,6 +424,7 @@ impl<'open> Window<'open> {
collapsible,
default_open,
with_title_bar,
fade_out,
} = self;

let header_color =
Expand All @@ -415,9 +438,8 @@ impl<'open> Window<'open> {

let is_explicitly_closed = matches!(open, Some(false));
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
area.show_open_close_animation(ctx, &window_frame, is_open);

if !is_open {
let opacity = ctx.animate_bool(area.id.with("fade-out"), is_open);
if opacity <= 0.0 {
return None;
}

Expand Down Expand Up @@ -477,6 +499,12 @@ impl<'open> Window<'open> {
);

let mut area_content_ui = area.content_ui(ctx);
if is_open {
// `Area` already takes care of fade-in animations,
// so we only need to handle fade-out animations here.
} else if fade_out {
area_content_ui.multiply_opacity(opacity);
}

let content_inner = {
// BEGIN FRAME --------------------------------
Expand Down
1 change: 1 addition & 0 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ impl ContextImpl {
pivot: Align2::LEFT_TOP,
size: screen_rect.size(),
interactable: true,
last_became_visible_at: f64::NEG_INFINITY,
},
);

Expand Down
2 changes: 1 addition & 1 deletion crates/egui_demo_lib/src/demo/widget_gallery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +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);
ui.multiply_opacity(self.opacity);

egui::Grid::new("my_grid")
.num_columns(2)
Expand Down
Loading