@@ -2,14 +2,19 @@ use std::sync::Arc;
use editor::{EditorSettings, ShowMinimap};
use fs::Fs;
-use gpui::{Action, App, FontFeatures, IntoElement, Pixels, Window};
+use fuzzy::{StringMatch, StringMatchCandidate};
+use gpui::{
+ Action, AnyElement, App, Context, FontFeatures, IntoElement, Pixels, SharedString, Task, Window,
+};
use language::language_settings::{AllLanguageSettings, FormatOnSave};
+use picker::{Picker, PickerDelegate};
use project::project_settings::ProjectSettings;
use settings::{Settings as _, update_settings_file};
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
use ui::{
- ButtonLike, ContextMenu, DropdownMenu, NumericStepper, SwitchField, ToggleButtonGroup,
- ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
+ ButtonLike, ListItem, ListItemSpacing, NumericStepper, PopoverMenu, SwitchField,
+ ToggleButtonGroup, ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip,
+ prelude::*,
};
use crate::{ImportCursorSettings, ImportVsCodeSettings, SettingsImportState};
@@ -246,9 +251,25 @@ fn render_import_settings_section(cx: &App) -> impl IntoElement {
fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme_settings = ThemeSettings::get_global(cx);
let ui_font_size = theme_settings.ui_font_size(cx);
- let font_family = theme_settings.buffer_font.family.clone();
+ let ui_font_family = theme_settings.ui_font.family.clone();
+ let buffer_font_family = theme_settings.buffer_font.family.clone();
let buffer_font_size = theme_settings.buffer_font_size(cx);
+ let ui_font_picker =
+ cx.new(|cx| font_picker(ui_font_family.clone(), write_ui_font_family, window, cx));
+
+ let buffer_font_picker = cx.new(|cx| {
+ font_picker(
+ buffer_font_family.clone(),
+ write_buffer_font_family,
+ window,
+ cx,
+ )
+ });
+
+ let ui_font_handle = ui::PopoverMenuHandle::default();
+ let buffer_font_handle = ui::PopoverMenuHandle::default();
+
h_flex()
.w_full()
.gap_4()
@@ -263,34 +284,35 @@ fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl
.justify_between()
.gap_2()
.child(
- DropdownMenu::new(
- "ui-font-family",
- theme_settings.ui_font.family.clone(),
- ContextMenu::build(window, cx, |mut menu, _, cx| {
- let font_family_cache = FontFamilyCache::global(cx);
-
- for font_name in font_family_cache.list_font_families(cx) {
- menu = menu.custom_entry(
- {
- let font_name = font_name.clone();
- move |_window, _cx| {
- Label::new(font_name.clone()).into_any_element()
- }
- },
- {
- let font_name = font_name.clone();
- move |_window, cx| {
- write_ui_font_family(font_name.clone(), cx);
- }
- },
- )
- }
-
- menu
- }),
- )
- .style(ui::DropdownStyle::Outlined)
- .full_width(true),
+ PopoverMenu::new("ui-font-picker")
+ .menu({
+ let ui_font_picker = ui_font_picker.clone();
+ move |_window, _cx| Some(ui_font_picker.clone())
+ })
+ .trigger(
+ ButtonLike::new("ui-font-family-button")
+ .style(ButtonStyle::Outlined)
+ .size(ButtonSize::Medium)
+ .full_width()
+ .child(
+ h_flex()
+ .w_full()
+ .justify_between()
+ .child(Label::new(ui_font_family))
+ .child(
+ Icon::new(IconName::ChevronUpDown)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ ),
+ ),
+ )
+ .full_width(true)
+ .anchor(gpui::Corner::TopLeft)
+ .offset(gpui::Point {
+ x: px(0.0),
+ y: px(4.0),
+ })
+ .with_handle(ui_font_handle),
)
.child(
NumericStepper::new(
@@ -318,34 +340,35 @@ fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl
.justify_between()
.gap_2()
.child(
- DropdownMenu::new(
- "buffer-font-family",
- font_family,
- ContextMenu::build(window, cx, |mut menu, _, cx| {
- let font_family_cache = FontFamilyCache::global(cx);
-
- for font_name in font_family_cache.list_font_families(cx) {
- menu = menu.custom_entry(
- {
- let font_name = font_name.clone();
- move |_window, _cx| {
- Label::new(font_name.clone()).into_any_element()
- }
- },
- {
- let font_name = font_name.clone();
- move |_window, cx| {
- write_buffer_font_family(font_name.clone(), cx);
- }
- },
- )
- }
-
- menu
- }),
- )
- .style(ui::DropdownStyle::Outlined)
- .full_width(true),
+ PopoverMenu::new("buffer-font-picker")
+ .menu({
+ let buffer_font_picker = buffer_font_picker.clone();
+ move |_window, _cx| Some(buffer_font_picker.clone())
+ })
+ .trigger(
+ ButtonLike::new("buffer-font-family-button")
+ .style(ButtonStyle::Outlined)
+ .size(ButtonSize::Medium)
+ .full_width()
+ .child(
+ h_flex()
+ .w_full()
+ .justify_between()
+ .child(Label::new(buffer_font_family))
+ .child(
+ Icon::new(IconName::ChevronUpDown)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ ),
+ ),
+ )
+ .full_width(true)
+ .anchor(gpui::Corner::TopLeft)
+ .offset(gpui::Point {
+ x: px(0.0),
+ y: px(4.0),
+ })
+ .with_handle(buffer_font_handle),
)
.child(
NumericStepper::new(
@@ -364,6 +387,175 @@ fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl
)
}
+type FontPicker = Picker<FontPickerDelegate>;
+
+pub struct FontPickerDelegate {
+ fonts: Vec<SharedString>,
+ filtered_fonts: Vec<StringMatch>,
+ selected_index: usize,
+ current_font: SharedString,
+ on_font_changed: Arc<dyn Fn(SharedString, &mut App) + 'static>,
+}
+
+impl FontPickerDelegate {
+ fn new(
+ current_font: SharedString,
+ on_font_changed: impl Fn(SharedString, &mut App) + 'static,
+ cx: &mut Context<FontPicker>,
+ ) -> Self {
+ let font_family_cache = FontFamilyCache::global(cx);
+
+ let fonts: Vec<SharedString> = font_family_cache
+ .list_font_families(cx)
+ .into_iter()
+ .collect();
+
+ let selected_index = fonts
+ .iter()
+ .position(|font| *font == current_font)
+ .unwrap_or(0);
+
+ Self {
+ fonts: fonts.clone(),
+ filtered_fonts: fonts
+ .iter()
+ .enumerate()
+ .map(|(index, font)| StringMatch {
+ candidate_id: index,
+ string: font.to_string(),
+ positions: Vec::new(),
+ score: 0.0,
+ })
+ .collect(),
+ selected_index,
+ current_font,
+ on_font_changed: Arc::new(on_font_changed),
+ }
+ }
+}
+
+impl PickerDelegate for FontPickerDelegate {
+ type ListItem = AnyElement;
+
+ fn match_count(&self) -> usize {
+ self.filtered_fonts.len()
+ }
+
+ fn selected_index(&self) -> usize {
+ self.selected_index
+ }
+
+ fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<FontPicker>) {
+ self.selected_index = ix.min(self.filtered_fonts.len().saturating_sub(1));
+ cx.notify();
+ }
+
+ fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
+ "Search fontsβ¦".into()
+ }
+
+ fn update_matches(
+ &mut self,
+ query: String,
+ _window: &mut Window,
+ cx: &mut Context<FontPicker>,
+ ) -> Task<()> {
+ let fonts = self.fonts.clone();
+ let current_font = self.current_font.clone();
+
+ let matches: Vec<StringMatch> = if query.is_empty() {
+ fonts
+ .iter()
+ .enumerate()
+ .map(|(index, font)| StringMatch {
+ candidate_id: index,
+ string: font.to_string(),
+ positions: Vec::new(),
+ score: 0.0,
+ })
+ .collect()
+ } else {
+ let _candidates: Vec<StringMatchCandidate> = fonts
+ .iter()
+ .enumerate()
+ .map(|(id, font)| StringMatchCandidate::new(id, font.as_ref()))
+ .collect();
+
+ fonts
+ .iter()
+ .enumerate()
+ .filter(|(_, font)| font.to_lowercase().contains(&query.to_lowercase()))
+ .map(|(index, font)| StringMatch {
+ candidate_id: index,
+ string: font.to_string(),
+ positions: Vec::new(),
+ score: 0.0,
+ })
+ .collect()
+ };
+
+ let selected_index = if query.is_empty() {
+ fonts
+ .iter()
+ .position(|font| *font == current_font)
+ .unwrap_or(0)
+ } else {
+ matches
+ .iter()
+ .position(|m| fonts[m.candidate_id] == current_font)
+ .unwrap_or(0)
+ };
+
+ self.filtered_fonts = matches;
+ self.selected_index = selected_index;
+ cx.notify();
+
+ Task::ready(())
+ }
+
+ fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<FontPicker>) {
+ if let Some(font_match) = self.filtered_fonts.get(self.selected_index) {
+ let font = font_match.string.clone();
+ (self.on_font_changed)(font.into(), cx);
+ }
+ }
+
+ fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<FontPicker>) {}
+
+ fn render_match(
+ &self,
+ ix: usize,
+ selected: bool,
+ _window: &mut Window,
+ _cx: &mut Context<FontPicker>,
+ ) -> Option<Self::ListItem> {
+ let font_match = self.filtered_fonts.get(ix)?;
+
+ Some(
+ ListItem::new(ix)
+ .inset(true)
+ .spacing(ListItemSpacing::Sparse)
+ .toggle_state(selected)
+ .child(Label::new(font_match.string.clone()))
+ .into_any_element(),
+ )
+ }
+}
+
+fn font_picker(
+ current_font: SharedString,
+ on_font_changed: impl Fn(SharedString, &mut App) + 'static,
+ window: &mut Window,
+ cx: &mut Context<FontPicker>,
+) -> FontPicker {
+ let delegate = FontPickerDelegate::new(current_font, on_font_changed, cx);
+
+ Picker::list(delegate, window, cx)
+ .show_scrollbar(true)
+ .width(rems_from_px(210.))
+ .max_height(Some(rems(20.).into()))
+}
+
fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
const LIGATURE_TOOLTIP: &'static str = "Ligatures are when a font creates a special character out of combining two characters into one. For example, with ligatures turned on, =/= would become β .";