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

Easing functions #4630

Merged
merged 4 commits into from
Jun 6, 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
1 change: 1 addition & 0 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ impl Prepared {
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);
let opacity = emath::easing::cubic_out(opacity); // slow fade-out = quick fade-in
ui.multiply_opacity(opacity);
if opacity < 1.0 {
ctx.request_repaint();
Expand Down
2 changes: 1 addition & 1 deletion crates/egui/src/containers/collapsing_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl CollapsingState {
if ctx.memory(|mem| mem.everything_is_visible()) {
1.0
} else {
ctx.animate_bool(self.id, self.state.open)
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
6 changes: 5 additions & 1 deletion 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
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
Loading
Loading