Add action button component for rendering the search options

This commit is contained in:
Mikayla 2023-08-17 15:30:40 -07:00
parent f451e3423d
commit 8630557ece
No known key found for this signature in database
11 changed files with 223 additions and 35 deletions

View file

@ -3313,11 +3313,20 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
&mut self, &mut self,
element_id: usize, element_id: usize,
initial: T, initial: T,
) -> ElementStateHandle<T> {
self.element_state_dynamic(TypeTag::new::<Tag>(), element_id, initial)
}
pub fn element_state_dynamic<T: 'static>(
&mut self,
tag: TypeTag,
element_id: usize,
initial: T,
) -> ElementStateHandle<T> { ) -> ElementStateHandle<T> {
let id = ElementStateId { let id = ElementStateId {
view_id: self.view_id(), view_id: self.view_id(),
element_id, element_id,
tag: TypeId::of::<Tag>(), tag,
}; };
self.element_states self.element_states
.entry(id) .entry(id)
@ -3331,11 +3340,20 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
) -> ElementStateHandle<T> { ) -> ElementStateHandle<T> {
self.element_state::<Tag, T>(element_id, T::default()) self.element_state::<Tag, T>(element_id, T::default())
} }
pub fn default_element_state_dynamic<T: 'static + Default>(
&mut self,
tag: TypeTag,
element_id: usize,
) -> ElementStateHandle<T> {
self.element_state_dynamic::<T>(tag, element_id, T::default())
}
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TypeTag { pub struct TypeTag {
tag: TypeId, tag: TypeId,
composed: Option<TypeId>,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
tag_type_name: &'static str, tag_type_name: &'static str,
} }
@ -3344,6 +3362,7 @@ impl TypeTag {
pub fn new<Tag: 'static>() -> Self { pub fn new<Tag: 'static>() -> Self {
Self { Self {
tag: TypeId::of::<Tag>(), tag: TypeId::of::<Tag>(),
composed: None,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
tag_type_name: std::any::type_name::<Tag>(), tag_type_name: std::any::type_name::<Tag>(),
} }
@ -3352,11 +3371,17 @@ impl TypeTag {
pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self { pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
Self { Self {
tag, tag,
composed: None,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
tag_type_name: type_name, tag_type_name: type_name,
} }
} }
pub fn compose(mut self, other: TypeTag) -> Self {
self.composed = Some(other.tag);
self
}
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
pub(crate) fn type_name(&self) -> &'static str { pub(crate) fn type_name(&self) -> &'static str {
self.tag_type_name self.tag_type_name
@ -4751,7 +4776,7 @@ impl Hash for AnyWeakViewHandle {
pub struct ElementStateId { pub struct ElementStateId {
view_id: usize, view_id: usize,
element_id: usize, element_id: usize,
tag: TypeId, tag: TypeTag,
} }
pub struct ElementStateHandle<T> { pub struct ElementStateHandle<T> {

View file

@ -1,10 +1,13 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use crate::TypeTag;
pub trait Action: 'static { pub trait Action: 'static {
fn id(&self) -> TypeId; fn id(&self) -> TypeId;
fn namespace(&self) -> &'static str; fn namespace(&self) -> &'static str;
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
fn type_tag(&self) -> TypeTag;
fn boxed_clone(&self) -> Box<dyn Action>; fn boxed_clone(&self) -> Box<dyn Action>;
fn eq(&self, other: &dyn Action) -> bool; fn eq(&self, other: &dyn Action) -> bool;
@ -107,6 +110,10 @@ macro_rules! __impl_action {
} }
} }
fn type_tag(&self) -> $crate::TypeTag {
$crate::TypeTag::new::<Self>()
}
$from_json_fn $from_json_fn
} }
}; };

View file

@ -34,8 +34,8 @@ use crate::{
rect::RectF, rect::RectF,
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext, json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, TypeTag, View,
WeakViewHandle, WindowContext, ViewContext, WeakViewHandle, WindowContext,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use collections::HashMap; use collections::HashMap;
@ -172,6 +172,20 @@ pub trait Element<V: View>: 'static {
FlexItem::new(self.into_any()).float() FlexItem::new(self.into_any()).float()
} }
fn with_dynamic_tooltip(
self,
tag: TypeTag,
id: usize,
text: impl Into<Cow<'static, str>>,
action: Option<Box<dyn Action>>,
style: TooltipStyle,
cx: &mut ViewContext<V>,
) -> Tooltip<V>
where
Self: 'static + Sized,
{
Tooltip::new_dynamic(tag, id, text, action, style, self.into_any(), cx)
}
fn with_tooltip<Tag: 'static>( fn with_tooltip<Tag: 'static>(
self, self,
id: usize, id: usize,

View file

@ -7,6 +7,34 @@ use crate::{
ViewContext, ViewContext,
}; };
use super::Empty;
pub trait GeneralComponent {
fn render<V: View>(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
}
pub trait StyleableComponent {
type Style: Clone;
type Output: GeneralComponent;
fn with_style(self, style: Self::Style) -> Self::Output;
}
impl GeneralComponent for () {
fn render<V: View>(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
Empty::new().into_any()
}
}
impl StyleableComponent for () {
type Style = ();
type Output = ();
fn with_style(self, _: Self::Style) -> Self::Output {
()
}
}
pub trait Component<V: View> { pub trait Component<V: View> {
fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>; fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
@ -18,6 +46,12 @@ pub trait Component<V: View> {
} }
} }
impl<V: View, C: GeneralComponent> Component<V> for C {
fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
self.render(v, cx)
}
}
pub struct ComponentAdapter<V, E> { pub struct ComponentAdapter<V, E> {
component: Option<E>, component: Option<E>,
phantom: PhantomData<V>, phantom: PhantomData<V>,

View file

@ -7,7 +7,7 @@ use crate::{
geometry::{rect::RectF, vector::Vector2F}, geometry::{rect::RectF, vector::Vector2F},
json::json, json::json,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
Task, View, ViewContext, Task, TypeTag, View, ViewContext,
}; };
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
@ -61,11 +61,23 @@ impl<V: View> Tooltip<V> {
child: AnyElement<V>, child: AnyElement<V>,
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) -> Self { ) -> Self {
struct ElementState<Tag>(Tag); Self::new_dynamic(TypeTag::new::<Tag>(), id, text, action, style, child, cx)
struct MouseEventHandlerState<Tag>(Tag); }
pub fn new_dynamic(
mut tag: TypeTag,
id: usize,
text: impl Into<Cow<'static, str>>,
action: Option<Box<dyn Action>>,
style: TooltipStyle,
child: AnyElement<V>,
cx: &mut ViewContext<V>,
) -> Self {
tag = tag.compose(TypeTag::new::<Self>());
let focused_view_id = cx.focused_view_id(); let focused_view_id = cx.focused_view_id();
let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id); let state_handle = cx.default_element_state_dynamic::<Rc<TooltipState>>(tag, id);
let state = state_handle.read(cx).clone(); let state = state_handle.read(cx).clone();
let text = text.into(); let text = text.into();
@ -95,7 +107,7 @@ impl<V: View> Tooltip<V> {
} else { } else {
None None
}; };
let child = MouseEventHandler::new::<MouseEventHandlerState<Tag>, _>(id, cx, |_, _| child) let child = MouseEventHandler::new_dynamic(tag, id, cx, |_, _| child)
.on_hover(move |e, _, cx| { .on_hover(move |e, _, cx| {
let position = e.position; let position = e.position;
if e.started { if e.started {

View file

@ -19,6 +19,7 @@ use gpui::{
use project::search::SearchQuery; use project::search::SearchQuery;
use serde::Deserialize; use serde::Deserialize;
use std::{any::Any, sync::Arc}; use std::{any::Any, sync::Arc};
use util::ResultExt; use util::ResultExt;
use workspace::{ use workspace::{
item::ItemHandle, item::ItemHandle,
@ -167,23 +168,17 @@ impl View for BufferSearchBar {
cx, cx,
) )
}; };
let render_search_option = let render_search_option = |options: bool, icon, option| {
|options: bool, icon, option, cx: &mut ViewContext<BufferSearchBar>| { options.then(|| {
options.then(|| { let is_active = self.search_options.contains(option);
let is_active = self.search_options.contains(option); option.as_button(
crate::search_bar::render_option_button_icon::<Self>( is_active,
is_active, icon,
icon, theme.tooltip.clone(),
option.bits as usize, theme.search.option_button_component.clone(),
format!("Toggle {}", option.label()), )
option.to_toggle_action(), })
move |_, this, cx| { };
this.toggle_search_option(option, cx);
},
cx,
)
})
};
let match_count = self let match_count = self
.active_searchable_item .active_searchable_item
.as_ref() .as_ref()
@ -242,13 +237,11 @@ impl View for BufferSearchBar {
supported_options.case, supported_options.case,
"icons/case_insensitive_12.svg", "icons/case_insensitive_12.svg",
SearchOptions::CASE_SENSITIVE, SearchOptions::CASE_SENSITIVE,
cx,
)) ))
.with_children(render_search_option( .with_children(render_search_option(
supported_options.word, supported_options.word,
"icons/word_search_12.svg", "icons/word_search_12.svg",
SearchOptions::WHOLE_WORD, SearchOptions::WHOLE_WORD,
cx,
)) ))
.flex_float() .flex_float()
.contained(), .contained(),

View file

@ -1,9 +1,14 @@
use bitflags::bitflags; use bitflags::bitflags;
pub use buffer_search::BufferSearchBar; pub use buffer_search::BufferSearchBar;
use gpui::{actions, Action, AppContext}; use gpui::{
actions,
elements::{Component, StyleableComponent, TooltipStyle},
Action, AnyElement, AppContext, Element, View,
};
pub use mode::SearchMode; pub use mode::SearchMode;
use project::search::SearchQuery; use project::search::SearchQuery;
pub use project_search::{ProjectSearchBar, ProjectSearchView}; pub use project_search::{ProjectSearchBar, ProjectSearchView};
use theme::components::{action_button::ActionButton, ComponentExt, ToggleIconButtonStyle};
pub mod buffer_search; pub mod buffer_search;
mod history; mod history;
@ -69,4 +74,23 @@ impl SearchOptions {
options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive()); options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
options options
} }
pub fn as_button<V: View>(
&self,
active: bool,
icon: &str,
tooltip_style: TooltipStyle,
button_style: ToggleIconButtonStyle,
) -> AnyElement<V> {
ActionButton::new_dynamic(
self.to_toggle_action(),
format!("Toggle {}", self.label()),
tooltip_style,
)
.with_contents(theme::components::svg::Svg::new(icon.to_owned()))
.toggleable(active)
.with_style(button_style)
.into_element()
.into_any()
}
} }

View file

@ -1,7 +1,9 @@
pub mod components;
mod theme_registry; mod theme_registry;
mod theme_settings; mod theme_settings;
pub mod ui; pub mod ui;
use components::ToggleIconButtonStyle;
use gpui::{ use gpui::{
color::Color, color::Color,
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle}, elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
@ -13,7 +15,7 @@ use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value; use serde_json::Value;
use settings::SettingsStore; use settings::SettingsStore;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle}; use ui::{CheckboxStyle, CopilotCTAButton, IconStyle, ModalStyle};
pub use theme_registry::*; pub use theme_registry::*;
pub use theme_settings::*; pub use theme_settings::*;
@ -182,7 +184,7 @@ pub struct CopilotAuth {
pub prompting: CopilotAuthPrompting, pub prompting: CopilotAuthPrompting,
pub not_authorized: CopilotAuthNotAuthorized, pub not_authorized: CopilotAuthNotAuthorized,
pub authorized: CopilotAuthAuthorized, pub authorized: CopilotAuthAuthorized,
pub cta_button: ButtonStyle, pub cta_button: CopilotCTAButton,
pub header: IconStyle, pub header: IconStyle,
} }
@ -196,7 +198,7 @@ pub struct CopilotAuthPrompting {
#[derive(Deserialize, Default, Clone, JsonSchema)] #[derive(Deserialize, Default, Clone, JsonSchema)]
pub struct DeviceCode { pub struct DeviceCode {
pub text: TextStyle, pub text: TextStyle,
pub cta: ButtonStyle, pub cta: CopilotCTAButton,
pub left: f32, pub left: f32,
pub left_container: ContainerStyle, pub left_container: ContainerStyle,
pub right: f32, pub right: f32,
@ -420,6 +422,7 @@ pub struct Search {
pub invalid_include_exclude_editor: ContainerStyle, pub invalid_include_exclude_editor: ContainerStyle,
pub include_exclude_inputs: ContainedText, pub include_exclude_inputs: ContainedText,
pub option_button: Toggleable<Interactive<IconButton>>, pub option_button: Toggleable<Interactive<IconButton>>,
pub option_button_component: ToggleIconButtonStyle,
pub action_button: Toggleable<Interactive<ContainedText>>, pub action_button: Toggleable<Interactive<ContainedText>>,
pub match_background: Color, pub match_background: Color,
pub match_index: ContainedText, pub match_index: ContainedText,
@ -887,12 +890,32 @@ pub struct Interactive<T> {
pub disabled: Option<T>, pub disabled: Option<T>,
} }
impl Interactive<()> {
pub fn new_blank() -> Self {
Self {
default: (),
hovered: None,
clicked: None,
disabled: None,
}
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
pub struct Toggleable<T> { pub struct Toggleable<T> {
active: T, active: T,
inactive: T, inactive: T,
} }
impl Toggleable<()> {
pub fn new_blank() -> Self {
Self {
active: (),
inactive: (),
}
}
}
impl<T> Toggleable<T> { impl<T> Toggleable<T> {
pub fn new(active: T, inactive: T) -> Self { pub fn new(active: T, inactive: T) -> Self {
Self { active, inactive } Self { active, inactive }

View file

@ -145,12 +145,12 @@ pub fn keystroke_label<V: View>(
.with_style(label_style.container) .with_style(label_style.container)
} }
pub type ButtonStyle = Interactive<ContainedText>; pub type CopilotCTAButton = Interactive<ContainedText>;
pub fn cta_button<Tag, L, V, F>( pub fn cta_button<Tag, L, V, F>(
label: L, label: L,
max_width: f32, max_width: f32,
style: &ButtonStyle, style: &CopilotCTAButton,
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
f: F, f: F,
) -> MouseEventHandler<V> ) -> MouseEventHandler<V>

View file

@ -320,7 +320,6 @@ impl Pane {
can_drop: Rc::new(|_, _| true), can_drop: Rc::new(|_, _| true),
can_split: true, can_split: true,
render_tab_bar_buttons: Rc::new(move |pane, cx| { render_tab_bar_buttons: Rc::new(move |pane, cx| {
let tooltip_style = theme::current(cx).tooltip.clone();
Flex::row() Flex::row()
// New menu // New menu
.with_child(Self::render_tab_bar_button( .with_child(Self::render_tab_bar_button(

View file

@ -96,6 +96,63 @@ export default function search(): any {
}, },
}, },
}), }),
option_button_component: toggleable({
base: interactive({
base: {
icon_size: 14,
color: foreground(theme.highest, "variant"),
button_width: 32,
background: background(theme.highest, "on"),
corner_radius: 2,
margin: { right: 2 },
border: {
width: 1., color: background(theme.highest, "on")
},
padding: {
left: 4,
right: 4,
top: 4,
bottom: 4,
},
},
state: {
hovered: {
...text(theme.highest, "mono", "variant", "hovered"),
background: background(theme.highest, "on", "hovered"),
border: {
width: 1., color: background(theme.highest, "on", "hovered")
},
},
clicked: {
...text(theme.highest, "mono", "variant", "pressed"),
background: background(theme.highest, "on", "pressed"),
border: {
width: 1., color: background(theme.highest, "on", "pressed")
},
},
},
}),
state: {
active: {
default: {
icon_size: 14,
button_width: 32,
color: foreground(theme.highest, "variant"),
background: background(theme.highest, "accent"),
border: border(theme.highest, "accent"),
},
hovered: {
background: background(theme.highest, "accent", "hovered"),
border: border(theme.highest, "accent", "hovered"),
},
clicked: {
background: background(theme.highest, "accent", "pressed"),
border: border(theme.highest, "accent", "pressed"),
},
},
},
}),
action_button: toggleable({ action_button: toggleable({
base: interactive({ base: interactive({
base: { base: {