Add action button component for rendering the search options
This commit is contained in:
parent
8630557ece
commit
21fa6090b8
3 changed files with 365 additions and 22 deletions
|
@ -168,16 +168,13 @@ impl View for BufferSearchBar {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let render_search_option = |options: bool, icon, option| {
|
let search_option_button = |option| {
|
||||||
options.then(|| {
|
let is_active = self.search_options.contains(option);
|
||||||
let is_active = self.search_options.contains(option);
|
option.as_button(
|
||||||
option.as_button(
|
is_active,
|
||||||
is_active,
|
theme.tooltip.clone(),
|
||||||
icon,
|
theme.search.option_button_component.clone(),
|
||||||
theme.tooltip.clone(),
|
)
|
||||||
theme.search.option_button_component.clone(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
let match_count = self
|
let match_count = self
|
||||||
.active_searchable_item
|
.active_searchable_item
|
||||||
|
@ -233,16 +230,16 @@ impl View for BufferSearchBar {
|
||||||
.with_child(ChildView::new(&self.query_editor, cx).flex(1., true))
|
.with_child(ChildView::new(&self.query_editor, cx).flex(1., true))
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_children(render_search_option(
|
.with_children(
|
||||||
supported_options.case,
|
supported_options
|
||||||
"icons/case_insensitive_12.svg",
|
.case
|
||||||
SearchOptions::CASE_SENSITIVE,
|
.then(|| search_option_button(SearchOptions::CASE_SENSITIVE)),
|
||||||
))
|
)
|
||||||
.with_children(render_search_option(
|
.with_children(
|
||||||
supported_options.word,
|
supported_options
|
||||||
"icons/word_search_12.svg",
|
.word
|
||||||
SearchOptions::WHOLE_WORD,
|
.then(|| search_option_button(SearchOptions::WHOLE_WORD)),
|
||||||
))
|
)
|
||||||
.flex_float()
|
.flex_float()
|
||||||
.contained(),
|
.contained(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -56,6 +56,14 @@ impl SearchOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn icon(&self) -> &'static str {
|
||||||
|
match *self {
|
||||||
|
SearchOptions::WHOLE_WORD => "icons/word_search_12.svg",
|
||||||
|
SearchOptions::CASE_SENSITIVE => "icons/case_insensitive_12.svg",
|
||||||
|
_ => panic!("{:?} is not a named SearchOption", self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_toggle_action(&self) -> Box<dyn Action> {
|
pub fn to_toggle_action(&self) -> Box<dyn Action> {
|
||||||
match *self {
|
match *self {
|
||||||
SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
|
SearchOptions::WHOLE_WORD => Box::new(ToggleWholeWord),
|
||||||
|
@ -78,7 +86,6 @@ impl SearchOptions {
|
||||||
pub fn as_button<V: View>(
|
pub fn as_button<V: View>(
|
||||||
&self,
|
&self,
|
||||||
active: bool,
|
active: bool,
|
||||||
icon: &str,
|
|
||||||
tooltip_style: TooltipStyle,
|
tooltip_style: TooltipStyle,
|
||||||
button_style: ToggleIconButtonStyle,
|
button_style: ToggleIconButtonStyle,
|
||||||
) -> AnyElement<V> {
|
) -> AnyElement<V> {
|
||||||
|
@ -87,7 +94,7 @@ impl SearchOptions {
|
||||||
format!("Toggle {}", self.label()),
|
format!("Toggle {}", self.label()),
|
||||||
tooltip_style,
|
tooltip_style,
|
||||||
)
|
)
|
||||||
.with_contents(theme::components::svg::Svg::new(icon.to_owned()))
|
.with_contents(theme::components::svg::Svg::new(self.icon()))
|
||||||
.toggleable(active)
|
.toggleable(active)
|
||||||
.with_style(button_style)
|
.with_style(button_style)
|
||||||
.into_element()
|
.into_element()
|
||||||
|
|
339
crates/theme/src/components.rs
Normal file
339
crates/theme/src/components.rs
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
use gpui::elements::StyleableComponent;
|
||||||
|
|
||||||
|
use crate::{Interactive, Toggleable};
|
||||||
|
|
||||||
|
use self::{action_button::ButtonStyle, svg::SvgStyle, toggle::Toggle};
|
||||||
|
|
||||||
|
pub type ToggleIconButtonStyle = Toggleable<Interactive<ButtonStyle<SvgStyle>>>;
|
||||||
|
|
||||||
|
pub trait ComponentExt<C: StyleableComponent> {
|
||||||
|
fn toggleable(self, active: bool) -> Toggle<C, ()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: StyleableComponent> ComponentExt<C> for C {
|
||||||
|
fn toggleable(self, active: bool) -> Toggle<C, ()> {
|
||||||
|
Toggle::new(self, active)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod toggle {
|
||||||
|
use gpui::elements::{GeneralComponent, StyleableComponent};
|
||||||
|
|
||||||
|
use crate::Toggleable;
|
||||||
|
|
||||||
|
pub struct Toggle<C, S> {
|
||||||
|
style: S,
|
||||||
|
active: bool,
|
||||||
|
component: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: StyleableComponent> Toggle<C, ()> {
|
||||||
|
pub fn new(component: C, active: bool) -> Self {
|
||||||
|
Toggle {
|
||||||
|
active,
|
||||||
|
component,
|
||||||
|
style: (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: StyleableComponent> StyleableComponent for Toggle<C, ()> {
|
||||||
|
type Style = Toggleable<C::Style>;
|
||||||
|
|
||||||
|
type Output = Toggle<C, Self::Style>;
|
||||||
|
|
||||||
|
fn with_style(self, style: Self::Style) -> Self::Output {
|
||||||
|
Toggle {
|
||||||
|
active: self.active,
|
||||||
|
component: self.component,
|
||||||
|
style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: StyleableComponent> GeneralComponent for Toggle<C, Toggleable<C::Style>> {
|
||||||
|
fn render<V: gpui::View>(
|
||||||
|
self,
|
||||||
|
v: &mut V,
|
||||||
|
cx: &mut gpui::ViewContext<V>,
|
||||||
|
) -> gpui::AnyElement<V> {
|
||||||
|
self.component
|
||||||
|
.with_style(self.style.in_state(self.active).clone())
|
||||||
|
.render(v, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod action_button {
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
elements::{
|
||||||
|
ContainerStyle, GeneralComponent, MouseEventHandler, StyleableComponent, TooltipStyle,
|
||||||
|
},
|
||||||
|
platform::{CursorStyle, MouseButton},
|
||||||
|
Action, Element, TypeTag, View,
|
||||||
|
};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
|
use crate::Interactive;
|
||||||
|
|
||||||
|
pub struct ActionButton<C, S> {
|
||||||
|
action: Box<dyn Action>,
|
||||||
|
tooltip: Cow<'static, str>,
|
||||||
|
tooltip_style: TooltipStyle,
|
||||||
|
tag: TypeTag,
|
||||||
|
contents: C,
|
||||||
|
style: Interactive<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default, JsonSchema)]
|
||||||
|
pub struct ButtonStyle<C> {
|
||||||
|
#[serde(flatten)]
|
||||||
|
container: ContainerStyle,
|
||||||
|
button_width: Option<f32>,
|
||||||
|
button_height: Option<f32>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
contents: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionButton<(), ()> {
|
||||||
|
pub fn new_dynamic(
|
||||||
|
action: Box<dyn Action>,
|
||||||
|
tooltip: impl Into<Cow<'static, str>>,
|
||||||
|
tooltip_style: TooltipStyle,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
contents: (),
|
||||||
|
tag: action.type_tag(),
|
||||||
|
style: Interactive::new_blank(),
|
||||||
|
tooltip: tooltip.into(),
|
||||||
|
tooltip_style,
|
||||||
|
action,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new<A: Action + Clone>(
|
||||||
|
action: A,
|
||||||
|
tooltip: impl Into<Cow<'static, str>>,
|
||||||
|
tooltip_style: TooltipStyle,
|
||||||
|
) -> Self {
|
||||||
|
Self::new_dynamic(Box::new(action), tooltip, tooltip_style)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
|
||||||
|
ActionButton {
|
||||||
|
action: self.action,
|
||||||
|
tag: self.tag,
|
||||||
|
style: self.style,
|
||||||
|
tooltip: self.tooltip,
|
||||||
|
tooltip_style: self.tooltip_style,
|
||||||
|
contents,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: StyleableComponent> StyleableComponent for ActionButton<C, ()> {
|
||||||
|
type Style = Interactive<ButtonStyle<C::Style>>;
|
||||||
|
type Output = ActionButton<C, ButtonStyle<C::Style>>;
|
||||||
|
|
||||||
|
fn with_style(self, style: Self::Style) -> Self::Output {
|
||||||
|
ActionButton {
|
||||||
|
action: self.action,
|
||||||
|
tag: self.tag,
|
||||||
|
contents: self.contents,
|
||||||
|
tooltip: self.tooltip,
|
||||||
|
tooltip_style: self.tooltip_style,
|
||||||
|
style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: StyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
|
||||||
|
fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
|
||||||
|
MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
|
||||||
|
let style = self.style.style_for(state);
|
||||||
|
let mut contents = self
|
||||||
|
.contents
|
||||||
|
.with_style(style.contents.to_owned())
|
||||||
|
.render(v, cx)
|
||||||
|
.contained()
|
||||||
|
.with_style(style.container)
|
||||||
|
.constrained();
|
||||||
|
|
||||||
|
if let Some(height) = style.button_height {
|
||||||
|
contents = contents.with_height(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(width) = style.button_width {
|
||||||
|
contents = contents.with_width(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.into_any()
|
||||||
|
})
|
||||||
|
.on_click(MouseButton::Left, {
|
||||||
|
let action = self.action.boxed_clone();
|
||||||
|
move |_, _, cx| {
|
||||||
|
cx.window()
|
||||||
|
.dispatch_action(cx.view_id(), action.as_ref(), cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.with_dynamic_tooltip(
|
||||||
|
self.tag,
|
||||||
|
0,
|
||||||
|
self.tooltip,
|
||||||
|
Some(self.action),
|
||||||
|
self.tooltip_style,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod svg {
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
elements::{GeneralComponent, StyleableComponent},
|
||||||
|
Element,
|
||||||
|
};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Default, JsonSchema)]
|
||||||
|
pub struct SvgStyle {
|
||||||
|
icon_width: f32,
|
||||||
|
icon_height: f32,
|
||||||
|
color: gpui::color::Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for SvgStyle {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum IconSize {
|
||||||
|
IconSize { icon_size: f32 },
|
||||||
|
Dimensions { width: f32, height: f32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct SvgStyleHelper {
|
||||||
|
#[serde(flatten)]
|
||||||
|
size: IconSize,
|
||||||
|
color: gpui::color::Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = SvgStyleHelper::deserialize(deserializer)?;
|
||||||
|
let color = json.color;
|
||||||
|
|
||||||
|
let result = match json.size {
|
||||||
|
IconSize::IconSize { icon_size } => SvgStyle {
|
||||||
|
icon_width: icon_size,
|
||||||
|
icon_height: icon_size,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
IconSize::Dimensions { width, height } => SvgStyle {
|
||||||
|
icon_width: width,
|
||||||
|
icon_height: height,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Svg<S> {
|
||||||
|
path: Cow<'static, str>,
|
||||||
|
style: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Svg<()> {
|
||||||
|
pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
|
||||||
|
Self {
|
||||||
|
path: path.into(),
|
||||||
|
style: (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleableComponent for Svg<()> {
|
||||||
|
type Style = SvgStyle;
|
||||||
|
|
||||||
|
type Output = Svg<SvgStyle>;
|
||||||
|
|
||||||
|
fn with_style(self, style: Self::Style) -> Self::Output {
|
||||||
|
Svg {
|
||||||
|
path: self.path,
|
||||||
|
style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GeneralComponent for Svg<SvgStyle> {
|
||||||
|
fn render<V: gpui::View>(
|
||||||
|
self,
|
||||||
|
_: &mut V,
|
||||||
|
_: &mut gpui::ViewContext<V>,
|
||||||
|
) -> gpui::AnyElement<V> {
|
||||||
|
gpui::elements::Svg::new(self.path)
|
||||||
|
.with_color(self.style.color)
|
||||||
|
.constrained()
|
||||||
|
.with_width(self.style.icon_width)
|
||||||
|
.with_height(self.style.icon_height)
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod label {
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
elements::{GeneralComponent, LabelStyle, StyleableComponent},
|
||||||
|
Element,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Label<S> {
|
||||||
|
text: Cow<'static, str>,
|
||||||
|
style: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Label<()> {
|
||||||
|
pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
|
||||||
|
Self {
|
||||||
|
text: text.into(),
|
||||||
|
style: (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleableComponent for Label<()> {
|
||||||
|
type Style = LabelStyle;
|
||||||
|
|
||||||
|
type Output = Label<LabelStyle>;
|
||||||
|
|
||||||
|
fn with_style(self, style: Self::Style) -> Self::Output {
|
||||||
|
Label {
|
||||||
|
text: self.text,
|
||||||
|
style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GeneralComponent for Label<LabelStyle> {
|
||||||
|
fn render<V: gpui::View>(
|
||||||
|
self,
|
||||||
|
_: &mut V,
|
||||||
|
_: &mut gpui::ViewContext<V>,
|
||||||
|
) -> gpui::AnyElement<V> {
|
||||||
|
gpui::elements::Label::new(self.text, self.style).into_any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue