setting: Add SettingFieldElement trait. (#1657)
Co-authored-by: Sunli <scott_s829@163.com>
This commit is contained in:
parent
ffeeee5d12
commit
44829a05e4
6 changed files with 220 additions and 79 deletions
|
|
@ -9,7 +9,10 @@ use gpui_component::{
|
|||
group_box::GroupBoxVariant,
|
||||
h_flex,
|
||||
label::Label,
|
||||
setting::{NumberFieldOptions, SettingField, SettingGroup, SettingItem, SettingPage, Settings},
|
||||
setting::{
|
||||
NumberFieldOptions, RenderOptions, SettingField, SettingFieldElement, SettingGroup,
|
||||
SettingItem, SettingPage, Settings,
|
||||
},
|
||||
text::TextView,
|
||||
v_flex,
|
||||
};
|
||||
|
|
@ -56,6 +59,34 @@ pub struct SettingsStory {
|
|||
size: Size,
|
||||
}
|
||||
|
||||
struct OpenURLSettingField {
|
||||
label: SharedString,
|
||||
url: SharedString,
|
||||
}
|
||||
|
||||
impl OpenURLSettingField {
|
||||
fn new(label: impl Into<SharedString>, url: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
url: url.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingFieldElement for OpenURLSettingField {
|
||||
type Element = Button;
|
||||
fn render_field(&self, options: &RenderOptions, _: &mut Window, _: &mut App) -> Self::Element {
|
||||
let url = self.url.clone();
|
||||
Button::new("open-url")
|
||||
.outline()
|
||||
.label(self.label.clone())
|
||||
.with_size(options.size)
|
||||
.on_click(move |_, _window, cx| {
|
||||
cx.open_url(url.as_str());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Story for SettingsStory {
|
||||
fn title() -> &'static str {
|
||||
"Settings"
|
||||
|
|
@ -232,7 +263,7 @@ impl SettingsStory {
|
|||
.description("Adjust the font size for better readability."),
|
||||
),
|
||||
SettingGroup::new().title("Other").items(vec![
|
||||
SettingItem::element(|options, _, _| {
|
||||
SettingItem::render(|options, _, _| {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
|
|
@ -301,7 +332,7 @@ impl SettingsStory {
|
|||
SettingPage::new("About")
|
||||
.resettable(resettable)
|
||||
.group(
|
||||
SettingGroup::new().item(SettingItem::element(|_options, _, cx| {
|
||||
SettingGroup::new().item(SettingItem::render(|_options, _, cx| {
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.w_full()
|
||||
|
|
@ -321,51 +352,41 @@ impl SettingsStory {
|
|||
})),
|
||||
)
|
||||
.group(SettingGroup::new().title("Links").items(vec![
|
||||
SettingItem::new(
|
||||
"GitHub Repository",
|
||||
SettingField::element(|options, _window, _cx| {
|
||||
Button::new("open-url")
|
||||
.outline()
|
||||
.label("Repository...")
|
||||
.with_size(options.size)
|
||||
.on_click(|_, _window, cx| {
|
||||
cx.open_url("https://github.com/longbridge/gpui-component");
|
||||
})
|
||||
}),
|
||||
)
|
||||
.description("Open the GitHub repository in your default browser."),
|
||||
SettingItem::new(
|
||||
"Documentation",
|
||||
SettingField::element(|options, _window, _cx| {
|
||||
Button::new("open-url")
|
||||
.outline()
|
||||
.label("Rust Docs...")
|
||||
.with_size(options.size)
|
||||
.on_click(|_, _window, cx| {
|
||||
cx.open_url("https://docs.rs/gpui-component");
|
||||
})
|
||||
}),
|
||||
)
|
||||
.description(TextView::markdown(
|
||||
"desc",
|
||||
"Rust doc for the `gpui-component` crate.",
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
SettingItem::new(
|
||||
"Website",
|
||||
SettingField::element(|options, _window, _cx| {
|
||||
Button::new("open-url")
|
||||
.outline()
|
||||
.label("Website...")
|
||||
.with_size(options.size)
|
||||
.on_click(|_, _window, cx| {
|
||||
cx.open_url("https://longbridge.github.io/gpui-component/");
|
||||
})
|
||||
}),
|
||||
)
|
||||
.description("Official website and documentation for the GPUI Component."),
|
||||
])),
|
||||
SettingItem::new(
|
||||
"GitHub Repository",
|
||||
SettingField::element(OpenURLSettingField::new(
|
||||
"Repository...",
|
||||
"https://github.com/longbridge/gpui-component",
|
||||
)),
|
||||
)
|
||||
.description("Open the GitHub repository in your default browser."),
|
||||
SettingItem::new(
|
||||
"Documentation",
|
||||
SettingField::element(OpenURLSettingField::new(
|
||||
"Rust Docs...",
|
||||
"https://docs.rs/gpui-component"
|
||||
)),
|
||||
)
|
||||
.description(TextView::markdown(
|
||||
"desc",
|
||||
"Rust doc for the `gpui-component` crate.",
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
SettingItem::new(
|
||||
"Website",
|
||||
SettingField::render(|options, _window, _cx| {
|
||||
Button::new("open-url")
|
||||
.outline()
|
||||
.label("Website...")
|
||||
.with_size(options.size)
|
||||
.on_click(|_, _window, cx| {
|
||||
cx.open_url("https://longbridge.github.io/gpui-component/");
|
||||
})
|
||||
}),
|
||||
)
|
||||
.description("Official website and documentation for the GPUI Component."),
|
||||
])),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,80 @@
|
|||
use gpui::{AnyElement, App, StyleRefinement, Window};
|
||||
use gpui::{AnyElement, App, IntoElement, StyleRefinement, Window};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::setting::{fields::SettingFieldRender, AnySettingField, RenderOptions};
|
||||
|
||||
/// A trait for rendering custom setting field elements.
|
||||
///
|
||||
/// For [`crate::setting::SettingField::element`] method.
|
||||
pub trait SettingFieldElement {
|
||||
type Element: IntoElement + 'static;
|
||||
|
||||
fn render_field(
|
||||
&self,
|
||||
options: &RenderOptions,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::Element;
|
||||
}
|
||||
|
||||
impl<F, E> SettingFieldElement for F
|
||||
where
|
||||
E: IntoElement + 'static,
|
||||
F: Fn(&RenderOptions, &mut Window, &mut App) -> E,
|
||||
{
|
||||
type Element = E;
|
||||
|
||||
fn render_field(
|
||||
&self,
|
||||
options: &RenderOptions,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::Element {
|
||||
(self)(options, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AnySettingFieldElement<T>(pub(crate) T);
|
||||
impl<T> SettingFieldElement for AnySettingFieldElement<T>
|
||||
where
|
||||
T: SettingFieldElement,
|
||||
{
|
||||
type Element = AnyElement;
|
||||
|
||||
fn render_field(
|
||||
&self,
|
||||
options: &RenderOptions,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::Element {
|
||||
self.0.render_field(options, window, cx).into_any_element()
|
||||
}
|
||||
}
|
||||
impl SettingFieldElement for Rc<dyn SettingFieldElement<Element = AnyElement>> {
|
||||
type Element = AnyElement;
|
||||
|
||||
fn render_field(
|
||||
&self,
|
||||
options: &RenderOptions,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::Element {
|
||||
self.as_ref().render_field(options, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ElementField {
|
||||
element_render: Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement>,
|
||||
element_render: Rc<dyn SettingFieldElement<Element = AnyElement>>,
|
||||
}
|
||||
|
||||
impl ElementField {
|
||||
pub(crate) fn new(
|
||||
element_render: Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement + 'static>,
|
||||
) -> Self {
|
||||
Self { element_render }
|
||||
pub(crate) fn new<E>(element_render: E) -> Self
|
||||
where
|
||||
E: SettingFieldElement<Element = AnyElement> + 'static,
|
||||
{
|
||||
Self {
|
||||
element_render: Rc::new(element_render),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +87,6 @@ impl SettingFieldRender for ElementField {
|
|||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
(self.element_render)(options, window, cx)
|
||||
(self.element_render).render_field(options, window, cx)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub(crate) use element::*;
|
|||
pub(crate) use number::*;
|
||||
pub(crate) use string::*;
|
||||
|
||||
pub use element::SettingFieldElement;
|
||||
pub use number::NumberFieldOptions;
|
||||
|
||||
use gpui::{AnyElement, App, IntoElement, SharedString, StyleRefinement, Styled, Window};
|
||||
|
|
@ -61,7 +62,7 @@ pub enum SettingFieldType {
|
|||
options: Vec<(SharedString, SharedString)>,
|
||||
},
|
||||
Element {
|
||||
element_render: Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement>,
|
||||
element: Rc<dyn SettingFieldElement<Element = AnyElement>>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -108,11 +109,9 @@ impl SettingFieldType {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn element_render(
|
||||
&self,
|
||||
) -> Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement + 'static> {
|
||||
pub(super) fn element(&self) -> Rc<dyn SettingFieldElement<Element = AnyElement>> {
|
||||
match self {
|
||||
SettingFieldType::Element { element_render } => element_render.clone(),
|
||||
SettingFieldType::Element { element } => element.clone(),
|
||||
_ => unreachable!("element_render called on non-element field"),
|
||||
}
|
||||
}
|
||||
|
|
@ -172,22 +171,36 @@ impl SettingField<SharedString> {
|
|||
Self::new(SettingFieldType::Dropdown { options }, value, set_value)
|
||||
}
|
||||
|
||||
/// Create a new setting field with the given element render function.
|
||||
pub fn element<R, E>(element_render: R) -> Self
|
||||
/// Create a new setting field with the given custom element that implements [`SettingFieldElement`] trait.
|
||||
///
|
||||
/// See also [`SettingField::render`] for simply building with a render closure.
|
||||
pub fn element<E>(element: E) -> Self
|
||||
where
|
||||
E: IntoElement,
|
||||
R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,
|
||||
E: SettingFieldElement + 'static,
|
||||
{
|
||||
Self::new(
|
||||
SettingFieldType::Element {
|
||||
element_render: Rc::new(move |options, window, cx| {
|
||||
element_render(options, window, cx).into_any_element()
|
||||
}),
|
||||
element: Rc::new(AnySettingFieldElement(element)),
|
||||
},
|
||||
|_| SharedString::default(),
|
||||
|_, _| {},
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new setting field with the given element render closure.
|
||||
///
|
||||
/// See also [`SettingField::element`] for building with a custom field for more complex scenarios.
|
||||
pub fn render<E, R>(element_render: R) -> Self
|
||||
where
|
||||
E: IntoElement + 'static,
|
||||
R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,
|
||||
{
|
||||
Self::element(
|
||||
move |options: &RenderOptions, window: &mut Window, cx: &mut App| {
|
||||
(element_render)(options, window, cx).into_any_element()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingField<f64> {
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ impl SettingGroup {
|
|||
.gap_4()
|
||||
.children(self.items.iter().enumerate().filter_map(|(item_ix, item)| {
|
||||
if item.is_match(&query) {
|
||||
Some(item.clone().render(item_ix, options, window, cx))
|
||||
Some(item.clone().render_item(item_ix, options, window, cx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ impl SettingItem {
|
|||
}
|
||||
}
|
||||
|
||||
/// Create a new custom element setting item.
|
||||
pub fn element<R, E>(render: R) -> Self
|
||||
/// Create a new custom element setting item with a render closure.
|
||||
pub fn render<R, E>(render: R) -> Self
|
||||
where
|
||||
E: IntoElement,
|
||||
R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,
|
||||
|
|
@ -140,16 +140,14 @@ impl SettingItem {
|
|||
t if t == TypeId::of::<String>() && field_type.is_dropdown() => {
|
||||
Box::new(DropdownField::<String>::new(field_type.dropdown_options()))
|
||||
}
|
||||
_ if field_type.is_element() => {
|
||||
Box::new(ElementField::new(field_type.element_render()))
|
||||
}
|
||||
_ if field_type.is_element() => Box::new(ElementField::new(field_type.element())),
|
||||
_ => unimplemented!("Unsupported setting type: {}", field.deref().type_name()),
|
||||
};
|
||||
|
||||
renderer.render(field, &options, &style, window, cx)
|
||||
}
|
||||
|
||||
pub(super) fn render(
|
||||
pub(super) fn render_item(
|
||||
self,
|
||||
ix: usize,
|
||||
options: &RenderOptions,
|
||||
|
|
|
|||
|
|
@ -174,12 +174,12 @@ SettingItem::new("Title", SettingField::switch(...))
|
|||
.description("Description text")
|
||||
```
|
||||
|
||||
### Custom Element Item
|
||||
### Custom Item with a render closure
|
||||
|
||||
You can create a fully custom setting item using `SettingItem::element`:
|
||||
You can create a fully custom setting item using `SettingItem::render`:
|
||||
|
||||
```rust
|
||||
SettingItem::element(|options, _, _| {
|
||||
SettingItem::render(|options, _, _| {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
|
|
@ -324,12 +324,14 @@ SettingItem::new(
|
|||
)
|
||||
```
|
||||
|
||||
### Custom Element Field
|
||||
### Custom Field by Render Closure
|
||||
|
||||
The `SettingField::render` method allows you to create a custom field using a closure that returns an element.
|
||||
|
||||
```rust
|
||||
SettingItem::new(
|
||||
"GitHub Repository",
|
||||
SettingField::element(|options, _window, _cx| {
|
||||
SettingField::render(|options, _window, _cx| {
|
||||
Button::new("open-url")
|
||||
.outline()
|
||||
.label("Repository...")
|
||||
|
|
@ -341,6 +343,48 @@ SettingItem::new(
|
|||
)
|
||||
```
|
||||
|
||||
### Custom Field Element
|
||||
|
||||
You may have a complex field that you want to reuse, you may want split the element into a separate struct to do the complex logic.
|
||||
|
||||
In this case, the [SettingFieldElement] trait can help you to create a custom field element.
|
||||
|
||||
````rust
|
||||
use gpui_component::setting::{SettingFieldElement, RenderOptions};
|
||||
|
||||
struct OpenURLSettingField {
|
||||
label: SharedString,
|
||||
url: SharedString,
|
||||
}
|
||||
|
||||
impl SettingFieldElement for OpenURLSettingField {
|
||||
type Element = Button;
|
||||
|
||||
fn render_field(&self, options: &RenderOptions, _: &mut Window, _: &mut App) -> Self::Element {
|
||||
let url = self.url.clone();
|
||||
Button::new("open-url")
|
||||
.outline()
|
||||
.label(self.label.clone())
|
||||
.with_size(options.size)
|
||||
.on_click(move |_, _window, cx| {
|
||||
cx.open_url(url.as_str());
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then use it in the setting item:
|
||||
|
||||
```rust
|
||||
SettingItem::new(
|
||||
"GitHub Repository",
|
||||
SettingField::element(OpenURLSettingField {
|
||||
label: "Repository...".into(),
|
||||
url: "https://github.com/longbridge/gpui-component".into(),
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- [Settings]
|
||||
|
|
@ -453,6 +497,8 @@ Settings::new("app-settings")
|
|||
[SettingGroup]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.SettingGroup.html
|
||||
[SettingItem]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.SettingItem.html
|
||||
[SettingField]: https://docs.rs/gpui-component/latest/gpui_component/setting/enum.SettingField.html
|
||||
[SettingFieldElement]: https://docs.rs/gpui-component/latest/gpui_component/setting/trait.SettingFieldElement.html
|
||||
[NumberFieldOptions]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.NumberFieldOptions.html
|
||||
[GroupBox]: ./group-box.md
|
||||
[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html
|
||||
````
|
||||
|
|
|
|||
Loading…
Reference in a new issue