feat(app): animate sync toggle motion
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
use std::time::Duration;
|
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_PRESS_MS: u64 = 120;
|
||||||
pub(crate) const MOTION_SELECTION_MS: u64 = 160;
|
pub(crate) const MOTION_SELECTION_MS: u64 = 160;
|
||||||
|
pub(crate) const MOTION_TOGGLE_MS: u64 = 150;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) const MOTION_FRAME_BUDGET_120HZ: Duration = Duration::from_micros(8_333);
|
pub(crate) const MOTION_FRAME_BUDGET_120HZ: Duration = Duration::from_micros(8_333);
|
||||||
|
|
||||||
@@ -83,9 +84,37 @@ where
|
|||||||
element.into_any_element()
|
element.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn toggle_thumb_motion<E>(
|
||||||
|
press_id: Option<SharedString>,
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
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]
|
#[test]
|
||||||
fn ease_out_cubic_anchors_at_endpoints() {
|
fn ease_out_cubic_anchors_at_endpoints() {
|
||||||
@@ -108,5 +137,6 @@ mod tests {
|
|||||||
assert_eq!(MOTION_FRAME_BUDGET_120HZ.as_micros(), 8_333);
|
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_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_SELECTION_MS <= 20 * MOTION_FRAME_BUDGET_120HZ.as_millis() as u64);
|
||||||
|
assert!(MOTION_TOGGLE_MS <= 19 * MOTION_FRAME_BUDGET_120HZ.as_millis() as u64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use gpui::{
|
|||||||
use gpui_component::{IconName, scroll::ScrollableElement, slider::Slider};
|
use gpui_component::{IconName, scroll::ScrollableElement, slider::Slider};
|
||||||
|
|
||||||
use crate::shell::ElyShell;
|
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(
|
pub(crate) fn render_appearance_form(
|
||||||
shell: &mut ElyShell,
|
shell: &mut ElyShell,
|
||||||
@@ -146,7 +146,7 @@ fn render_appearance_rows(
|
|||||||
.child(render_theme_mode_row(appearance.theme_mode(), cx))
|
.child(render_theme_mode_row(appearance.theme_mode(), cx))
|
||||||
.child(render_accent_row())
|
.child(render_accent_row())
|
||||||
.child(render_translucency_row(shell, appearance.translucency_pct()))
|
.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))
|
.child(render_reset_row(cx))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
@@ -288,12 +288,18 @@ fn swatch(color: u32, selected: bool) -> AnyElement {
|
|||||||
element.into_any_element()
|
element.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_reduce_motion_row(reduce: bool, cx: &mut Context<ElyShell>) -> AnyElement {
|
fn render_reduce_motion_row(
|
||||||
|
shell: &ElyShell,
|
||||||
|
reduce: bool,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
let id = "toggle-reduce-motion";
|
||||||
|
let press_id = shell.chrome_motion_animation_id(id);
|
||||||
settings_row(
|
settings_row(
|
||||||
"Reduce motion",
|
"Reduce motion",
|
||||||
"Mute transitions and ambient effects.",
|
"Mute transitions and ambient effects.",
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from("toggle-reduce-motion"))
|
.id(SharedString::from(id))
|
||||||
.w(px(34.0))
|
.w(px(34.0))
|
||||||
.h(px(20.0))
|
.h(px(20.0))
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
@@ -302,14 +308,16 @@ fn render_reduce_motion_row(reduce: bool, cx: &mut Context<ElyShell>) -> AnyElem
|
|||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.hover(|style| style.opacity(0.9))
|
.hover(|style| style.opacity(0.9))
|
||||||
.active(|style| style.opacity(0.78))
|
.active(|style| style.opacity(0.78))
|
||||||
.on_click(cx.listener(|shell, _, _, cx| shell.toggle_reduce_motion(cx)))
|
.on_click(cx.listener(move |shell, _, _, cx| {
|
||||||
.child(
|
shell.toggle_reduce_motion(cx);
|
||||||
div()
|
shell.trigger_chrome_motion(id);
|
||||||
.size(px(16.0))
|
}))
|
||||||
.rounded_full()
|
.child(toggle_thumb_motion(
|
||||||
.bg(rgb(0xffffff))
|
press_id,
|
||||||
.when(reduce, |el| el.ml(px(14.0))),
|
reduce,
|
||||||
)
|
14.0,
|
||||||
|
div().size(px(16.0)).rounded_full().bg(rgb(0xffffff)),
|
||||||
|
))
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
|
|
||||||
use super::ElyShell;
|
use super::{ElyShell, ShellState};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct ChromeMotionState {
|
pub(crate) struct ChromeMotionState {
|
||||||
@@ -9,12 +9,28 @@ pub(crate) struct ChromeMotionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ElyShell {
|
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<SharedString>) {
|
pub(crate) fn trigger_chrome_motion(&mut self, target: impl Into<SharedString>) {
|
||||||
|
if !self.chrome_motion_enabled() {
|
||||||
|
self.chrome_motion.target = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.chrome_motion.target = Some(target.into());
|
self.chrome_motion.target = Some(target.into());
|
||||||
self.chrome_motion.epoch = self.chrome_motion.epoch.wrapping_add(1);
|
self.chrome_motion.epoch = self.chrome_motion.epoch.wrapping_add(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn chrome_motion_animation_id(&self, target: &str) -> Option<SharedString> {
|
pub(crate) fn chrome_motion_animation_id(&self, target: &str) -> Option<SharedString> {
|
||||||
|
if !self.chrome_motion_enabled() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
self.chrome_motion
|
self.chrome_motion
|
||||||
.target
|
.target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::shell::ElyShell;
|
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<F>(
|
pub(super) fn render_primary_button<F>(
|
||||||
shell: &ElyShell,
|
shell: &ElyShell,
|
||||||
@@ -106,6 +106,7 @@ pub(super) fn render_policy_toggle(
|
|||||||
let track_color = if enabled { colors::accent() } else { 0x281e1426 };
|
let track_color = if enabled { colors::accent() } else { 0x281e1426 };
|
||||||
let id = SharedString::from(format!("sync-policy-{index}"));
|
let id = SharedString::from(format!("sync-policy-{index}"));
|
||||||
let press_id = shell.chrome_motion_animation_id(id.as_str());
|
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 selection_id = SharedString::from(format!("{}-selection", id.as_str()));
|
||||||
|
|
||||||
let element = div()
|
let element = div()
|
||||||
@@ -122,13 +123,12 @@ pub(super) fn render_policy_toggle(
|
|||||||
shell.trigger_chrome_motion(id.clone());
|
shell.trigger_chrome_motion(id.clone());
|
||||||
shell.set_sync_object_policy(kind, next_policy, cx);
|
shell.set_sync_object_policy(kind, next_policy, cx);
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(toggle_thumb_motion(
|
||||||
div()
|
thumb_press_id,
|
||||||
.size(px(16.0))
|
enabled,
|
||||||
.rounded_full()
|
14.0,
|
||||||
.bg(rgb(0xffffff))
|
div().size(px(16.0)).rounded_full().bg(rgb(0xffffff)),
|
||||||
.when(enabled, |this| this.ml(px(14.0))),
|
));
|
||||||
);
|
|
||||||
|
|
||||||
chrome_motion_feedback(press_id, selection_id, enabled, element)
|
chrome_motion_feedback(press_id, selection_id, enabled, element)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user