Skip to content

motion: Keep cubic-bezier anticipation and overshoot in Easing - #3432

Merged
huacnlee merged 2 commits into
longbridge:mainfrom
costajohnt:fix/3401-easing-overshoot
Oct 11, 2026
Merged

huacnlee merged 2 commits into
longbridge:mainfrom
costajohnt:fix/3401-easing-overshoot

Conversation

@costajohnt

Copy link
Copy Markdown
Contributor

Closes #3401

Description

Easing::sample for Easing::CubicBezier went through the shared solver that clamps its output to [0, 1], so CSS curves with control points outside that range lost their anticipation and overshoot (e.g. cubic-bezier(0.175, 0.885, 0.32, 1.275) never passed 1).

  • crates/base/src/animation.rs: the solver body is now pub(crate) fn unclamped_cubic_bezier (input t is still clamped, output isn't). The public cubic_bezier wraps it with .clamp(0.0, 1.0), so its signature and output are unchanged for the existing callers. Removed the comment saying GPUI asserts easing deltas stay in [0, 1]: gpui-pre 0.3.8 with_easing only debug_assert!s that the value is finite.
  • crates/base/src/motion/easing.rs: only the CubicBezier arm uses the unclamped solver; the keyword presets keep the clamped one (their control points are all in range anyway).
  • crates/component/src/dialog/dialog.rs: the Fade/FadeSlide entrance now clamps easing.sample(delta) before with_easing, because that value drives the dialog opacity and shadow alpha.

This touches gpui-base because that is where the clamp lives.

Same exposure elsewhere, not changed here to keep this to one thing: resizable.rs (handle opacity on easing_move), input/group.rs (border/background colour on easing_move) and plot/hover.rs (hover progress on easing_enter/easing_exit) feed a theme easing straight into alpha or colour. The default theme curves stay in [0, 1], and these paths could already go out of range with Easing::Custom; happy to follow up with call-site clamps if you want them.

The change was written with an AI coding assistant; I reviewed and tested it.

Public API

No signatures change. Behaviour change:

  • gpui_base::motion::Easing::sample — for Easing::CubicBezier with control points outside 0..=1, the output can now leave [0, 1]. Clamp at the call site when the value drives opacity or colour. gpui_base::animation::cubic_bezier still returns values in [0, 1].

Breaking Changes

  • Code that relied on Easing::sample staying within [0, 1] for an out-of-range cubic-bezier curve now sees overshoot:
- let alpha = easing.sample(delta);
+ let alpha = easing.sample(delta).clamp(0., 1.);

How to Test

  • New test cubic_bezier_easing_keeps_anticipation_and_overshoot (crates/base/src/motion.rs, css_timing_tests) uses the curves from the issue: (1.0, -0.4, 0.35, 0.95) samples below 0 at 0.2 and (0.175, 0.885, 0.32, 1.275) above 1 at 0.8; progress -1 and 2 still map to 0 and 1. It fails on main.
  • cargo test -p gpui-base --lib (1325 passed), cargo test -p gpui-base -p gpui-component --tests (all pass, incl. 661 gpui-component lib tests), cargo clippy -p gpui-base -p gpui-component --all-targets -- --deny warnings and cargo fmt --all --check clean.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (If any) is accurate.
  • Passed cargo run for story tests related to the changes. (Not run; the default theme curves stay within [0, 1], so the story's dialogs render the same.)
  • Tested macOS, Windows and Linux platforms performance (if the change is platform-specific) (not platform-specific)

costajohnt and others added 2 commits October 10, 2026 16:13
`Easing::sample` sampled `CubicBezier` through `animation::cubic_bezier`,
which clamps its output to 0..1, so control points with y outside 0..1
lost their anticipation and overshoot.

Sample `Easing::CubicBezier` with an unclamped solver; elapsed progress
stays clamped. The public `cubic_bezier` keeps its clamped output for
`Animation::with_easing` callers. Dialog clamps the theme easing it hands
to `with_easing`, since the delta drives opacity.

Fixes longbridge#3401
… or a sweep

The plot hover progress, the resize handle opacity and the input group
colors now clamp the eased value, and the progress circle keeps its
sweep within one turn. Document that Easing::sample may leave 0..=1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@huacnlee
huacnlee enabled auto-merge (squash) October 11, 2026 04:33
@huacnlee
huacnlee merged commit 5e1da31 into longbridge:main Oct 11, 2026
16 checks passed
@huacnlee huacnlee mentioned this pull request Oct 12, 2026
@huacnlee huacnlee added the Breaking Changes Contains breaking changes; excluded from automatic cherry-picks to stable. label Oct 12, 2026
huacnlee added a commit that referenced this pull request Oct 12, 2026
## Description

Prepare v0.7.2 on `stable` from the PRs merged into `main` since v0.7.1,
leaving out every change that breaks existing consumers. `stable`
already carries #3371 and #3373; this adds the remaining compatible PRs
with `git cherry-pick -x` and bumps the workspace to 0.7.2.

### Included

- kit: Add optional gpui-fast backend (#3375)
- tooltip: Make show delay and grace period configurable with
`TooltipDefaults` (#3376)
- docs: Explain recovering focus when the focused element stops
rendering (#3386)
- diff: Add readonly patch viewer (#3378)
- kit: Use one Windows manifest with GPUI Fast (#3380)
- test: Make Progress and TitleBar findable in UI tests (#3383)
- input: Preserve selection, focus ring and context menu actions (#3382)
- diff: Add a Git history example and complete review APIs (#3388)
- text_view: Ignore gestures that stay inside one block plugin (#3390)
- text_view: Round the first and last table rows to the frame radius
(#3309)
- menu: Open a context menu with a long press on touch (#3393)
- text_view: Keep narrow table columns on one line (#3399)
- settings: Keep focus rings inside the list viewport (#3394)
- scrollable_mask: Keep rounded scroll-table corners clean at any scroll
offset (#3400)
- dialog: Add margin_top to AlertDialog (#3404)
- input: Add InputState::add_selection for programmatic multi-cursor
(#3403)
- text_selection: Stop writing selection state on every frame (#3407)
- table: Make the sort icon a labelled button (#3396)
- table: Report visible row ranges of one row or none (#3397)
- dialog: Support configurable entrance animations (#3405)
- docs: Explain the open_dialog builder and footer contracts (#3406)
- dock: Hand the focus to the tab that replaces a closed one (#3409)
- dropdown_button: Add A11y names for the menu trigger (#3410)
- text, resizable: Stop notifying state that did not change (#3418)
- editor: Add on-demand completion commands (#3411)
- table: Clear a right-clicked cell's outline on the next click (#3413)
- tab: Scroll a clicked tab into view with the next one peeking (#3419)
- table: Allow a header row height separate from the body (#3420)
- menu: Focus a context menu before its first frame (#3422)
- text: Preserve logical selection across presentation changes (#3421)
- text: Keep inline HTML mark highlights in Markdown (#3425)
- input: Anchor completion/code-action popups to the current frame's
caret (#3412)
- input: Run custom context menu builders after releasing the input
(#3433)
- input: Keep auto-grow rows fitted when the limits change (#3430)
- text: Paint TextView selection under the glyphs (#3439)

Already on `stable`: #3371, #3373.

### Excluded (Breaking Changes)

- #3432 motion: `Easing::sample` now returns values outside `[0, 1]` for
overshooting curves.
- #3415 tab: new `TabVariant::Folder` breaks exhaustive matches.
- #3402 kit: pointer helpers now keep held modifiers; nested
context-menu behavior changes in tests.
- #3384 test: `TestWindowExt` gains required methods.
- #3302 resizable: `ResizeHandle::on_drag` takes `&T` instead of
`Rc<T>`.
- #3389, #3395, #3398 webview: wrapper API changes, `gpui-wry` →
`gpui-webview` rename, and the GPUI Fast requirement.

#3411, #3405 and #3396 have a Breaking Changes section that reads "None"
and are included. #3376 only deprecates `TooltipRequest::placement`, and
#3397 / #3382 change behavior compatibly, so they are included too.

### Adaptations

- `crates/kit/tests/collections.rs` and `crates/kit/tests/rendering.rs`:
conflict resolutions drop the tests that belong to #3402 and keep each
backported PR's own test.
- `test: Keep backported tests on the v0.7.1 test API`: the menu and
completion tests from #3422 and #3412 used `find_all` and
`pointer_down`/`pointer_up` from #3384/#3402. They now query
`gpui_kit::base::test_support::snapshots` and dispatch mouse events
directly.
- `kit: Prepare v0.7.2`: workspace version bump; `Cargo.lock` only
updates workspace members.

## Public API

No new public API beyond what the included PRs add; each lists its items
in its own `## Public API` section (notably #3375, #3376, #3378, #3388,
#3403, #3404, #3405, #3411, #3420).

## How to Test

- `cargo clippy --workspace --all-targets -- --deny warnings` passes on
Linux.
- `cargo test -p gpui-kit --features "test-support component assets"
--test collections --test menu --test input` passes on Linux.
- The macOS-only `rendering` test (#3439 adds one case, the count is
updated to 11) was not run.

AI-assisted: the backport, conflict resolution and test adaptations were
done with Claude Code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Floyd Wang <codelife721@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Gustave <gustavel@gmail.com>
Co-authored-by: luren <84951845+lurenjia534@users.noreply.github.com>
Co-authored-by: Ylin <ylinwind@gmail.com>
Co-authored-by: ihavecoke <mixboip@gmail.com>
Co-authored-by: Lorenzo <150276837+Bombatomica64@users.noreply.github.com>
Co-authored-by: MR. XIE <113512030+xxww0098@users.noreply.github.com>
Co-authored-by: xxww0098 <shefkie1957@gmail.com>
Co-authored-by: Sola 🌙🤖 <266175376+sola-ryu@users.noreply.github.com>
Co-authored-by: Sola <sola-ryu@users.noreply.github.com>
Co-authored-by: Andy Hsu <i@nn.ci>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Floyd Wang <gassnake999@gmail.com>
Co-authored-by: Daiki Tomioka <20753105+TomiXRM@users.noreply.github.com>
Co-authored-by: Henrik Øgård <henrik@ogard.no>
Co-authored-by: Henrik Øgård <henrikogaard@users.noreply.github.com>
Co-authored-by: Endless <endless@longbridge.com>
Co-authored-by: John Costa <costajohnt@gmail.com>
Co-authored-by: Alexander <87220898+sneffets@users.noreply.github.com>
Co-authored-by: Cesar Talén <154442565+cesartalen@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Breaking Changes Contains breaking changes; excluded from automatic cherry-picks to stable.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

motion::Easing clamps valid cubic-bezier anticipation and overshoot

2 participants