onboarding: Use a picker for the font dropdowns (#35638)
Release Notes: - N/A
This commit is contained in:
parent
351e8c4cd9
commit
5940ed979f
6 changed files with 272 additions and 76 deletions
|
@ -25,12 +25,14 @@ db.workspace = true
|
|||
editor.workspace = true
|
||||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
menu.workspace = true
|
||||
notifications.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
@ -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 ≠.";
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue