From 69265b4fd4da888e2d8e1268021af4d358e67afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 8 Oct 2026 21:08:58 -0400 Subject: [PATCH] ZoomControl: owner-driven mode for content zoom --- src/shell/mod.rs | 2 +- src/shell/status.rs | 59 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 02c3aa5..787458b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -24,7 +24,7 @@ pub use dialogs::{AboutDialog, SplashScreen}; pub use frame::ResizeBorder; pub use native::{TrayIcon, TrayItem, dock_badge, set_dock_badge}; pub(crate) use status::connectivity_dot; -pub use status::{Connectivity, OfflineIndicator, ZoomControl}; +pub use status::{Connectivity, OfflineIndicator, ZoomControl, ZoomStep}; pub use switcher::WindowSwitcher; pub use tabs::{TabBar, WindowTab}; pub use update::{UpdateBanner, UpdateDialog, UpdateState}; diff --git a/src/shell/status.rs b/src/shell/status.rs index 00783a0..f87c63b 100644 --- a/src/shell/status.rs +++ b/src/shell/status.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::{rc::Rc, time::Duration}; use gpui::{ Animation, AnimationExt, AnyElement, App, ElementId, IntoElement, ParentElement, RenderOnce, @@ -165,15 +165,32 @@ fn step(current: u32, direction: i8) -> Option { } } -/// Minus, percentage, plus. Scales the window's rem size, and all it holds. +/// A press on a `ZoomControl` its owner handles. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ZoomStep { + Out, + In, + Reset, +} + +type OnZoom = Rc; + +/// Minus, percentage, plus. Scales the window's rem size, and all it holds, unless an owner zooms its own content. #[derive(IntoElement)] pub struct ZoomControl { id: ElementId, + owned: Option<(u32, OnZoom)>, } impl ZoomControl { pub fn new(id: impl Into) -> Self { - Self { id: id.into() } + Self { id: id.into(), owned: None } + } + + /// Shows `percent` and hands each press to `on_zoom`. + pub fn owned(mut self, percent: u32, on_zoom: impl Fn(ZoomStep, &mut Window, &mut App) + 'static) -> Self { + self.owned = Some((percent, Rc::new(on_zoom))); + self } } @@ -187,6 +204,9 @@ fn apply(percent: u32, window: &mut Window, cx: &mut App) { impl RenderOnce for ZoomControl { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let theme = cx.theme(); + if let Some((percent, on_zoom)) = self.owned { + return owned(self.id, percent, on_zoom, cx).into_any_element(); + } let percent = (window.rem_size() / theme.base_rem() * 100.0).round() as u32; let (down, up) = (step(percent, -1), step(percent, 1)); div() @@ -226,9 +246,42 @@ impl RenderOnce for ZoomControl { button.on_click(move |_, window, cx| apply(next, window, cx)) }), ) + .into_any_element() } } +/// The owner's zoom: its percent, its steps. +fn owned(id: ElementId, percent: u32, on_zoom: OnZoom, cx: &App) -> impl IntoElement { + let theme = cx.theme(); + let press = |step: ZoomStep| { + let on_zoom = on_zoom.clone(); + move |_: &gpui::ClickEvent, window: &mut Window, cx: &mut App| on_zoom(step, window, cx) + }; + div() + .id(id) + .flex() + .flex_none() + .items_center() + .gap_0p5() + .child(IconButton::new("zoom-out", IconName::ZoomOut).size(ControlSize::Sm).on_click(press(ZoomStep::Out))) + .child( + div() + .id("zoom-reset") + .flex() + .items_center() + .justify_center() + .min_w(theme.control_height(ControlSize::Lg) * 1.5) + .h(theme.control_height(ControlSize::Sm)) + .rounded_full() + .cursor_pointer() + .hover(|style| style.bg(theme.colors.hover)) + .on_click(press(ZoomStep::Reset)) + .child(AnimatedNumber::new("zoom-value", percent as f64).size(TextSize::Sm)) + .child(div().text_size(theme.text_size(TextSize::Sm)).child("%")), + ) + .child(IconButton::new("zoom-in", IconName::ZoomIn).size(ControlSize::Sm).on_click(press(ZoomStep::In))) +} + #[cfg(test)] mod tests { use super::step;