diff --git a/Cargo.lock b/Cargo.lock index c6fbe523b135e3f8c3c15bc7b37ceb918b7e6276..f79a7b851d04598f00db12bf89d3fdc6fb35fa63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4012,6 +4012,7 @@ dependencies = [ "env_logger", "gpui", "menu", + "parking_lot 0.11.2", "serde_json", "settings", "theme", diff --git a/crates/collab_ui/src/contact_finder.rs b/crates/collab_ui/src/contact_finder.rs index 5165c3b1f690cc9eeeca491f4ebad7bb897be89e..98f70e83f0ecf1eb3c312429a8461a35b028199e 100644 --- a/crates/collab_ui/src/contact_finder.rs +++ b/crates/collab_ui/src/contact_finder.rs @@ -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::().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, diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index a4a0b5d18e8ae7ce9507c2fa9424ea26b789fb78..bc8b2947c4b278fa05d6e78c091a6517b971e890 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -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); diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 51474be1bed9ac13a57e5c05f54561d5a31a040d..b472da3bb530b8189e5d9b3c2b3852541273c4db 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -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, diff --git a/crates/editor/src/blink_manager.rs b/crates/editor/src/blink_manager.rs index 681692f0f5428362c35caca8f4030d8222821cec..9651182bd8b3bcf44a3d4c2f1fcd040e80520579 100644 --- a/crates/editor/src/blink_manager.rs +++ b/crates/editor/src/blink_manager.rs @@ -93,6 +93,9 @@ impl BlinkManager { pub fn enable(&mut self, cx: &mut ModelContext) { 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); } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 12790df442467bb75c6b0938b8e1244643784bc7..a049239f70cda660422d00bac8aab8f720c69e93 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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; #[derive(Clone, Copy)] @@ -523,7 +522,7 @@ pub struct Editor { scroll_top_anchor: Anchor, autoscroll_request: Option<(Autoscroll, bool)>, soft_wrap_mode_override: Option, - get_field_editor_theme: Option, + get_field_editor_theme: Option>, override_text_style: Option>, project: Option>, focused: bool, @@ -1070,7 +1069,7 @@ enum GotoDefinitionKind { impl Editor { pub fn single_line( - field_editor_style: Option, + field_editor_style: Option>, cx: &mut ViewContext, ) -> 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, + field_editor_style: Option>, cx: &mut ViewContext, ) -> 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, project: Option>, - get_field_editor_theme: Option, + get_field_editor_theme: Option>, cx: &mut ViewContext, ) -> Self { let display_map = cx.add_model(|cx| { let settings = cx.global::(); - 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::(), - 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, + get_field_editor_theme: Option<&GetFieldEditorTheme>, override_text_style: Option<&OverrideTextStyle>, cx: &AppContext, ) -> EditorStyle { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index dc62b2e9236424673917669f2592e2bdbfb5144b..25100037d79f2261cb28a73e2e07ecb6642163d8 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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 diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 1a82613b84329eea6c214073185e26f886110fd2..06041f8b0b015e22c2148fdad1fa3ea7e4fd9483 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -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, diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index ad1dacf7434e5bb30e7d4b04170aa0828e911b59..68a69801cf028e7f2516dc2aed5c6c16e4c10f4c 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -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, cx: &mut ViewContext) -> 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(), diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index cee5388800ab3a84c749dfc96cdbf371b551d61c..e0136f50053f5749ba219f91a948cbdf39e1e73f 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -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, diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index bceb263e235cb6af02958352cb6a2ff7e77e6e9a..64386979103dcb62ff503ad7cfbd44bc07374109 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -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"] } diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 0945f6771bc6d640fb05a7df6233cbc6d71d71e7..a9cf23fb3f5890a56333d321165b6988fdc8ac13 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -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 { delegate: WeakViewHandle, query_editor: ViewHandle, list_state: UniformListState, max_size: Vector2F, - theme: Box &theme::Picker>, + theme: Arc theme::Picker>>>, confirmed: bool, } @@ -52,8 +49,8 @@ impl View for Picker { } fn render(&mut self, cx: &mut RenderContext) -> gpui::ElementBox { - let theme = (self.theme)(cx); - let container_style = theme.container; + let theme = (self.theme.lock())(&cx.global::().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 View for Picker { 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 View for Picker { ) .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 Picker { cx.add_action(Self::cancel); } - pub fn new(delegate: WeakViewHandle, cx: &mut ViewContext) -> Self { - let query_editor = cx.add_view(|cx| { - Editor::single_line(Some(|theme| theme.picker.input_editor.clone()), cx) + pub fn new

(placeholder: P, delegate: WeakViewHandle, cx: &mut ViewContext) -> Self + where + P: Into>, + { + let theme = Arc::new(Mutex::new( + Box::new(|theme: &theme::Theme| theme.picker.clone()) + as Box 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 Picker { list_state: Default::default(), delegate, max_size: vec2f(540., 420.), - theme: Box::new(|cx| &cx.global::().theme.picker), + theme, confirmed: false, }; cx.defer(|this, cx| { @@ -162,11 +193,11 @@ impl Picker { self } - pub fn with_theme(mut self, theme: F) -> Self + pub fn with_theme(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 } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 8f5d9b0e55e680fedab595f96eaa8606e958cc0b..392fd73e0342df18d6783294a2858a88c097b08d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -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, ) }); diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index e4a251de00cdfc5de619c6e8d2beb305fadd1ea1..440e11e7e4c482a458db0ac833f07a40f70fbf88 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -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(), diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index cd7a74ce8e3233f0d5b4404a3bc597ae4271b6a6..7d668f6b3ede85106b5e833a177374671a3f953d 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -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 { 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(); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8eb8e110e460972ada29f41f38150d5f8c08785f..6ba185b589aa17fa15a41fcc682485518d444596 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -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 }); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 97c0b0a79d42a01dea6470151056fb14d3f97a3e..cf7aa6e551cada7850e16a3d874ad3b073fdf7d6 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -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, } diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 1caeae75f597050f145b8ebc36aa3a0b827261fc..252a64c7fda465c9ad4b69ae013fd71f189054c1 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -38,7 +38,7 @@ pub enum Event { impl ThemeSelector { fn new(registry: Arc, cx: &mut ViewContext) -> 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::(); let original_theme = settings.theme.clone(); diff --git a/styles/src/styleTree/contactFinder.ts b/styles/src/styleTree/contactFinder.ts index c389c126a36b00f78d38f158043f60a2ec094f96..d696f2c4b89f62c14f23f08011bae596a9af3193 100644 --- a/styles/src/styleTree/contactFinder.ts +++ b/styles/src/styleTree/contactFinder.ts @@ -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: { diff --git a/styles/src/styleTree/contactList.ts b/styles/src/styleTree/contactList.ts index 5addca96d2a14403ef984c4d7a53b5e5ea88ce44..456786c4be2a44f32d2cba19b9ddbc72f866fdb6 100644 --- a/styles/src/styleTree/contactList.ts +++ b/styles/src/styleTree/contactList.ts @@ -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: { diff --git a/styles/src/styleTree/picker.ts b/styles/src/styleTree/picker.ts index 30608eeb9ed23186098f78720c0d41938bea90f0..d124ea180e7580d58566f3d29e6735d78fcfb39d 100644 --- a/styles/src/styleTree/picker.ts +++ b/styles/src/styleTree/picker.ts @@ -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, - }, - }, }; }