feat(chrome): animate high-frequency actions

This commit is contained in:
2026-05-16 00:15:33 -04:00
parent 40ab6b4874
commit 42740842b6
7 changed files with 263 additions and 102 deletions
+47 -2
View File
@@ -1,6 +1,11 @@
use std::time::Duration;
use gpui::{Animation, AnimationExt, ElementId, IntoElement, Styled};
use gpui::{Animation, AnimationExt, AnyElement, ElementId, IntoElement, SharedString, Styled};
pub(crate) const MOTION_PRESS_MS: u64 = 120;
pub(crate) const MOTION_SELECTION_MS: u64 = 160;
#[cfg(test)]
pub(crate) const MOTION_FRAME_BUDGET_120HZ: Duration = Duration::from_micros(8_333);
/// One-second blink that toggles between fully visible and invisible at the
/// half-period mark, matching the design's
@@ -45,9 +50,42 @@ where
element.with_animation(id, animation, |element, t| element.opacity(ease_out_cubic(t)))
}
pub(crate) fn chrome_motion_feedback<E>(
press_id: Option<SharedString>,
selection_id: impl Into<ElementId>,
selected: bool,
element: E,
) -> AnyElement
where
E: IntoElement + Styled + 'static,
{
if let Some(press_id) = press_id {
return element
.with_animation(
press_id,
Animation::new(Duration::from_millis(MOTION_PRESS_MS)).with_easing(ease_out_cubic),
|element, t| element.opacity(0.84 + 0.16 * t),
)
.into_any_element();
}
if selected {
return element
.with_animation(
selection_id,
Animation::new(Duration::from_millis(MOTION_SELECTION_MS))
.with_easing(ease_out_cubic),
|element, t| element.opacity(0.88 + 0.12 * t),
)
.into_any_element();
}
element.into_any_element()
}
#[cfg(test)]
mod tests {
use super::ease_out_cubic;
use super::{MOTION_FRAME_BUDGET_120HZ, MOTION_PRESS_MS, MOTION_SELECTION_MS, ease_out_cubic};
#[test]
fn ease_out_cubic_anchors_at_endpoints() {
@@ -64,4 +102,11 @@ mod tests {
assert!(first_half > 0.5, "ease-out spends more time near 1.0 ({first_half})");
assert!(second_half < 0.5);
}
#[test]
fn shell_motion_tokens_fit_120hz_frame_budget() {
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);
}
}
+72 -31
View File
@@ -9,6 +9,7 @@ use gpui::{
use gpui_component::{IconName, StyledExt, scroll::ScrollableElement};
use crate::shell::ElyShell;
use crate::shell::chrome::animations::chrome_motion_feedback;
use crate::shell::chrome::sidebar_chrome::{
ROW_CLOSE_SIZE, active_nav_bg, active_nav_bg_hover, close_hover_bg, highlight_border,
hover_nav_bg, panel_bg, panel_shadow, profile_initial, render_sidebar_resize_handle,
@@ -105,8 +106,10 @@ impl ElyShell {
// so it uses hover_nav_bg() directly. Keeping it lighter than the
// active nav card means the eye still finds the active selection
// first when both are visible.
div()
.id(SharedString::from("nav-settings"))
let target = "nav-settings";
let press_id = self.chrome_motion_animation_id(target);
let element = div()
.id(SharedString::from(target))
.rounded(px(spacing::RADIUS_NAV))
.px(px(10.0))
.py(px(7.0))
@@ -118,12 +121,14 @@ impl ElyShell {
.cursor_pointer()
.hover(|style| style.bg(rgba(hover_nav_bg())).text_color(rgb(colors::ink())))
.active(|style| style.opacity(0.82))
.on_click(cx.listener(|shell, _, window, cx| {
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(target);
shell.open_internal_tab("ely://settings", window, cx);
}))
.child(div().text_color(rgb(colors::ink_3())).child(IconName::Settings))
.child("Settings")
.into_any_element()
.child("Settings");
chrome_motion_feedback(press_id, "nav-settings-selection", false, element)
}
fn render_profile_row(
@@ -133,8 +138,10 @@ impl ElyShell {
) -> AnyElement {
let profile_name = snapshot.active_profile_name.clone();
div()
.id(SharedString::from("nav-profile"))
let target = "nav-profile";
let press_id = self.chrome_motion_animation_id(target);
let element = div()
.id(SharedString::from(target))
.rounded(px(spacing::RADIUS_NAV))
.px(px(10.0))
.py(px(6.0))
@@ -144,7 +151,8 @@ impl ElyShell {
.cursor_pointer()
.hover(|style| style.bg(rgba(hover_nav_bg())))
.active(|style| style.opacity(0.82))
.on_click(cx.listener(|shell, _, window, cx| {
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(target);
shell.open_internal_tab("ely://settings/profiles", window, cx);
}))
.child(
@@ -183,8 +191,9 @@ impl ElyShell {
// promises "this opens a page" instead of the down
// chevron that promises "this opens a menu inline".
div().text_color(rgb(colors::ink_4())).child(IconName::ChevronRight),
)
.into_any_element()
);
chrome_motion_feedback(press_id, "nav-profile-selection", false, element)
}
fn render_home_anchor_row(
@@ -195,9 +204,11 @@ impl ElyShell {
let active_tab = snapshot.tabs.iter().find(|tab| tab.id() == &snapshot.active_tab_id);
let active = active_tab.map(|tab| tab.url().as_str() == "ely://new-tab").unwrap_or(false);
let palette = nav_row_palette(active);
let target = "nav-home";
let press_id = self.chrome_motion_animation_id(target);
div()
.id(SharedString::from("nav-home"))
let element = div()
.id(SharedString::from(target))
.rounded(px(spacing::RADIUS_NAV))
.px(px(10.0))
.py(px(7.0))
@@ -209,7 +220,8 @@ impl ElyShell {
.active(|style| style.opacity(0.82))
.bg(rgba(palette.bg))
.when(active, |el| el.shadow(soft_shadow()))
.on_click(cx.listener(|shell, _, window, cx| {
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(target);
shell.open_internal_tab("ely://new-tab", window, cx);
}))
.child(div().text_color(rgb(colors::ink_3())).child(IconName::Frame))
@@ -220,8 +232,9 @@ impl ElyShell {
.font_weight(FontWeight(500.0))
.text_color(rgb(palette.text))
.child("Home"),
)
.into_any_element()
);
chrome_motion_feedback(press_id, "nav-home-selection", active, element)
}
fn render_launcher_row(
@@ -237,11 +250,17 @@ impl ElyShell {
let title = tab.title().to_string();
let initial = title.chars().next().unwrap_or('?').to_string();
let unread = tab.unread_count();
let row_id = format!("nav-{}", tab.id().as_str());
let row_motion_target = SharedString::from(row_id.clone());
let row_press_id = self.chrome_motion_animation_id(&row_id);
let row_selection_id = SharedString::from(format!("{row_id}-selection"));
let group_name = SharedString::from(format!("launcher-{}", tab.id().as_str()));
let close_id = SharedString::from(format!("launcher-close-{}", tab.id().as_str()));
let close_motion_target = close_id.clone();
let close_press_id = self.chrome_motion_animation_id(close_id.as_str());
div()
.id(SharedString::from(format!("nav-{}", tab.id().as_str())))
let element = div()
.id(SharedString::from(row_id))
.group(group_name.clone())
.rounded(px(spacing::RADIUS_NAV))
.px(px(10.0))
@@ -255,6 +274,7 @@ impl ElyShell {
.bg(rgba(palette.bg))
.when(active, |el| el.shadow(soft_shadow()))
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(row_motion_target.clone());
shell.select_tab(&tab_id, window, cx);
}))
.child(render_glyph_for(host.as_deref(), &initial, 18.0))
@@ -273,17 +293,22 @@ impl ElyShell {
.child(render_row_close_button(
close_id,
group_name,
close_press_id,
cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(close_motion_target.clone());
shell.close_tab_by_id(&close_tab_id, window, cx);
cx.stop_propagation();
}),
))
.into_any_element()
));
chrome_motion_feedback(row_press_id, row_selection_id, active, element)
}
fn render_new_tab_row(&mut self, cx: &mut Context<Self>) -> AnyElement {
div()
.id(SharedString::from("nav-new-tab"))
let target = "nav-new-tab";
let press_id = self.chrome_motion_animation_id(target);
let element = div()
.id(SharedString::from(target))
.rounded(px(spacing::RADIUS_NAV))
.px(px(10.0))
.py(px(7.0))
@@ -295,12 +320,14 @@ impl ElyShell {
.cursor_pointer()
.hover(|style| style.bg(rgba(hover_nav_bg())).text_color(rgb(colors::ink())))
.active(|style| style.opacity(0.82))
.on_click(cx.listener(|shell, _, window, cx| {
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(target);
shell.open_new_tab(window, cx);
}))
.child(div().text_color(rgb(colors::ink_4())).child(IconName::Plus))
.child("New Tab")
.into_any_element()
.child("New Tab");
chrome_motion_feedback(press_id, "nav-new-tab-selection", false, element)
}
pub(crate) fn render_tab_row(
@@ -316,9 +343,15 @@ impl ElyShell {
let close_id = SharedString::from(format!("tab-close-{}", tab.id().as_str()));
let title = tab.title().to_string();
let initial = title.chars().next().unwrap_or('?').to_string();
let row_id = tab.id().as_str().to_string();
let row_motion_target = SharedString::from(row_id.clone());
let row_press_id = self.chrome_motion_animation_id(&row_id);
let row_selection_id = SharedString::from(format!("tab-selection-{row_id}"));
let close_motion_target = close_id.clone();
let close_press_id = self.chrome_motion_animation_id(close_id.as_str());
div()
.id(SharedString::from(tab.id().as_str().to_string()))
let element = div()
.id(SharedString::from(row_id))
.group(group_name.clone())
.rounded(px(spacing::RADIUS_NAV))
.px(px(10.0))
@@ -332,6 +365,7 @@ impl ElyShell {
.bg(rgba(palette.bg))
.when(active, |el| el.shadow(soft_shadow()))
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(row_motion_target.clone());
shell.select_tab(&tab_id, window, cx);
}))
.child(render_tab_favicon(tab, &initial))
@@ -361,12 +395,15 @@ impl ElyShell {
.child(render_row_close_button(
close_id,
group_name,
close_press_id,
cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(close_motion_target.clone());
shell.close_tab_by_id(&close_tab_id, window, cx);
cx.stop_propagation();
}),
))
.into_any_element()
));
chrome_motion_feedback(row_press_id, row_selection_id, active, element)
}
}
@@ -401,12 +438,14 @@ struct NavRowPalette {
fn render_row_close_button<F>(
close_id: SharedString,
group_name: SharedString,
press_id: Option<SharedString>,
on_click: F,
) -> impl IntoElement
) -> AnyElement
where
F: Fn(&gpui::ClickEvent, &mut gpui::Window, &mut gpui::App) + 'static,
{
div()
let selection_id = SharedString::from(format!("{}-selection", close_id.as_str()));
let element = div()
.id(close_id)
.size(px(ROW_CLOSE_SIZE))
.rounded_full()
@@ -420,7 +459,9 @@ where
.hover(|style| style.bg(rgba(close_hover_bg())).text_color(rgb(colors::ink())))
.cursor_pointer()
.on_click(on_click)
.child(IconName::Close)
.child(IconName::Close);
chrome_motion_feedback(press_id, selection_id, false, element)
}
/// Resolve the favicon glyph for a tab row. Prefers the favicon URL
+54 -20
View File
@@ -9,6 +9,7 @@ use gpui::{
use gpui_component::{IconName, input::Input};
use crate::shell::ElyShell;
use crate::shell::chrome::animations::chrome_motion_feedback;
use crate::shell::sidebar::render_command_bar_identity;
pub(crate) fn render_topbar(
@@ -30,6 +31,7 @@ pub(crate) fn render_topbar(
.border_color(rgba(colors::divider()))
.when(sidebar_collapsed, |el| el.child(render_command_bar_identity(snapshot, 56.0, true)))
.child(render_nav_arrow(
shell,
"nav-back",
IconName::ArrowLeft,
active_tab.can_navigate_back(),
@@ -37,6 +39,7 @@ pub(crate) fn render_topbar(
|shell, window, cx| shell.navigate_active_tab_back(window, cx),
))
.child(render_nav_arrow(
shell,
"nav-forward",
IconName::ArrowRight,
active_tab.can_navigate_forward(),
@@ -44,19 +47,24 @@ pub(crate) fn render_topbar(
|shell, window, cx| shell.navigate_active_tab_forward(window, cx),
))
.child(render_omnibar(shell, active_tab, window, cx))
.child(render_topbar_action("share-url", IconName::Copy, cx, |shell, window, cx| {
.child(render_topbar_action(shell, "share-url", IconName::Copy, cx, |shell, window, cx| {
shell.copy_active_tab_url(window, cx)
}))
.child(render_topbar_action("open-downloads", IconName::Folder, cx, |shell, window, cx| {
shell.open_downloads(window, cx)
}))
.child(render_topbar_action(
shell,
"open-downloads",
IconName::Folder,
cx,
|shell, window, cx| shell.open_downloads(window, cx),
))
.child(render_topbar_action(
shell,
"toggle-theme",
theme_mode_icon(snapshot.appearance.theme_mode()),
cx,
|shell, _window, cx| shell.cycle_theme_mode(cx),
))
.child(render_topbar_action("open-menu", IconName::Menu, cx, |shell, window, cx| {
.child(render_topbar_action(shell, "open-menu", IconName::Menu, cx, |shell, window, cx| {
shell.open_internal_tab("ely://settings", window, cx)
}))
.into_any_element()
@@ -74,6 +82,8 @@ fn render_omnibar(
let command_focused = shell.command_input.read(cx).focus_handle(cx).is_focused(window);
let show_styled = !command_focused && active_url != "ely://new-tab";
let secure = active_url.starts_with("https://") || active_url.starts_with("ely://");
let omnibar_motion_target = "omnibar-content";
let omnibar_press_id = shell.chrome_motion_animation_id(omnibar_motion_target);
div()
.flex_1()
@@ -86,14 +96,18 @@ fn render_omnibar(
.items_center()
.gap(px(10.0))
.child(render_lock_or_search(secure, show_styled))
.child(
.child(chrome_motion_feedback(
omnibar_press_id,
"omnibar-content-selection",
false,
div()
.id(SharedString::from("omnibar-content"))
.flex_1()
.min_w_0()
.cursor_pointer()
.hover(|style| style.opacity(0.92))
.on_click(cx.listener(|shell, _, window, cx| {
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(omnibar_motion_target);
shell.focus_address_bar(window, cx);
}))
.child(if show_styled {
@@ -101,8 +115,9 @@ fn render_omnibar(
} else {
render_omnibar_input(shell)
}),
)
))
.child(render_omnibar_chip(
shell,
"omnibar-filters",
IconName::Settings2,
false,
@@ -110,6 +125,7 @@ fn render_omnibar(
|shell, window, cx| shell.focus_address_bar(window, cx),
))
.child(render_omnibar_chip(
shell,
"omnibar-favorite",
favorite_icon,
favorite_active,
@@ -149,6 +165,7 @@ fn render_lock_or_search(secure: bool, show_styled: bool) -> AnyElement {
}
fn render_omnibar_chip<F>(
shell: &ElyShell,
id: &'static str,
icon: IconName,
active: bool,
@@ -159,7 +176,8 @@ where
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
{
let color = if active { colors::accent() } else { colors::ink_4() };
div()
let press_id = shell.chrome_motion_animation_id(id);
let element = div()
.id(SharedString::from(id))
.size(px(22.0))
.rounded(px(6.0))
@@ -170,12 +188,17 @@ where
.cursor_pointer()
.hover(|style| style.bg(rgba(chip_hover_bg())).text_color(rgb(colors::ink())))
.active(|style| style.opacity(0.7))
.on_click(cx.listener(move |shell, _, window, cx| handler(shell, window, cx)))
.child(icon)
.into_any_element()
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(id);
handler(shell, window, cx);
}))
.child(icon);
chrome_motion_feedback(press_id, SharedString::from(format!("{id}-selection")), active, element)
}
fn render_nav_arrow<F>(
shell: &ElyShell,
id: &'static str,
icon: IconName,
enabled: bool,
@@ -186,7 +209,8 @@ where
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
{
let color = if enabled { colors::ink_3() } else { colors::ink_5() };
div()
let press_id = if enabled { shell.chrome_motion_animation_id(id) } else { None };
let element = div()
.id(SharedString::from(id))
.size(px(30.0))
.rounded(px(8.0))
@@ -198,13 +222,18 @@ where
el.cursor_pointer()
.hover(|style| style.bg(rgba(omnibar_bg())).text_color(rgb(colors::ink())))
.active(|style| style.opacity(0.82))
.on_click(cx.listener(move |shell, _, window, cx| handler(shell, window, cx)))
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(id);
handler(shell, window, cx);
}))
})
.child(icon)
.into_any_element()
.child(icon);
chrome_motion_feedback(press_id, SharedString::from(format!("{id}-selection")), false, element)
}
fn render_topbar_action<F>(
shell: &ElyShell,
id: &'static str,
icon: IconName,
cx: &mut Context<ElyShell>,
@@ -213,7 +242,8 @@ fn render_topbar_action<F>(
where
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
{
div()
let press_id = shell.chrome_motion_animation_id(id);
let element = div()
.id(SharedString::from(id))
.size(px(30.0))
.rounded(px(8.0))
@@ -224,9 +254,13 @@ where
.text_color(rgb(colors::ink_3()))
.hover(|style| style.bg(rgba(omnibar_bg())).text_color(rgb(colors::ink())))
.active(|style| style.opacity(0.82))
.on_click(cx.listener(move |shell, _, window, cx| handler(shell, window, cx)))
.child(icon)
.into_any_element()
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(id);
handler(shell, window, cx);
}))
.child(icon);
chrome_motion_feedback(press_id, SharedString::from(format!("{id}-selection")), false, element)
}
fn omnibar_bg() -> u32 {
+24
View File
@@ -0,0 +1,24 @@
use gpui::SharedString;
use super::ElyShell;
#[derive(Default)]
pub(crate) struct ChromeMotionState {
target: Option<SharedString>,
epoch: u64,
}
impl ElyShell {
pub(crate) fn trigger_chrome_motion(&mut self, target: impl Into<SharedString>) {
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<SharedString> {
self.chrome_motion
.target
.as_ref()
.filter(|current| current.as_str() == target)
.map(|_| SharedString::from(format!("{target}-motion-{}", self.chrome_motion.epoch)))
}
}
+3 -30
View File
@@ -3,6 +3,7 @@ mod auth;
mod bookmark_files;
mod bookmarks;
pub(crate) mod chrome;
mod chrome_motion;
mod command_actions;
mod download_targets;
mod downloads;
@@ -70,6 +71,7 @@ pub struct ElyShell {
pub(crate) workspace_picker_open: bool,
pub(crate) sidebar_hover_expanded: bool,
pub(crate) command_selected_index: usize,
chrome_motion: chrome_motion::ChromeMotionState,
/// Live state for sidebar-resize drag. While the user holds the
/// resize handle: `(mouse_x_at_drag_start, sidebar_width_px_at_drag_start)`.
/// `None` whenever no drag is in flight.
@@ -202,6 +204,7 @@ impl ElyShell {
workspace_picker_open: false,
sidebar_hover_expanded: false,
command_selected_index: 0,
chrome_motion: chrome_motion::ChromeMotionState::default(),
sidebar_resize_origin: None,
download_action_error: None,
download_clear_confirmation: false,
@@ -239,36 +242,6 @@ impl ElyShell {
shell
}
/// Inspect the on-disk bearer token (if any) and seed the
/// `SyncConnectionState` so the Sync settings page reads the right
/// label on first render — without any sync setting page open it
/// would otherwise stay `SignedOut` until the user clicks Sync now.
fn probe_initial_sync_state(&mut self) {
let ShellState::Ready(core) = &mut self.state else {
return;
};
let Some(snapshot) = core.snapshot().ok() else {
return;
};
let active_profile_id = snapshot.active_profile_id.clone();
let Some(profile_root) = crate::services::servo_profile_data::default_profile_data_root()
else {
return;
};
let profile_dir = crate::services::servo_profile_data::profile_data_dir(
&profile_root,
&active_profile_id,
);
let bearer_path = profile_dir.join("sync").join("bearer.token");
let bearer_present = std::fs::metadata(&bearer_path).map(|m| m.len() > 0).unwrap_or(false);
let state = if bearer_present {
ely_domain::SyncConnectionState::SignedIn
} else {
ely_domain::SyncConnectionState::SignedOut
};
core.set_sync_connection_state(state);
}
/// Drain any sync upload outcomes the off-thread worker pushed
/// since the previous tick and stamp the resulting connection
/// state on `BrowserCore`. Returns `true` when at least one
+31 -19
View File
@@ -5,15 +5,15 @@ use ely_domain::{
HIDDEN_SIDEBAR_WIDTH_PX, Space,
};
use gpui::{
AnyElement, BoxShadow, Context, IntoElement, ParentElement, Styled, Window, div, hsla, point,
px, rgb, rgba,
AnyElement, BoxShadow, Context, IntoElement, ParentElement, SharedString, Styled, Window, div,
hsla, point, px, rgb, rgba,
};
use gpui_component::{
IconName, Selectable, Sizable, StyledExt,
button::{Button, ButtonVariants},
};
use super::chrome::panel_bg;
use super::chrome::{animations::chrome_motion_feedback, panel_bg};
use super::{ElyShell, ShellState, render::tab_profile_label};
use crate::ToggleSidebar;
@@ -167,16 +167,21 @@ impl ElyShell {
cx: &mut Context<Self>,
) -> AnyElement {
let space_id = space.id().clone();
Button::new(("compact-space", index))
let target = SharedString::from(format!("compact-space-{}", space.id().as_str()));
let press_id = self.chrome_motion_animation_id(target.as_str());
let selection_id = SharedString::from(format!("{}-selection", target.as_str()));
let button = Button::new(("compact-space", index))
.ghost()
.small()
.selected(active)
.label(space.icon().to_string())
.tooltip(space.name().to_string())
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(target.clone());
shell.select_space(&space_id, window, cx);
}))
.into_any_element()
}));
chrome_motion_feedback(press_id, selection_id, active, div().child(button))
}
fn render_compact_tab_button(
@@ -190,16 +195,18 @@ impl ElyShell {
) -> AnyElement {
let tab_id = tab.id().clone();
let tooltip = format!("{} - {}", tab.title(), tab_profile_label(tab, &snapshot.profiles));
Button::new(id)
.ghost()
.small()
.selected(active)
.icon(icon)
.tooltip(tooltip)
.on_click(cx.listener(move |shell, _, window, cx| {
shell.select_tab(&tab_id, window, cx);
}))
.into_any_element()
let target = SharedString::from(format!("compact-tab-{}", tab.id().as_str()));
let press_id = self.chrome_motion_animation_id(target.as_str());
let selection_id = SharedString::from(format!("{}-selection", target.as_str()));
let button =
Button::new(id).ghost().small().selected(active).icon(icon).tooltip(tooltip).on_click(
cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(target.clone());
shell.select_tab(&tab_id, window, cx);
}),
);
chrome_motion_feedback(press_id, selection_id, active, div().child(button))
}
fn render_compact_archived_button(
@@ -210,15 +217,20 @@ impl ElyShell {
) -> AnyElement {
let tab = archived_tab.tab();
let tab_id = tab.id().clone();
Button::new(("compact-archive", index))
let target = SharedString::from(format!("compact-archive-{}", tab.id().as_str()));
let press_id = self.chrome_motion_animation_id(target.as_str());
let selection_id = SharedString::from(format!("{}-selection", target.as_str()));
let button = Button::new(("compact-archive", index))
.ghost()
.small()
.icon(IconName::Undo2)
.tooltip(tab.title().to_string())
.on_click(cx.listener(move |shell, _, window, cx| {
shell.trigger_chrome_motion(target.clone());
shell.restore_archived_tab(&tab_id, window, cx);
}))
.into_any_element()
}));
chrome_motion_feedback(press_id, selection_id, false, div().child(button))
}
}
+32
View File
@@ -1,3 +1,5 @@
use super::{ElyShell, ShellState};
/// Messages the off-thread sync workers push back to the shell so
/// `SyncConnectionState` on `BrowserCore` and the in-flight auth
/// form reflect live state without the UI thread ever touching the
@@ -28,3 +30,33 @@ pub(crate) const fn sync_platform_label() -> &'static str {
"other"
}
}
impl ElyShell {
/// Inspect the on-disk bearer token and seed `SyncConnectionState`
/// so the Sync settings page reads the startup state on first render.
pub(super) fn probe_initial_sync_state(&mut self) {
let ShellState::Ready(core) = &mut self.state else {
return;
};
let Some(snapshot) = core.snapshot().ok() else {
return;
};
let active_profile_id = snapshot.active_profile_id.clone();
let Some(profile_root) = crate::services::servo_profile_data::default_profile_data_root()
else {
return;
};
let profile_dir = crate::services::servo_profile_data::profile_data_dir(
&profile_root,
&active_profile_id,
);
let bearer_path = profile_dir.join("sync").join("bearer.token");
let bearer_present = std::fs::metadata(&bearer_path).map(|m| m.len() > 0).unwrap_or(false);
let state = if bearer_present {
ely_domain::SyncConnectionState::SignedIn
} else {
ely_domain::SyncConnectionState::SignedOut
};
core.set_sync_connection_state(state);
}
}