Project search design (#2834)
TODO before merging: - [x] Re-run project search when options (case, word, regex) change /cc @PixelJanitor Release Notes: - Revamped project & buffer search UI. - Added "Cycle Mode" command for search
This commit is contained in:
commit
8451e7eb7e
33 changed files with 2150 additions and 1064 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,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||
|
||||
use crate::{
|
||||
|
@ -7,6 +5,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,22 +44,32 @@ pub trait Component<V: View> {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct ComponentAdapter<V, E> {
|
||||
component: Option<E>,
|
||||
phantom: PhantomData<V>,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, V> ComponentAdapter<V, E> {
|
||||
pub struct ComponentAdapter<V: View, E> {
|
||||
component: Option<E>,
|
||||
element: Option<AnyElement<V>>,
|
||||
#[cfg(debug_assertions)]
|
||||
_component_name: &'static str,
|
||||
}
|
||||
|
||||
impl<E, V: View> ComponentAdapter<V, E> {
|
||||
pub fn new(e: E) -> Self {
|
||||
Self {
|
||||
component: Some(e),
|
||||
phantom: PhantomData,
|
||||
element: None,
|
||||
#[cfg(debug_assertions)]
|
||||
_component_name: std::any::type_name::<E>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
||||
type LayoutState = AnyElement<V>;
|
||||
type LayoutState = ();
|
||||
|
||||
type PaintState = ();
|
||||
|
||||
|
@ -43,10 +79,12 @@ impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
|||
view: &mut V,
|
||||
cx: &mut LayoutContext<V>,
|
||||
) -> (Vector2F, Self::LayoutState) {
|
||||
let component = self.component.take().unwrap();
|
||||
let mut element = component.render(view, cx.view_context());
|
||||
let constraint = element.layout(constraint, view, cx);
|
||||
(constraint, element)
|
||||
if self.element.is_none() {
|
||||
let component = self.component.take().unwrap();
|
||||
self.element = Some(component.render(view, cx.view_context()));
|
||||
}
|
||||
let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx);
|
||||
(constraint, ())
|
||||
}
|
||||
|
||||
fn paint(
|
||||
|
@ -54,11 +92,14 @@ impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
|||
scene: &mut SceneBuilder,
|
||||
bounds: RectF,
|
||||
visible_bounds: RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
_: &mut Self::LayoutState,
|
||||
view: &mut V,
|
||||
cx: &mut PaintContext<V>,
|
||||
) -> Self::PaintState {
|
||||
layout.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||
self.element
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||
}
|
||||
|
||||
fn rect_for_text_range(
|
||||
|
@ -66,25 +107,35 @@ impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
|||
range_utf16: std::ops::Range<usize>,
|
||||
_: RectF,
|
||||
_: RectF,
|
||||
element: &Self::LayoutState,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> Option<RectF> {
|
||||
element.rect_for_text_range(range_utf16, view, cx)
|
||||
self.element
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.rect_for_text_range(range_utf16, view, cx)
|
||||
}
|
||||
|
||||
fn debug(
|
||||
&self,
|
||||
_: RectF,
|
||||
element: &Self::LayoutState,
|
||||
_: &Self::LayoutState,
|
||||
_: &Self::PaintState,
|
||||
view: &V,
|
||||
cx: &ViewContext<V>,
|
||||
) -> serde_json::Value {
|
||||
#[cfg(debug_assertions)]
|
||||
let component_name = self._component_name;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let component_name = "Unknown";
|
||||
|
||||
serde_json::json!({
|
||||
"type": "ComponentAdapter",
|
||||
"child": element.debug(view, cx),
|
||||
"child": self.element.as_ref().unwrap().debug(view, cx),
|
||||
"component_name": component_name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue