settings_ui: Add pickers for theme and icon themes (#40829)
In the process of adding pickers for the theme and icon themes fields in the settings UI, I felt like there was an improvement opportunity in regards to where some of these components are stored. The `ui_input` crate originally was meant only for the text field-like component, which couldn't be in the regular `ui` crate due to the dependency with `editor`. Given we had also added the number field there—which is similar in also having the same dependency—it made sense to think of this crate more like a home for form-like components rather than for only one component. However, we were also storing some settings UI-specific stuff in that crate, which didn't feel right. So I ended up creating a new directory within the `settings_ui` for components and moved all the pickers and the custom input field there. I think this makes it for a cleaner structure. Release Notes: - settings_ui: Added the ability to search for theme and icon themes in their respective fields.
This commit is contained in:
Generated
+1
-3
@@ -10788,7 +10788,6 @@ dependencies = [
|
||||
"telemetry",
|
||||
"theme",
|
||||
"ui",
|
||||
"ui_input",
|
||||
"util",
|
||||
"vim_mode_setting",
|
||||
"workspace",
|
||||
@@ -15271,6 +15270,7 @@ dependencies = [
|
||||
"menu",
|
||||
"node_runtime",
|
||||
"paths",
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"schemars 1.0.4",
|
||||
@@ -18191,10 +18191,8 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"component",
|
||||
"editor",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"menu",
|
||||
"picker",
|
||||
"settings",
|
||||
"theme",
|
||||
"ui",
|
||||
|
||||
@@ -10,7 +10,7 @@ use settings::{OpenAiCompatibleSettingsContent, update_settings_file};
|
||||
use ui::{
|
||||
Banner, Checkbox, KeyBinding, Modal, ModalFooter, ModalHeader, Section, ToggleState, prelude::*,
|
||||
};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use workspace::{ModalView, Workspace};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -33,9 +33,9 @@ impl LlmCompatibleProvider {
|
||||
}
|
||||
|
||||
struct AddLlmProviderInput {
|
||||
provider_name: Entity<SingleLineInput>,
|
||||
api_url: Entity<SingleLineInput>,
|
||||
api_key: Entity<SingleLineInput>,
|
||||
provider_name: Entity<InputField>,
|
||||
api_url: Entity<InputField>,
|
||||
api_key: Entity<InputField>,
|
||||
models: Vec<ModelInput>,
|
||||
}
|
||||
|
||||
@@ -76,10 +76,10 @@ struct ModelCapabilityToggles {
|
||||
}
|
||||
|
||||
struct ModelInput {
|
||||
name: Entity<SingleLineInput>,
|
||||
max_completion_tokens: Entity<SingleLineInput>,
|
||||
max_output_tokens: Entity<SingleLineInput>,
|
||||
max_tokens: Entity<SingleLineInput>,
|
||||
name: Entity<InputField>,
|
||||
max_completion_tokens: Entity<InputField>,
|
||||
max_output_tokens: Entity<InputField>,
|
||||
max_tokens: Entity<InputField>,
|
||||
capabilities: ModelCapabilityToggles,
|
||||
}
|
||||
|
||||
@@ -171,9 +171,9 @@ fn single_line_input(
|
||||
text: Option<&str>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<SingleLineInput> {
|
||||
) -> Entity<InputField> {
|
||||
cx.new(|cx| {
|
||||
let input = SingleLineInput::new(window, cx, placeholder).label(label);
|
||||
let input = InputField::new(window, cx, placeholder).label(label);
|
||||
if let Some(text) = text {
|
||||
input
|
||||
.editor()
|
||||
@@ -757,12 +757,7 @@ mod tests {
|
||||
models: Vec<(&str, &str, &str, &str)>,
|
||||
cx: &mut VisualTestContext,
|
||||
) -> Option<SharedString> {
|
||||
fn set_text(
|
||||
input: &Entity<SingleLineInput>,
|
||||
text: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
fn set_text(input: &Entity<InputField>, text: &str, window: &mut Window, cx: &mut App) {
|
||||
input.update(cx, |input, cx| {
|
||||
input.editor().update(cx, |editor, cx| {
|
||||
editor.set_text(text, window, cx);
|
||||
|
||||
@@ -32,7 +32,7 @@ use ui::{
|
||||
SharedString, Styled as _, Table, TableColumnWidths, TableInteractionState,
|
||||
TableResizeBehavior, Tooltip, Window, prelude::*,
|
||||
};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _,
|
||||
@@ -2114,7 +2114,7 @@ struct KeybindingEditorModal {
|
||||
editing_keybind: ProcessedBinding,
|
||||
editing_keybind_idx: usize,
|
||||
keybind_editor: Entity<KeystrokeInput>,
|
||||
context_editor: Entity<SingleLineInput>,
|
||||
context_editor: Entity<InputField>,
|
||||
action_arguments_editor: Option<Entity<ActionArgumentsEditor>>,
|
||||
fs: Arc<dyn Fs>,
|
||||
error: Option<InputError>,
|
||||
@@ -2148,8 +2148,8 @@ impl KeybindingEditorModal {
|
||||
let keybind_editor = cx
|
||||
.new(|cx| KeystrokeInput::new(editing_keybind.keystrokes().map(Vec::from), window, cx));
|
||||
|
||||
let context_editor: Entity<SingleLineInput> = cx.new(|cx| {
|
||||
let input = SingleLineInput::new(window, cx, "Keybinding Context")
|
||||
let context_editor: Entity<InputField> = cx.new(|cx| {
|
||||
let input = InputField::new(window, cx, "Keybinding Context")
|
||||
.label("Edit Context")
|
||||
.label_size(LabelSize::Default);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::str::FromStr;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{Icon, IconName, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use zed_env_vars::{EnvVar, env_var};
|
||||
|
||||
@@ -823,7 +823,7 @@ fn convert_usage(usage: &Usage) -> language_model::TokenUsage {
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
target_agent: ConfigurationViewTargetAgent,
|
||||
@@ -862,7 +862,7 @@ impl ConfigurationView {
|
||||
}));
|
||||
|
||||
Self {
|
||||
api_key_editor: cx.new(|cx| SingleLineInput::new(window, cx, Self::PLACEHOLDER_TEXT)),
|
||||
api_key_editor: cx.new(|cx| InputField::new(window, cx, Self::PLACEHOLDER_TEXT)),
|
||||
state,
|
||||
load_credentials_task,
|
||||
target_agent,
|
||||
|
||||
@@ -42,7 +42,7 @@ use settings::{BedrockAvailableModel as AvailableModel, Settings, SettingsStore}
|
||||
use smol::lock::OnceCell;
|
||||
use strum::{EnumIter, IntoEnumIterator, IntoStaticStr};
|
||||
use ui::{Icon, IconName, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::AllLanguageModelSettings;
|
||||
@@ -1006,10 +1006,10 @@ pub fn map_to_language_model_completion_events(
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
access_key_id_editor: Entity<SingleLineInput>,
|
||||
secret_access_key_editor: Entity<SingleLineInput>,
|
||||
session_token_editor: Entity<SingleLineInput>,
|
||||
region_editor: Entity<SingleLineInput>,
|
||||
access_key_id_editor: Entity<InputField>,
|
||||
secret_access_key_editor: Entity<InputField>,
|
||||
session_token_editor: Entity<InputField>,
|
||||
region_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
@@ -1047,20 +1047,19 @@ impl ConfigurationView {
|
||||
|
||||
Self {
|
||||
access_key_id_editor: cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, Self::PLACEHOLDER_ACCESS_KEY_ID_TEXT)
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_ACCESS_KEY_ID_TEXT)
|
||||
.label("Access Key ID")
|
||||
}),
|
||||
secret_access_key_editor: cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, Self::PLACEHOLDER_SECRET_ACCESS_KEY_TEXT)
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_SECRET_ACCESS_KEY_TEXT)
|
||||
.label("Secret Access Key")
|
||||
}),
|
||||
session_token_editor: cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, Self::PLACEHOLDER_SESSION_TOKEN_TEXT)
|
||||
InputField::new(window, cx, Self::PLACEHOLDER_SESSION_TOKEN_TEXT)
|
||||
.label("Session Token (Optional)")
|
||||
}),
|
||||
region_editor: cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, Self::PLACEHOLDER_REGION).label("Region")
|
||||
}),
|
||||
region_editor: cx
|
||||
.new(|cx| InputField::new(window, cx, Self::PLACEHOLDER_REGION).label("Region")),
|
||||
state,
|
||||
load_credentials_task,
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::str::FromStr;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use ui::{Icon, IconName, List, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use zed_env_vars::{EnvVar, env_var};
|
||||
|
||||
@@ -525,7 +525,7 @@ impl DeepSeekEventMapper {
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
@@ -533,7 +533,7 @@ struct ConfigurationView {
|
||||
impl ConfigurationView {
|
||||
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_editor =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "sk-00000000000000000000000000000000"));
|
||||
cx.new(|cx| InputField::new(window, cx, "sk-00000000000000000000000000000000"));
|
||||
|
||||
cx.observe(&state, |_, _, cx| {
|
||||
cx.notify();
|
||||
|
||||
@@ -29,7 +29,7 @@ use std::sync::{
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{Icon, IconName, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use zed_env_vars::EnvVar;
|
||||
|
||||
@@ -751,7 +751,7 @@ fn convert_usage(usage: &UsageMetadata) -> language_model::TokenUsage {
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
target_agent: language_model::ConfigurationViewTargetAgent,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
@@ -788,7 +788,7 @@ impl ConfigurationView {
|
||||
}));
|
||||
|
||||
Self {
|
||||
api_key_editor: cx.new(|cx| SingleLineInput::new(window, cx, "AIzaSy...")),
|
||||
api_key_editor: cx.new(|cx| InputField::new(window, cx, "AIzaSy...")),
|
||||
target_agent,
|
||||
state,
|
||||
load_credentials_task,
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::str::FromStr;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{Icon, IconName, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use zed_env_vars::{EnvVar, env_var};
|
||||
|
||||
@@ -744,8 +744,8 @@ struct RawToolCall {
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
codestral_api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
codestral_api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
@@ -753,9 +753,9 @@ struct ConfigurationView {
|
||||
impl ConfigurationView {
|
||||
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_editor =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "0aBCDEFGhIjKLmNOpqrSTUVwxyzabCDE1f2"));
|
||||
cx.new(|cx| InputField::new(window, cx, "0aBCDEFGhIjKLmNOpqrSTUVwxyzabCDE1f2"));
|
||||
let codestral_api_key_editor =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "0aBCDEFGhIjKLmNOpqrSTUVwxyzabCDE1f2"));
|
||||
cx.new(|cx| InputField::new(window, cx, "0aBCDEFGhIjKLmNOpqrSTUVwxyzabCDE1f2"));
|
||||
|
||||
cx.observe(&state, |_, _, cx| {
|
||||
cx.notify();
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::sync::LazyLock;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use ui::{ButtonLike, ElevationIndex, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use zed_env_vars::{EnvVar, env_var};
|
||||
|
||||
use crate::AllLanguageModelSettings;
|
||||
@@ -623,18 +623,17 @@ fn map_to_language_model_completion_events(
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_url_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
api_url_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
}
|
||||
|
||||
impl ConfigurationView {
|
||||
pub fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_editor =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "63e02e...").label("API key"));
|
||||
let api_key_editor = cx.new(|cx| InputField::new(window, cx, "63e02e...").label("API key"));
|
||||
|
||||
let api_url_editor = cx.new(|cx| {
|
||||
let input = SingleLineInput::new(window, cx, OLLAMA_API_URL).label("API URL");
|
||||
let input = InputField::new(window, cx, OLLAMA_API_URL).label("API URL");
|
||||
input.set_text(OllamaLanguageModelProvider::api_url(cx), window, cx);
|
||||
input
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::str::FromStr as _;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{ElevationIndex, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use zed_env_vars::{EnvVar, env_var};
|
||||
|
||||
@@ -675,7 +675,7 @@ pub fn count_open_ai_tokens(
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
@@ -683,7 +683,7 @@ struct ConfigurationView {
|
||||
impl ConfigurationView {
|
||||
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_editor = cx.new(|cx| {
|
||||
SingleLineInput::new(
|
||||
InputField::new(
|
||||
window,
|
||||
cx,
|
||||
"sk-000000000000000000000000000000000000000000000000",
|
||||
|
||||
@@ -14,7 +14,7 @@ use open_ai::{ResponseStreamEvent, stream_completion};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
use ui::{ElevationIndex, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use zed_env_vars::EnvVar;
|
||||
|
||||
@@ -340,7 +340,7 @@ impl LanguageModel for OpenAiCompatibleLanguageModel {
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
@@ -348,7 +348,7 @@ struct ConfigurationView {
|
||||
impl ConfigurationView {
|
||||
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_editor = cx.new(|cx| {
|
||||
SingleLineInput::new(
|
||||
InputField::new(
|
||||
window,
|
||||
cx,
|
||||
"000000000000000000000000000000000000000000000000000",
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::pin::Pin;
|
||||
use std::str::FromStr as _;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use ui::{Icon, IconName, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use zed_env_vars::{EnvVar, env_var};
|
||||
|
||||
@@ -692,7 +692,7 @@ pub fn count_open_router_tokens(
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
@@ -700,7 +700,7 @@ struct ConfigurationView {
|
||||
impl ConfigurationView {
|
||||
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_editor = cx.new(|cx| {
|
||||
SingleLineInput::new(
|
||||
InputField::new(
|
||||
window,
|
||||
cx,
|
||||
"sk_or_000000000000000000000000000000000000000000000000",
|
||||
|
||||
@@ -15,7 +15,7 @@ use settings::{Settings, SettingsStore};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{ElevationIndex, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use vercel::{Model, VERCEL_API_URL};
|
||||
use zed_env_vars::{EnvVar, env_var};
|
||||
@@ -362,7 +362,7 @@ pub fn count_vercel_tokens(
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
@@ -370,7 +370,7 @@ struct ConfigurationView {
|
||||
impl ConfigurationView {
|
||||
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_editor = cx.new(|cx| {
|
||||
SingleLineInput::new(
|
||||
InputField::new(
|
||||
window,
|
||||
cx,
|
||||
"v1:0000000000000000000000000000000000000000000000000",
|
||||
|
||||
@@ -15,7 +15,7 @@ use settings::{Settings, SettingsStore};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::{ElevationIndex, List, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, truncate_and_trailoff};
|
||||
use x_ai::{Model, XAI_API_URL};
|
||||
use zed_env_vars::{EnvVar, env_var};
|
||||
@@ -359,7 +359,7 @@ pub fn count_xai_tokens(
|
||||
}
|
||||
|
||||
struct ConfigurationView {
|
||||
api_key_editor: Entity<SingleLineInput>,
|
||||
api_key_editor: Entity<InputField>,
|
||||
state: Entity<State>,
|
||||
load_credentials_task: Option<Task<()>>,
|
||||
}
|
||||
@@ -367,7 +367,7 @@ struct ConfigurationView {
|
||||
impl ConfigurationView {
|
||||
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let api_key_editor = cx.new(|cx| {
|
||||
SingleLineInput::new(
|
||||
InputField::new(
|
||||
window,
|
||||
cx,
|
||||
"xai-0000000000000000000000000000000000000000000000000",
|
||||
|
||||
@@ -34,7 +34,6 @@ settings.workspace = true
|
||||
telemetry.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
ui_input.workspace = true
|
||||
util.workspace = true
|
||||
vim_mode_setting.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
@@ -17,7 +17,6 @@ use ui::{
|
||||
Divider, KeyBinding, ParentElement as _, StatefulInteractiveElement, Vector, VectorName,
|
||||
WithScrollbar as _, prelude::*, rems_from_px,
|
||||
};
|
||||
pub use ui_input::font_picker;
|
||||
use workspace::{
|
||||
AppState, Workspace, WorkspaceId,
|
||||
dock::DockPosition,
|
||||
|
||||
@@ -26,6 +26,7 @@ fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
schemars.workspace = true
|
||||
search.workspace = true
|
||||
|
||||
@@ -1,96 +1,9 @@
|
||||
use editor::Editor;
|
||||
use gpui::{Focusable, div};
|
||||
use ui::{
|
||||
ActiveTheme as _, App, FluentBuilder as _, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, RenderOnce, Styled as _, Window,
|
||||
};
|
||||
mod font_picker;
|
||||
mod icon_theme_picker;
|
||||
mod input_field;
|
||||
mod theme_picker;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct SettingsEditor {
|
||||
initial_text: Option<String>,
|
||||
placeholder: Option<&'static str>,
|
||||
confirm: Option<Box<dyn Fn(Option<String>, &mut App)>>,
|
||||
tab_index: Option<isize>,
|
||||
}
|
||||
|
||||
impl SettingsEditor {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
initial_text: None,
|
||||
placeholder: None,
|
||||
confirm: None,
|
||||
tab_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_initial_text(mut self, initial_text: String) -> Self {
|
||||
self.initial_text = Some(initial_text);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_placeholder(mut self, placeholder: &'static str) -> Self {
|
||||
self.placeholder = Some(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_confirm(mut self, confirm: impl Fn(Option<String>, &mut App) + 'static) -> Self {
|
||||
self.confirm = Some(Box::new(confirm));
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn tab_index(mut self, arg: isize) -> Self {
|
||||
self.tab_index = Some(arg);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for SettingsEditor {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
|
||||
let editor = window.use_state(cx, {
|
||||
move |window, cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
if let Some(text) = self.initial_text {
|
||||
editor.set_text(text, window, cx);
|
||||
}
|
||||
|
||||
if let Some(placeholder) = self.placeholder {
|
||||
editor.set_placeholder_text(placeholder, window, cx);
|
||||
}
|
||||
// todo(settings_ui): We should have an observe global use for settings store
|
||||
// so whenever a settings file is updated, the settings ui updates too
|
||||
editor
|
||||
}
|
||||
});
|
||||
|
||||
let weak_editor = editor.downgrade();
|
||||
|
||||
let theme_colors = cx.theme().colors();
|
||||
|
||||
div()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.min_w_64()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(theme_colors.border)
|
||||
.bg(theme_colors.editor_background)
|
||||
.when_some(self.tab_index, |this, tab_index| {
|
||||
let focus_handle = editor.focus_handle(cx).tab_index(tab_index).tab_stop(true);
|
||||
this.track_focus(&focus_handle)
|
||||
.focus(|s| s.border_color(theme_colors.border_focused))
|
||||
})
|
||||
.child(editor)
|
||||
.when_some(self.confirm, |this, confirm| {
|
||||
this.on_action::<menu::Confirm>({
|
||||
move |_, _, cx| {
|
||||
let Some(editor) = weak_editor.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
|
||||
let new_value = (!new_value.is_empty()).then_some(new_value);
|
||||
confirm(new_value, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
pub use font_picker::font_picker;
|
||||
pub use icon_theme_picker::icon_theme_picker;
|
||||
pub use input_field::*;
|
||||
pub use theme_picker::theme_picker;
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{AnyElement, App, Context, DismissEvent, SharedString, Task, Window};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use theme::ThemeRegistry;
|
||||
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||
|
||||
type IconThemePicker = Picker<IconThemePickerDelegate>;
|
||||
|
||||
pub struct IconThemePickerDelegate {
|
||||
icon_themes: Vec<SharedString>,
|
||||
filtered_themes: Vec<StringMatch>,
|
||||
selected_index: usize,
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: Arc<dyn Fn(SharedString, &mut App) + 'static>,
|
||||
}
|
||||
|
||||
impl IconThemePickerDelegate {
|
||||
fn new(
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
cx: &mut Context<IconThemePicker>,
|
||||
) -> Self {
|
||||
let theme_registry = ThemeRegistry::global(cx);
|
||||
|
||||
let icon_themes: Vec<SharedString> = theme_registry
|
||||
.list_icon_themes()
|
||||
.into_iter()
|
||||
.map(|theme_meta| theme_meta.name)
|
||||
.collect();
|
||||
|
||||
let selected_index = icon_themes
|
||||
.iter()
|
||||
.position(|icon_themes| *icon_themes == current_theme)
|
||||
.unwrap_or(0);
|
||||
|
||||
let filtered_themes = icon_themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, icon_themes)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: icon_themes.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
icon_themes,
|
||||
filtered_themes,
|
||||
selected_index,
|
||||
current_theme,
|
||||
on_theme_changed: Arc::new(on_theme_changed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for IconThemePickerDelegate {
|
||||
type ListItem = AnyElement;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.filtered_themes.len()
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<IconThemePicker>) {
|
||||
self.selected_index = ix.min(self.filtered_themes.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search icon theme…".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<IconThemePicker>,
|
||||
) -> Task<()> {
|
||||
let icon_themes = self.icon_themes.clone();
|
||||
let current_theme = self.current_theme.clone();
|
||||
|
||||
let matches: Vec<StringMatch> = if query.is_empty() {
|
||||
icon_themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, icon_theme)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: icon_theme.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
let _candidates: Vec<StringMatchCandidate> = icon_themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, icon_theme)| StringMatchCandidate::new(id, icon_theme.as_ref()))
|
||||
.collect();
|
||||
|
||||
icon_themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, icon_theme)| icon_theme.to_lowercase().contains(&query.to_lowercase()))
|
||||
.map(|(index, icon_theme)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: icon_theme.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let selected_index = if query.is_empty() {
|
||||
icon_themes
|
||||
.iter()
|
||||
.position(|icon_theme| *icon_theme == current_theme)
|
||||
.unwrap_or(0)
|
||||
} else {
|
||||
matches
|
||||
.iter()
|
||||
.position(|m| icon_themes[m.candidate_id] == current_theme)
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
self.filtered_themes = matches;
|
||||
self.selected_index = selected_index;
|
||||
cx.notify();
|
||||
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(
|
||||
&mut self,
|
||||
_secondary: bool,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<IconThemePicker>,
|
||||
) {
|
||||
if let Some(theme_match) = self.filtered_themes.get(self.selected_index) {
|
||||
let theme = theme_match.string.clone();
|
||||
(self.on_theme_changed)(theme.into(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, window: &mut Window, cx: &mut Context<IconThemePicker>) {
|
||||
cx.defer_in(window, |picker, window, cx| {
|
||||
picker.set_query("", window, cx);
|
||||
});
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<IconThemePicker>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let theme_match = self.filtered_themes.get(ix)?;
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(Label::new(theme_match.string.clone()))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon_theme_picker(
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<IconThemePicker>,
|
||||
) -> IconThemePicker {
|
||||
let delegate = IconThemePickerDelegate::new(current_theme, on_theme_changed, cx);
|
||||
|
||||
Picker::uniform_list(delegate, window, cx)
|
||||
.show_scrollbar(true)
|
||||
.width(rems_from_px(210.))
|
||||
.max_height(Some(rems(18.).into()))
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
use editor::Editor;
|
||||
use gpui::{Focusable, div};
|
||||
use ui::{
|
||||
ActiveTheme as _, App, FluentBuilder as _, InteractiveElement as _, IntoElement,
|
||||
ParentElement as _, RenderOnce, Styled as _, Window,
|
||||
};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct SettingsInputField {
|
||||
initial_text: Option<String>,
|
||||
placeholder: Option<&'static str>,
|
||||
confirm: Option<Box<dyn Fn(Option<String>, &mut App)>>,
|
||||
tab_index: Option<isize>,
|
||||
}
|
||||
|
||||
impl SettingsInputField {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
initial_text: None,
|
||||
placeholder: None,
|
||||
confirm: None,
|
||||
tab_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_initial_text(mut self, initial_text: String) -> Self {
|
||||
self.initial_text = Some(initial_text);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_placeholder(mut self, placeholder: &'static str) -> Self {
|
||||
self.placeholder = Some(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_confirm(mut self, confirm: impl Fn(Option<String>, &mut App) + 'static) -> Self {
|
||||
self.confirm = Some(Box::new(confirm));
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn tab_index(mut self, arg: isize) -> Self {
|
||||
self.tab_index = Some(arg);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for SettingsInputField {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl ui::IntoElement {
|
||||
let editor = window.use_state(cx, {
|
||||
move |window, cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
if let Some(text) = self.initial_text {
|
||||
editor.set_text(text, window, cx);
|
||||
}
|
||||
|
||||
if let Some(placeholder) = self.placeholder {
|
||||
editor.set_placeholder_text(placeholder, window, cx);
|
||||
}
|
||||
// todo(settings_ui): We should have an observe global use for settings store
|
||||
// so whenever a settings file is updated, the settings ui updates too
|
||||
editor
|
||||
}
|
||||
});
|
||||
|
||||
let weak_editor = editor.downgrade();
|
||||
|
||||
let theme_colors = cx.theme().colors();
|
||||
|
||||
div()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.min_w_64()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(theme_colors.border)
|
||||
.bg(theme_colors.editor_background)
|
||||
.when_some(self.tab_index, |this, tab_index| {
|
||||
let focus_handle = editor.focus_handle(cx).tab_index(tab_index).tab_stop(true);
|
||||
this.track_focus(&focus_handle)
|
||||
.focus(|s| s.border_color(theme_colors.border_focused))
|
||||
})
|
||||
.child(editor)
|
||||
.when_some(self.confirm, |this, confirm| {
|
||||
this.on_action::<menu::Confirm>({
|
||||
move |_, _, cx| {
|
||||
let Some(editor) = weak_editor.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let new_value = editor.read_with(cx, |editor, cx| editor.text(cx));
|
||||
let new_value = (!new_value.is_empty()).then_some(new_value);
|
||||
confirm(new_value, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{AnyElement, App, Context, DismissEvent, SharedString, Task, Window};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use theme::ThemeRegistry;
|
||||
use ui::{ListItem, ListItemSpacing, prelude::*};
|
||||
|
||||
type ThemePicker = Picker<ThemePickerDelegate>;
|
||||
|
||||
pub struct ThemePickerDelegate {
|
||||
themes: Vec<SharedString>,
|
||||
filtered_themes: Vec<StringMatch>,
|
||||
selected_index: usize,
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: Arc<dyn Fn(SharedString, &mut App) + 'static>,
|
||||
}
|
||||
|
||||
impl ThemePickerDelegate {
|
||||
fn new(
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
cx: &mut Context<ThemePicker>,
|
||||
) -> Self {
|
||||
let theme_registry = ThemeRegistry::global(cx);
|
||||
|
||||
let themes = theme_registry.list_names();
|
||||
let selected_index = themes
|
||||
.iter()
|
||||
.position(|theme| *theme == current_theme)
|
||||
.unwrap_or(0);
|
||||
|
||||
let filtered_themes = themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, theme)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: theme.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
themes,
|
||||
filtered_themes,
|
||||
selected_index,
|
||||
current_theme,
|
||||
on_theme_changed: Arc::new(on_theme_changed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for ThemePickerDelegate {
|
||||
type ListItem = AnyElement;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.filtered_themes.len()
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<ThemePicker>) {
|
||||
self.selected_index = ix.min(self.filtered_themes.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search theme…".into()
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<ThemePicker>,
|
||||
) -> Task<()> {
|
||||
let themes = self.themes.clone();
|
||||
let current_theme = self.current_theme.clone();
|
||||
|
||||
let matches: Vec<StringMatch> = if query.is_empty() {
|
||||
themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, theme)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: theme.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
let _candidates: Vec<StringMatchCandidate> = themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, theme)| StringMatchCandidate::new(id, theme.as_ref()))
|
||||
.collect();
|
||||
|
||||
themes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, theme)| theme.to_lowercase().contains(&query.to_lowercase()))
|
||||
.map(|(index, theme)| StringMatch {
|
||||
candidate_id: index,
|
||||
string: theme.to_string(),
|
||||
positions: Vec::new(),
|
||||
score: 0.0,
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let selected_index = if query.is_empty() {
|
||||
themes
|
||||
.iter()
|
||||
.position(|theme| *theme == current_theme)
|
||||
.unwrap_or(0)
|
||||
} else {
|
||||
matches
|
||||
.iter()
|
||||
.position(|m| themes[m.candidate_id] == current_theme)
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
self.filtered_themes = matches;
|
||||
self.selected_index = selected_index;
|
||||
cx.notify();
|
||||
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, _window: &mut Window, cx: &mut Context<ThemePicker>) {
|
||||
if let Some(theme_match) = self.filtered_themes.get(self.selected_index) {
|
||||
let theme = theme_match.string.clone();
|
||||
(self.on_theme_changed)(theme.into(), cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, window: &mut Window, cx: &mut Context<ThemePicker>) {
|
||||
cx.defer_in(window, |picker, window, cx| {
|
||||
picker.set_query("", window, cx);
|
||||
});
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<ThemePicker>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let theme_match = self.filtered_themes.get(ix)?;
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.child(Label::new(theme_match.string.clone()))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn theme_picker(
|
||||
current_theme: SharedString,
|
||||
on_theme_changed: impl Fn(SharedString, &mut App) + 'static,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<ThemePicker>,
|
||||
) -> ThemePicker {
|
||||
let delegate = ThemePickerDelegate::new(current_theme, on_theme_changed, cx);
|
||||
|
||||
Picker::uniform_list(delegate, window, cx)
|
||||
.show_scrollbar(true)
|
||||
.width(rems_from_px(210.))
|
||||
.max_height(Some(rems(18.).into()))
|
||||
}
|
||||
@@ -36,7 +36,7 @@ use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath};
|
||||
use workspace::{OpenOptions, OpenVisible, Workspace, client_side_decorations};
|
||||
use zed_actions::OpenSettings;
|
||||
|
||||
use crate::components::SettingsEditor;
|
||||
use crate::components::{SettingsInputField, font_picker, icon_theme_picker, theme_picker};
|
||||
|
||||
const NAVBAR_CONTAINER_TAB_INDEX: isize = 0;
|
||||
const NAVBAR_GROUP_TAB_INDEX: isize = 1;
|
||||
@@ -2869,7 +2869,7 @@ fn render_text_field<T: From<String> + Into<String> + AsRef<str> + Clone>(
|
||||
SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
|
||||
let initial_text = initial_text.filter(|s| !s.as_ref().is_empty());
|
||||
|
||||
SettingsEditor::new()
|
||||
SettingsInputField::new()
|
||||
.tab_index(0)
|
||||
.when_some(initial_text, |editor, text| {
|
||||
editor.with_initial_text(text.as_ref().to_string())
|
||||
@@ -2920,54 +2920,6 @@ fn render_toggle_button<B: Into<bool> + From<bool> + Copy>(
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_font_picker(
|
||||
field: SettingField<settings::FontFamilyName>,
|
||||
file: SettingsUiFile,
|
||||
_metadata: Option<&SettingsFieldMetadata>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
let current_value = SettingsStore::global(cx)
|
||||
.get_value_from_file(file.to_settings(), field.pick)
|
||||
.1
|
||||
.cloned()
|
||||
.unwrap_or_else(|| SharedString::default().into());
|
||||
|
||||
let font_picker = cx.new(|cx| {
|
||||
ui_input::font_picker(
|
||||
current_value.clone().into(),
|
||||
move |font_name, cx| {
|
||||
update_settings_file(file.clone(), cx, move |settings, _cx| {
|
||||
(field.write)(settings, Some(font_name.into()));
|
||||
})
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
PopoverMenu::new("font-picker")
|
||||
.menu(move |_window, _cx| Some(font_picker.clone()))
|
||||
.trigger(
|
||||
Button::new("font-family-button", current_value)
|
||||
.tab_index(0_isize)
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.icon(IconName::ChevronUpDown)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::End),
|
||||
)
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(2.0),
|
||||
})
|
||||
.with_handle(ui::PopoverMenuHandle::default())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_number_field<T: NumberFieldType + Send + Sync>(
|
||||
field: SettingField<T>,
|
||||
file: SettingsUiFile,
|
||||
@@ -3056,6 +3008,59 @@ where
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_picker_trigger_button(id: SharedString, label: SharedString) -> Button {
|
||||
Button::new(id, label)
|
||||
.tab_index(0_isize)
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.icon(IconName::ChevronUpDown)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_position(IconPosition::End)
|
||||
}
|
||||
|
||||
fn render_font_picker(
|
||||
field: SettingField<settings::FontFamilyName>,
|
||||
file: SettingsUiFile,
|
||||
_metadata: Option<&SettingsFieldMetadata>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
let current_value = SettingsStore::global(cx)
|
||||
.get_value_from_file(file.to_settings(), field.pick)
|
||||
.1
|
||||
.cloned()
|
||||
.unwrap_or_else(|| SharedString::default().into());
|
||||
|
||||
let font_picker = cx.new(|cx| {
|
||||
font_picker(
|
||||
current_value.clone().into(),
|
||||
move |font_name, cx| {
|
||||
update_settings_file(file.clone(), cx, move |settings, _cx| {
|
||||
(field.write)(settings, Some(font_name.into()));
|
||||
})
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
PopoverMenu::new("font-picker")
|
||||
.menu(move |_window, _cx| Some(font_picker.clone()))
|
||||
.trigger(render_picker_trigger_button(
|
||||
"font_family_picker_trigger".into(),
|
||||
current_value.into(),
|
||||
))
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(2.0),
|
||||
})
|
||||
.with_handle(ui::PopoverMenuHandle::default())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_theme_picker(
|
||||
field: SettingField<settings::ThemeName>,
|
||||
file: SettingsUiFile,
|
||||
@@ -3069,42 +3074,33 @@ fn render_theme_picker(
|
||||
.map(|theme_name| theme_name.0.into())
|
||||
.unwrap_or_else(|| cx.theme().name.clone());
|
||||
|
||||
DropdownMenu::new(
|
||||
"font-picker",
|
||||
current_value.clone(),
|
||||
ContextMenu::build(window, cx, move |mut menu, _, cx| {
|
||||
let all_theme_names = theme::ThemeRegistry::global(cx).list_names();
|
||||
for theme_name in all_theme_names {
|
||||
let file = file.clone();
|
||||
let selected = theme_name.as_ref() == current_value.as_ref();
|
||||
menu = menu.toggleable_entry(
|
||||
theme_name.clone(),
|
||||
selected,
|
||||
IconPosition::End,
|
||||
None,
|
||||
move |_, cx| {
|
||||
if selected {
|
||||
return;
|
||||
}
|
||||
let theme_name = theme_name.clone();
|
||||
update_settings_file(file.clone(), cx, move |settings, _cx| {
|
||||
(field.write)(settings, Some(settings::ThemeName(theme_name.into())));
|
||||
})
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
},
|
||||
);
|
||||
}
|
||||
menu
|
||||
}),
|
||||
)
|
||||
.trigger_size(ButtonSize::Medium)
|
||||
.style(DropdownStyle::Outlined)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(2.0),
|
||||
})
|
||||
.tab_index(0)
|
||||
.into_any_element()
|
||||
let theme_picker = cx.new(|cx| {
|
||||
theme_picker(
|
||||
current_value.clone(),
|
||||
move |theme_name, cx| {
|
||||
update_settings_file(file.clone(), cx, move |settings, _cx| {
|
||||
(field.write)(settings, Some(settings::ThemeName(theme_name.into())));
|
||||
})
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
PopoverMenu::new("theme-picker")
|
||||
.menu(move |_window, _cx| Some(theme_picker.clone()))
|
||||
.trigger(render_picker_trigger_button(
|
||||
"theme_picker_trigger".into(),
|
||||
current_value,
|
||||
))
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(2.0),
|
||||
})
|
||||
.with_handle(ui::PopoverMenuHandle::default())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_icon_theme_picker(
|
||||
@@ -3117,51 +3113,36 @@ fn render_icon_theme_picker(
|
||||
let (_, value) = SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
|
||||
let current_value = value
|
||||
.cloned()
|
||||
.map(|icon_theme_name| icon_theme_name.0.into())
|
||||
.unwrap_or_else(|| theme::default_icon_theme().name.clone());
|
||||
.map(|theme_name| theme_name.0.into())
|
||||
.unwrap_or_else(|| cx.theme().name.clone());
|
||||
|
||||
DropdownMenu::new(
|
||||
"font-picker",
|
||||
current_value.clone(),
|
||||
ContextMenu::build(window, cx, move |mut menu, _, cx| {
|
||||
let all_theme_names = theme::ThemeRegistry::global(cx)
|
||||
.list_icon_themes()
|
||||
.into_iter()
|
||||
.map(|theme| theme.name);
|
||||
for theme_name in all_theme_names {
|
||||
let file = file.clone();
|
||||
let selected = theme_name.as_ref() == current_value.as_ref();
|
||||
menu = menu.toggleable_entry(
|
||||
theme_name.clone(),
|
||||
selected,
|
||||
IconPosition::End,
|
||||
None,
|
||||
move |_, cx| {
|
||||
if selected {
|
||||
return;
|
||||
}
|
||||
let theme_name = theme_name.clone();
|
||||
update_settings_file(file.clone(), cx, move |settings, _cx| {
|
||||
(field.write)(
|
||||
settings,
|
||||
Some(settings::IconThemeName(theme_name.into())),
|
||||
);
|
||||
})
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
},
|
||||
);
|
||||
}
|
||||
menu
|
||||
}),
|
||||
)
|
||||
.trigger_size(ButtonSize::Medium)
|
||||
.style(DropdownStyle::Outlined)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(2.0),
|
||||
})
|
||||
.tab_index(0)
|
||||
.into_any_element()
|
||||
let icon_theme_picker = cx.new(|cx| {
|
||||
icon_theme_picker(
|
||||
current_value.clone(),
|
||||
move |theme_name, cx| {
|
||||
update_settings_file(file.clone(), cx, move |settings, _cx| {
|
||||
(field.write)(settings, Some(settings::IconThemeName(theme_name.into())));
|
||||
})
|
||||
.log_err(); // todo(settings_ui) don't log err
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
PopoverMenu::new("icon-theme-picker")
|
||||
.menu(move |_window, _cx| Some(icon_theme_picker.clone()))
|
||||
.trigger(render_picker_trigger_button(
|
||||
"icon_theme_picker_trigger".into(),
|
||||
current_value,
|
||||
))
|
||||
.anchor(gpui::Corner::TopLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(2.0),
|
||||
})
|
||||
.with_handle(ui::PopoverMenuHandle::default())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -14,10 +14,8 @@ path = "src/ui_input.rs"
|
||||
[dependencies]
|
||||
component.workspace = true
|
||||
editor.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
picker.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
use component::{example_group, single_example};
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, Length, TextStyle};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use theme::ThemeSettings;
|
||||
use ui::prelude::*;
|
||||
|
||||
pub struct InputFieldStyle {
|
||||
text_color: Hsla,
|
||||
background_color: Hsla,
|
||||
border_color: Hsla,
|
||||
}
|
||||
|
||||
/// An Input Field component that can be used to create text fields like search inputs, form fields, etc.
|
||||
///
|
||||
/// It wraps a single line [`Editor`] and allows for common field properties like labels, placeholders, icons, etc.
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct InputField {
|
||||
/// An optional label for the text field.
|
||||
///
|
||||
/// Its position is determined by the [`FieldLabelLayout`].
|
||||
label: Option<SharedString>,
|
||||
/// The size of the label text.
|
||||
label_size: LabelSize,
|
||||
/// The placeholder text for the text field.
|
||||
placeholder: SharedString,
|
||||
/// Exposes the underlying [`Entity<Editor>`] to allow for customizing the editor beyond the provided API.
|
||||
///
|
||||
/// This likely will only be public in the short term, ideally the API will be expanded to cover necessary use cases.
|
||||
pub editor: Entity<Editor>,
|
||||
/// An optional icon that is displayed at the start of the text field.
|
||||
///
|
||||
/// For example, a magnifying glass icon in a search field.
|
||||
start_icon: Option<IconName>,
|
||||
/// Whether the text field is disabled.
|
||||
disabled: bool,
|
||||
/// The minimum width of for the input
|
||||
min_width: Length,
|
||||
}
|
||||
|
||||
impl Focusable for InputField {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl InputField {
|
||||
pub fn new(window: &mut Window, cx: &mut App, placeholder: impl Into<SharedString>) -> Self {
|
||||
let placeholder_text = placeholder.into();
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut input = Editor::single_line(window, cx);
|
||||
input.set_placeholder_text(&placeholder_text, window, cx);
|
||||
input
|
||||
});
|
||||
|
||||
Self {
|
||||
label: None,
|
||||
label_size: LabelSize::Small,
|
||||
placeholder: placeholder_text,
|
||||
editor,
|
||||
start_icon: None,
|
||||
disabled: false,
|
||||
min_width: px(192.).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_icon(mut self, icon: IconName) -> Self {
|
||||
self.start_icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
|
||||
self.label = Some(label.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_size(mut self, size: LabelSize) -> Self {
|
||||
self.label_size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_min_width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.min_width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
|
||||
self.disabled = disabled;
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(disabled))
|
||||
}
|
||||
|
||||
pub fn is_empty(&self, cx: &App) -> bool {
|
||||
self.editor().read(cx).text(cx).trim().is_empty()
|
||||
}
|
||||
|
||||
pub fn editor(&self) -> &Entity<Editor> {
|
||||
&self.editor
|
||||
}
|
||||
|
||||
pub fn text(&self, cx: &App) -> String {
|
||||
self.editor().read(cx).text(cx)
|
||||
}
|
||||
|
||||
pub fn set_text(&self, text: impl Into<Arc<str>>, window: &mut Window, cx: &mut App) {
|
||||
self.editor()
|
||||
.update(cx, |editor, cx| editor.set_text(text, window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for InputField {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let theme_color = cx.theme().colors();
|
||||
|
||||
let mut style = InputFieldStyle {
|
||||
text_color: theme_color.text,
|
||||
background_color: theme_color.editor_background,
|
||||
border_color: theme_color.border_variant,
|
||||
};
|
||||
|
||||
if self.disabled {
|
||||
style.text_color = theme_color.text_disabled;
|
||||
style.background_color = theme_color.editor_background;
|
||||
style.border_color = theme_color.border_disabled;
|
||||
}
|
||||
|
||||
// if self.error_message.is_some() {
|
||||
// style.text_color = cx.theme().status().error;
|
||||
// style.border_color = cx.theme().status().error_border
|
||||
// }
|
||||
|
||||
let text_style = TextStyle {
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.2),
|
||||
color: style.text_color,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let editor_style = EditorStyle {
|
||||
background: theme_color.ghost_element_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.id(self.placeholder.clone())
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.when_some(self.label.clone(), |this, label| {
|
||||
this.child(
|
||||
Label::new(label)
|
||||
.size(self.label_size)
|
||||
.color(if self.disabled {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Default
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.min_w(self.min_width)
|
||||
.min_h_8()
|
||||
.w_full()
|
||||
.px_2()
|
||||
.py_1p5()
|
||||
.flex_grow()
|
||||
.text_color(style.text_color)
|
||||
.rounded_md()
|
||||
.bg(style.background_color)
|
||||
.border_1()
|
||||
.border_color(style.border_color)
|
||||
.when_some(self.start_icon, |this, icon| {
|
||||
this.gap_1()
|
||||
.child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
|
||||
})
|
||||
.child(EditorElement::new(&self.editor, editor_style)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for InputField {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let input_small =
|
||||
cx.new(|cx| InputField::new(window, cx, "placeholder").label("Small Label"));
|
||||
|
||||
let input_regular = cx.new(|cx| {
|
||||
InputField::new(window, cx, "placeholder")
|
||||
.label("Regular Label")
|
||||
.label_size(LabelSize::Default)
|
||||
});
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group(vec![
|
||||
single_example(
|
||||
"Small Label (Default)",
|
||||
div().child(input_small).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Regular Label",
|
||||
div().child(input_regular).into_any_element(),
|
||||
),
|
||||
])])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,233 +1,9 @@
|
||||
//! # UI – Text Field
|
||||
//!
|
||||
//! This crate provides a text field component that can be used to create text fields like search inputs, form fields, etc.
|
||||
//! This crate provides UI components that can be used for form-like scenarios, such as a input and number field.
|
||||
//!
|
||||
//! It can't be located in the `ui` crate because it depends on `editor`.
|
||||
//!
|
||||
mod font_picker;
|
||||
mod input_field;
|
||||
mod number_field;
|
||||
|
||||
use component::{example_group, single_example};
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
pub use font_picker::*;
|
||||
use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, Length, TextStyle};
|
||||
pub use input_field::*;
|
||||
pub use number_field::*;
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use theme::ThemeSettings;
|
||||
use ui::prelude::*;
|
||||
|
||||
pub struct SingleLineInputStyle {
|
||||
text_color: Hsla,
|
||||
background_color: Hsla,
|
||||
border_color: Hsla,
|
||||
}
|
||||
|
||||
/// A Text Field that can be used to create text fields like search inputs, form fields, etc.
|
||||
///
|
||||
/// It wraps a single line [`Editor`] and allows for common field properties like labels, placeholders, icons, etc.
|
||||
#[derive(RegisterComponent)]
|
||||
pub struct SingleLineInput {
|
||||
/// An optional label for the text field.
|
||||
///
|
||||
/// Its position is determined by the [`FieldLabelLayout`].
|
||||
label: Option<SharedString>,
|
||||
/// The size of the label text.
|
||||
label_size: LabelSize,
|
||||
/// The placeholder text for the text field.
|
||||
placeholder: SharedString,
|
||||
/// Exposes the underlying [`Entity<Editor>`] to allow for customizing the editor beyond the provided API.
|
||||
///
|
||||
/// This likely will only be public in the short term, ideally the API will be expanded to cover necessary use cases.
|
||||
pub editor: Entity<Editor>,
|
||||
/// An optional icon that is displayed at the start of the text field.
|
||||
///
|
||||
/// For example, a magnifying glass icon in a search field.
|
||||
start_icon: Option<IconName>,
|
||||
/// Whether the text field is disabled.
|
||||
disabled: bool,
|
||||
/// The minimum width of for the input
|
||||
min_width: Length,
|
||||
}
|
||||
|
||||
impl Focusable for SingleLineInput {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.editor.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl SingleLineInput {
|
||||
pub fn new(window: &mut Window, cx: &mut App, placeholder: impl Into<SharedString>) -> Self {
|
||||
let placeholder_text = placeholder.into();
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut input = Editor::single_line(window, cx);
|
||||
input.set_placeholder_text(&placeholder_text, window, cx);
|
||||
input
|
||||
});
|
||||
|
||||
Self {
|
||||
label: None,
|
||||
label_size: LabelSize::Small,
|
||||
placeholder: placeholder_text,
|
||||
editor,
|
||||
start_icon: None,
|
||||
disabled: false,
|
||||
min_width: px(192.).into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_icon(mut self, icon: IconName) -> Self {
|
||||
self.start_icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
|
||||
self.label = Some(label.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_size(mut self, size: LabelSize) -> Self {
|
||||
self.label_size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn label_min_width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.min_width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
|
||||
self.disabled = disabled;
|
||||
self.editor
|
||||
.update(cx, |editor, _| editor.set_read_only(disabled))
|
||||
}
|
||||
|
||||
pub fn is_empty(&self, cx: &App) -> bool {
|
||||
self.editor().read(cx).text(cx).trim().is_empty()
|
||||
}
|
||||
|
||||
pub fn editor(&self) -> &Entity<Editor> {
|
||||
&self.editor
|
||||
}
|
||||
|
||||
pub fn text(&self, cx: &App) -> String {
|
||||
self.editor().read(cx).text(cx)
|
||||
}
|
||||
|
||||
pub fn set_text(&self, text: impl Into<Arc<str>>, window: &mut Window, cx: &mut App) {
|
||||
self.editor()
|
||||
.update(cx, |editor, cx| editor.set_text(text, window, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for SingleLineInput {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let theme_color = cx.theme().colors();
|
||||
|
||||
let mut style = SingleLineInputStyle {
|
||||
text_color: theme_color.text,
|
||||
background_color: theme_color.editor_background,
|
||||
border_color: theme_color.border_variant,
|
||||
};
|
||||
|
||||
if self.disabled {
|
||||
style.text_color = theme_color.text_disabled;
|
||||
style.background_color = theme_color.editor_background;
|
||||
style.border_color = theme_color.border_disabled;
|
||||
}
|
||||
|
||||
// if self.error_message.is_some() {
|
||||
// style.text_color = cx.theme().status().error;
|
||||
// style.border_color = cx.theme().status().error_border
|
||||
// }
|
||||
|
||||
let text_style = TextStyle {
|
||||
font_family: settings.ui_font.family.clone(),
|
||||
font_features: settings.ui_font.features.clone(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
font_style: FontStyle::Normal,
|
||||
line_height: relative(1.2),
|
||||
color: style.text_color,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let editor_style = EditorStyle {
|
||||
background: theme_color.ghost_element_background,
|
||||
local_player: cx.theme().players().local(),
|
||||
syntax: cx.theme().syntax().clone(),
|
||||
text: text_style,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.id(self.placeholder.clone())
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.when_some(self.label.clone(), |this, label| {
|
||||
this.child(
|
||||
Label::new(label)
|
||||
.size(self.label_size)
|
||||
.color(if self.disabled {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Default
|
||||
}),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.min_w(self.min_width)
|
||||
.min_h_8()
|
||||
.w_full()
|
||||
.px_2()
|
||||
.py_1p5()
|
||||
.flex_grow()
|
||||
.text_color(style.text_color)
|
||||
.rounded_md()
|
||||
.bg(style.background_color)
|
||||
.border_1()
|
||||
.border_color(style.border_color)
|
||||
.when_some(self.start_icon, |this, icon| {
|
||||
this.gap_1()
|
||||
.child(Icon::new(icon).size(IconSize::Small).color(Color::Muted))
|
||||
})
|
||||
.child(EditorElement::new(&self.editor, editor_style)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SingleLineInput {
|
||||
fn scope() -> ComponentScope {
|
||||
ComponentScope::Input
|
||||
}
|
||||
|
||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||
let input_small =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "placeholder").label("Small Label"));
|
||||
|
||||
let input_regular = cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, "placeholder")
|
||||
.label("Regular Label")
|
||||
.label_size(LabelSize::Default)
|
||||
});
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group(vec![
|
||||
single_example(
|
||||
"Small Label (Default)",
|
||||
div().child(input_small).into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Regular Label",
|
||||
div().child(input_regular).into_any_element(),
|
||||
),
|
||||
])])
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use persistence::COMPONENT_PREVIEW_DB;
|
||||
use project::Project;
|
||||
use std::{iter::Iterator, ops::Range, sync::Arc};
|
||||
use ui::{ButtonLike, Divider, HighlightedLabel, ListItem, ListSubHeader, Tooltip, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use workspace::{
|
||||
AppState, Item, ItemId, SerializableItem, Workspace, WorkspaceId, delete_unloaded_items,
|
||||
item::ItemEvent,
|
||||
@@ -99,7 +99,7 @@ struct ComponentPreview {
|
||||
component_map: HashMap<ComponentId, ComponentMetadata>,
|
||||
components: Vec<ComponentMetadata>,
|
||||
cursor_index: usize,
|
||||
filter_editor: Entity<SingleLineInput>,
|
||||
filter_editor: Entity<InputField>,
|
||||
filter_text: String,
|
||||
focus_handle: FocusHandle,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
@@ -126,8 +126,7 @@ impl ComponentPreview {
|
||||
let sorted_components = component_registry.sorted_components();
|
||||
let selected_index = selected_index.into().unwrap_or(0);
|
||||
let active_page = active_page.unwrap_or(PreviewPage::AllComponents);
|
||||
let filter_editor =
|
||||
cx.new(|cx| SingleLineInput::new(window, cx, "Find components or usages…"));
|
||||
let filter_editor = cx.new(|cx| InputField::new(window, cx, "Find components or usages…"));
|
||||
|
||||
let component_list = ListState::new(
|
||||
sorted_components.len(),
|
||||
|
||||
@@ -17,7 +17,7 @@ use language::{Buffer, DiskState};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::{Project, WorktreeId, telemetry_snapshot::TelemetrySnapshot};
|
||||
use ui::{ButtonLike, ContextMenu, ContextMenuEntry, DropdownMenu, KeyBinding, prelude::*};
|
||||
use ui_input::SingleLineInput;
|
||||
use ui_input::InputField;
|
||||
use util::{ResultExt, paths::PathStyle, rel_path::RelPath};
|
||||
use workspace::{Item, SplitDirection, Workspace};
|
||||
use zeta2::{PredictionDebugInfo, Zeta, Zeta2FeatureFlag, ZetaOptions};
|
||||
@@ -65,11 +65,11 @@ pub struct Zeta2Inspector {
|
||||
focus_handle: FocusHandle,
|
||||
project: Entity<Project>,
|
||||
last_prediction: Option<LastPrediction>,
|
||||
max_excerpt_bytes_input: Entity<SingleLineInput>,
|
||||
min_excerpt_bytes_input: Entity<SingleLineInput>,
|
||||
cursor_context_ratio_input: Entity<SingleLineInput>,
|
||||
max_prompt_bytes_input: Entity<SingleLineInput>,
|
||||
max_retrieved_declarations: Entity<SingleLineInput>,
|
||||
max_excerpt_bytes_input: Entity<InputField>,
|
||||
min_excerpt_bytes_input: Entity<InputField>,
|
||||
cursor_context_ratio_input: Entity<InputField>,
|
||||
max_prompt_bytes_input: Entity<InputField>,
|
||||
max_retrieved_declarations: Entity<InputField>,
|
||||
active_view: ActiveView,
|
||||
zeta: Entity<Zeta>,
|
||||
_active_editor_subscription: Option<Subscription>,
|
||||
@@ -225,9 +225,9 @@ impl Zeta2Inspector {
|
||||
label: &'static str,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<SingleLineInput> {
|
||||
) -> Entity<InputField> {
|
||||
let input = cx.new(|cx| {
|
||||
SingleLineInput::new(window, cx, "")
|
||||
InputField::new(window, cx, "")
|
||||
.label(label)
|
||||
.label_min_width(px(64.))
|
||||
});
|
||||
@@ -241,7 +241,7 @@ impl Zeta2Inspector {
|
||||
};
|
||||
|
||||
fn number_input_value<T: FromStr + Default>(
|
||||
input: &Entity<SingleLineInput>,
|
||||
input: &Entity<InputField>,
|
||||
cx: &App,
|
||||
) -> T {
|
||||
input
|
||||
|
||||
Reference in New Issue
Block a user