Fade in windows, tooltips, popups, etc (#4587)
All `Area`s now have a quick fade-in animation. You can turn it off with `Area::fade_in` or `Window::fade_in` . The `Window` fade-out animation is now nicer: it fades all elements of the window, not just the frame. It can be controlled with `Window::fade_out`.
This commit is contained in:
parent
6282844f65
commit
84d204246f
|
|
@ -26,6 +26,11 @@ pub struct AreaState {
|
||||||
|
|
||||||
/// If false, clicks goes straight through to what is behind us. Useful for tooltips etc.
|
/// If false, clicks goes straight through to what is behind us. Useful for tooltips etc.
|
||||||
pub interactable: bool,
|
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 {
|
impl AreaState {
|
||||||
|
|
@ -88,6 +93,7 @@ pub struct Area {
|
||||||
pivot: Align2,
|
pivot: Align2,
|
||||||
anchor: Option<(Align2, Vec2)>,
|
anchor: Option<(Align2, Vec2)>,
|
||||||
new_pos: Option<Pos2>,
|
new_pos: Option<Pos2>,
|
||||||
|
fade_in: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetWithState for Area {
|
impl WidgetWithState for Area {
|
||||||
|
|
@ -111,6 +117,7 @@ impl Area {
|
||||||
new_pos: None,
|
new_pos: None,
|
||||||
pivot: Align2::LEFT_TOP,
|
pivot: Align2::LEFT_TOP,
|
||||||
anchor: None,
|
anchor: None,
|
||||||
|
fade_in: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,6 +289,15 @@ impl Area {
|
||||||
Align2::LEFT_TOP
|
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 {
|
pub(crate) struct Prepared {
|
||||||
|
|
@ -298,6 +314,8 @@ pub(crate) struct Prepared {
|
||||||
/// and then can correctly position the window and its contents the next frame,
|
/// 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.
|
/// without having one frame where the window is wrongly positioned or sized.
|
||||||
sizing_pass: bool,
|
sizing_pass: bool,
|
||||||
|
|
||||||
|
fade_in: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Area {
|
impl Area {
|
||||||
|
|
@ -328,6 +346,7 @@ impl Area {
|
||||||
anchor,
|
anchor,
|
||||||
constrain,
|
constrain,
|
||||||
constrain_rect,
|
constrain_rect,
|
||||||
|
fade_in,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let layer_id = LayerId::new(order, id);
|
let layer_id = LayerId::new(order, id);
|
||||||
|
|
@ -363,11 +382,19 @@ impl Area {
|
||||||
pivot,
|
pivot,
|
||||||
size,
|
size,
|
||||||
interactable,
|
interactable,
|
||||||
|
last_became_visible_at: ctx.input(|i| i.time),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
|
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
|
||||||
state.interactable = interactable;
|
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 {
|
if let Some((anchor, offset)) = anchor {
|
||||||
let screen = ctx.available_rect();
|
let screen = ctx.available_rect();
|
||||||
state.set_left_top_pos(
|
state.set_left_top_pos(
|
||||||
|
|
@ -421,7 +448,7 @@ impl Area {
|
||||||
|
|
||||||
state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos()));
|
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.rect = state.rect();
|
||||||
move_response.interact_rect = state.rect();
|
move_response.interact_rect = state.rect();
|
||||||
|
|
||||||
|
|
@ -433,34 +460,7 @@ impl Area {
|
||||||
constrain,
|
constrain,
|
||||||
constrain_rect,
|
constrain_rect,
|
||||||
sizing_pass: is_new,
|
sizing_pass: is_new,
|
||||||
}
|
fade_in,
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -509,6 +509,17 @@ impl Prepared {
|
||||||
max_rect,
|
max_rect,
|
||||||
clip_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);
|
ui.set_enabled(self.enabled);
|
||||||
if self.sizing_pass {
|
if self.sizing_pass {
|
||||||
ui.set_sizing_pass();
|
ui.set_sizing_pass();
|
||||||
|
|
@ -522,10 +533,7 @@ impl Prepared {
|
||||||
layer_id,
|
layer_id,
|
||||||
mut state,
|
mut state,
|
||||||
move_response,
|
move_response,
|
||||||
enabled: _,
|
..
|
||||||
constrain: _,
|
|
||||||
constrain_rect: _,
|
|
||||||
sizing_pass: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
state.size = content_ui.min_size();
|
state.size = content_ui.min_size();
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ pub struct Window<'open> {
|
||||||
collapsible: bool,
|
collapsible: bool,
|
||||||
default_open: bool,
|
default_open: bool,
|
||||||
with_title_bar: bool,
|
with_title_bar: bool,
|
||||||
|
fade_out: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'open> Window<'open> {
|
impl<'open> Window<'open> {
|
||||||
|
|
@ -62,6 +63,7 @@ impl<'open> Window<'open> {
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
default_open: true,
|
default_open: true,
|
||||||
with_title_bar: true,
|
with_title_bar: true,
|
||||||
|
fade_out: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,6 +113,26 @@ impl<'open> Window<'open> {
|
||||||
self
|
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))`
|
/// 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.
|
// TODO(emilk): I'm not sure this is a good interface for this.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -402,6 +424,7 @@ impl<'open> Window<'open> {
|
||||||
collapsible,
|
collapsible,
|
||||||
default_open,
|
default_open,
|
||||||
with_title_bar,
|
with_title_bar,
|
||||||
|
fade_out,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let header_color =
|
let header_color =
|
||||||
|
|
@ -415,9 +438,8 @@ impl<'open> Window<'open> {
|
||||||
|
|
||||||
let is_explicitly_closed = matches!(open, Some(false));
|
let is_explicitly_closed = matches!(open, Some(false));
|
||||||
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
|
let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
|
||||||
area.show_open_close_animation(ctx, &window_frame, is_open);
|
let opacity = ctx.animate_bool(area.id.with("fade-out"), is_open);
|
||||||
|
if opacity <= 0.0 {
|
||||||
if !is_open {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -477,6 +499,12 @@ impl<'open> Window<'open> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut area_content_ui = area.content_ui(ctx);
|
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 = {
|
let content_inner = {
|
||||||
// BEGIN FRAME --------------------------------
|
// BEGIN FRAME --------------------------------
|
||||||
|
|
|
||||||
|
|
@ -496,6 +496,7 @@ impl ContextImpl {
|
||||||
pivot: Align2::LEFT_TOP,
|
pivot: Align2::LEFT_TOP,
|
||||||
size: screen_rect.size(),
|
size: screen_rect.size(),
|
||||||
interactable: true,
|
interactable: true,
|
||||||
|
last_became_visible_at: f64::NEG_INFINITY,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ impl super::View for WidgetGallery {
|
||||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
ui.add_enabled_ui(self.enabled, |ui| {
|
ui.add_enabled_ui(self.enabled, |ui| {
|
||||||
ui.set_visible(self.visible);
|
ui.set_visible(self.visible);
|
||||||
ui.set_opacity(self.opacity);
|
ui.multiply_opacity(self.opacity);
|
||||||
|
|
||||||
egui::Grid::new("my_grid")
|
egui::Grid::new("my_grid")
|
||||||
.num_columns(2)
|
.num_columns(2)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue