feat(chrome): animate high-frequency actions
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
use std::time::Duration;
|
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
|
/// One-second blink that toggles between fully visible and invisible at the
|
||||||
/// half-period mark, matching the design's
|
/// 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)))
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::ease_out_cubic;
|
use super::{MOTION_FRAME_BUDGET_120HZ, MOTION_PRESS_MS, MOTION_SELECTION_MS, ease_out_cubic};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ease_out_cubic_anchors_at_endpoints() {
|
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!(first_half > 0.5, "ease-out spends more time near 1.0 ({first_half})");
|
||||||
assert!(second_half < 0.5);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use gpui::{
|
|||||||
use gpui_component::{IconName, StyledExt, scroll::ScrollableElement};
|
use gpui_component::{IconName, StyledExt, scroll::ScrollableElement};
|
||||||
|
|
||||||
use crate::shell::ElyShell;
|
use crate::shell::ElyShell;
|
||||||
|
use crate::shell::chrome::animations::chrome_motion_feedback;
|
||||||
use crate::shell::chrome::sidebar_chrome::{
|
use crate::shell::chrome::sidebar_chrome::{
|
||||||
ROW_CLOSE_SIZE, active_nav_bg, active_nav_bg_hover, close_hover_bg, highlight_border,
|
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,
|
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
|
// so it uses hover_nav_bg() directly. Keeping it lighter than the
|
||||||
// active nav card means the eye still finds the active selection
|
// active nav card means the eye still finds the active selection
|
||||||
// first when both are visible.
|
// first when both are visible.
|
||||||
div()
|
let target = "nav-settings";
|
||||||
.id(SharedString::from("nav-settings"))
|
let press_id = self.chrome_motion_animation_id(target);
|
||||||
|
let element = div()
|
||||||
|
.id(SharedString::from(target))
|
||||||
.rounded(px(spacing::RADIUS_NAV))
|
.rounded(px(spacing::RADIUS_NAV))
|
||||||
.px(px(10.0))
|
.px(px(10.0))
|
||||||
.py(px(7.0))
|
.py(px(7.0))
|
||||||
@@ -118,12 +121,14 @@ impl ElyShell {
|
|||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.hover(|style| style.bg(rgba(hover_nav_bg())).text_color(rgb(colors::ink())))
|
.hover(|style| style.bg(rgba(hover_nav_bg())).text_color(rgb(colors::ink())))
|
||||||
.active(|style| style.opacity(0.82))
|
.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);
|
shell.open_internal_tab("ely://settings", window, cx);
|
||||||
}))
|
}))
|
||||||
.child(div().text_color(rgb(colors::ink_3())).child(IconName::Settings))
|
.child(div().text_color(rgb(colors::ink_3())).child(IconName::Settings))
|
||||||
.child("Settings")
|
.child("Settings");
|
||||||
.into_any_element()
|
|
||||||
|
chrome_motion_feedback(press_id, "nav-settings-selection", false, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_profile_row(
|
fn render_profile_row(
|
||||||
@@ -133,8 +138,10 @@ impl ElyShell {
|
|||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let profile_name = snapshot.active_profile_name.clone();
|
let profile_name = snapshot.active_profile_name.clone();
|
||||||
|
|
||||||
div()
|
let target = "nav-profile";
|
||||||
.id(SharedString::from("nav-profile"))
|
let press_id = self.chrome_motion_animation_id(target);
|
||||||
|
let element = div()
|
||||||
|
.id(SharedString::from(target))
|
||||||
.rounded(px(spacing::RADIUS_NAV))
|
.rounded(px(spacing::RADIUS_NAV))
|
||||||
.px(px(10.0))
|
.px(px(10.0))
|
||||||
.py(px(6.0))
|
.py(px(6.0))
|
||||||
@@ -144,7 +151,8 @@ impl ElyShell {
|
|||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.hover(|style| style.bg(rgba(hover_nav_bg())))
|
.hover(|style| style.bg(rgba(hover_nav_bg())))
|
||||||
.active(|style| style.opacity(0.82))
|
.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);
|
shell.open_internal_tab("ely://settings/profiles", window, cx);
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
@@ -183,8 +191,9 @@ impl ElyShell {
|
|||||||
// promises "this opens a page" instead of the down
|
// promises "this opens a page" instead of the down
|
||||||
// chevron that promises "this opens a menu inline".
|
// chevron that promises "this opens a menu inline".
|
||||||
div().text_color(rgb(colors::ink_4())).child(IconName::ChevronRight),
|
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(
|
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_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 active = active_tab.map(|tab| tab.url().as_str() == "ely://new-tab").unwrap_or(false);
|
||||||
let palette = nav_row_palette(active);
|
let palette = nav_row_palette(active);
|
||||||
|
let target = "nav-home";
|
||||||
|
let press_id = self.chrome_motion_animation_id(target);
|
||||||
|
|
||||||
div()
|
let element = div()
|
||||||
.id(SharedString::from("nav-home"))
|
.id(SharedString::from(target))
|
||||||
.rounded(px(spacing::RADIUS_NAV))
|
.rounded(px(spacing::RADIUS_NAV))
|
||||||
.px(px(10.0))
|
.px(px(10.0))
|
||||||
.py(px(7.0))
|
.py(px(7.0))
|
||||||
@@ -209,7 +220,8 @@ impl ElyShell {
|
|||||||
.active(|style| style.opacity(0.82))
|
.active(|style| style.opacity(0.82))
|
||||||
.bg(rgba(palette.bg))
|
.bg(rgba(palette.bg))
|
||||||
.when(active, |el| el.shadow(soft_shadow()))
|
.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);
|
shell.open_internal_tab("ely://new-tab", window, cx);
|
||||||
}))
|
}))
|
||||||
.child(div().text_color(rgb(colors::ink_3())).child(IconName::Frame))
|
.child(div().text_color(rgb(colors::ink_3())).child(IconName::Frame))
|
||||||
@@ -220,8 +232,9 @@ impl ElyShell {
|
|||||||
.font_weight(FontWeight(500.0))
|
.font_weight(FontWeight(500.0))
|
||||||
.text_color(rgb(palette.text))
|
.text_color(rgb(palette.text))
|
||||||
.child("Home"),
|
.child("Home"),
|
||||||
)
|
);
|
||||||
.into_any_element()
|
|
||||||
|
chrome_motion_feedback(press_id, "nav-home-selection", active, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_launcher_row(
|
fn render_launcher_row(
|
||||||
@@ -237,11 +250,17 @@ impl ElyShell {
|
|||||||
let title = tab.title().to_string();
|
let title = tab.title().to_string();
|
||||||
let initial = title.chars().next().unwrap_or('?').to_string();
|
let initial = title.chars().next().unwrap_or('?').to_string();
|
||||||
let unread = tab.unread_count();
|
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 group_name = SharedString::from(format!("launcher-{}", tab.id().as_str()));
|
||||||
let close_id = SharedString::from(format!("launcher-close-{}", 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()
|
let element = div()
|
||||||
.id(SharedString::from(format!("nav-{}", tab.id().as_str())))
|
.id(SharedString::from(row_id))
|
||||||
.group(group_name.clone())
|
.group(group_name.clone())
|
||||||
.rounded(px(spacing::RADIUS_NAV))
|
.rounded(px(spacing::RADIUS_NAV))
|
||||||
.px(px(10.0))
|
.px(px(10.0))
|
||||||
@@ -255,6 +274,7 @@ impl ElyShell {
|
|||||||
.bg(rgba(palette.bg))
|
.bg(rgba(palette.bg))
|
||||||
.when(active, |el| el.shadow(soft_shadow()))
|
.when(active, |el| el.shadow(soft_shadow()))
|
||||||
.on_click(cx.listener(move |shell, _, window, cx| {
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.trigger_chrome_motion(row_motion_target.clone());
|
||||||
shell.select_tab(&tab_id, window, cx);
|
shell.select_tab(&tab_id, window, cx);
|
||||||
}))
|
}))
|
||||||
.child(render_glyph_for(host.as_deref(), &initial, 18.0))
|
.child(render_glyph_for(host.as_deref(), &initial, 18.0))
|
||||||
@@ -273,17 +293,22 @@ impl ElyShell {
|
|||||||
.child(render_row_close_button(
|
.child(render_row_close_button(
|
||||||
close_id,
|
close_id,
|
||||||
group_name,
|
group_name,
|
||||||
|
close_press_id,
|
||||||
cx.listener(move |shell, _, window, cx| {
|
cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.trigger_chrome_motion(close_motion_target.clone());
|
||||||
shell.close_tab_by_id(&close_tab_id, window, cx);
|
shell.close_tab_by_id(&close_tab_id, window, cx);
|
||||||
cx.stop_propagation();
|
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 {
|
fn render_new_tab_row(&mut self, cx: &mut Context<Self>) -> AnyElement {
|
||||||
div()
|
let target = "nav-new-tab";
|
||||||
.id(SharedString::from("nav-new-tab"))
|
let press_id = self.chrome_motion_animation_id(target);
|
||||||
|
let element = div()
|
||||||
|
.id(SharedString::from(target))
|
||||||
.rounded(px(spacing::RADIUS_NAV))
|
.rounded(px(spacing::RADIUS_NAV))
|
||||||
.px(px(10.0))
|
.px(px(10.0))
|
||||||
.py(px(7.0))
|
.py(px(7.0))
|
||||||
@@ -295,12 +320,14 @@ impl ElyShell {
|
|||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.hover(|style| style.bg(rgba(hover_nav_bg())).text_color(rgb(colors::ink())))
|
.hover(|style| style.bg(rgba(hover_nav_bg())).text_color(rgb(colors::ink())))
|
||||||
.active(|style| style.opacity(0.82))
|
.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);
|
shell.open_new_tab(window, cx);
|
||||||
}))
|
}))
|
||||||
.child(div().text_color(rgb(colors::ink_4())).child(IconName::Plus))
|
.child(div().text_color(rgb(colors::ink_4())).child(IconName::Plus))
|
||||||
.child("New Tab")
|
.child("New Tab");
|
||||||
.into_any_element()
|
|
||||||
|
chrome_motion_feedback(press_id, "nav-new-tab-selection", false, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render_tab_row(
|
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 close_id = SharedString::from(format!("tab-close-{}", tab.id().as_str()));
|
||||||
let title = tab.title().to_string();
|
let title = tab.title().to_string();
|
||||||
let initial = title.chars().next().unwrap_or('?').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()
|
let element = div()
|
||||||
.id(SharedString::from(tab.id().as_str().to_string()))
|
.id(SharedString::from(row_id))
|
||||||
.group(group_name.clone())
|
.group(group_name.clone())
|
||||||
.rounded(px(spacing::RADIUS_NAV))
|
.rounded(px(spacing::RADIUS_NAV))
|
||||||
.px(px(10.0))
|
.px(px(10.0))
|
||||||
@@ -332,6 +365,7 @@ impl ElyShell {
|
|||||||
.bg(rgba(palette.bg))
|
.bg(rgba(palette.bg))
|
||||||
.when(active, |el| el.shadow(soft_shadow()))
|
.when(active, |el| el.shadow(soft_shadow()))
|
||||||
.on_click(cx.listener(move |shell, _, window, cx| {
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.trigger_chrome_motion(row_motion_target.clone());
|
||||||
shell.select_tab(&tab_id, window, cx);
|
shell.select_tab(&tab_id, window, cx);
|
||||||
}))
|
}))
|
||||||
.child(render_tab_favicon(tab, &initial))
|
.child(render_tab_favicon(tab, &initial))
|
||||||
@@ -361,12 +395,15 @@ impl ElyShell {
|
|||||||
.child(render_row_close_button(
|
.child(render_row_close_button(
|
||||||
close_id,
|
close_id,
|
||||||
group_name,
|
group_name,
|
||||||
|
close_press_id,
|
||||||
cx.listener(move |shell, _, window, cx| {
|
cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.trigger_chrome_motion(close_motion_target.clone());
|
||||||
shell.close_tab_by_id(&close_tab_id, window, cx);
|
shell.close_tab_by_id(&close_tab_id, window, cx);
|
||||||
cx.stop_propagation();
|
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>(
|
fn render_row_close_button<F>(
|
||||||
close_id: SharedString,
|
close_id: SharedString,
|
||||||
group_name: SharedString,
|
group_name: SharedString,
|
||||||
|
press_id: Option<SharedString>,
|
||||||
on_click: F,
|
on_click: F,
|
||||||
) -> impl IntoElement
|
) -> AnyElement
|
||||||
where
|
where
|
||||||
F: Fn(&gpui::ClickEvent, &mut gpui::Window, &mut gpui::App) + 'static,
|
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)
|
.id(close_id)
|
||||||
.size(px(ROW_CLOSE_SIZE))
|
.size(px(ROW_CLOSE_SIZE))
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
@@ -420,7 +459,9 @@ where
|
|||||||
.hover(|style| style.bg(rgba(close_hover_bg())).text_color(rgb(colors::ink())))
|
.hover(|style| style.bg(rgba(close_hover_bg())).text_color(rgb(colors::ink())))
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.on_click(on_click)
|
.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
|
/// Resolve the favicon glyph for a tab row. Prefers the favicon URL
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use gpui::{
|
|||||||
use gpui_component::{IconName, input::Input};
|
use gpui_component::{IconName, input::Input};
|
||||||
|
|
||||||
use crate::shell::ElyShell;
|
use crate::shell::ElyShell;
|
||||||
|
use crate::shell::chrome::animations::chrome_motion_feedback;
|
||||||
use crate::shell::sidebar::render_command_bar_identity;
|
use crate::shell::sidebar::render_command_bar_identity;
|
||||||
|
|
||||||
pub(crate) fn render_topbar(
|
pub(crate) fn render_topbar(
|
||||||
@@ -30,6 +31,7 @@ pub(crate) fn render_topbar(
|
|||||||
.border_color(rgba(colors::divider()))
|
.border_color(rgba(colors::divider()))
|
||||||
.when(sidebar_collapsed, |el| el.child(render_command_bar_identity(snapshot, 56.0, true)))
|
.when(sidebar_collapsed, |el| el.child(render_command_bar_identity(snapshot, 56.0, true)))
|
||||||
.child(render_nav_arrow(
|
.child(render_nav_arrow(
|
||||||
|
shell,
|
||||||
"nav-back",
|
"nav-back",
|
||||||
IconName::ArrowLeft,
|
IconName::ArrowLeft,
|
||||||
active_tab.can_navigate_back(),
|
active_tab.can_navigate_back(),
|
||||||
@@ -37,6 +39,7 @@ pub(crate) fn render_topbar(
|
|||||||
|shell, window, cx| shell.navigate_active_tab_back(window, cx),
|
|shell, window, cx| shell.navigate_active_tab_back(window, cx),
|
||||||
))
|
))
|
||||||
.child(render_nav_arrow(
|
.child(render_nav_arrow(
|
||||||
|
shell,
|
||||||
"nav-forward",
|
"nav-forward",
|
||||||
IconName::ArrowRight,
|
IconName::ArrowRight,
|
||||||
active_tab.can_navigate_forward(),
|
active_tab.can_navigate_forward(),
|
||||||
@@ -44,19 +47,24 @@ pub(crate) fn render_topbar(
|
|||||||
|shell, window, cx| shell.navigate_active_tab_forward(window, cx),
|
|shell, window, cx| shell.navigate_active_tab_forward(window, cx),
|
||||||
))
|
))
|
||||||
.child(render_omnibar(shell, active_tab, 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)
|
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(
|
.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",
|
"toggle-theme",
|
||||||
theme_mode_icon(snapshot.appearance.theme_mode()),
|
theme_mode_icon(snapshot.appearance.theme_mode()),
|
||||||
cx,
|
cx,
|
||||||
|shell, _window, cx| shell.cycle_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)
|
shell.open_internal_tab("ely://settings", window, cx)
|
||||||
}))
|
}))
|
||||||
.into_any_element()
|
.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 command_focused = shell.command_input.read(cx).focus_handle(cx).is_focused(window);
|
||||||
let show_styled = !command_focused && active_url != "ely://new-tab";
|
let show_styled = !command_focused && active_url != "ely://new-tab";
|
||||||
let secure = active_url.starts_with("https://") || active_url.starts_with("ely://");
|
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()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
@@ -86,14 +96,18 @@ fn render_omnibar(
|
|||||||
.items_center()
|
.items_center()
|
||||||
.gap(px(10.0))
|
.gap(px(10.0))
|
||||||
.child(render_lock_or_search(secure, show_styled))
|
.child(render_lock_or_search(secure, show_styled))
|
||||||
.child(
|
.child(chrome_motion_feedback(
|
||||||
|
omnibar_press_id,
|
||||||
|
"omnibar-content-selection",
|
||||||
|
false,
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from("omnibar-content"))
|
.id(SharedString::from("omnibar-content"))
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.min_w_0()
|
.min_w_0()
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.hover(|style| style.opacity(0.92))
|
.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);
|
shell.focus_address_bar(window, cx);
|
||||||
}))
|
}))
|
||||||
.child(if show_styled {
|
.child(if show_styled {
|
||||||
@@ -101,8 +115,9 @@ fn render_omnibar(
|
|||||||
} else {
|
} else {
|
||||||
render_omnibar_input(shell)
|
render_omnibar_input(shell)
|
||||||
}),
|
}),
|
||||||
)
|
))
|
||||||
.child(render_omnibar_chip(
|
.child(render_omnibar_chip(
|
||||||
|
shell,
|
||||||
"omnibar-filters",
|
"omnibar-filters",
|
||||||
IconName::Settings2,
|
IconName::Settings2,
|
||||||
false,
|
false,
|
||||||
@@ -110,6 +125,7 @@ fn render_omnibar(
|
|||||||
|shell, window, cx| shell.focus_address_bar(window, cx),
|
|shell, window, cx| shell.focus_address_bar(window, cx),
|
||||||
))
|
))
|
||||||
.child(render_omnibar_chip(
|
.child(render_omnibar_chip(
|
||||||
|
shell,
|
||||||
"omnibar-favorite",
|
"omnibar-favorite",
|
||||||
favorite_icon,
|
favorite_icon,
|
||||||
favorite_active,
|
favorite_active,
|
||||||
@@ -149,6 +165,7 @@ fn render_lock_or_search(secure: bool, show_styled: bool) -> AnyElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_omnibar_chip<F>(
|
fn render_omnibar_chip<F>(
|
||||||
|
shell: &ElyShell,
|
||||||
id: &'static str,
|
id: &'static str,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
active: bool,
|
active: bool,
|
||||||
@@ -159,7 +176,8 @@ where
|
|||||||
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
||||||
{
|
{
|
||||||
let color = if active { colors::accent() } else { colors::ink_4() };
|
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))
|
.id(SharedString::from(id))
|
||||||
.size(px(22.0))
|
.size(px(22.0))
|
||||||
.rounded(px(6.0))
|
.rounded(px(6.0))
|
||||||
@@ -170,12 +188,17 @@ where
|
|||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.hover(|style| style.bg(rgba(chip_hover_bg())).text_color(rgb(colors::ink())))
|
.hover(|style| style.bg(rgba(chip_hover_bg())).text_color(rgb(colors::ink())))
|
||||||
.active(|style| style.opacity(0.7))
|
.active(|style| style.opacity(0.7))
|
||||||
.on_click(cx.listener(move |shell, _, window, cx| handler(shell, window, cx)))
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
.child(icon)
|
shell.trigger_chrome_motion(id);
|
||||||
.into_any_element()
|
handler(shell, window, cx);
|
||||||
|
}))
|
||||||
|
.child(icon);
|
||||||
|
|
||||||
|
chrome_motion_feedback(press_id, SharedString::from(format!("{id}-selection")), active, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_nav_arrow<F>(
|
fn render_nav_arrow<F>(
|
||||||
|
shell: &ElyShell,
|
||||||
id: &'static str,
|
id: &'static str,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
@@ -186,7 +209,8 @@ where
|
|||||||
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
||||||
{
|
{
|
||||||
let color = if enabled { colors::ink_3() } else { colors::ink_5() };
|
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))
|
.id(SharedString::from(id))
|
||||||
.size(px(30.0))
|
.size(px(30.0))
|
||||||
.rounded(px(8.0))
|
.rounded(px(8.0))
|
||||||
@@ -198,13 +222,18 @@ where
|
|||||||
el.cursor_pointer()
|
el.cursor_pointer()
|
||||||
.hover(|style| style.bg(rgba(omnibar_bg())).text_color(rgb(colors::ink())))
|
.hover(|style| style.bg(rgba(omnibar_bg())).text_color(rgb(colors::ink())))
|
||||||
.active(|style| style.opacity(0.82))
|
.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)
|
.child(icon);
|
||||||
.into_any_element()
|
|
||||||
|
chrome_motion_feedback(press_id, SharedString::from(format!("{id}-selection")), false, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_topbar_action<F>(
|
fn render_topbar_action<F>(
|
||||||
|
shell: &ElyShell,
|
||||||
id: &'static str,
|
id: &'static str,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
cx: &mut Context<ElyShell>,
|
cx: &mut Context<ElyShell>,
|
||||||
@@ -213,7 +242,8 @@ fn render_topbar_action<F>(
|
|||||||
where
|
where
|
||||||
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
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))
|
.id(SharedString::from(id))
|
||||||
.size(px(30.0))
|
.size(px(30.0))
|
||||||
.rounded(px(8.0))
|
.rounded(px(8.0))
|
||||||
@@ -224,9 +254,13 @@ where
|
|||||||
.text_color(rgb(colors::ink_3()))
|
.text_color(rgb(colors::ink_3()))
|
||||||
.hover(|style| style.bg(rgba(omnibar_bg())).text_color(rgb(colors::ink())))
|
.hover(|style| style.bg(rgba(omnibar_bg())).text_color(rgb(colors::ink())))
|
||||||
.active(|style| style.opacity(0.82))
|
.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| {
|
||||||
.child(icon)
|
shell.trigger_chrome_motion(id);
|
||||||
.into_any_element()
|
handler(shell, window, cx);
|
||||||
|
}))
|
||||||
|
.child(icon);
|
||||||
|
|
||||||
|
chrome_motion_feedback(press_id, SharedString::from(format!("{id}-selection")), false, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn omnibar_bg() -> u32 {
|
fn omnibar_bg() -> u32 {
|
||||||
|
|||||||
@@ -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,6 +3,7 @@ mod auth;
|
|||||||
mod bookmark_files;
|
mod bookmark_files;
|
||||||
mod bookmarks;
|
mod bookmarks;
|
||||||
pub(crate) mod chrome;
|
pub(crate) mod chrome;
|
||||||
|
mod chrome_motion;
|
||||||
mod command_actions;
|
mod command_actions;
|
||||||
mod download_targets;
|
mod download_targets;
|
||||||
mod downloads;
|
mod downloads;
|
||||||
@@ -70,6 +71,7 @@ pub struct ElyShell {
|
|||||||
pub(crate) workspace_picker_open: bool,
|
pub(crate) workspace_picker_open: bool,
|
||||||
pub(crate) sidebar_hover_expanded: bool,
|
pub(crate) sidebar_hover_expanded: bool,
|
||||||
pub(crate) command_selected_index: usize,
|
pub(crate) command_selected_index: usize,
|
||||||
|
chrome_motion: chrome_motion::ChromeMotionState,
|
||||||
/// Live state for sidebar-resize drag. While the user holds the
|
/// Live state for sidebar-resize drag. While the user holds the
|
||||||
/// resize handle: `(mouse_x_at_drag_start, sidebar_width_px_at_drag_start)`.
|
/// resize handle: `(mouse_x_at_drag_start, sidebar_width_px_at_drag_start)`.
|
||||||
/// `None` whenever no drag is in flight.
|
/// `None` whenever no drag is in flight.
|
||||||
@@ -202,6 +204,7 @@ impl ElyShell {
|
|||||||
workspace_picker_open: false,
|
workspace_picker_open: false,
|
||||||
sidebar_hover_expanded: false,
|
sidebar_hover_expanded: false,
|
||||||
command_selected_index: 0,
|
command_selected_index: 0,
|
||||||
|
chrome_motion: chrome_motion::ChromeMotionState::default(),
|
||||||
sidebar_resize_origin: None,
|
sidebar_resize_origin: None,
|
||||||
download_action_error: None,
|
download_action_error: None,
|
||||||
download_clear_confirmation: false,
|
download_clear_confirmation: false,
|
||||||
@@ -239,36 +242,6 @@ impl ElyShell {
|
|||||||
shell
|
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
|
/// Drain any sync upload outcomes the off-thread worker pushed
|
||||||
/// since the previous tick and stamp the resulting connection
|
/// since the previous tick and stamp the resulting connection
|
||||||
/// state on `BrowserCore`. Returns `true` when at least one
|
/// state on `BrowserCore`. Returns `true` when at least one
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ use ely_domain::{
|
|||||||
HIDDEN_SIDEBAR_WIDTH_PX, Space,
|
HIDDEN_SIDEBAR_WIDTH_PX, Space,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, BoxShadow, Context, IntoElement, ParentElement, Styled, Window, div, hsla, point,
|
AnyElement, BoxShadow, Context, IntoElement, ParentElement, SharedString, Styled, Window, div,
|
||||||
px, rgb, rgba,
|
hsla, point, px, rgb, rgba,
|
||||||
};
|
};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
IconName, Selectable, Sizable, StyledExt,
|
IconName, Selectable, Sizable, StyledExt,
|
||||||
button::{Button, ButtonVariants},
|
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 super::{ElyShell, ShellState, render::tab_profile_label};
|
||||||
use crate::ToggleSidebar;
|
use crate::ToggleSidebar;
|
||||||
|
|
||||||
@@ -167,16 +167,21 @@ impl ElyShell {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let space_id = space.id().clone();
|
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()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.selected(active)
|
.selected(active)
|
||||||
.label(space.icon().to_string())
|
.label(space.icon().to_string())
|
||||||
.tooltip(space.name().to_string())
|
.tooltip(space.name().to_string())
|
||||||
.on_click(cx.listener(move |shell, _, window, cx| {
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.trigger_chrome_motion(target.clone());
|
||||||
shell.select_space(&space_id, window, cx);
|
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(
|
fn render_compact_tab_button(
|
||||||
@@ -190,16 +195,18 @@ impl ElyShell {
|
|||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let tab_id = tab.id().clone();
|
let tab_id = tab.id().clone();
|
||||||
let tooltip = format!("{} - {}", tab.title(), tab_profile_label(tab, &snapshot.profiles));
|
let tooltip = format!("{} - {}", tab.title(), tab_profile_label(tab, &snapshot.profiles));
|
||||||
Button::new(id)
|
let target = SharedString::from(format!("compact-tab-{}", tab.id().as_str()));
|
||||||
.ghost()
|
let press_id = self.chrome_motion_animation_id(target.as_str());
|
||||||
.small()
|
let selection_id = SharedString::from(format!("{}-selection", target.as_str()));
|
||||||
.selected(active)
|
let button =
|
||||||
.icon(icon)
|
Button::new(id).ghost().small().selected(active).icon(icon).tooltip(tooltip).on_click(
|
||||||
.tooltip(tooltip)
|
cx.listener(move |shell, _, window, cx| {
|
||||||
.on_click(cx.listener(move |shell, _, window, cx| {
|
shell.trigger_chrome_motion(target.clone());
|
||||||
shell.select_tab(&tab_id, window, cx);
|
shell.select_tab(&tab_id, window, cx);
|
||||||
}))
|
}),
|
||||||
.into_any_element()
|
);
|
||||||
|
|
||||||
|
chrome_motion_feedback(press_id, selection_id, active, div().child(button))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_compact_archived_button(
|
fn render_compact_archived_button(
|
||||||
@@ -210,15 +217,20 @@ impl ElyShell {
|
|||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let tab = archived_tab.tab();
|
let tab = archived_tab.tab();
|
||||||
let tab_id = tab.id().clone();
|
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()
|
.ghost()
|
||||||
.small()
|
.small()
|
||||||
.icon(IconName::Undo2)
|
.icon(IconName::Undo2)
|
||||||
.tooltip(tab.title().to_string())
|
.tooltip(tab.title().to_string())
|
||||||
.on_click(cx.listener(move |shell, _, window, cx| {
|
.on_click(cx.listener(move |shell, _, window, cx| {
|
||||||
|
shell.trigger_chrome_motion(target.clone());
|
||||||
shell.restore_archived_tab(&tab_id, window, cx);
|
shell.restore_archived_tab(&tab_id, window, cx);
|
||||||
}))
|
}));
|
||||||
.into_any_element()
|
|
||||||
|
chrome_motion_feedback(press_id, selection_id, false, div().child(button))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use super::{ElyShell, ShellState};
|
||||||
|
|
||||||
/// Messages the off-thread sync workers push back to the shell so
|
/// Messages the off-thread sync workers push back to the shell so
|
||||||
/// `SyncConnectionState` on `BrowserCore` and the in-flight auth
|
/// `SyncConnectionState` on `BrowserCore` and the in-flight auth
|
||||||
/// form reflect live state without the UI thread ever touching the
|
/// 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"
|
"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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user