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,
|
group_box::GroupBoxVariant,
|
||||||
h_flex,
|
h_flex,
|
||||||
label::Label,
|
label::Label,
|
||||||
setting::{NumberFieldOptions, SettingField, SettingGroup, SettingItem, SettingPage, Settings},
|
setting::{
|
||||||
|
NumberFieldOptions, RenderOptions, SettingField, SettingFieldElement, SettingGroup,
|
||||||
|
SettingItem, SettingPage, Settings,
|
||||||
|
},
|
||||||
text::TextView,
|
text::TextView,
|
||||||
v_flex,
|
v_flex,
|
||||||
};
|
};
|
||||||
|
|
@ -56,6 +59,34 @@ pub struct SettingsStory {
|
||||||
size: Size,
|
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 {
|
impl super::Story for SettingsStory {
|
||||||
fn title() -> &'static str {
|
fn title() -> &'static str {
|
||||||
"Settings"
|
"Settings"
|
||||||
|
|
@ -232,7 +263,7 @@ impl SettingsStory {
|
||||||
.description("Adjust the font size for better readability."),
|
.description("Adjust the font size for better readability."),
|
||||||
),
|
),
|
||||||
SettingGroup::new().title("Other").items(vec![
|
SettingGroup::new().title("Other").items(vec![
|
||||||
SettingItem::element(|options, _, _| {
|
SettingItem::render(|options, _, _| {
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
|
|
@ -301,7 +332,7 @@ impl SettingsStory {
|
||||||
SettingPage::new("About")
|
SettingPage::new("About")
|
||||||
.resettable(resettable)
|
.resettable(resettable)
|
||||||
.group(
|
.group(
|
||||||
SettingGroup::new().item(SettingItem::element(|_options, _, cx| {
|
SettingGroup::new().item(SettingItem::render(|_options, _, cx| {
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
|
@ -323,28 +354,18 @@ impl SettingsStory {
|
||||||
.group(SettingGroup::new().title("Links").items(vec![
|
.group(SettingGroup::new().title("Links").items(vec![
|
||||||
SettingItem::new(
|
SettingItem::new(
|
||||||
"GitHub Repository",
|
"GitHub Repository",
|
||||||
SettingField::element(|options, _window, _cx| {
|
SettingField::element(OpenURLSettingField::new(
|
||||||
Button::new("open-url")
|
"Repository...",
|
||||||
.outline()
|
"https://github.com/longbridge/gpui-component",
|
||||||
.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."),
|
.description("Open the GitHub repository in your default browser."),
|
||||||
SettingItem::new(
|
SettingItem::new(
|
||||||
"Documentation",
|
"Documentation",
|
||||||
SettingField::element(|options, _window, _cx| {
|
SettingField::element(OpenURLSettingField::new(
|
||||||
Button::new("open-url")
|
"Rust Docs...",
|
||||||
.outline()
|
"https://docs.rs/gpui-component"
|
||||||
.label("Rust Docs...")
|
)),
|
||||||
.with_size(options.size)
|
|
||||||
.on_click(|_, _window, cx| {
|
|
||||||
cx.open_url("https://docs.rs/gpui-component");
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.description(TextView::markdown(
|
.description(TextView::markdown(
|
||||||
"desc",
|
"desc",
|
||||||
|
|
@ -354,7 +375,7 @@ impl SettingsStory {
|
||||||
)),
|
)),
|
||||||
SettingItem::new(
|
SettingItem::new(
|
||||||
"Website",
|
"Website",
|
||||||
SettingField::element(|options, _window, _cx| {
|
SettingField::render(|options, _window, _cx| {
|
||||||
Button::new("open-url")
|
Button::new("open-url")
|
||||||
.outline()
|
.outline()
|
||||||
.label("Website...")
|
.label("Website...")
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,80 @@
|
||||||
use gpui::{AnyElement, App, StyleRefinement, Window};
|
use gpui::{AnyElement, App, IntoElement, StyleRefinement, Window};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::setting::{fields::SettingFieldRender, AnySettingField, RenderOptions};
|
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 {
|
pub(crate) struct ElementField {
|
||||||
element_render: Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement>,
|
element_render: Rc<dyn SettingFieldElement<Element = AnyElement>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ElementField {
|
impl ElementField {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new<E>(element_render: E) -> Self
|
||||||
element_render: Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement + 'static>,
|
where
|
||||||
) -> Self {
|
E: SettingFieldElement<Element = AnyElement> + 'static,
|
||||||
Self { element_render }
|
{
|
||||||
|
Self {
|
||||||
|
element_render: Rc::new(element_render),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,6 +87,6 @@ impl SettingFieldRender for ElementField {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> AnyElement {
|
) -> 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 number::*;
|
||||||
pub(crate) use string::*;
|
pub(crate) use string::*;
|
||||||
|
|
||||||
|
pub use element::SettingFieldElement;
|
||||||
pub use number::NumberFieldOptions;
|
pub use number::NumberFieldOptions;
|
||||||
|
|
||||||
use gpui::{AnyElement, App, IntoElement, SharedString, StyleRefinement, Styled, Window};
|
use gpui::{AnyElement, App, IntoElement, SharedString, StyleRefinement, Styled, Window};
|
||||||
|
|
@ -61,7 +62,7 @@ pub enum SettingFieldType {
|
||||||
options: Vec<(SharedString, SharedString)>,
|
options: Vec<(SharedString, SharedString)>,
|
||||||
},
|
},
|
||||||
Element {
|
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]
|
#[inline]
|
||||||
pub(super) fn element_render(
|
pub(super) fn element(&self) -> Rc<dyn SettingFieldElement<Element = AnyElement>> {
|
||||||
&self,
|
|
||||||
) -> Rc<dyn Fn(&RenderOptions, &mut Window, &mut App) -> AnyElement + 'static> {
|
|
||||||
match self {
|
match self {
|
||||||
SettingFieldType::Element { element_render } => element_render.clone(),
|
SettingFieldType::Element { element } => element.clone(),
|
||||||
_ => unreachable!("element_render called on non-element field"),
|
_ => unreachable!("element_render called on non-element field"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,22 +171,36 @@ impl SettingField<SharedString> {
|
||||||
Self::new(SettingFieldType::Dropdown { options }, value, set_value)
|
Self::new(SettingFieldType::Dropdown { options }, value, set_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new setting field with the given element render function.
|
/// Create a new setting field with the given custom element that implements [`SettingFieldElement`] trait.
|
||||||
pub fn element<R, E>(element_render: R) -> Self
|
///
|
||||||
|
/// See also [`SettingField::render`] for simply building with a render closure.
|
||||||
|
pub fn element<E>(element: E) -> Self
|
||||||
where
|
where
|
||||||
E: IntoElement,
|
E: SettingFieldElement + 'static,
|
||||||
R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,
|
|
||||||
{
|
{
|
||||||
Self::new(
|
Self::new(
|
||||||
SettingFieldType::Element {
|
SettingFieldType::Element {
|
||||||
element_render: Rc::new(move |options, window, cx| {
|
element: Rc::new(AnySettingFieldElement(element)),
|
||||||
element_render(options, window, cx).into_any_element()
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
|_| SharedString::default(),
|
|_| 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> {
|
impl SettingField<f64> {
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ impl SettingGroup {
|
||||||
.gap_4()
|
.gap_4()
|
||||||
.children(self.items.iter().enumerate().filter_map(|(item_ix, item)| {
|
.children(self.items.iter().enumerate().filter_map(|(item_ix, item)| {
|
||||||
if item.is_match(&query) {
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ impl SettingItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new custom element setting item.
|
/// Create a new custom element setting item with a render closure.
|
||||||
pub fn element<R, E>(render: R) -> Self
|
pub fn render<R, E>(render: R) -> Self
|
||||||
where
|
where
|
||||||
E: IntoElement,
|
E: IntoElement,
|
||||||
R: Fn(&RenderOptions, &mut Window, &mut App) -> E + 'static,
|
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() => {
|
t if t == TypeId::of::<String>() && field_type.is_dropdown() => {
|
||||||
Box::new(DropdownField::<String>::new(field_type.dropdown_options()))
|
Box::new(DropdownField::<String>::new(field_type.dropdown_options()))
|
||||||
}
|
}
|
||||||
_ if field_type.is_element() => {
|
_ if field_type.is_element() => Box::new(ElementField::new(field_type.element())),
|
||||||
Box::new(ElementField::new(field_type.element_render()))
|
|
||||||
}
|
|
||||||
_ => unimplemented!("Unsupported setting type: {}", field.deref().type_name()),
|
_ => unimplemented!("Unsupported setting type: {}", field.deref().type_name()),
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.render(field, &options, &style, window, cx)
|
renderer.render(field, &options, &style, window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn render(
|
pub(super) fn render_item(
|
||||||
self,
|
self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
options: &RenderOptions,
|
options: &RenderOptions,
|
||||||
|
|
|
||||||
|
|
@ -174,12 +174,12 @@ SettingItem::new("Title", SettingField::switch(...))
|
||||||
.description("Description text")
|
.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
|
```rust
|
||||||
SettingItem::element(|options, _, _| {
|
SettingItem::render(|options, _, _| {
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_between()
|
.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
|
```rust
|
||||||
SettingItem::new(
|
SettingItem::new(
|
||||||
"GitHub Repository",
|
"GitHub Repository",
|
||||||
SettingField::element(|options, _window, _cx| {
|
SettingField::render(|options, _window, _cx| {
|
||||||
Button::new("open-url")
|
Button::new("open-url")
|
||||||
.outline()
|
.outline()
|
||||||
.label("Repository...")
|
.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
|
## API Reference
|
||||||
|
|
||||||
- [Settings]
|
- [Settings]
|
||||||
|
|
@ -453,6 +497,8 @@ Settings::new("app-settings")
|
||||||
[SettingGroup]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.SettingGroup.html
|
[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
|
[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
|
[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
|
[NumberFieldOptions]: https://docs.rs/gpui-component/latest/gpui_component/setting/struct.NumberFieldOptions.html
|
||||||
[GroupBox]: ./group-box.md
|
[GroupBox]: ./group-box.md
|
||||||
[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html
|
[Sizable]: https://docs.rs/gpui-component/latest/gpui_component/trait.Sizable.html
|
||||||
|
````
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue