Skip to content

Commit

Permalink
Use cubic ease-out as the default easing function in egui
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Jun 6, 2024
1 parent bb7ad39 commit 28519fc
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 34 deletions.
7 changes: 1 addition & 6 deletions crates/egui/src/containers/collapsing_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,7 @@ impl CollapsingState {
if ctx.memory(|mem| mem.everything_is_visible()) {
1.0
} else {
let t = ctx.animate_bool(self.id, self.state.open);
if self.state.open {
emath::easing::quadratic_out(t)
} else {
emath::easing::quadratic_in(t)
}
ctx.animate_bool_responsive(self.id, self.state.open)
}
}

Expand Down
30 changes: 14 additions & 16 deletions crates/egui/src/containers/panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

use crate::*;

fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 {
ctx.animate_bool_responsive(id, is_expanded)
}

/// State regarding panels.
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
Expand Down Expand Up @@ -387,7 +391,7 @@ impl SidePanel {
is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = ctx.animate_bool(self.id.with("animation"), is_expanded);
let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);

if 0.0 == how_expanded {
None
Expand Down Expand Up @@ -420,9 +424,7 @@ impl SidePanel {
is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = ui
.ctx()
.animate_bool(self.id.with("animation"), is_expanded);
let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);

if 0.0 == how_expanded {
None
Expand Down Expand Up @@ -455,7 +457,7 @@ impl SidePanel {
expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = ctx.animate_bool(expanded_panel.id.with("animation"), is_expanded);
let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);

if 0.0 == how_expanded {
Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
Expand Down Expand Up @@ -487,9 +489,8 @@ impl SidePanel {
expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> InnerResponse<R> {
let how_expanded = ui
.ctx()
.animate_bool(expanded_panel.id.with("animation"), is_expanded);
let how_expanded =
animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);

if 0.0 == how_expanded {
collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
Expand Down Expand Up @@ -875,7 +876,7 @@ impl TopBottomPanel {
is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = ctx.animate_bool(self.id.with("animation"), is_expanded);
let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);

if 0.0 == how_expanded {
None
Expand Down Expand Up @@ -910,9 +911,7 @@ impl TopBottomPanel {
is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = ui
.ctx()
.animate_bool(self.id.with("animation"), is_expanded);
let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);

if 0.0 == how_expanded {
None
Expand Down Expand Up @@ -947,7 +946,7 @@ impl TopBottomPanel {
expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = ctx.animate_bool(expanded_panel.id.with("animation"), is_expanded);
let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);

if 0.0 == how_expanded {
Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
Expand Down Expand Up @@ -985,9 +984,8 @@ impl TopBottomPanel {
expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> InnerResponse<R> {
let how_expanded = ui
.ctx()
.animate_bool(expanded_panel.id.with("animation"), is_expanded);
let how_expanded =
animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);

if 0.0 == how_expanded {
collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
Expand Down
12 changes: 6 additions & 6 deletions crates/egui/src/containers/scroll_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,8 +514,8 @@ impl ScrollArea {
};

let show_bars_factor = Vec2::new(
ctx.animate_bool(id.with("h"), show_bars[0]),
ctx.animate_bool(id.with("v"), show_bars[1]),
ctx.animate_bool_responsive(id.with("h"), show_bars[0]),
ctx.animate_bool_responsive(id.with("v"), show_bars[1]),
);

let current_bar_use = show_bars_factor.yx() * ui.spacing().scroll.allocated_width();
Expand Down Expand Up @@ -928,10 +928,10 @@ impl Prepared {

// Avoid frame delay; start showing scroll bar right away:
if show_scroll_this_frame[0] && show_bars_factor.x <= 0.0 {
show_bars_factor.x = ui.ctx().animate_bool(id.with("h"), true);
show_bars_factor.x = ui.ctx().animate_bool_responsive(id.with("h"), true);
}
if show_scroll_this_frame[1] && show_bars_factor.y <= 0.0 {
show_bars_factor.y = ui.ctx().animate_bool(id.with("v"), true);
show_bars_factor.y = ui.ctx().animate_bool_responsive(id.with("v"), true);
}

let scroll_style = ui.spacing().scroll;
Expand Down Expand Up @@ -970,7 +970,7 @@ impl Prepared {
|| state.scroll_bar_interaction[d];
let is_hovering_bar_area_t = ui
.ctx()
.animate_bool(id.with((d, "bar_hover")), is_hovering_bar_area);
.animate_bool_responsive(id.with((d, "bar_hover")), is_hovering_bar_area);
let width = show_factor
* lerp(
scroll_style.floating_width..=scroll_style.bar_width,
Expand Down Expand Up @@ -1125,7 +1125,7 @@ impl Prepared {
if response.hovered() || response.dragged() {
scroll_style.interact_handle_opacity
} else {
let is_hovering_outer_rect_t = ui.ctx().animate_bool(
let is_hovering_outer_rect_t = ui.ctx().animate_bool_responsive(
id.with((d, "is_hovering_outer_rect")),
is_hovering_outer_rect,
);
Expand Down
7 changes: 5 additions & 2 deletions crates/egui/src/containers/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,11 @@ 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());
let opacity = ctx.animate_bool(area.id.with("fade-out"), is_open);
let opacity = ctx.animate_bool_with_easing(
area.id.with("fade-out"),
is_open,
emath::easing::cubic_out,
);
if opacity <= 0.0 {
return None;
}
Expand Down Expand Up @@ -502,7 +506,6 @@ impl<'open> Window<'open> {
// `Area` already takes care of fade-in animations,
// so we only need to handle fade-out animations here.
} else if fade_out {
let opacity = emath::easing::cubic_in(opacity); // slow fade-in = quick fade-out
area_content_ui.multiply_opacity(opacity);
}

Expand Down
49 changes: 47 additions & 2 deletions crates/egui/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2442,12 +2442,51 @@ impl Context {
#[track_caller] // To track repaint cause
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
let animation_time = self.style().animation_time;
self.animate_bool_with_time(id, value, animation_time)
self.animate_bool_with_time_and_easing(id, value, animation_time, emath::easing::linear)
}

/// Like [`Self::animate_bool`], but uses an easing function that makes the value move
/// quickly in the beginning and slow down towards the end.
///
/// The exact easing function may come to change in future versions of egui.
#[track_caller] // To track repaint cause
pub fn animate_bool_responsive(&self, id: Id, value: bool) -> f32 {
self.animate_bool_with_easing(id, value, emath::easing::cubic_out)
}

/// Like [`Self::animate_bool`] but allows you to control the easing function.
#[track_caller] // To track repaint cause
pub fn animate_bool_with_easing(&self, id: Id, value: bool, easing: fn(f32) -> f32) -> f32 {
let animation_time = self.style().animation_time;
self.animate_bool_with_time_and_easing(id, value, animation_time, easing)
}

/// Like [`Self::animate_bool`] but allows you to control the animation time.
#[track_caller] // To track repaint cause
pub fn animate_bool_with_time(&self, id: Id, target_value: bool, animation_time: f32) -> f32 {
self.animate_bool_with_time_and_easing(
id,
target_value,
animation_time,
emath::easing::linear,
)
}

/// Like [`Self::animate_bool`] but allows you to control the animation time and easing function.
///
/// Use e.g. [`emath::easing::quadratic_out`]
/// for a responsive start and a slow end.
///
/// The easing function flips when `target_value` is `false`,
/// so that when going back towards 0.0, we get
#[track_caller] // To track repaint cause
pub fn animate_bool_with_time_and_easing(
&self,
id: Id,
target_value: bool,
animation_time: f32,
easing: fn(f32) -> f32,
) -> f32 {
let animated_value = self.write(|ctx| {
ctx.animation_manager.animate_bool(
&ctx.viewports.entry(ctx.viewport_id()).or_default().input,
Expand All @@ -2456,11 +2495,17 @@ impl Context {
target_value,
)
});

let animation_in_progress = 0.0 < animated_value && animated_value < 1.0;
if animation_in_progress {
self.request_repaint();
}
animated_value

if target_value {
easing(animated_value)
} else {
1.0 - easing(1.0 - animated_value)
}
}

/// Smoothly animate an `f32` value.
Expand Down
4 changes: 2 additions & 2 deletions crates/egui_demo_lib/src/demo/toggle_switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
// Let's ask for a simple animation from egui.
// egui keeps track of changes in the boolean associated with the id and
// returns an animated value in the 0-1 range for how much "on" we are.
let how_on = ui.ctx().animate_bool(response.id, *on);
let how_on = ui.ctx().animate_bool_responsive(response.id, *on);
// We will follow the current style by asking
// "how should something that is being interacted with be painted?".
// This will, for instance, give us different colors when the widget is hovered or clicked.
Expand Down Expand Up @@ -80,7 +80,7 @@ fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));

if ui.is_rect_visible(rect) {
let how_on = ui.ctx().animate_bool(response.id, *on);
let how_on = ui.ctx().animate_bool_responsive(response.id, *on);
let visuals = ui.style().interact_selectable(&response, *on);
let rect = rect.expand(visuals.expansion);
let radius = 0.5 * rect.height();
Expand Down

0 comments on commit 28519fc

Please sign in to comment.