Add action button component for rendering the search options
This commit is contained in:
parent
f451e3423d
commit
8630557ece
11 changed files with 223 additions and 35 deletions
|
@ -3313,11 +3313,20 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
|
|||
&mut self,
|
||||
element_id: usize,
|
||||
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> {
|
||||
let id = ElementStateId {
|
||||
view_id: self.view_id(),
|
||||
element_id,
|
||||
tag: TypeId::of::<Tag>(),
|
||||
tag,
|
||||
};
|
||||
self.element_states
|
||||
.entry(id)
|
||||
|
@ -3331,11 +3340,20 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
|
|||
) -> ElementStateHandle<T> {
|
||||
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)]
|
||||
pub struct TypeTag {
|
||||
tag: TypeId,
|
||||
composed: Option<TypeId>,
|
||||
#[cfg(debug_assertions)]
|
||||
tag_type_name: &'static str,
|
||||
}
|
||||
|
@ -3344,6 +3362,7 @@ impl TypeTag {
|
|||
pub fn new<Tag: 'static>() -> Self {
|
||||
Self {
|
||||
tag: TypeId::of::<Tag>(),
|
||||
composed: None,
|
||||
#[cfg(debug_assertions)]
|
||||
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 {
|
||||
Self {
|
||||
tag,
|
||||
composed: None,
|
||||
#[cfg(debug_assertions)]
|
||||
tag_type_name: type_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compose(mut self, other: TypeTag) -> Self {
|
||||
self.composed = Some(other.tag);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) fn type_name(&self) -> &'static str {
|
||||
self.tag_type_name
|
||||
|
@ -4751,7 +4776,7 @@ impl Hash for AnyWeakViewHandle {
|
|||
pub struct ElementStateId {
|
||||
view_id: usize,
|
||||
element_id: usize,
|
||||
tag: TypeId,
|
||||
tag: TypeTag,
|
||||
}
|
||||
|
||||
pub struct ElementStateHandle<T> {
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
use std::any::{Any, TypeId};
|
||||
|
||||
use crate::TypeTag;
|
||||
|
||||
pub trait Action: 'static {
|
||||
fn id(&self) -> TypeId;
|
||||
fn namespace(&self) -> &'static str;
|
||||
fn name(&self) -> &'static str;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn type_tag(&self) -> TypeTag;
|
||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||
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
|
||||
}
|
||||
};
|
||||
|
|
|
@ -34,8 +34,8 @@ use crate::{
|
|||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
|
||||
WeakViewHandle, WindowContext,
|
||||
json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, TypeTag, View,
|
||||
ViewContext, WeakViewHandle, WindowContext,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::HashMap;
|
||||
|
@ -172,6 +172,20 @@ pub trait Element<V: View>: 'static {
|
|||
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>(
|
||||
self,
|
||||
id: usize,
|
||||
|
|
|
@ -7,6 +7,34 @@ use crate::{
|
|||
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> {
|
||||
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> {
|
||||
component: Option<E>,
|
||||
phantom: PhantomData<V>,
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::json,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
|
||||
Task, View, ViewContext,
|
||||
Task, TypeTag, View, ViewContext,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
@ -61,11 +61,23 @@ impl<V: View> Tooltip<V> {
|
|||
child: AnyElement<V>,
|
||||
cx: &mut ViewContext<V>,
|
||||
) -> Self {
|
||||
struct ElementState<Tag>(Tag);
|
||||
struct MouseEventHandlerState<Tag>(Tag);
|
||||
Self::new_dynamic(TypeTag::new::<Tag>(), id, text, action, style, child, cx)
|
||||
}
|
||||
|
||||
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 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 text = text.into();
|
||||
|
||||
|
@ -95,7 +107,7 @@ impl<V: View> Tooltip<V> {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let child = MouseEventHandler::new::<MouseEventHandlerState<Tag>, _>(id, cx, |_, _| child)
|
||||
let child = MouseEventHandler::new_dynamic(tag, id, cx, |_, _| child)
|
||||
.on_hover(move |e, _, cx| {
|
||||
let position = e.position;
|
||||
if e.started {
|
||||
|
|
|
@ -19,6 +19,7 @@ use gpui::{
|
|||
use project::search::SearchQuery;
|
||||
use serde::Deserialize;
|
||||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use util::ResultExt;
|
||||
use workspace::{
|
||||
item::ItemHandle,
|
||||
|
@ -167,23 +168,17 @@ impl View for BufferSearchBar {
|
|||
cx,
|
||||
)
|
||||
};
|
||||
let render_search_option =
|
||||
|options: bool, icon, option, cx: &mut ViewContext<BufferSearchBar>| {
|
||||
options.then(|| {
|
||||
let is_active = self.search_options.contains(option);
|
||||
crate::search_bar::render_option_button_icon::<Self>(
|
||||
is_active,
|
||||
icon,
|
||||
option.bits as usize,
|
||||
format!("Toggle {}", option.label()),
|
||||
option.to_toggle_action(),
|
||||
move |_, this, cx| {
|
||||
this.toggle_search_option(option, cx);
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
};
|
||||
let render_search_option = |options: bool, icon, option| {
|
||||
options.then(|| {
|
||||
let is_active = self.search_options.contains(option);
|
||||
option.as_button(
|
||||
is_active,
|
||||
icon,
|
||||
theme.tooltip.clone(),
|
||||
theme.search.option_button_component.clone(),
|
||||
)
|
||||
})
|
||||
};
|
||||
let match_count = self
|
||||
.active_searchable_item
|
||||
.as_ref()
|
||||
|
@ -242,13 +237,11 @@ impl View for BufferSearchBar {
|
|||
supported_options.case,
|
||||
"icons/case_insensitive_12.svg",
|
||||
SearchOptions::CASE_SENSITIVE,
|
||||
cx,
|
||||
))
|
||||
.with_children(render_search_option(
|
||||
supported_options.word,
|
||||
"icons/word_search_12.svg",
|
||||
SearchOptions::WHOLE_WORD,
|
||||
cx,
|
||||
))
|
||||
.flex_float()
|
||||
.contained(),
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
use bitflags::bitflags;
|
||||
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;
|
||||
use project::search::SearchQuery;
|
||||
pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||
use theme::components::{action_button::ActionButton, ComponentExt, ToggleIconButtonStyle};
|
||||
|
||||
pub mod buffer_search;
|
||||
mod history;
|
||||
|
@ -69,4 +74,23 @@ impl SearchOptions {
|
|||
options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
pub mod components;
|
||||
mod theme_registry;
|
||||
mod theme_settings;
|
||||
pub mod ui;
|
||||
|
||||
use components::ToggleIconButtonStyle;
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
|
||||
|
@ -13,7 +15,7 @@ use serde::{de::DeserializeOwned, Deserialize};
|
|||
use serde_json::Value;
|
||||
use settings::SettingsStore;
|
||||
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_settings::*;
|
||||
|
@ -182,7 +184,7 @@ pub struct CopilotAuth {
|
|||
pub prompting: CopilotAuthPrompting,
|
||||
pub not_authorized: CopilotAuthNotAuthorized,
|
||||
pub authorized: CopilotAuthAuthorized,
|
||||
pub cta_button: ButtonStyle,
|
||||
pub cta_button: CopilotCTAButton,
|
||||
pub header: IconStyle,
|
||||
}
|
||||
|
||||
|
@ -196,7 +198,7 @@ pub struct CopilotAuthPrompting {
|
|||
#[derive(Deserialize, Default, Clone, JsonSchema)]
|
||||
pub struct DeviceCode {
|
||||
pub text: TextStyle,
|
||||
pub cta: ButtonStyle,
|
||||
pub cta: CopilotCTAButton,
|
||||
pub left: f32,
|
||||
pub left_container: ContainerStyle,
|
||||
pub right: f32,
|
||||
|
@ -420,6 +422,7 @@ pub struct Search {
|
|||
pub invalid_include_exclude_editor: ContainerStyle,
|
||||
pub include_exclude_inputs: ContainedText,
|
||||
pub option_button: Toggleable<Interactive<IconButton>>,
|
||||
pub option_button_component: ToggleIconButtonStyle,
|
||||
pub action_button: Toggleable<Interactive<ContainedText>>,
|
||||
pub match_background: Color,
|
||||
pub match_index: ContainedText,
|
||||
|
@ -887,12 +890,32 @@ pub struct Interactive<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)]
|
||||
pub struct Toggleable<T> {
|
||||
active: T,
|
||||
inactive: T,
|
||||
}
|
||||
|
||||
impl Toggleable<()> {
|
||||
pub fn new_blank() -> Self {
|
||||
Self {
|
||||
active: (),
|
||||
inactive: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Toggleable<T> {
|
||||
pub fn new(active: T, inactive: T) -> Self {
|
||||
Self { active, inactive }
|
||||
|
|
|
@ -145,12 +145,12 @@ pub fn keystroke_label<V: View>(
|
|||
.with_style(label_style.container)
|
||||
}
|
||||
|
||||
pub type ButtonStyle = Interactive<ContainedText>;
|
||||
pub type CopilotCTAButton = Interactive<ContainedText>;
|
||||
|
||||
pub fn cta_button<Tag, L, V, F>(
|
||||
label: L,
|
||||
max_width: f32,
|
||||
style: &ButtonStyle,
|
||||
style: &CopilotCTAButton,
|
||||
cx: &mut ViewContext<V>,
|
||||
f: F,
|
||||
) -> MouseEventHandler<V>
|
||||
|
|
|
@ -320,7 +320,6 @@ impl Pane {
|
|||
can_drop: Rc::new(|_, _| true),
|
||||
can_split: true,
|
||||
render_tab_bar_buttons: Rc::new(move |pane, cx| {
|
||||
let tooltip_style = theme::current(cx).tooltip.clone();
|
||||
Flex::row()
|
||||
// New menu
|
||||
.with_child(Self::render_tab_bar_button(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue