picker: Simplify presentation and sizing APIs, add preview-aware defaults (#59719)
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions

The recent "pickers with previews" overhaul left the picker
sizing/presentation API spread across several overlapping knobs that
every call site had to set correctly — and many didn't, causing pickers
across the app to render at the wrong width, lose their container, or
stop dismissing. This PR consolidates that surface into a small,
hard-to-misuse API and makes correct sizing the default.

Net effect: a plain `Picker::uniform_list(delegate, …)` now renders
correctly out of the box (standard width, standard max-height, shrinks
to fit, dismisses properly), and the ~35 call sites only specify what
genuinely differs.

## API changes

**Presentation** — three overlapping booleans (`is_modal`, `is_popover`,
`is_resizable`) collapsed into one enum, with resizability living inside
the only variant where it's meaningful:

```rust
enum Presentation {
    Modal { resizable: bool }, // own chrome, dismisses on blur, optionally resizable
    Popover,                   // own chrome, dismisses on blur, never resizable
    Embedded,                  // host container owns chrome + dismissal
}
```

- `modal(bool)` is **removed** in favor of explicit, self-documenting
builders:
  - *(default)* → `Modal` (resizable iff it has a preview)
  - `.popover()` → `Popover` (menu-attached surfaces)
  - `.embedded()` → `Embedded` (pickers nested in a larger modal/view)
- Dynamic callers use `.when(cond, Picker::embedded)` (added `impl
FluentBuilder for Picker`).

**Sizing** — preview-vs-not now drives everything; the manual padding
knob is gone:

| Before | After |
|---|---|
| `vertical_padding` field + `no_vertical_padding()` | removed — derived
from whether a preview is visible |
| `height(...)` (ambiguous: fixed vs max) | `max_height(...)` (plain
pickers shrink-to-fit, capped here) |
| `minimum_results_width(...)` | removed — a plain picker's min width
tracks its opening width; preview pickers use standard internal pane
mins |
| default size = 60% viewport | default = `DEFAULT_MODAL_WIDTH` (34rem)
× `DEFAULT_MODAL_MAX_HEIGHT` (24rem, max) |
| resize handles gated on `is_modal` | gated on `is_resizable` (new
`resizable(bool)` builder; auto-`true` for preview pickers) |

Call sites now only override the exceptions: narrow popover selectors
(`initial_width`), the taller outline view (`max_height`), and preview
pickers (constructed via `*_with_preview`).

## Behavior fixes

- **Wrong widths everywhere**: pickers were falling back to
60%-of-viewport because the original migration set
`minimum_results_width` but never `initial_width`. Fixed at the source
via the new defaults.
- **Popovers had no container and wouldn't dismiss**: `is_modal=false`
was suppressing both the elevated background *and* blur-dismiss. Split
out so popovers keep their chrome and dismiss on click-away/escape. This
fixed the agent-panel model/profile selectors, sidebar recent projects,
and the settings theme/font/icon/ollama pickers (which were incorrectly
using `modal(false)`).
- **Sidebar recent projects stretched to full height**: was missing the
shrink-to-fit behavior; now capped and content-sized like other
popovers.
- **Preview crash**: removed an over-strict `debug_assert!` that
panicked when previewing an empty file (`message == None && editor
empty` is valid).
- **Preview-aware default size**: pickers open at standard width with
the preview hidden, and expand to the larger "telescope" size when a
preview is shown. Fixes the text finder rendering super-wide by default,
and makes the file finder expand (rather than cram its results) when you
toggle the preview.

---

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2026-06-23 06:33:47 -03:00 committed by GitHub
parent 4eb039b451
commit f88bc7e18a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 286 additions and 374 deletions

View file

@ -451,7 +451,7 @@
"ctrl-shift-h": "search::ToggleReplace",
"alt-ctrl-g": "search::ToggleRegex",
"alt-ctrl-x": "search::ToggleRegex",
"ctrl-alt-p": "project_search::OpenTextFinder",
"ctrl-alt-f": "project_search::OpenTextFinder",
},
},
{
@ -481,7 +481,7 @@
"ctrl-shift-h": "search::ToggleReplace",
"alt-ctrl-g": "search::ToggleRegex",
"alt-ctrl-x": "search::ToggleRegex",
"ctrl-alt-p": "project_search::OpenTextFinder",
"ctrl-alt-f": "project_search::OpenTextFinder",
},
},
{

View file

@ -508,7 +508,7 @@
"cmd-shift-h": "search::ToggleReplace",
"alt-cmd-g": "search::ToggleRegex",
"alt-cmd-x": "search::ToggleRegex",
"cmd-alt-p": "project_search::OpenTextFinder",
"alt-cmd-f": "project_search::OpenTextFinder",
},
},
{
@ -542,7 +542,7 @@
"cmd-shift-h": "search::ToggleReplace",
"alt-cmd-g": "search::ToggleRegex",
"alt-cmd-x": "search::ToggleRegex",
"cmd-alt-p": "project_search::OpenTextFinder",
"alt-cmd-f": "project_search::OpenTextFinder",
},
},
{

View file

@ -452,7 +452,7 @@
"ctrl-shift-f": "search::FocusSearch",
"ctrl-shift-h": "search::ToggleReplace",
"alt-r": "search::ToggleRegex", // vscode
"ctrl-alt-p": "project_search::OpenTextFinder",
"ctrl-alt-f": "project_search::OpenTextFinder",
},
},
{
@ -483,7 +483,7 @@
"escape": "project_search::ToggleFocus",
"ctrl-shift-h": "search::ToggleReplace",
"alt-r": "search::ToggleRegex", // vscode
"ctrl-alt-p": "project_search::OpenTextFinder",
"ctrl-alt-f": "project_search::OpenTextFinder",
},
},
{

View file

@ -32,7 +32,7 @@
"context": "TextFinder || (TextFinder > Picker > Editor) || (TextFinder > Picker > menu)",
"use_key_equivalents": true,
"bindings": {
"cmd-alt-p": "text_finder::ToProjectSearch",
"alt-cmd-f": "text_finder::ToProjectSearch",
"cmd-j": "pane::SplitDown",
"cmd-k": "pane::SplitUp",
"cmd-h": "pane::SplitLeft",

View file

@ -29,7 +29,7 @@
{
"context": "TextFinder || (TextFinder > Picker > Editor) || (TextFinder > Picker > menu)",
"bindings": {
"ctrl-alt-p": "text_finder::ToProjectSearch",
"ctrl-alt-f": "text_finder::ToProjectSearch",
"ctrl-j": "pane::SplitDown",
"ctrl-k": "pane::SplitUp",
"ctrl-h": "pane::SplitLeft",

View file

@ -295,7 +295,7 @@ impl ManageProfilesModal {
window,
cx,
)
.modal(false)
.embedded()
});
let dismiss_subscription = cx.subscribe_in(&model_picker, window, {

View file

@ -25,14 +25,7 @@ impl ToolPicker {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.modal(false)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded());
Self { picker }
}
@ -41,14 +34,7 @@ impl ToolPicker {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| {
Picker::list(delegate, window, cx)
.modal(false)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::list(delegate, window, cx).embedded());
Self { picker }
}
}

View file

@ -299,9 +299,7 @@ impl ConfigOptionSelector {
Picker::nonsearchable_list(delegate, window, picker_cx)
}
.show_scrollbar(true)
.minimum_results_width(rems(20.))
.height(rems(20.))
.no_vertical_padding()
.initial_width(rems(20.))
})
};

View file

@ -47,15 +47,12 @@ pub fn language_model_selector(
if popover_styles {
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.minimum_results_width(rems(20.))
.height(rems(20.))
.no_vertical_padding()
.modal(false)
.initial_width(rems(20.))
.popover()
} else {
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.minimum_results_width(rems(20.))
.initial_width(rems(20.))
.embedded()
}
}

View file

@ -35,9 +35,7 @@ pub fn acp_model_selector(
let delegate = ModelPickerDelegate::new(selector, focus_handle, window, cx);
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.minimum_results_width(rems(20.))
.height(rems(20.))
.no_vertical_padding()
.initial_width(rems(20.))
}
enum ModelPickerEntry {

View file

@ -123,9 +123,7 @@ impl ProfileSelector {
let picker = cx.new(|cx| {
Picker::list(delegate, window, cx)
.show_scrollbar(true)
.minimum_results_width(rems(18.))
.height(rems(20.))
.no_vertical_padding()
.initial_width(rems(18.))
});
self.picker = Some(picker);

View file

@ -1134,11 +1134,7 @@ impl ProjectPickerModal {
let picker = cx.new(|cx| {
Picker::list(delegate, window, cx)
.list_measure_all()
.modal(false)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
.embedded()
});
let picker_focus_handle = picker.focus_handle(cx);

View file

@ -65,11 +65,7 @@ impl ChannelModal {
window,
cx,
)
.modal(false)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
.embedded()
});
Self {

View file

@ -21,14 +21,7 @@ impl ContactFinder {
potential_contacts: Arc::from([]),
selected_index: 0,
};
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.modal(false)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded());
Self { picker }
}

View file

@ -125,11 +125,7 @@ impl CommandPalette {
);
let picker = cx.new(|cx| {
let picker = Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding();
let picker = Picker::uniform_list(delegate, window, cx);
picker.set_query(query, window, cx);
picker
});

View file

@ -103,11 +103,7 @@ impl AttachModal {
window,
cx,
)
.modal(modal)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
.when(!modal, |picker| picker.embedded())
});
Self {
_subscription: cx.subscribe(&picker, |_, _, _, cx| {

View file

@ -106,9 +106,7 @@ impl NewProcessModal {
let delegate =
DebugDelegate::new(debug_panel.downgrade(), task_store.clone());
Picker::list(delegate, window, cx)
.modal(false)
.height(rems(24.))
.no_vertical_padding()
.embedded()
.list_measure_all()
});

View file

@ -1184,8 +1184,7 @@ impl StatefulModal for DevContainerModal {
}),
);
let picker =
cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded());
self.picker = Some(picker);
Some(DevContainerState::TemplateQueryReturned(Ok(items)))
} else {
@ -1316,8 +1315,7 @@ impl StatefulModal for DevContainerModal {
}),
);
let picker =
cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded());
self.features_picker = Some(picker);
Some(DevContainerState::FeaturesQueryReturned(template_entry))
} else {

View file

@ -95,13 +95,7 @@ impl EncodingSelector {
fn new(buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade(), buffer);
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(gpui::rems(34.))
.minimum_results_width(gpui::rems(34.))
.height(gpui::rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}

View file

@ -40,13 +40,7 @@ impl ExtensionVersionSelector {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}

View file

@ -276,7 +276,6 @@ pub fn init(cx: &mut App) {
workspace.toggle_modal(window, cx, |window, cx| {
let delegate = DevExtensionRebuildPickerDelegate::new(dev_extensions);
Picker::uniform_list(delegate, window, cx)
.minimum_results_width(rems(34.))
});
}
}

View file

@ -169,20 +169,10 @@ impl FileFinder {
let modal_max_width_setting = FileFinderSettings::get_global(cx).modal_max_width;
let project = delegate.project.clone();
let modal_max_width = Self::modal_max_width(modal_max_width_setting, window);
let picker = cx.new(|cx| {
let picker = Picker::uniform_list_with_preview(delegate, project, window, cx)
.minimum_results_width(gpui::rems(34.))
.height(gpui::rems(24.))
.no_vertical_padding();
// The dedicated file finder width setting has been removed in favor of
// persisted picker sizes. For migration we still honor it as the initial
// width, but only if the user actually changed it from the default;
if modal_max_width_setting != FileFinderWidth::default() {
let modal_max_width = Self::modal_max_width(modal_max_width_setting, window);
picker.initial_width(Rems::from_pixels(modal_max_width, window))
} else {
picker
}
Picker::uniform_list_with_preview(delegate, project, window, cx)
.initial_width(Rems::from_pixels(modal_max_width, window))
});
let picker_focus_handle = picker.focus_handle(cx);
picker.update(cx, |picker, _| {

View file

@ -277,11 +277,9 @@ impl BranchList {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.minimum_results_width(width)
.height(rems(24.))
.no_vertical_padding()
.initial_width(width)
.show_scrollbar(true)
.modal(!embedded)
.when(embedded, |picker| picker.embedded())
});
let picker_focus_handle = picker.focus_handle(cx);

View file

@ -111,9 +111,6 @@ impl CommitTagPicker {
let picker = cx.new(|cx| {
Picker::nonsearchable_uniform_list(delegate, window, cx)
.initial_width(COMMIT_TAG_LIST_WIDTH_IN_REMS)
.minimum_results_width(COMMIT_TAG_LIST_WIDTH_IN_REMS)
.height(rems(24.))
.no_vertical_padding()
});
Self { picker }
}

View file

@ -55,13 +55,8 @@ impl PickerPrompt {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(rem_width))
.minimum_results_width(rems(rem_width))
.height(rems(24.))
.no_vertical_padding()
});
let picker =
cx.new(|cx| Picker::uniform_list(delegate, window, cx).initial_width(rems(rem_width)));
let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
Self {
picker,

View file

@ -64,9 +64,7 @@ impl RepositorySelector {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.minimum_results_width(width)
.height(rems(20.))
.no_vertical_padding()
.initial_width(width)
.show_scrollbar(true)
});

View file

@ -128,11 +128,9 @@ impl StashList {
let delegate = StashListDelegate::new(repository, workspace, window, cx);
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.minimum_results_width(width)
.height(rems(24.))
.no_vertical_padding()
.initial_width(width)
.show_scrollbar(true)
.modal(!embedded)
.when(embedded, |picker| picker.embedded())
});
let picker_focus_handle = picker.focus_handle(cx);
picker.update(cx, |picker, _| {

View file

@ -8,7 +8,7 @@ use git::repository::Worktree as GitWorktree;
use gpui::{
Action, AnyElement, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, IntoElement, Modifiers, ModifiersChangedEvent, ParentElement, PromptLevel,
Render, SharedString, Styled, Subscription, Task, TaskExt, WeakEntity, Window, actions, rems,
Render, SharedString, Styled, Subscription, Task, TaskExt, WeakEntity, Window, actions,
};
use picker::{Picker, PickerDelegate, PickerEditorPosition};
use project::Project;
@ -128,11 +128,7 @@ impl WorktreePicker {
Picker::list(delegate, window, cx)
.list_measure_all()
.show_scrollbar(true)
.modal(false)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(20.))
.no_vertical_padding()
.embedded()
});
let picker_focus_handle = picker.focus_handle(cx);

View file

@ -83,13 +83,7 @@ impl LanguageSelector {
current_language_name,
);
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}

View file

@ -65,13 +65,7 @@ impl LineEndingSelector {
let line_ending = buffer.read(cx).line_ending();
let delegate =
LineEndingSelectorDelegate::new(cx.entity().downgrade(), buffer, project, line_ending);
let picker = cx.new(|cx| {
Picker::nonsearchable_uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::nonsearchable_uniform_list(delegate, window, cx));
Self { picker }
}
}

View file

@ -61,13 +61,7 @@ impl BaseKeymapSelector {
window: &mut Window,
cx: &mut Context<BaseKeymapSelector>,
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}

View file

@ -233,8 +233,7 @@ impl OpenPathPrompt {
workspace.toggle_modal(window, cx, |window, cx| {
let delegate =
OpenPathDelegate::new(tx, lister.clone(), creating_path, cx).show_hidden();
let picker =
Picker::uniform_list(delegate, window, cx).minimum_results_width(rems(34.));
let picker = Picker::uniform_list(delegate, window, cx);
let mut query = lister.default_query(cx);
if let Some(suggested_name) = suggested_name {
query.push_str(&suggested_name);

View file

@ -5,7 +5,6 @@ use gpui::{AppContext, Entity, TestAppContext, VisualTestContext};
use picker::{Picker, PickerDelegate};
use project::Project;
use serde_json::json;
use ui::rems;
use util::path;
use workspace::{AppState, MultiWorkspace};
@ -509,9 +508,7 @@ fn build_open_path_prompt(
delegate
};
cx.new(|cx| {
let picker = Picker::uniform_list(delegate, window, cx)
.minimum_results_width(rems(34.))
.modal(false);
let picker = Picker::uniform_list(delegate, window, cx).embedded();
let query = lister.default_query(cx);
picker.set_query(&query, window, cx);
picker

View file

@ -179,12 +179,10 @@ impl OutlineView {
let delegate = OutlineViewDelegate::new(cx.entity().downgrade(), outline, editor, cx);
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.minimum_results_width(rems(34.))
.height(Rems::from_pixels(
.max_height(Rems::from_pixels(
window.viewport_size().height * 0.75,
window,
))
.no_vertical_padding()
.show_scrollbar(true)
});
OutlineView { picker }

View file

@ -127,14 +127,12 @@ impl<D: PickerDelegate> Picker<D> {
cx: &mut Context<Self>,
) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let toggle_focus_handle = focus_handle.clone();
let right_focus_handle = focus_handle.clone();
let below_focus_handle = focus_handle.clone();
let current = self.preview_layout().unwrap_or(preview::Layout::Hidden);
let preview_visible = current != preview::Layout::Hidden;
h_flex()
.gap_1()
.child(
Button::new("picker-preview-toggle", "Preview")
.when(preview_visible, |this| this.color(Color::Accent))
@ -142,22 +140,12 @@ impl<D: PickerDelegate> Picker<D> {
KeyBinding::for_action_in(&TogglePreview, &focus_handle, cx)
.size(rems_from_px(12.)),
)
.tooltip(move |_window, cx| {
Tooltip::for_action_in(
"Toggle Preview",
&TogglePreview,
&toggle_focus_handle,
cx,
)
})
.on_click(
cx.listener(|this, _, window, cx| this.toggle_preview_visible(window, cx)),
),
)
// The layout buttons (preview to the right / below) are only relevant
// once the preview is showing, so hide them while it's hidden.
.when(preview_visible, |this| {
this.child(div().child(Divider::vertical().color(ui::DividerColor::Border)))
this.child(Divider::vertical().h_4().mx_1())
.child(
IconButton::new("picker-preview-right", IconName::DiffSplit)
.icon_size(IconSize::Small)

View file

@ -6,7 +6,7 @@ use ui::Window;
use crate::preview;
use crate::shape::{self, Centered, RelativeHeight, RelativeWidth, Shape, ViewportFraction};
const PICKERS_NAMESPACE: &str = "pickers";
const PICKERS_NAMESPACE: &str = "pickers_v2";
pub(crate) fn store_shape_for_this_layout(
picker_delegate: &'static str,

View file

@ -38,6 +38,9 @@ pub use preview::PreviewSource;
pub use preview::Update as PreviewUpdate;
pub use ui_input::ErasedEditor;
pub const DEFAULT_MODAL_WIDTH: Rems = Rems(34.0);
pub const DEFAULT_MODAL_MAX_HEIGHT: Rems = Rems(24.0);
enum ElementContainer {
List(ListState),
UniformList(UniformListScrollHandle),
@ -88,6 +91,30 @@ struct PendingUpdateMatches {
_task: Task<Result<()>>,
}
#[derive(Clone, Copy)]
enum Presentation {
/// A self-contained modal: draws its own elevated background and dismisses
/// when it loses focus. May optionally be resized (persisting its size);
/// resizing only makes sense for modals.
Modal { resizable: bool },
/// A popover attached to a menu/trigger: draws its own elevated background
/// and dismisses when it loses focus, but is never resizable.
Popover,
/// Embedded inside a larger container (e.g. another modal) that provides its
/// own chrome and handles dismissal.
Embedded,
}
/// The default size for a given preview layout. With the preview hidden the
/// picker uses its standard size; showing a preview expands it to the larger
/// "telescope" size so the results pane isn't cramped beside the preview.
fn default_shape_for_layout(hidden: shape::Centered, layout: preview::Layout) -> shape::Centered {
match layout {
preview::Layout::Hidden => hidden,
preview::Layout::Right | preview::Layout::Below => shape::Centered::default(),
}
}
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
element_container: ElementContainer,
@ -96,16 +123,14 @@ pub struct Picker<D: PickerDelegate> {
pending_update_matches: Option<PendingUpdateMatches>,
confirm_on_update: Option<bool>,
shape: shape::Shape,
/// set through [Picker::width] and [Picker::height]
/// The size the picker opens at (and resets to). Defaults depend on whether
/// the picker has a preview; see [`Picker::initial_width`] / [`Picker::max_height`].
default_shape: shape::Centered,
vertical_padding: shape::VerticalPadding,
size_bounds: shape::SizeBounds,
/// An external control to display a scrollbar in the `Picker`.
show_scrollbar: bool,
/// Whether the `Picker` is rendered as a self-contained modal.
///
/// Set this to `false` when rendering the `Picker` as part of a larger modal.
is_modal: bool,
/// How the picker is presented, which controls its chrome and behavior.
presentation: Presentation,
/// Bounds tracking for the picker container (for aside positioning)
picker_bounds: Rc<Cell<Option<Bounds<Pixels>>>>,
/// Bounds tracking for items (for aside positioning) - maps item index to bounds
@ -474,9 +499,27 @@ impl<D: PickerDelegate> Picker<D> {
.flatten()
.unwrap_or_default();
};
let shape = persistence::try_load_shape(D::name(), preview.as_ref().map(|p| p.layout), cx)
.log_err()
.flatten();
let has_preview = preview.is_some();
let persisted_shape =
persistence::try_load_shape(D::name(), preview.as_ref().map(|p| p.layout), cx)
.log_err()
.flatten();
// Every picker opens at the standard "simple" size: a fixed width and a
// standard max height it shrinks below when there's little content.
// Showing a preview expands it to the larger "telescope" size (see
// `default_shape_for_layout`).
let default_shape = shape::Centered::simple();
let initial_layout = preview
.as_ref()
.map(|p| p.layout)
.unwrap_or(preview::Layout::Hidden);
let mut size_bounds = shape::SizeBounds::default();
// For a plain picker the whole-picker minimum is just its opening width,
// so it can't be resized/clamped narrower than it opens. Preview pickers
// keep the standard per-pane minimums.
if !has_preview && let Some(width) = default_shape.width.as_rems() {
size_bounds.min_results.width = width;
}
let mut this = Self {
delegate,
head,
@ -484,15 +527,21 @@ impl<D: PickerDelegate> Picker<D> {
pending_update_matches: None,
confirm_on_update: None,
preview,
shape_loaded_from_persistence: shape.is_some(),
shape: shape.unwrap_or_default(),
default_shape: shape::Centered::default(),
vertical_padding: shape::VerticalPadding::default(),
shape_loaded_from_persistence: persisted_shape.is_some(),
shape: persisted_shape.unwrap_or_else(|| {
shape::Shape::HorizontallyCentered(default_shape_for_layout(
default_shape,
initial_layout,
))
}),
default_shape,
show_scrollbar: false,
is_modal: true,
presentation: Presentation::Modal {
resizable: has_preview,
},
picker_bounds: Rc::new(Cell::new(None)),
item_bounds: Rc::new(RefCell::new(HashMap::default())),
size_bounds: shape::SizeBounds::default(),
size_bounds,
actions_menu_handle: PopoverMenuHandle::default(),
};
this.update_matches("".to_string(), window, cx);
@ -513,34 +562,33 @@ impl<D: PickerDelegate> Picker<D> {
}
}
/// Sets the width the picker appears with if the user has never resized it
/// or when the user sets it back to it's default size.
/// Overrides the width the picker opens at (and resets to). Plain pickers
/// default to [`DEFAULT_MODAL_WIDTH`]; only call this for pickers that need a
/// different width (e.g. narrow popover selectors).
///
/// For a plain (no-preview) picker the opening width is also its minimum, so
/// it can't be resized or clamped narrower than it opens.
pub fn initial_width(mut self, width: impl Into<RelativeWidth>) -> Self {
let width = width.into();
self.default_shape.width = width;
if !self.shape_loaded_from_persistence {
self.shape.set_initial_width(width);
}
// A plain picker's whole-picker minimum tracks its opening width. Preview
// pickers keep the standard per-pane minimums.
if self.preview.is_none()
&& let Some(rems) = width.as_rems()
{
self.size_bounds.min_results.width = rems;
}
self
}
/// Sets the minimum width, the picker can not be resized smaller then this.
/// Leave unset to use sane defaults.
///
/// This applies to the results. If there is no preview that is the whole picker.
pub fn minimum_results_width(mut self, width: impl Into<Rems>) -> Self {
self.size_bounds.min_results.width = width.into();
self
}
/// Sets the width the picker appears with if the user has never resized it
/// or when the user sets it back to it's default size.
///
/// # Padding
/// By default the picker will fill this space. If you want it to only grow
/// as large as it needs and treat the height as a bound use
/// [`no_vertical_padding`]
pub fn height(mut self, height: impl Into<RelativeHeight>) -> Self {
/// Overrides the picker's max height. Plain pickers default to
/// [`DEFAULT_MODAL_MAX_HEIGHT`] and shrink below it to fit their content;
/// only call this for pickers that want a different cap (e.g. the outline
/// view, which is taller).
pub fn max_height(mut self, height: impl Into<RelativeHeight>) -> Self {
let height = height.into();
self.default_shape.height = height;
if !self.shape_loaded_from_persistence {
@ -549,23 +597,12 @@ impl<D: PickerDelegate> Picker<D> {
self
}
/// Makes the picker shrink to fit its content rather than padding out to its
/// full height when there are fewer results than fit.
pub fn no_vertical_padding(mut self) -> Self {
self.vertical_padding = shape::VerticalPadding::None;
self
}
fn vertical_padding(&self) -> shape::VerticalPadding {
let preview_visible = self
.preview
/// Whether the picker fills its full height (preview visible) or shrinks to
/// fit its content, treating the height as a maximum (no preview visible).
fn fill_height(&self) -> bool {
self.preview
.as_ref()
.is_some_and(|preview| preview.layout != preview::Layout::Hidden);
if preview_visible {
shape::VerticalPadding::Pad
} else {
self.vertical_padding
}
.is_some_and(|preview| preview.layout != preview::Layout::Hidden)
}
pub fn show_scrollbar(mut self, show_scrollbar: bool) -> Self {
@ -573,11 +610,50 @@ impl<D: PickerDelegate> Picker<D> {
self
}
pub fn modal(mut self, modal: bool) -> Self {
self.is_modal = modal;
/// Presents the picker as embedded inside a larger container that provides
/// its own chrome and dismissal, rather than the default self-contained
/// modal. Use [`popover`](Self::popover) instead for menu-attached pickers.
pub fn embedded(mut self) -> Self {
self.presentation = Presentation::Embedded;
self
}
/// Presents the picker as a popover surface: it draws its own elevated
/// background (like a modal) but is not resizable. Use this for pickers
/// shown inside a menu/popover.
pub fn popover(mut self) -> Self {
self.set_popover();
self
}
pub(crate) fn set_popover(&mut self) {
self.presentation = Presentation::Popover;
}
/// Controls whether the user can drag to resize the picker (and whether its
/// size is persisted). Only applies to modal pickers; no-op otherwise.
/// Defaults to `true` for pickers with a preview and `false` otherwise.
pub fn resizable(mut self, resizable: bool) -> Self {
if let Presentation::Modal { resizable: r } = &mut self.presentation {
*r = resizable;
}
self
}
/// Whether the picker draws its own elevated background and dismisses on
/// blur (modals and popovers, but not embedded pickers).
fn draws_own_container(&self) -> bool {
matches!(
self.presentation,
Presentation::Modal { .. } | Presentation::Popover
)
}
/// Whether the picker can be resized (only ever true for modals).
fn is_resizable(&self) -> bool {
matches!(self.presentation, Presentation::Modal { resizable: true })
}
pub fn list_measure_all(mut self) -> Self {
match self.element_container {
ElementContainer::List(state) => {
@ -888,7 +964,7 @@ impl<D: PickerDelegate> Picker<D> {
let menu_focused = self.actions_menu_handle.is_focused(window, cx)
|| self.actions_menu_handle.is_deployed()
|| self.delegate.has_another_open_menu(window, cx);
if self.is_modal && window.is_window_active() && !menu_focused {
if self.draws_own_container() && window.is_window_active() && !menu_focused {
self.cancel(&menu::Cancel, window, cx);
}
}
@ -1107,12 +1183,13 @@ impl<D: PickerDelegate> Picker<D> {
}
fn render_element_container(&self, cx: &mut Context<Self>) -> impl IntoElement {
// When the picker shrinks to fit content (`None`), the list infers its
// size from its items. When the picker pads to its full height (`Pad`),
// the list fills the available space.
let sizing_behavior = match self.vertical_padding() {
shape::VerticalPadding::None => ListSizingBehavior::Infer,
shape::VerticalPadding::Pad => ListSizingBehavior::Auto,
// When the picker shrinks to fit its content, the list infers its size
// from its items. When it fills its full height (preview visible), the
// list fills the available space.
let sizing_behavior = if self.fill_height() {
ListSizingBehavior::Auto
} else {
ListSizingBehavior::Infer
};
match &self.element_container {
@ -1179,12 +1256,17 @@ impl<D: PickerDelegate> Picker<D> {
return;
};
preview.layout = layout;
if let Some(previously_resized) = persistence::try_load_shape(D::name(), layout, cx)
// Restore the size the user last left this layout at, or fall back to the
// layout's default (simple when hidden, larger when a preview is shown).
self.shape = persistence::try_load_shape(D::name(), layout, cx)
.log_err()
.flatten()
{
self.shape = previously_resized;
}
.unwrap_or_else(|| {
shape::Shape::HorizontallyCentered(default_shape_for_layout(
self.default_shape,
layout,
))
});
self.delegate
.preview_layout_changed(matches!(layout, preview::Layout::Right));
cx.notify();
@ -1366,3 +1448,4 @@ mod tests {
impl<D: PickerDelegate> EventEmitter<DismissEvent> for Picker<D> {}
impl<D: PickerDelegate> ModalView for Picker<D> {}
impl<D: PickerDelegate> ui::FluentBuilder for Picker<D> {}

View file

@ -36,7 +36,7 @@ where
anchor: Anchor,
cx: &mut App,
) -> Self {
picker.update(cx, |picker, _| picker.is_modal = false);
picker.update(cx, |picker, _| picker.set_popover());
Self {
_subscriptions: vec![cx.subscribe(&picker, |picker, &DismissEvent, cx| {
picker.update(cx, |_, cx| cx.emit(DismissEvent));

View file

@ -37,7 +37,6 @@ impl EditorPreview {
if let Some(message) = &self.message {
self.render_message(message, cx).into_any_element()
} else {
debug_assert!(!self.preview_editor.read(cx).is_empty(cx));
div()
.flex_1()
.overflow_hidden()

View file

@ -54,22 +54,23 @@ impl<D: PickerDelegate> Render for Picker<D> {
// off.
let has_preview = self.preview.is_some();
let content = div()
.when(self.is_modal, |this| this.elevation_3(cx))
.when(self.draws_own_container(), |this| this.elevation_3(cx))
.when(has_preview, |this| this.overflow_hidden())
.child(content);
let layout = self.preview_layout().unwrap_or(Layout::Hidden);
// Embedded pickers are not resizable
div().relative().child(content).when(self.is_modal, |this| {
// While resizing offset the (parent-centered) container
this.left(self.shape.horizontal_offset(window))
.child(self.render_resize(Left, window, cx))
.child(self.render_resize(Right(layout), window, cx))
.child(self.render_resize(Bottom(layout), window, cx))
.child(self.render_resize(LeftCorner(layout), window, cx))
.child(self.render_resize(RightCorner(layout), window, cx))
})
div()
.relative()
.child(content)
.when(self.is_resizable(), |this| {
this.left(self.shape.horizontal_offset(window))
.child(self.render_resize(Left, window, cx))
.child(self.render_resize(Right(layout), window, cx))
.child(self.render_resize(Bottom(layout), window, cx))
.child(self.render_resize(LeftCorner(layout), window, cx))
.child(self.render_resize(RightCorner(layout), window, cx))
})
}
}
@ -95,7 +96,7 @@ impl<D: PickerDelegate> Picker<D> {
self.shape.apply_results_size(
self.preview_layout(),
&self.size_bounds,
self.vertical_padding(),
self.fill_height(),
this,
window,
)
@ -154,7 +155,7 @@ impl<D: PickerDelegate> Picker<D> {
.when_some(
self.shape.results_max_height(
&self.size_bounds,
self.vertical_padding(),
self.fill_height(),
window,
),
|this, max_height| this.max_h(max_height),
@ -300,7 +301,9 @@ impl<D: PickerDelegate> Picker<D> {
.child(preview.render(cx)),
),
)
.child(self.render_resize(window_controls::Middle(preview.layout), window, cx))
.when(self.is_resizable(), |this| {
this.child(self.render_resize(window_controls::Middle(preview.layout), window, cx))
})
}
pub(crate) fn render_with_preview_right(
@ -342,7 +345,9 @@ impl<D: PickerDelegate> Picker<D> {
.child(preview.render(cx)),
),
)
.child(self.render_resize(Middle(Layout::Right), window, cx))
.when(self.is_resizable(), |this| {
this.child(self.render_resize(Middle(Layout::Right), window, cx))
})
}
fn finish_any_completed_resize(

View file

@ -72,6 +72,13 @@ macro_rules! relative_size {
}
}
/// Returns this size as [`Rems`] when it has no viewport-relative
/// component. Used to derive a rems-based minimum from an initial
/// size without needing a [`Window`].
pub fn as_rems(&self) -> Option<Rems> {
(self.viewport_fraction == 0.0).then_some(self.rems)
}
pub fn as_viewport_fraction(&self, window: &Window) -> ViewportFraction {
ViewportFraction(
self.viewport_fraction
@ -181,15 +188,6 @@ impl std::ops::Mul<f32> for ViewportFraction {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum VerticalPadding {
/// The picker always fills its height even if there are no results
#[default]
Pad,
/// Picker might be shorter then it's height if there is not enough to display
None,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Centered {
pub(crate) width: RelativeWidth,
@ -197,6 +195,19 @@ pub(crate) struct Centered {
pub(crate) preview_size: ViewportFraction,
}
impl Centered {
/// The default size for a plain picker (no preview): a fixed standard width
/// and a standard *max* height that the picker shrinks below when it has
/// little content.
pub(crate) fn simple() -> Self {
Centered {
width: RelativeWidth::rems(crate::DEFAULT_MODAL_WIDTH),
height: RelativeHeight::rems(crate::DEFAULT_MODAL_MAX_HEIGHT),
preview_size: ViewportFraction::ZERO,
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Shape {
Resizing(PositionAndShape),
@ -211,7 +222,6 @@ pub(crate) enum Shape {
pub struct SizeBounds {
pub(crate) max_width: RelativeWidth,
pub(crate) max_height: RelativeHeight,
/// Minimum size of the results pane.
pub(crate) min_results: Size<Rems>,
/// Minimum size of the preview pane. Along the split axis this only needs to
/// be large enough to grab the divider and shrink it back.
@ -487,7 +497,7 @@ impl Shape {
&self,
layout: impl Into<Option<Layout>>,
bounds: &SizeBounds,
vertical_padding: VerticalPadding,
fill_height: bool,
div: Div,
window: &Window,
) -> Div {
@ -500,29 +510,27 @@ impl Shape {
_ => full.right - full.left,
};
let div = div.w(width);
match vertical_padding {
VerticalPadding::None => div,
VerticalPadding::Pad => {
let height = match layout {
Some(Layout::Below) => (full.bottom - full.top) - full.preview,
_ => full.bottom - full.top,
};
div.h(height)
}
if fill_height {
let height = match layout {
Some(Layout::Below) => (full.bottom - full.top) - full.preview,
_ => full.bottom - full.top,
};
div.h(height)
} else {
div
}
}
pub(crate) fn results_max_height(
&self,
bounds: &SizeBounds,
vertical_padding: VerticalPadding,
fill_height: bool,
window: &Window,
) -> Option<Pixels> {
match vertical_padding {
// No preview when results size themselves (no vertical padding), so
// clamp against the results-only minimum.
VerticalPadding::None => Some(self.height(None, bounds, window)),
VerticalPadding::Pad => None,
if fill_height {
None
} else {
Some(self.height(None, bounds, window))
}
}

View file

@ -2,7 +2,7 @@ use editor::{Bias, Editor, SelectionEffects, scroll::Autoscroll, styled_runs_for
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
App, Context, DismissEvent, Entity, HighlightStyle, ParentElement, StyledText, Task, TaskExt,
TextStyle, WeakEntity, Window, relative, rems,
TextStyle, WeakEntity, Window, relative,
};
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate};
@ -26,7 +26,7 @@ pub fn init(cx: &mut App) {
let handle = cx.entity().downgrade();
workspace.toggle_modal(window, cx, move |window, cx| {
let delegate = ProjectSymbolsDelegate::new(handle, project);
Picker::uniform_list(delegate, window, cx).minimum_results_width(rems(34.))
Picker::uniform_list(delegate, window, cx)
})
},
);

View file

@ -632,9 +632,7 @@ impl RecentProjects {
let picker = cx.new(|cx| {
Picker::list(delegate, window, cx)
.list_measure_all()
.minimum_results_width(rems(rem_width))
.height(rems(24.))
.no_vertical_padding()
.initial_width(rems(rem_width))
.show_scrollbar(true)
});
@ -2573,8 +2571,7 @@ mod tests {
Picker::list(delegate, window, cx)
.list_measure_all()
.show_scrollbar(true)
.height(Rems::from_pixels(px(240.0), window))
.no_vertical_padding()
.max_height(Rems::from_pixels(px(240.0), window))
});
draw(cx);
(picker, cx)

View file

@ -132,7 +132,7 @@ impl AddWslDistro {
use crate::wsl_picker::{WslDistroSelected, WslPickerDelegate, WslPickerDismissed};
let delegate = WslPickerDelegate::new();
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded());
cx.subscribe_in(
&picker,
@ -398,12 +398,7 @@ impl ProjectPicker {
let delegate = open_path_prompt::OpenPathDelegate::new(tx, lister, false, cx).show_hidden();
let picker = cx.new(|cx| {
let picker = Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
.modal(false);
let picker = Picker::uniform_list(delegate, window, cx).embedded();
picker.set_query(&home_dir.to_string(), window, cx);
picker
});
@ -1457,7 +1452,7 @@ impl RemoteServerProjects {
if configs.len() > 1 {
let delegate = DevContainerPickerDelegate::new(configs, cx.weak_entity());
this.dev_container_picker =
Some(cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)));
Some(cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded()));
} else if let Some(context) = dev_container_context {
let config = configs.into_iter().next();
this.open_dev_container(config, app_state, context, window, cx);
@ -1506,10 +1501,7 @@ impl RemoteServerProjects {
true,
cx,
);
Picker::list(delegate, window, cx)
.modal(false)
.initial_width(rems(34.))
.height(rems(24.))
Picker::list(delegate, window, cx).embedded()
});
let mut read_ssh_config = RemoteSettings::get_global(cx).read_ssh_config;
let ssh_config_updates = if read_ssh_config {
@ -2217,7 +2209,7 @@ impl RemoteServerProjects {
if configs.len() > 1 {
let delegate = DevContainerPickerDelegate::new(configs, cx.weak_entity());
self.dev_container_picker =
Some(cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false)));
Some(cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded()));
let state =
CreateRemoteDevContainer::new(DevContainerCreationProgress::SelectingConfig, cx);

View file

@ -56,8 +56,7 @@ impl SidebarRecentProjects {
.list_measure_all()
.show_scrollbar(true)
.initial_width(rems(18.))
.minimum_results_width(rems(18.))
.modal(false)
.popover()
});
let picker_focus_handle = picker.focus_handle(cx);

View file

@ -203,7 +203,7 @@ impl WslOpenModal {
cx: &mut Context<Self>,
) -> Self {
let delegate = WslPickerDelegate::new();
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).embedded());
let selected = cx.subscribe_in(
&picker,

View file

@ -481,10 +481,7 @@ where
let picker_view = cx.new(|cx| {
Picker::list(delegate, window, cx)
.list_measure_all()
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
.modal(false)
.popover()
});
PopoverMenu::new("kernel-switcher")

View file

@ -9,7 +9,7 @@ use picker::Picker;
use project::ProjectPath;
use text::Anchor;
use ui::{Rems, Window};
use ui::Window;
use workspace::{DismissDecision, ModalView, Workspace};
mod delegate;
@ -223,10 +223,7 @@ impl TextFinder {
fn new(delegate: Delegate, window: &mut Window, cx: &mut Context<Self>) -> Self {
let project = delegate.project(cx).clone();
let picker = cx.new(|cx| {
Picker::list_with_preview(delegate, project, window, cx)
.minimum_results_width(Rems(20.0))
});
let picker = cx.new(|cx| Picker::list_with_preview(delegate, project, window, cx));
let picker_weak = picker.downgrade();
let picker_focus_handle = picker.focus_handle(cx);
picker.update(cx, |picker, cx| {

View file

@ -612,7 +612,7 @@ impl PickerDelegate for Delegate {
),
picker::PickerAction::separator(),
picker::PickerAction::button("Open File", menu::Confirm.boxed_clone()),
picker::PickerAction::button("To project search", super::ToProjectSearch.boxed_clone()),
picker::PickerAction::button("Open as Tab", super::ToProjectSearch.boxed_clone()),
]
}

View file

@ -52,13 +52,8 @@ impl SettingsProfileSelector {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(22.))
.minimum_results_width(rems(22.))
.height(rems(24.))
.no_vertical_padding()
});
let picker =
cx.new(|cx| Picker::uniform_list(delegate, window, cx).initial_width(rems(22.)));
Self { picker }
}
}

View file

@ -180,8 +180,7 @@ pub fn font_picker(
Picker::uniform_list(delegate, window, cx)
.show_scrollbar(true)
.minimum_results_width(rems_from_px(210.))
.height(rems(18.))
.no_vertical_padding()
.modal(false)
.initial_width(rems_from_px(210.))
.max_height(rems(18.))
.popover()
}

View file

@ -193,8 +193,7 @@ pub fn icon_theme_picker(
Picker::uniform_list(delegate, window, cx)
.show_scrollbar(true)
.minimum_results_width(rems_from_px(210.))
.height(rems(18.))
.no_vertical_padding()
.modal(false)
.initial_width(rems_from_px(210.))
.max_height(rems(18.))
.popover()
}

View file

@ -204,10 +204,9 @@ pub fn render_ollama_model_picker(
Picker::uniform_list(delegate, window, cx)
.show_scrollbar(true)
.minimum_results_width(rems_from_px(210.))
.height(rems(18.))
.no_vertical_padding()
.modal(false)
.initial_width(rems_from_px(210.))
.max_height(rems(18.))
.popover()
}))
})
.anchor(gpui::Anchor::TopLeft)

View file

@ -178,8 +178,7 @@ pub fn theme_picker(
Picker::uniform_list(delegate, window, cx)
.show_scrollbar(true)
.minimum_results_width(rems_from_px(210.))
.height(rems(18.))
.no_vertical_padding()
.modal(false)
.initial_width(rems_from_px(210.))
.max_height(rems(18.))
.popover()
}

View file

@ -111,13 +111,7 @@ impl ScopeSelector {
let delegate =
ScopeSelectorDelegate::new(workspace, cx.entity().downgrade(), language_registry);
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}

View file

@ -177,7 +177,6 @@ impl TabSwitcher {
Picker::nonsearchable_list(delegate, window, cx)
}
.initial_width(rems(PANEL_WIDTH_REMS))
.minimum_results_width(rems(PANEL_WIDTH_REMS))
}),
init_modifiers,
}

View file

@ -6,7 +6,7 @@ use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
Action, AnyElement, App, AppContext as _, Context, DismissEvent, Entity, EventEmitter,
Focusable, InteractiveElement, ParentElement, Render, Styled, Subscription, Task, WeakEntity,
Window, rems,
Window,
};
use itertools::Itertools;
use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};
@ -148,11 +148,7 @@ impl TasksModal {
window,
cx,
)
.modal(is_modal)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
.height(rems(24.))
.no_vertical_padding()
.when(!is_modal, |picker| picker.embedded())
});
let mut _subscriptions = [
cx.subscribe(&picker, |_, _, _: &DismissEvent, cx| {

View file

@ -45,11 +45,7 @@ impl IconThemeSelector {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}

View file

@ -119,11 +119,7 @@ impl ThemeSelector {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let picker = cx.new(|cx| {
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
});
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}

View file

@ -112,9 +112,7 @@ impl AddToolchainState {
let (lister, rx) = Self::create_path_browser_delegate(project.clone(), cx);
let path_style = project.read(cx).path_style(cx);
let picker = cx.new(|cx| {
let picker = Picker::uniform_list(lister, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.));
let picker = Picker::uniform_list(lister, window, cx);
let mut worktree_root = worktree_root_path.to_string_lossy().into_owned();
worktree_root.push_str(path_style.primary_separator());
picker.set_query(&worktree_root, window, cx);
@ -234,9 +232,7 @@ impl AddToolchainState {
let (delegate, rx) =
Self::create_path_browser_delegate(this.project.clone(), cx);
picker.update(cx, |picker, cx| {
*picker = Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.));
*picker = Picker::uniform_list(delegate, window, cx);
picker.set_query(path.to_string_lossy().as_ref(), window, cx);
});
*input_state = Self::wait_for_path(rx, window, cx);
@ -688,8 +684,6 @@ impl ToolchainSelector {
cx,
);
Picker::uniform_list(delegate, window, cx)
.initial_width(rems(34.))
.minimum_results_width(rems(34.))
});
let picker_focus_handle = picker.focus_handle(cx);
picker.update(cx, |picker, _| {

View file

@ -1433,9 +1433,7 @@ impl RegistersView {
matches,
};
Picker::nonsearchable_uniform_list(delegate, window, cx)
.initial_width(rems(36.))
.modal(true)
Picker::nonsearchable_uniform_list(delegate, window, cx).initial_width(rems(36.))
}
}
@ -1800,9 +1798,7 @@ impl MarksView {
matches,
workspace,
};
Picker::nonsearchable_uniform_list(delegate, window, cx)
.initial_width(rems(36.))
.modal(true)
Picker::nonsearchable_uniform_list(delegate, window, cx).initial_width(rems(36.))
}
}