scrollbar: Fix scrollbar width on always show mode. (#1196)

And fixes #1192 render bug.
This commit is contained in:
Jason Lee 2025-09-02 15:48:06 +08:00 committed by GitHub
parent 6569bead8b
commit 02b432ed17
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 33 additions and 43 deletions

View file

@ -143,13 +143,11 @@ actions!(story, [TestAction, Tab, TabPrev]);
pub struct AppState { pub struct AppState {
pub invisible_panels: Entity<Vec<SharedString>>, pub invisible_panels: Entity<Vec<SharedString>>,
pub theme_name: Option<SharedString>,
} }
impl AppState { impl AppState {
fn init(cx: &mut App) { fn init(cx: &mut App) {
let state = Self { let state = Self {
invisible_panels: cx.new(|_| Vec::new()), invisible_panels: cx.new(|_| Vec::new()),
theme_name: None,
}; };
cx.set_global::<AppState>(state); cx.set_global::<AppState>(state);
} }

View file

@ -6,41 +6,50 @@ use gpui::{
use gpui_component::{ use gpui_component::{
button::{Button, ButtonVariants}, button::{Button, ButtonVariants},
popup_menu::PopupMenuExt, popup_menu::PopupMenuExt,
scroll::ScrollbarShow,
ActiveTheme, IconName, Sizable, Theme, ThemeRegistry, ActiveTheme, IconName, Sizable, Theme, ThemeRegistry,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::AppState;
const STATE_FILE: &str = "target/state.json"; const STATE_FILE: &str = "target/state.json";
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
struct State { struct State {
theme: SharedString, theme: SharedString,
scrollbar_show: Option<ScrollbarShow>,
} }
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
// Load last theme state // Load last theme state
let json = std::fs::read_to_string(STATE_FILE).unwrap_or(String::default()); let json = std::fs::read_to_string(STATE_FILE).unwrap_or(String::default());
tracing::info!("Load themes..."); tracing::info!("Load themes...");
if let Err(err) = ThemeRegistry::watch_dir(PathBuf::from("./themes"), cx, move |cx| { if let Ok(state) = serde_json::from_str::<State>(&json) {
if let Ok(state) = serde_json::from_str::<State>(&json) { if let Err(err) = ThemeRegistry::watch_dir(PathBuf::from("./themes"), cx, move |cx| {
if let Some(theme) = ThemeRegistry::global(cx) if let Some(theme) = ThemeRegistry::global(cx)
.themes() .themes()
.get(&state.theme) .get(&state.theme)
.cloned() .cloned()
{ {
AppState::global_mut(cx).theme_name = Some(state.theme.clone());
Theme::global_mut(cx).apply_config(&theme); Theme::global_mut(cx).apply_config(&theme);
cx.refresh_windows();
} }
}) {
tracing::error!("Failed to watch themes directory: {}", err);
} }
}) {
tracing::error!("Failed to watch themes directory: {}", err); if let Some(scrollbar_show) = state.scrollbar_show {
Theme::global_mut(cx).scrollbar_show = scrollbar_show;
}
cx.refresh_windows();
} }
cx.observe_global::<Theme>(|cx| { cx.observe_global::<Theme>(|cx| {
AppState::global_mut(cx).theme_name = Some(cx.theme().theme_name().into()); let state = State {
theme: cx.theme().theme_name().clone(),
scrollbar_show: Some(cx.theme().scrollbar_show),
};
let json = serde_json::to_string_pretty(&state).unwrap();
std::fs::write(STATE_FILE, json).unwrap();
}) })
.detach(); .detach();
} }
@ -63,31 +72,17 @@ impl Render for ThemeSwitcher {
_: &mut gpui::Window, _: &mut gpui::Window,
cx: &mut gpui::Context<Self>, cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement { ) -> impl gpui::IntoElement {
let theme_name = AppState::global(cx) let theme_name = cx.theme().theme_name().clone();
.theme_name
.clone()
.unwrap_or("default-light".into());
div() div()
.id("theme-switcher") .id("theme-switcher")
.on_action(cx.listener(|_, switch: &SwitchTheme, _, cx| { .on_action(cx.listener(|_, switch: &SwitchTheme, _, cx| {
let theme_name = switch.0.clone(); let theme_name = switch.0.clone();
// Save AppState
let mut state = State {
theme: theme_name.clone(),
};
if let Some(theme_config) = if let Some(theme_config) =
ThemeRegistry::global(cx).themes().get(&theme_name).cloned() ThemeRegistry::global(cx).themes().get(&theme_name).cloned()
{ {
Theme::global_mut(cx).apply_config(&theme_config); Theme::global_mut(cx).apply_config(&theme_config);
state.theme = theme_config.name.clone();
AppState::global_mut(cx).theme_name = Some(theme_name.clone());
} }
let json = serde_json::to_string_pretty(&state).unwrap();
std::fs::write(STATE_FILE, json).unwrap();
cx.notify(); cx.notify();
})) }))
.child( .child(

View file

@ -34,13 +34,6 @@ impl AppTitleBar {
) -> Self { ) -> Self {
let locale_selector = cx.new(|cx| LocaleSelector::new(window, cx)); let locale_selector = cx.new(|cx| LocaleSelector::new(window, cx));
let font_size_selector = cx.new(|cx| FontSizeSelector::new(window, cx)); let font_size_selector = cx.new(|cx| FontSizeSelector::new(window, cx));
if cx.should_auto_hide_scrollbars() {
Theme::global_mut(cx).scrollbar_show = ScrollbarShow::Scrolling;
} else {
Theme::global_mut(cx).scrollbar_show = ScrollbarShow::Hover;
}
let theme_switcher = cx.new(|cx| ThemeSwitcher::new(cx)); let theme_switcher = cx.new(|cx| ThemeSwitcher::new(cx));
Self { Self {

View file

@ -386,13 +386,18 @@ impl Scrollbar {
} }
fn style_for_normal(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) { fn style_for_normal(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels, Pixels) {
let (width, inset, radius) = match cx.theme().scrollbar_show {
ScrollbarShow::Scrolling => (THUMB_WIDTH, THUMB_INSET, THUMB_RADIUS),
_ => (THUMB_ACTIVE_WIDTH, THUMB_ACTIVE_INSET, THUMB_ACTIVE_RADIUS),
};
( (
cx.theme().scrollbar_thumb, cx.theme().scrollbar_thumb,
cx.theme().scrollbar, cx.theme().scrollbar,
gpui::transparent_black(), gpui::transparent_black(),
THUMB_WIDTH, width,
THUMB_INSET, inset,
THUMB_RADIUS, radius,
) )
} }
@ -509,7 +514,7 @@ impl Element for Scrollbar {
// The horizontal scrollbar is set avoid overlapping with the vertical scrollbar, if the vertical scrollbar is visible. // The horizontal scrollbar is set avoid overlapping with the vertical scrollbar, if the vertical scrollbar is visible.
let margin_end = if has_both && !is_vertical { let margin_end = if has_both && !is_vertical {
THUMB_ACTIVE_WIDTH WIDTH
} else { } else {
px(0.) px(0.)
}; };
@ -551,7 +556,6 @@ impl Element for Scrollbar {
let state = self.state.clone(); let state = self.state.clone();
let is_always_to_show = cx.theme().scrollbar_show.is_always(); let is_always_to_show = cx.theme().scrollbar_show.is_always();
let is_hover_to_show = cx.theme().scrollbar_show.is_hover();
let is_hovered_on_bar = state.get().hovered_axis == Some(axis); let is_hovered_on_bar = state.get().hovered_axis == Some(axis);
let is_hovered_on_thumb = state.get().hovered_on_thumb == Some(axis); let is_hovered_on_thumb = state.get().hovered_on_thumb == Some(axis);
let is_offset_changed = state.get().last_scroll_offset != self.scroll_handle.offset(); let is_offset_changed = state.get().last_scroll_offset != self.scroll_handle.offset();
@ -559,7 +563,7 @@ impl Element for Scrollbar {
let (thumb_bg, bar_bg, bar_border, thumb_width, inset, radius) = let (thumb_bg, bar_bg, bar_border, thumb_width, inset, radius) =
if state.get().dragged_axis == Some(axis) { if state.get().dragged_axis == Some(axis) {
Self::style_for_active(cx) Self::style_for_active(cx)
} else if is_hover_to_show && (is_hovered_on_bar || is_hovered_on_thumb) { } else if is_hovered_on_bar || is_hovered_on_thumb {
if is_hovered_on_thumb { if is_hovered_on_thumb {
Self::style_for_hovered_thumb(cx) Self::style_for_hovered_thumb(cx)
} else { } else {

View file

@ -140,11 +140,11 @@ impl Theme {
/// Sync the Scrollbar showing behavior with the system /// Sync the Scrollbar showing behavior with the system
pub fn sync_scrollbar_appearance(cx: &mut App) { pub fn sync_scrollbar_appearance(cx: &mut App) {
if cx.should_auto_hide_scrollbars() { Theme::global_mut(cx).scrollbar_show = if cx.should_auto_hide_scrollbars() {
cx.global_mut::<Theme>().scrollbar_show = ScrollbarShow::Scrolling; ScrollbarShow::Scrolling
} else { } else {
cx.global_mut::<Theme>().scrollbar_show = ScrollbarShow::Hover; ScrollbarShow::Hover
} };
} }
pub fn change(mode: impl Into<ThemeMode>, window: Option<&mut Window>, cx: &mut App) { pub fn change(mode: impl Into<ThemeMode>, window: Option<&mut Window>, cx: &mut App) {