diff --git a/crates/ely_app/src/shell/chrome/animations.rs b/crates/ely_app/src/shell/chrome/animations.rs index 50c12e2..10dd0b4 100644 --- a/crates/ely_app/src/shell/chrome/animations.rs +++ b/crates/ely_app/src/shell/chrome/animations.rs @@ -1,9 +1,10 @@ use std::time::Duration; -use gpui::{Animation, AnimationExt, AnyElement, ElementId, IntoElement, SharedString, Styled}; +use gpui::{Animation, AnimationExt, AnyElement, ElementId, IntoElement, SharedString, Styled, px}; pub(crate) const MOTION_PRESS_MS: u64 = 120; pub(crate) const MOTION_SELECTION_MS: u64 = 160; +pub(crate) const MOTION_TOGGLE_MS: u64 = 150; #[cfg(test)] pub(crate) const MOTION_FRAME_BUDGET_120HZ: Duration = Duration::from_micros(8_333); @@ -83,9 +84,37 @@ where element.into_any_element() } +pub(crate) fn toggle_thumb_motion( + press_id: Option, + checked: bool, + active_offset_px: f32, + element: E, +) -> AnyElement +where + E: IntoElement + Styled + 'static, +{ + let end = if checked { active_offset_px } else { 0.0 }; + let element = element.ml(px(end)); + let Some(press_id) = press_id else { + return element.into_any_element(); + }; + + let start = if checked { 0.0 } else { active_offset_px }; + element + .with_animation( + press_id, + Animation::new(Duration::from_millis(MOTION_TOGGLE_MS)).with_easing(ease_out_cubic), + move |element, t| element.ml(px(start + (end - start) * t)), + ) + .into_any_element() +} + #[cfg(test)] mod tests { - use super::{MOTION_FRAME_BUDGET_120HZ, MOTION_PRESS_MS, MOTION_SELECTION_MS, ease_out_cubic}; + use super::{ + MOTION_FRAME_BUDGET_120HZ, MOTION_PRESS_MS, MOTION_SELECTION_MS, MOTION_TOGGLE_MS, + ease_out_cubic, + }; #[test] fn ease_out_cubic_anchors_at_endpoints() { @@ -108,5 +137,6 @@ mod tests { assert_eq!(MOTION_FRAME_BUDGET_120HZ.as_micros(), 8_333); assert!(MOTION_PRESS_MS <= 15 * MOTION_FRAME_BUDGET_120HZ.as_millis() as u64); assert!(MOTION_SELECTION_MS <= 20 * MOTION_FRAME_BUDGET_120HZ.as_millis() as u64); + assert!(MOTION_TOGGLE_MS <= 19 * MOTION_FRAME_BUDGET_120HZ.as_millis() as u64); } } diff --git a/crates/ely_app/src/shell/chrome/appearance_form.rs b/crates/ely_app/src/shell/chrome/appearance_form.rs index da2feb6..5d0af0e 100644 --- a/crates/ely_app/src/shell/chrome/appearance_form.rs +++ b/crates/ely_app/src/shell/chrome/appearance_form.rs @@ -9,7 +9,7 @@ use gpui::{ use gpui_component::{IconName, scroll::ScrollableElement, slider::Slider}; use crate::shell::ElyShell; -use crate::shell::chrome::SERIF_FAMILY; +use crate::shell::chrome::{SERIF_FAMILY, animations::toggle_thumb_motion}; pub(crate) fn render_appearance_form( shell: &mut ElyShell, @@ -146,7 +146,7 @@ fn render_appearance_rows( .child(render_theme_mode_row(appearance.theme_mode(), cx)) .child(render_accent_row()) .child(render_translucency_row(shell, appearance.translucency_pct())) - .child(render_reduce_motion_row(appearance.reduce_motion(), cx)) + .child(render_reduce_motion_row(shell, appearance.reduce_motion(), cx)) .child(render_reset_row(cx)) .into_any_element() } @@ -288,12 +288,18 @@ fn swatch(color: u32, selected: bool) -> AnyElement { element.into_any_element() } -fn render_reduce_motion_row(reduce: bool, cx: &mut Context) -> AnyElement { +fn render_reduce_motion_row( + shell: &ElyShell, + reduce: bool, + cx: &mut Context, +) -> AnyElement { + let id = "toggle-reduce-motion"; + let press_id = shell.chrome_motion_animation_id(id); settings_row( "Reduce motion", "Mute transitions and ambient effects.", div() - .id(SharedString::from("toggle-reduce-motion")) + .id(SharedString::from(id)) .w(px(34.0)) .h(px(20.0)) .rounded_full() @@ -302,14 +308,16 @@ fn render_reduce_motion_row(reduce: bool, cx: &mut Context) -> AnyElem .cursor_pointer() .hover(|style| style.opacity(0.9)) .active(|style| style.opacity(0.78)) - .on_click(cx.listener(|shell, _, _, cx| shell.toggle_reduce_motion(cx))) - .child( - div() - .size(px(16.0)) - .rounded_full() - .bg(rgb(0xffffff)) - .when(reduce, |el| el.ml(px(14.0))), - ) + .on_click(cx.listener(move |shell, _, _, cx| { + shell.toggle_reduce_motion(cx); + shell.trigger_chrome_motion(id); + })) + .child(toggle_thumb_motion( + press_id, + reduce, + 14.0, + div().size(px(16.0)).rounded_full().bg(rgb(0xffffff)), + )) .into_any_element(), ) } diff --git a/crates/ely_app/src/shell/chrome_motion.rs b/crates/ely_app/src/shell/chrome_motion.rs index da0d411..846f8ef 100644 --- a/crates/ely_app/src/shell/chrome_motion.rs +++ b/crates/ely_app/src/shell/chrome_motion.rs @@ -1,6 +1,6 @@ use gpui::SharedString; -use super::ElyShell; +use super::{ElyShell, ShellState}; #[derive(Default)] pub(crate) struct ChromeMotionState { @@ -9,12 +9,28 @@ pub(crate) struct ChromeMotionState { } impl ElyShell { + pub(crate) fn chrome_motion_enabled(&self) -> bool { + match &self.state { + ShellState::Ready(core) => !core.appearance().reduce_motion(), + ShellState::StartupError(_) => true, + } + } + pub(crate) fn trigger_chrome_motion(&mut self, target: impl Into) { + if !self.chrome_motion_enabled() { + self.chrome_motion.target = None; + return; + } + self.chrome_motion.target = Some(target.into()); self.chrome_motion.epoch = self.chrome_motion.epoch.wrapping_add(1); } pub(crate) fn chrome_motion_animation_id(&self, target: &str) -> Option { + if !self.chrome_motion_enabled() { + return None; + } + self.chrome_motion .target .as_ref() diff --git a/crates/ely_app/src/shell/internal_pages/sync_controls.rs b/crates/ely_app/src/shell/internal_pages/sync_controls.rs index 6fc5de0..f99b80f 100644 --- a/crates/ely_app/src/shell/internal_pages/sync_controls.rs +++ b/crates/ely_app/src/shell/internal_pages/sync_controls.rs @@ -6,7 +6,7 @@ use gpui::{ }; use crate::shell::ElyShell; -use crate::shell::chrome::animations::chrome_motion_feedback; +use crate::shell::chrome::animations::{chrome_motion_feedback, toggle_thumb_motion}; pub(super) fn render_primary_button( shell: &ElyShell, @@ -106,6 +106,7 @@ pub(super) fn render_policy_toggle( let track_color = if enabled { colors::accent() } else { 0x281e1426 }; let id = SharedString::from(format!("sync-policy-{index}")); let press_id = shell.chrome_motion_animation_id(id.as_str()); + let thumb_press_id = press_id.clone(); let selection_id = SharedString::from(format!("{}-selection", id.as_str())); let element = div() @@ -122,13 +123,12 @@ pub(super) fn render_policy_toggle( shell.trigger_chrome_motion(id.clone()); shell.set_sync_object_policy(kind, next_policy, cx); })) - .child( - div() - .size(px(16.0)) - .rounded_full() - .bg(rgb(0xffffff)) - .when(enabled, |this| this.ml(px(14.0))), - ); + .child(toggle_thumb_motion( + thumb_press_id, + enabled, + 14.0, + div().size(px(16.0)).rounded_full().bg(rgb(0xffffff)), + )); chrome_motion_feedback(press_id, selection_id, enabled, element) }