Cargo.lock 🔗
@@ -4012,6 +4012,7 @@ dependencies = [
"env_logger",
"gpui",
"menu",
+ "parking_lot 0.11.2",
"serde_json",
"settings",
"theme",
Antonio Scandurra created
Improve styling of command palettes
Cargo.lock | 1
crates/collab_ui/src/contact_finder.rs | 4
crates/collab_ui/src/contact_list.rs | 4
crates/command_palette/src/command_palette.rs | 2
crates/editor/src/blink_manager.rs | 3
crates/editor/src/editor.rs | 19 ++--
crates/editor/src/element.rs | 2
crates/file_finder/src/file_finder.rs | 2
crates/go_to_line/src/go_to_line.rs | 11 +
crates/outline/src/outline.rs | 4
crates/picker/Cargo.toml | 2
crates/picker/src/picker.rs | 87 ++++++++++++++------
crates/project_panel/src/project_panel.rs | 5
crates/project_symbols/src/project_symbols.rs | 2
crates/search/src/buffer_search.rs | 8 +
crates/search/src/project_search.rs | 7 +
crates/theme/src/theme.rs | 6
crates/theme_selector/src/theme_selector.rs | 2
styles/src/styleTree/contactFinder.ts | 49 ++++++-----
styles/src/styleTree/contactList.ts | 2
styles/src/styleTree/picker.ts | 47 +++++++----
21 files changed, 171 insertions(+), 98 deletions(-)
@@ -4012,6 +4012,7 @@ dependencies = [
"env_logger",
"gpui",
"menu",
+ "parking_lot 0.11.2",
"serde_json",
"settings",
"theme",
@@ -170,8 +170,8 @@ impl ContactFinder {
let this = cx.weak_handle();
Self {
picker: cx.add_view(|cx| {
- Picker::new(this, cx)
- .with_theme(|cx| &cx.global::<Settings>().theme.contact_finder.picker)
+ Picker::new("Search collaborator by username...", this, cx)
+ .with_theme(|theme| theme.contact_finder.picker.clone())
}),
potential_contacts: Arc::from([]),
user_store,
@@ -175,7 +175,9 @@ impl ContactList {
) -> Self {
let filter_editor = cx.add_view(|cx| {
let mut editor = Editor::single_line(
- Some(|theme| theme.contact_list.user_query_editor.clone()),
+ Some(Arc::new(|theme| {
+ theme.contact_list.user_query_editor.clone()
+ })),
cx,
);
editor.set_placeholder_text("Filter contacts", cx);
@@ -70,7 +70,7 @@ impl CommandPalette {
})
.collect();
- let picker = cx.add_view(|cx| Picker::new(this, cx));
+ let picker = cx.add_view(|cx| Picker::new("Execute a command...", this, cx));
Self {
picker,
actions,
@@ -93,6 +93,9 @@ impl BlinkManager {
pub fn enable(&mut self, cx: &mut ModelContext<Self>) {
self.enabled = true;
+ // Set cursors as invisible and start blinking: this causes cursors
+ // to be visible during the next render.
+ self.visible = false;
self.blink_cursors(self.blink_epoch, cx);
}
@@ -437,8 +437,7 @@ pub struct EditorStyle {
type CompletionId = usize;
-pub type GetFieldEditorTheme = fn(&theme::Theme) -> theme::FieldEditor;
-
+type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
#[derive(Clone, Copy)]
@@ -523,7 +522,7 @@ pub struct Editor {
scroll_top_anchor: Anchor,
autoscroll_request: Option<(Autoscroll, bool)>,
soft_wrap_mode_override: Option<settings::SoftWrap>,
- get_field_editor_theme: Option<GetFieldEditorTheme>,
+ get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
override_text_style: Option<Box<OverrideTextStyle>>,
project: Option<ModelHandle<Project>>,
focused: bool,
@@ -1070,7 +1069,7 @@ enum GotoDefinitionKind {
impl Editor {
pub fn single_line(
- field_editor_style: Option<GetFieldEditorTheme>,
+ field_editor_style: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
@@ -1080,7 +1079,7 @@ impl Editor {
pub fn auto_height(
max_lines: usize,
- field_editor_style: Option<GetFieldEditorTheme>,
+ field_editor_style: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx));
@@ -1116,7 +1115,7 @@ impl Editor {
self.mode,
self.buffer.clone(),
self.project.clone(),
- self.get_field_editor_theme,
+ self.get_field_editor_theme.clone(),
cx,
);
self.display_map.update(cx, |display_map, cx| {
@@ -1136,12 +1135,12 @@ impl Editor {
mode: EditorMode,
buffer: ModelHandle<MultiBuffer>,
project: Option<ModelHandle<Project>>,
- get_field_editor_theme: Option<GetFieldEditorTheme>,
+ get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
cx: &mut ViewContext<Self>,
) -> Self {
let display_map = cx.add_model(|cx| {
let settings = cx.global::<Settings>();
- let style = build_style(&*settings, get_field_editor_theme, None, cx);
+ let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx);
DisplayMap::new(
buffer.clone(),
style.text.font_id,
@@ -1289,7 +1288,7 @@ impl Editor {
fn style(&self, cx: &AppContext) -> EditorStyle {
build_style(
cx.global::<Settings>(),
- self.get_field_editor_theme,
+ self.get_field_editor_theme.as_deref(),
self.override_text_style.as_deref(),
cx,
)
@@ -6846,7 +6845,7 @@ impl View for Editor {
fn build_style(
settings: &Settings,
- get_field_editor_theme: Option<GetFieldEditorTheme>,
+ get_field_editor_theme: Option<&GetFieldEditorTheme>,
override_text_style: Option<&OverrideTextStyle>,
cx: &AppContext,
) -> EditorStyle {
@@ -1186,7 +1186,7 @@ impl EditorElement {
}
// When the editor is empty and unfocused, then show the placeholder.
- if snapshot.is_empty() && !snapshot.is_focused() {
+ if snapshot.is_empty() {
let placeholder_style = self
.style
.placeholder_text
@@ -119,7 +119,7 @@ impl FileFinder {
cx.observe(&project, Self::project_updated).detach();
Self {
project,
- picker: cx.add_view(|cx| Picker::new(handle, cx)),
+ picker: cx.add_view(|cx| Picker::new("Search project files...", handle, cx)),
search_count: 0,
latest_search_id: 0,
latest_search_did_cancel: false,
@@ -1,3 +1,5 @@
+use std::sync::Arc;
+
use editor::{display_map::ToDisplayPoint, Autoscroll, DisplayPoint, Editor};
use gpui::{
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, Axis, Entity,
@@ -31,7 +33,10 @@ pub enum Event {
impl GoToLine {
pub fn new(active_editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) -> Self {
let line_editor = cx.add_view(|cx| {
- Editor::single_line(Some(|theme| theme.picker.input_editor.clone()), cx)
+ Editor::single_line(
+ Some(Arc::new(|theme| theme.picker.input_editor.clone())),
+ cx,
+ )
});
cx.subscribe(&line_editor, Self::on_line_editor_event)
.detach();
@@ -170,8 +175,8 @@ impl View for GoToLine {
.boxed(),
)
.with_child(
- Container::new(Label::new(label, theme.empty.label.clone()).boxed())
- .with_style(theme.empty.container)
+ Container::new(Label::new(label, theme.no_matches.label.clone()).boxed())
+ .with_style(theme.no_matches.container)
.boxed(),
)
.boxed(),
@@ -67,7 +67,9 @@ impl OutlineView {
) -> Self {
let handle = cx.weak_handle();
Self {
- picker: cx.add_view(|cx| Picker::new(handle, cx).with_max_size(800., 1200.)),
+ picker: cx.add_view(|cx| {
+ Picker::new("Search buffer symbols...", handle, cx).with_max_size(800., 1200.)
+ }),
last_query: Default::default(),
matches: Default::default(),
selected_match_index: 0,
@@ -16,6 +16,8 @@ util = { path = "../util" }
theme = { path = "../theme" }
workspace = { path = "../workspace" }
+parking_lot = "0.11.1"
+
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
@@ -1,25 +1,22 @@
use editor::Editor;
use gpui::{
- elements::{
- ChildView, Flex, Label, MouseEventHandler, ParentElement, ScrollTarget, UniformList,
- UniformListState,
- },
+ elements::*,
geometry::vector::{vec2f, Vector2F},
keymap,
platform::CursorStyle,
- AnyViewHandle, AppContext, Axis, Element, ElementBox, Entity, MouseButton, MouseState,
- MutableAppContext, RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ AnyViewHandle, AppContext, Axis, Entity, MouseButton, MouseState, MutableAppContext,
+ RenderContext, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
-use settings::Settings;
-use std::cmp;
+use parking_lot::Mutex;
+use std::{cmp, sync::Arc};
pub struct Picker<D: PickerDelegate> {
delegate: WeakViewHandle<D>,
query_editor: ViewHandle<Editor>,
list_state: UniformListState,
max_size: Vector2F,
- theme: Box<dyn FnMut(&AppContext) -> &theme::Picker>,
+ theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
confirmed: bool,
}
@@ -52,8 +49,8 @@ impl<D: PickerDelegate> View for Picker<D> {
}
fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
- let theme = (self.theme)(cx);
- let container_style = theme.container;
+ let theme = (self.theme.lock())(&cx.global::<settings::Settings>().theme);
+ let query = self.query(cx);
let delegate = self.delegate.clone();
let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
delegate.read(cx).match_count()
@@ -61,19 +58,36 @@ impl<D: PickerDelegate> View for Picker<D> {
0
};
+ let container_style;
+ let editor_style;
+ if query.is_empty() && match_count == 0 {
+ container_style = theme.empty_container;
+ editor_style = theme.empty_input_editor.container;
+ } else {
+ container_style = theme.container;
+ editor_style = theme.input_editor.container;
+ };
+
Flex::new(Axis::Vertical)
.with_child(
ChildView::new(&self.query_editor, cx)
.contained()
- .with_style(theme.input_editor.container)
+ .with_style(editor_style)
.boxed(),
)
- .with_child(
- if match_count == 0 {
- Label::new("No matches".into(), theme.empty.label.clone())
- .contained()
- .with_style(theme.empty.container)
+ .with_children(if match_count == 0 {
+ if query.is_empty() {
+ None
} else {
+ Some(
+ Label::new("No matches".into(), theme.no_matches.label.clone())
+ .contained()
+ .with_style(theme.no_matches.container)
+ .boxed(),
+ )
+ }
+ } else {
+ Some(
UniformList::new(
self.list_state.clone(),
match_count,
@@ -98,10 +112,10 @@ impl<D: PickerDelegate> View for Picker<D> {
)
.contained()
.with_margin_top(6.0)
- }
- .flex(1., false)
- .boxed(),
- )
+ .flex(1., false)
+ .boxed(),
+ )
+ })
.contained()
.with_style(container_style)
.constrained()
@@ -134,9 +148,26 @@ impl<D: PickerDelegate> Picker<D> {
cx.add_action(Self::cancel);
}
- pub fn new(delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self {
- let query_editor = cx.add_view(|cx| {
- Editor::single_line(Some(|theme| theme.picker.input_editor.clone()), cx)
+ pub fn new<P>(placeholder: P, delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self
+ where
+ P: Into<Arc<str>>,
+ {
+ let theme = Arc::new(Mutex::new(
+ Box::new(|theme: &theme::Theme| theme.picker.clone())
+ as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
+ ));
+ let query_editor = cx.add_view({
+ let picker_theme = theme.clone();
+ |cx| {
+ let mut editor = Editor::single_line(
+ Some(Arc::new(move |theme| {
+ (picker_theme.lock())(theme).input_editor.clone()
+ })),
+ cx,
+ );
+ editor.set_placeholder_text(placeholder, cx);
+ editor
+ }
});
cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach();
@@ -145,7 +176,7 @@ impl<D: PickerDelegate> Picker<D> {
list_state: Default::default(),
delegate,
max_size: vec2f(540., 420.),
- theme: Box::new(|cx| &cx.global::<Settings>().theme.picker),
+ theme,
confirmed: false,
};
cx.defer(|this, cx| {
@@ -162,11 +193,11 @@ impl<D: PickerDelegate> Picker<D> {
self
}
- pub fn with_theme<F>(mut self, theme: F) -> Self
+ pub fn with_theme<F>(self, theme: F) -> Self
where
- F: 'static + FnMut(&AppContext) -> &theme::Picker,
+ F: 'static + Fn(&theme::Theme) -> theme::Picker,
{
- self.theme = Box::new(theme);
+ *self.theme.lock() = Box::new(theme);
self
}
@@ -23,6 +23,7 @@ use std::{
ffi::OsStr,
ops::Range,
path::{Path, PathBuf},
+ sync::Arc,
};
use unicase::UniCase;
use workspace::Workspace;
@@ -175,11 +176,11 @@ impl ProjectPanel {
let filename_editor = cx.add_view(|cx| {
Editor::single_line(
- Some(|theme| {
+ Some(Arc::new(|theme| {
let mut style = theme.project_panel.filename_editor.clone();
style.container.background_color.take();
style
- }),
+ })),
cx,
)
});
@@ -63,7 +63,7 @@ impl ProjectSymbolsView {
let handle = cx.weak_handle();
Self {
project,
- picker: cx.add_view(|cx| Picker::new(handle, cx)),
+ picker: cx.add_view(|cx| Picker::new("Search project symbols...", handle, cx)),
selected_match_index: 0,
symbols: Default::default(),
visible_match_candidates: Default::default(),
@@ -12,7 +12,7 @@ use gpui::{
use project::search::SearchQuery;
use serde::Deserialize;
use settings::Settings;
-use std::any::Any;
+use std::{any::Any, sync::Arc};
use workspace::{
searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
ItemHandle, Pane, ToolbarItemLocation, ToolbarItemView,
@@ -232,7 +232,11 @@ impl ToolbarItemView for BufferSearchBar {
impl BufferSearchBar {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
let query_editor = cx.add_view(|cx| {
- Editor::auto_height(2, Some(|theme| theme.search.editor.input.clone()), cx)
+ Editor::auto_height(
+ 2,
+ Some(Arc::new(|theme| theme.search.editor.input.clone())),
+ cx,
+ )
});
cx.subscribe(&query_editor, Self::on_query_editor_event)
.detach();
@@ -20,6 +20,7 @@ use std::{
any::{Any, TypeId},
ops::Range,
path::PathBuf,
+ sync::Arc,
};
use util::ResultExt as _;
use workspace::{
@@ -378,8 +379,10 @@ impl ProjectSearchView {
.detach();
let query_editor = cx.add_view(|cx| {
- let mut editor =
- Editor::single_line(Some(|theme| theme.search.editor.input.clone()), cx);
+ let mut editor = Editor::single_line(
+ Some(Arc::new(|theme| theme.search.editor.input.clone())),
+ cx,
+ );
editor.set_text(query_text, cx);
editor
});
@@ -423,12 +423,14 @@ pub struct ChannelName {
pub name: TextStyle,
}
-#[derive(Deserialize, Default)]
+#[derive(Clone, Deserialize, Default)]
pub struct Picker {
#[serde(flatten)]
pub container: ContainerStyle,
- pub empty: ContainedLabel,
+ pub empty_container: ContainerStyle,
pub input_editor: FieldEditor,
+ pub empty_input_editor: FieldEditor,
+ pub no_matches: ContainedLabel,
pub item: Interactive<ContainedLabel>,
}
@@ -38,7 +38,7 @@ pub enum Event {
impl ThemeSelector {
fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<Self>) -> Self {
let handle = cx.weak_handle();
- let picker = cx.add_view(|cx| Picker::new(handle, cx));
+ let picker = cx.add_view(|cx| Picker::new("Select Theme...", handle, cx));
let settings = cx.global::<Settings>();
let original_theme = settings.theme.clone();
@@ -3,7 +3,7 @@ import { ColorScheme } from "../themes/common/colorScheme";
import { background, border, foreground, text } from "./components";
export default function contactFinder(colorScheme: ColorScheme) {
- let layer = colorScheme.highest;
+ let layer = colorScheme.middle;
const sideMargin = 6;
const contactButton = {
@@ -14,31 +14,36 @@ export default function contactFinder(colorScheme: ColorScheme) {
cornerRadius: 8,
};
+ const pickerStyle = picker(colorScheme);
+ const pickerInput = {
+ background: background(layer, "on"),
+ cornerRadius: 6,
+ text: text(layer, "mono",),
+ placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
+ selection: colorScheme.players[0],
+ border: border(layer),
+ padding: {
+ bottom: 4,
+ left: 8,
+ right: 8,
+ top: 4,
+ },
+ margin: {
+ left: sideMargin,
+ right: sideMargin,
+ }
+ };
+
return {
picker: {
+ emptyContainer: {},
item: {
- ...picker(colorScheme).item,
- margin: { left: sideMargin, right: sideMargin }
+ ...pickerStyle.item,
+ margin: { left: sideMargin, right: sideMargin },
},
- empty: picker(colorScheme).empty,
- inputEditor: {
- background: background(layer, "on"),
- cornerRadius: 6,
- text: text(layer, "mono",),
- placeholderText: text(layer, "mono", "variant", { size: "sm" }),
- selection: colorScheme.players[0],
- border: border(layer),
- padding: {
- bottom: 4,
- left: 8,
- right: 8,
- top: 4,
- },
- margin: {
- left: sideMargin,
- right: sideMargin,
- }
- }
+ noMatches: pickerStyle.noMatches,
+ inputEditor: pickerInput,
+ emptyInputEditor: pickerInput
},
rowHeight: 28,
contactAvatar: {
@@ -53,7 +53,7 @@ export default function contactsPanel(colorScheme: ColorScheme) {
background: background(layer, "on"),
cornerRadius: 6,
text: text(layer, "mono", "on"),
- placeholderText: text(layer, "mono", "on", "disabled", { size: "sm" }),
+ placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
selection: colorScheme.players[0],
border: border(layer, "on"),
padding: {
@@ -3,13 +3,39 @@ import { background, border, text } from "./components";
export default function picker(colorScheme: ColorScheme) {
let layer = colorScheme.lowest;
- return {
+ const container = {
background: background(layer),
border: border(layer),
shadow: colorScheme.modalShadow,
cornerRadius: 12,
padding: {
bottom: 4,
+ }
+ };
+ const inputEditor = {
+ placeholderText: text(layer, "sans", "on", "disabled"),
+ selection: colorScheme.players[0],
+ text: text(layer, "mono", "on"),
+ border: border(layer, { bottom: true }),
+ padding: {
+ bottom: 8,
+ left: 16,
+ right: 16,
+ top: 8,
+ },
+ margin: {
+ bottom: 4,
+ },
+ };
+ const emptyInputEditor = { ...inputEditor };
+ delete emptyInputEditor.border;
+ delete emptyInputEditor.margin;
+
+ return {
+ ...container,
+ emptyContainer: {
+ ...container,
+ padding: {}
},
item: {
padding: {
@@ -37,7 +63,9 @@ export default function picker(colorScheme: ColorScheme) {
background: background(layer, "hovered"),
},
},
- empty: {
+ inputEditor,
+ emptyInputEditor,
+ noMatches: {
text: text(layer, "sans", "variant"),
padding: {
bottom: 8,
@@ -46,20 +74,5 @@ export default function picker(colorScheme: ColorScheme) {
top: 8,
},
},
- inputEditor: {
- placeholderText: text(layer, "sans", "on", "disabled"),
- selection: colorScheme.players[0],
- text: text(layer, "mono", "on"),
- border: border(layer, { bottom: true }),
- padding: {
- bottom: 8,
- left: 16,
- right: 16,
- top: 8,
- },
- margin: {
- bottom: 4,
- },
- },
};
}