Detailed changes
@@ -44,7 +44,11 @@ impl EditableSettingControl for BufferFontSizeControl {
settings.buffer_font_size
}
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
+ fn apply(
+ settings: &mut <Self::Settings as Settings>::FileContent,
+ value: Self::Value,
+ _cx: &AppContext,
+ ) {
settings.buffer_font_size = Some(value.into());
}
}
@@ -84,7 +88,11 @@ impl EditableSettingControl for BufferFontWeightControl {
settings.buffer_font.weight
}
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
+ fn apply(
+ settings: &mut <Self::Settings as Settings>::FileContent,
+ value: Self::Value,
+ _cx: &AppContext,
+ ) {
settings.buffer_font_weight = Some(value.0);
}
}
@@ -133,7 +141,11 @@ impl EditableSettingControl for InlineGitBlameControl {
settings.git.inline_blame_enabled()
}
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
+ fn apply(
+ settings: &mut <Self::Settings as Settings>::FileContent,
+ value: Self::Value,
+ _cx: &AppContext,
+ ) {
if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
inline_blame.enabled = value;
} else {
@@ -20,14 +20,18 @@ pub trait EditableSettingControl: RenderOnce {
/// Applies the given setting file to the settings file contents.
///
/// This will be called when writing the setting value back to the settings file.
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value);
+ fn apply(
+ settings: &mut <Self::Settings as Settings>::FileContent,
+ value: Self::Value,
+ cx: &AppContext,
+ );
/// Writes the given setting value to the settings files.
fn write(value: Self::Value, cx: &AppContext) {
let fs = <dyn Fs>::global(cx);
- update_settings_file::<Self::Settings>(fs, cx, move |settings, _cx| {
- Self::apply(settings, value);
+ update_settings_file::<Self::Settings>(fs, cx, move |settings, cx| {
+ Self::apply(settings, value, cx);
});
}
}
@@ -0,0 +1,257 @@
+use gpui::{AppContext, FontWeight};
+use settings::{EditableSettingControl, Settings};
+use theme::{SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings};
+use ui::{
+ prelude::*, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
+ ToggleButton,
+};
+
+#[derive(IntoElement)]
+pub struct AppearanceSettingsControls {}
+
+impl AppearanceSettingsControls {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl RenderOnce for AppearanceSettingsControls {
+ fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+ SettingsContainer::new()
+ .child(
+ SettingsGroup::new("Theme").child(
+ h_flex()
+ .gap_2()
+ .justify_between()
+ .child(ThemeControl)
+ .child(ThemeModeControl),
+ ),
+ )
+ .child(
+ SettingsGroup::new("Font")
+ .child(UiFontSizeControl)
+ .child(UiFontWeightControl),
+ )
+ }
+}
+
+#[derive(IntoElement)]
+struct ThemeControl;
+
+impl EditableSettingControl for ThemeControl {
+ type Value = String;
+ type Settings = ThemeSettings;
+
+ fn name(&self) -> SharedString {
+ "Theme".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ThemeSettings::get_global(cx);
+ let appearance = SystemAppearance::global(cx);
+ settings
+ .theme_selection
+ .as_ref()
+ .map(|selection| selection.theme(appearance.0).to_string())
+ .unwrap_or_else(|| ThemeSettings::default_theme(*appearance).to_string())
+ }
+
+ fn apply(
+ settings: &mut <Self::Settings as Settings>::FileContent,
+ value: Self::Value,
+ cx: &AppContext,
+ ) {
+ let appearance = SystemAppearance::global(cx);
+ settings.set_theme(value, appearance.0);
+ }
+}
+
+impl RenderOnce for ThemeControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ DropdownMenu::new(
+ "theme",
+ value.clone(),
+ ContextMenu::build(cx, |mut menu, cx| {
+ let theme_registry = ThemeRegistry::global(cx);
+
+ for theme in theme_registry.list_names(false) {
+ menu = menu.custom_entry(
+ {
+ let theme = theme.clone();
+ move |_cx| Label::new(theme.clone()).into_any_element()
+ },
+ {
+ let theme = theme.clone();
+ move |cx| {
+ Self::write(theme.to_string(), cx);
+ }
+ },
+ )
+ }
+
+ menu
+ }),
+ )
+ .full_width(true)
+ }
+}
+
+#[derive(IntoElement)]
+struct ThemeModeControl;
+
+impl EditableSettingControl for ThemeModeControl {
+ type Value = ThemeMode;
+ type Settings = ThemeSettings;
+
+ fn name(&self) -> SharedString {
+ "Theme Mode".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ThemeSettings::get_global(cx);
+ settings
+ .theme_selection
+ .as_ref()
+ .and_then(|selection| selection.mode())
+ .unwrap_or_default()
+ }
+
+ fn apply(
+ settings: &mut <Self::Settings as Settings>::FileContent,
+ value: Self::Value,
+ _cx: &AppContext,
+ ) {
+ settings.set_mode(value);
+ }
+}
+
+impl RenderOnce for ThemeModeControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ h_flex()
+ .child(
+ ToggleButton::new("light", "Light")
+ .style(ButtonStyle::Filled)
+ .size(ButtonSize::Large)
+ .selected(value == ThemeMode::Light)
+ .on_click(|_, cx| Self::write(ThemeMode::Light, cx))
+ .first(),
+ )
+ .child(
+ ToggleButton::new("system", "System")
+ .style(ButtonStyle::Filled)
+ .size(ButtonSize::Large)
+ .selected(value == ThemeMode::System)
+ .on_click(|_, cx| Self::write(ThemeMode::System, cx))
+ .middle(),
+ )
+ .child(
+ ToggleButton::new("dark", "Dark")
+ .style(ButtonStyle::Filled)
+ .size(ButtonSize::Large)
+ .selected(value == ThemeMode::Dark)
+ .on_click(|_, cx| Self::write(ThemeMode::Dark, cx))
+ .last(),
+ )
+ }
+}
+
+#[derive(IntoElement)]
+struct UiFontSizeControl;
+
+impl EditableSettingControl for UiFontSizeControl {
+ type Value = Pixels;
+ type Settings = ThemeSettings;
+
+ fn name(&self) -> SharedString {
+ "UI Font Size".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ThemeSettings::get_global(cx);
+ settings.ui_font_size
+ }
+
+ fn apply(
+ settings: &mut <Self::Settings as Settings>::FileContent,
+ value: Self::Value,
+ _cx: &AppContext,
+ ) {
+ settings.ui_font_size = Some(value.into());
+ }
+}
+
+impl RenderOnce for UiFontSizeControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ h_flex()
+ .gap_2()
+ .child(Icon::new(IconName::FontSize))
+ .child(NumericStepper::new(
+ value.to_string(),
+ move |_, cx| {
+ Self::write(value - px(1.), cx);
+ },
+ move |_, cx| {
+ Self::write(value + px(1.), cx);
+ },
+ ))
+ }
+}
+
+#[derive(IntoElement)]
+struct UiFontWeightControl;
+
+impl EditableSettingControl for UiFontWeightControl {
+ type Value = FontWeight;
+ type Settings = ThemeSettings;
+
+ fn name(&self) -> SharedString {
+ "UI Font Weight".into()
+ }
+
+ fn read(cx: &AppContext) -> Self::Value {
+ let settings = ThemeSettings::get_global(cx);
+ settings.ui_font.weight
+ }
+
+ fn apply(
+ settings: &mut <Self::Settings as Settings>::FileContent,
+ value: Self::Value,
+ _cx: &AppContext,
+ ) {
+ settings.ui_font_weight = Some(value.0);
+ }
+}
+
+impl RenderOnce for UiFontWeightControl {
+ fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+ let value = Self::read(cx);
+
+ h_flex()
+ .gap_2()
+ .child(Icon::new(IconName::FontWeight))
+ .child(DropdownMenu::new(
+ "ui-font-weight",
+ value.0.to_string(),
+ ContextMenu::build(cx, |mut menu, _cx| {
+ for weight in FontWeight::ALL {
+ menu = menu.custom_entry(
+ move |_cx| Label::new(weight.0.to_string()).into_any_element(),
+ {
+ move |cx| {
+ Self::write(weight, cx);
+ }
+ },
+ )
+ }
+
+ menu
+ }),
+ ))
+ }
+}
@@ -1,4 +1,4 @@
-mod theme_settings_controls;
+mod appearance_settings_controls;
use std::any::TypeId;
@@ -10,7 +10,7 @@ use ui::prelude::*;
use workspace::item::{Item, ItemEvent};
use workspace::Workspace;
-use crate::theme_settings_controls::ThemeSettingsControls;
+use crate::appearance_settings_controls::AppearanceSettingsControls;
pub struct SettingsUiFeatureFlag;
@@ -110,7 +110,7 @@ impl Render for SettingsPage {
v_flex()
.gap_1()
.child(Label::new("Appearance"))
- .child(ThemeSettingsControls::new()),
+ .child(AppearanceSettingsControls::new()),
)
.child(
v_flex()
@@ -1,112 +0,0 @@
-use gpui::{AppContext, FontWeight};
-use settings::{EditableSettingControl, Settings};
-use theme::ThemeSettings;
-use ui::{prelude::*, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup};
-
-#[derive(IntoElement)]
-pub struct ThemeSettingsControls {}
-
-impl ThemeSettingsControls {
- pub fn new() -> Self {
- Self {}
- }
-}
-
-impl RenderOnce for ThemeSettingsControls {
- fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
- SettingsContainer::new().child(
- SettingsGroup::new("Font")
- .child(UiFontSizeControl)
- .child(UiFontWeightControl),
- )
- }
-}
-
-#[derive(IntoElement)]
-struct UiFontSizeControl;
-
-impl EditableSettingControl for UiFontSizeControl {
- type Value = Pixels;
- type Settings = ThemeSettings;
-
- fn name(&self) -> SharedString {
- "UI Font Size".into()
- }
-
- fn read(cx: &AppContext) -> Self::Value {
- let settings = ThemeSettings::get_global(cx);
- settings.ui_font_size
- }
-
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
- settings.ui_font_size = Some(value.into());
- }
-}
-
-impl RenderOnce for UiFontSizeControl {
- fn render(self, cx: &mut WindowContext) -> impl IntoElement {
- let value = Self::read(cx);
-
- h_flex()
- .gap_2()
- .child(Icon::new(IconName::FontSize))
- .child(NumericStepper::new(
- value.to_string(),
- move |_, cx| {
- Self::write(value - px(1.), cx);
- },
- move |_, cx| {
- Self::write(value + px(1.), cx);
- },
- ))
- }
-}
-
-#[derive(IntoElement)]
-struct UiFontWeightControl;
-
-impl EditableSettingControl for UiFontWeightControl {
- type Value = FontWeight;
- type Settings = ThemeSettings;
-
- fn name(&self) -> SharedString {
- "UI Font Weight".into()
- }
-
- fn read(cx: &AppContext) -> Self::Value {
- let settings = ThemeSettings::get_global(cx);
- settings.ui_font.weight
- }
-
- fn apply(settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value) {
- settings.ui_font_weight = Some(value.0);
- }
-}
-
-impl RenderOnce for UiFontWeightControl {
- fn render(self, cx: &mut WindowContext) -> impl IntoElement {
- let value = Self::read(cx);
-
- h_flex()
- .gap_2()
- .child(Icon::new(IconName::FontWeight))
- .child(DropdownMenu::new(
- "ui-font-weight",
- value.0.to_string(),
- ContextMenu::build(cx, |mut menu, _cx| {
- for weight in FontWeight::ALL {
- menu = menu.custom_entry(
- move |_cx| Label::new(weight.0.to_string()).into_any_element(),
- {
- move |cx| {
- Self::write(weight, cx);
- }
- },
- )
- }
-
- menu
- }),
- ))
- }
-}
@@ -94,6 +94,17 @@ pub struct ThemeSettings {
}
impl ThemeSettings {
+ const DEFAULT_LIGHT_THEME: &'static str = "One Light";
+ const DEFAULT_DARK_THEME: &'static str = "One Dark";
+
+ /// Returns the name of the default theme for the given [`Appearance`].
+ pub fn default_theme(appearance: Appearance) -> &'static str {
+ match appearance {
+ Appearance::Light => Self::DEFAULT_LIGHT_THEME,
+ Appearance::Dark => Self::DEFAULT_DARK_THEME,
+ }
+ }
+
/// Reloads the current theme.
///
/// Reads the [`ThemeSettings`] to know which theme should be loaded,
@@ -109,10 +120,7 @@ impl ThemeSettings {
// based on the system appearance.
let theme_registry = ThemeRegistry::global(cx);
if theme_registry.get(theme_name).ok().is_none() {
- theme_name = match *system_appearance {
- Appearance::Light => "One Light",
- Appearance::Dark => "One Dark",
- };
+ theme_name = Self::default_theme(*system_appearance);
};
if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) {
@@ -190,7 +198,7 @@ fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
Schema::new_ref("#/definitions/ThemeName".into())
}
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ThemeMode {
/// Use the specified `light` theme.
@@ -218,6 +226,13 @@ impl ThemeSelection {
},
}
}
+
+ pub fn mode(&self) -> Option<ThemeMode> {
+ match self {
+ ThemeSelection::Static(_) => None,
+ ThemeSelection::Dynamic { mode, .. } => Some(*mode),
+ }
+ }
}
/// Settings for rendering text in UI and text buffers.
@@ -267,6 +282,56 @@ pub struct ThemeSettingsContent {
pub theme_overrides: Option<ThemeStyleContent>,
}
+impl ThemeSettingsContent {
+ /// Sets the theme for the given appearance to the theme with the specified name.
+ pub fn set_theme(&mut self, theme_name: String, appearance: Appearance) {
+ if let Some(selection) = self.theme.as_mut() {
+ let theme_to_update = match selection {
+ ThemeSelection::Static(theme) => theme,
+ ThemeSelection::Dynamic { mode, light, dark } => match mode {
+ ThemeMode::Light => light,
+ ThemeMode::Dark => dark,
+ ThemeMode::System => match appearance {
+ Appearance::Light => light,
+ Appearance::Dark => dark,
+ },
+ },
+ };
+
+ *theme_to_update = theme_name.to_string();
+ } else {
+ self.theme = Some(ThemeSelection::Static(theme_name.to_string()));
+ }
+ }
+
+ pub fn set_mode(&mut self, mode: ThemeMode) {
+ if let Some(selection) = self.theme.as_mut() {
+ match selection {
+ ThemeSelection::Static(theme) => {
+ // If the theme was previously set to a single static theme,
+ // we don't know whether it was a light or dark theme, so we
+ // just use it for both.
+ self.theme = Some(ThemeSelection::Dynamic {
+ mode,
+ light: theme.clone(),
+ dark: theme.clone(),
+ });
+ }
+ ThemeSelection::Dynamic {
+ mode: mode_to_update,
+ ..
+ } => *mode_to_update = mode,
+ }
+ } else {
+ self.theme = Some(ThemeSelection::Dynamic {
+ mode,
+ light: ThemeSettings::DEFAULT_LIGHT_THEME.into(),
+ dark: ThemeSettings::DEFAULT_DARK_THEME.into(),
+ });
+ }
+ }
+}
+
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum BufferLineHeight {
@@ -10,9 +10,7 @@ use picker::{Picker, PickerDelegate};
use serde::Deserialize;
use settings::{update_settings_file, SettingsStore};
use std::sync::Arc;
-use theme::{
- Appearance, Theme, ThemeMeta, ThemeMode, ThemeRegistry, ThemeSelection, ThemeSettings,
-};
+use theme::{Appearance, Theme, ThemeMeta, ThemeRegistry, ThemeSettings};
use ui::{prelude::*, v_flex, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ui::HighlightedLabel, ModalView, Workspace};
@@ -197,23 +195,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
let appearance = Appearance::from(cx.appearance());
update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
- if let Some(selection) = settings.theme.as_mut() {
- let theme_to_update = match selection {
- ThemeSelection::Static(theme) => theme,
- ThemeSelection::Dynamic { mode, light, dark } => match mode {
- ThemeMode::Light => light,
- ThemeMode::Dark => dark,
- ThemeMode::System => match appearance {
- Appearance::Light => light,
- Appearance::Dark => dark,
- },
- },
- };
-
- *theme_to_update = theme_name.to_string();
- } else {
- settings.theme = Some(ThemeSelection::Static(theme_name.to_string()));
- }
+ settings.set_theme(theme_name.to_string(), appearance);
});
self.view
@@ -38,6 +38,7 @@ impl RenderOnce for ApplicationMenu {
))
},
)
+ .reserve_space_for_reset(true)
.when(
theme::has_adjusted_buffer_font_size(cx),
|stepper| {
@@ -72,6 +73,7 @@ impl RenderOnce for ApplicationMenu {
))
},
)
+ .reserve_space_for_reset(true)
.when(
theme::has_adjusted_ui_font_size(cx),
|stepper| {
@@ -42,8 +42,9 @@ impl Disableable for DropdownMenu {
impl RenderOnce for DropdownMenu {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
PopoverMenu::new(self.id)
+ .full_width(self.full_width)
.menu(move |_cx| Some(self.menu.clone()))
- .trigger(DropdownMenuTrigger::new(self.label))
+ .trigger(DropdownMenuTrigger::new(self.label).full_width(self.full_width))
}
}
@@ -68,6 +69,11 @@ impl DropdownMenuTrigger {
on_click: None,
}
}
+
+ pub fn full_width(mut self, full_width: bool) -> Self {
+ self.full_width = full_width;
+ self
+ }
}
impl Disableable for DropdownMenuTrigger {
@@ -7,6 +7,8 @@ pub struct NumericStepper {
value: SharedString,
on_decrement: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
on_increment: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
+ /// Whether to reserve space for the reset button.
+ reserve_space_for_reset: bool,
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
}
@@ -20,10 +22,16 @@ impl NumericStepper {
value: value.into(),
on_decrement: Box::new(on_decrement),
on_increment: Box::new(on_increment),
+ reserve_space_for_reset: false,
on_reset: None,
}
}
+ pub fn reserve_space_for_reset(mut self, reserve_space_for_reset: bool) -> Self {
+ self.reserve_space_for_reset = reserve_space_for_reset;
+ self
+ }
+
pub fn on_reset(
mut self,
on_reset: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
@@ -48,13 +56,15 @@ impl RenderOnce for NumericStepper {
.icon_size(icon_size)
.on_click(on_reset),
)
- } else {
+ } else if self.reserve_space_for_reset {
element.child(
h_flex()
.size(icon_size.square(cx))
.flex_none()
.into_any_element(),
)
+ } else {
+ element
}
})
.child(
@@ -1,10 +1,10 @@
use std::{cell::RefCell, rc::Rc};
use gpui::{
- anchored, deferred, div, point, prelude::FluentBuilder, px, AnchorCorner, AnyElement, Bounds,
- DismissEvent, DispatchPhase, Element, ElementId, GlobalElementId, HitboxId, InteractiveElement,
- IntoElement, LayoutId, ManagedView, MouseDownEvent, ParentElement, Pixels, Point, View,
- VisualContext, WindowContext,
+ anchored, deferred, div, point, prelude::FluentBuilder, px, size, AnchorCorner, AnyElement,
+ Bounds, DismissEvent, DispatchPhase, Element, ElementId, GlobalElementId, HitboxId,
+ InteractiveElement, IntoElement, LayoutId, Length, ManagedView, MouseDownEvent, ParentElement,
+ Pixels, Point, Style, View, VisualContext, WindowContext,
};
use crate::prelude::*;
@@ -74,6 +74,7 @@ pub struct PopoverMenu<M: ManagedView> {
attach: Option<AnchorCorner>,
offset: Option<Point<Pixels>>,
trigger_handle: Option<PopoverMenuHandle<M>>,
+ full_width: bool,
}
impl<M: ManagedView> PopoverMenu<M> {
@@ -87,9 +88,15 @@ impl<M: ManagedView> PopoverMenu<M> {
attach: None,
offset: None,
trigger_handle: None,
+ full_width: false,
}
}
+ pub fn full_width(mut self, full_width: bool) -> Self {
+ self.full_width = full_width;
+ self
+ }
+
pub fn menu(mut self, f: impl Fn(&mut WindowContext) -> Option<View<M>> + 'static) -> Self {
self.menu_builder = Some(Rc::new(f));
self
@@ -258,10 +265,13 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
.as_mut()
.map(|child_element| child_element.request_layout(cx));
- let layout_id = cx.request_layout(
- gpui::Style::default(),
- menu_layout_id.into_iter().chain(child_layout_id),
- );
+ let mut style = Style::default();
+ if self.full_width {
+ style.size = size(relative(1.).into(), Length::Auto);
+ }
+
+ let layout_id =
+ cx.request_layout(style, menu_layout_id.into_iter().chain(child_layout_id));
(
(