wip: picker

co-authored-by: nathan <nathan@zed.dev>
co-authored-by: max <max@zed.dev>
This commit is contained in:
Mikayla 2023-11-06 17:09:38 -08:00
parent 3c93b585ab
commit 85000eba81
No known key found for this signature in database
21 changed files with 460 additions and 278 deletions

4
Cargo.lock generated
View File

@ -4970,7 +4970,8 @@ dependencies = [
name = "menu2" name = "menu2"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"gpui2", "serde",
"serde_derive",
] ]
[[package]] [[package]]
@ -8542,6 +8543,7 @@ dependencies = [
"gpui2", "gpui2",
"itertools 0.11.0", "itertools 0.11.0",
"log", "log",
"menu2",
"picker2", "picker2",
"rust-embed", "rust-embed",
"serde", "serde",

View File

@ -194,6 +194,15 @@ pub fn red() -> Hsla {
} }
} }
pub fn blue() -> Hsla {
Hsla {
h: 0.6,
s: 1.,
l: 0.5,
a: 1.,
}
}
impl Hsla { impl Hsla {
/// Returns true if the HSLA color is fully transparent, false otherwise. /// Returns true if the HSLA color is fully transparent, false otherwise.
pub fn is_transparent(&self) -> bool { pub fn is_transparent(&self) -> bool {

View File

@ -1,28 +1,28 @@
use crate::{ use crate::{
point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId, point, AnyElement, BorrowWindow, Bounds, Component, Element, ElementFocus, ElementId,
ElementInteraction, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable, ElementInteractivity, FocusDisabled, FocusEnabled, FocusHandle, FocusListeners, Focusable,
GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement, GlobalElementId, GroupBounds, InteractiveElementState, LayoutId, Overflow, ParentElement,
Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility, StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility,
}; };
use refineable::Refineable; use refineable::Refineable;
use smallvec::SmallVec; use smallvec::SmallVec;
pub struct Div< pub struct Div<
V: 'static, V: 'static,
I: ElementInteraction<V> = StatelessInteraction<V>, I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled, F: ElementFocus<V> = FocusDisabled,
> { > {
interaction: I, interactivity: I,
focus: F, focus: F,
children: SmallVec<[AnyElement<V>; 2]>, children: SmallVec<[AnyElement<V>; 2]>,
group: Option<SharedString>, group: Option<SharedString>,
base_style: StyleRefinement, base_style: StyleRefinement,
} }
pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> { pub fn div<V: 'static>() -> Div<V, StatelessInteractivity<V>, FocusDisabled> {
Div { Div {
interaction: StatelessInteraction::default(), interactivity: StatelessInteractivity::default(),
focus: FocusDisabled, focus: FocusDisabled,
children: SmallVec::new(), children: SmallVec::new(),
group: None, group: None,
@ -30,14 +30,14 @@ pub fn div<V: 'static>() -> Div<V, StatelessInteraction<V>, FocusDisabled> {
} }
} }
impl<V, F> Div<V, StatelessInteraction<V>, F> impl<V, F> Div<V, StatelessInteractivity<V>, F>
where where
V: 'static, V: 'static,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteraction<V>, F> { pub fn id(self, id: impl Into<ElementId>) -> Div<V, StatefulInteractivity<V>, F> {
Div { Div {
interaction: id.into().into(), interactivity: id.into().into(),
focus: self.focus, focus: self.focus,
children: self.children, children: self.children,
group: self.group, group: self.group,
@ -48,7 +48,7 @@ where
impl<V, I, F> Div<V, I, F> impl<V, I, F> Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn group(mut self, group: impl Into<SharedString>) -> Self { pub fn group(mut self, group: impl Into<SharedString>) -> Self {
@ -98,16 +98,20 @@ where
let mut computed_style = Style::default(); let mut computed_style = Style::default();
computed_style.refine(&self.base_style); computed_style.refine(&self.base_style);
self.focus.refine_style(&mut computed_style, cx); self.focus.refine_style(&mut computed_style, cx);
self.interaction self.interactivity.refine_style(
.refine_style(&mut computed_style, bounds, &element_state.interactive, cx); &mut computed_style,
bounds,
&element_state.interactive,
cx,
);
computed_style computed_style
} }
} }
impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> { impl<V: 'static> Div<V, StatefulInteractivity<V>, FocusDisabled> {
pub fn focusable(self) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> { pub fn focusable(self) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div { Div {
interaction: self.interaction, interactivity: self.interactivity,
focus: FocusEnabled::new(), focus: FocusEnabled::new(),
children: self.children, children: self.children,
group: self.group, group: self.group,
@ -118,9 +122,9 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
pub fn track_focus( pub fn track_focus(
self, self,
handle: &FocusHandle, handle: &FocusHandle,
) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> { ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div { Div {
interaction: self.interaction, interactivity: self.interactivity,
focus: FocusEnabled::tracked(handle), focus: FocusEnabled::tracked(handle),
children: self.children, children: self.children,
group: self.group, group: self.group,
@ -145,13 +149,13 @@ impl<V: 'static> Div<V, StatefulInteraction<V>, FocusDisabled> {
} }
} }
impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> { impl<V: 'static> Div<V, StatelessInteractivity<V>, FocusDisabled> {
pub fn track_focus( pub fn track_focus(
self, self,
handle: &FocusHandle, handle: &FocusHandle,
) -> Div<V, StatefulInteraction<V>, FocusEnabled<V>> { ) -> Div<V, StatefulInteractivity<V>, FocusEnabled<V>> {
Div { Div {
interaction: self.interaction.into_stateful(handle), interactivity: self.interactivity.into_stateful(handle),
focus: handle.clone().into(), focus: handle.clone().into(),
children: self.children, children: self.children,
group: self.group, group: self.group,
@ -163,7 +167,7 @@ impl<V: 'static> Div<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>> impl<V, I> Focusable<V> for Div<V, I, FocusEnabled<V>>
where where
V: 'static, V: 'static,
I: ElementInteraction<V>, I: ElementInteractivity<V>,
{ {
fn focus_listeners(&mut self) -> &mut FocusListeners<V> { fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
&mut self.focus.focus_listeners &mut self.focus.focus_listeners
@ -191,13 +195,13 @@ pub struct DivState {
impl<V, I, F> Element<V> for Div<V, I, F> impl<V, I, F> Element<V> for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
type ElementState = DivState; type ElementState = DivState;
fn id(&self) -> Option<ElementId> { fn id(&self) -> Option<ElementId> {
self.interaction self.interactivity
.as_stateful() .as_stateful()
.map(|identified| identified.id.clone()) .map(|identified| identified.id.clone())
} }
@ -212,7 +216,7 @@ where
self.focus self.focus
.initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| { .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
element_state.focus_handle = focus_handle; element_state.focus_handle = focus_handle;
self.interaction.initialize(cx, |cx| { self.interactivity.initialize(cx, |cx| {
for child in &mut self.children { for child in &mut self.children {
child.initialize(view_state, cx); child.initialize(view_state, cx);
} }
@ -281,11 +285,11 @@ where
(child_max - child_min).into() (child_max - child_min).into()
}; };
cx.stack(z_index, |cx| { cx.with_z_index(z_index, |cx| {
cx.stack(0, |cx| { cx.with_z_index(0, |cx| {
style.paint(bounds, cx); style.paint(bounds, cx);
this.focus.paint(bounds, cx); this.focus.paint(bounds, cx);
this.interaction.paint( this.interactivity.paint(
bounds, bounds,
content_size, content_size,
style.overflow, style.overflow,
@ -293,7 +297,7 @@ where
cx, cx,
); );
}); });
cx.stack(1, |cx| { cx.with_z_index(1, |cx| {
style.apply_text_style(cx, |cx| { style.apply_text_style(cx, |cx| {
style.apply_overflow(bounds, cx, |cx| { style.apply_overflow(bounds, cx, |cx| {
let scroll_offset = element_state.interactive.scroll_offset(); let scroll_offset = element_state.interactive.scroll_offset();
@ -316,7 +320,7 @@ where
impl<V, I, F> Component<V> for Div<V, I, F> impl<V, I, F> Component<V> for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn render(self) -> AnyElement<V> { fn render(self) -> AnyElement<V> {
@ -326,7 +330,7 @@ where
impl<V, I, F> ParentElement<V> for Div<V, I, F> impl<V, I, F> ParentElement<V> for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
@ -336,7 +340,7 @@ where
impl<V, I, F> Styled for Div<V, I, F> impl<V, I, F> Styled for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn style(&mut self) -> &mut StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
@ -346,19 +350,19 @@ where
impl<V, I, F> StatelessInteractive<V> for Div<V, I, F> impl<V, I, F> StatelessInteractive<V> for Div<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.interaction.as_stateless_mut() self.interactivity.as_stateless_mut()
} }
} }
impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteraction<V>, F> impl<V, F> StatefulInteractive<V> for Div<V, StatefulInteractivity<V>, F>
where where
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
&mut self.interaction &mut self.interactivity
} }
} }

View File

@ -1,17 +1,15 @@
use std::sync::Arc;
use crate::{ use crate::{
div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus, div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus,
ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable,
LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity,
StatelessInteractive, StyleRefinement, Styled, ViewContext, StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
}; };
use futures::FutureExt; use futures::FutureExt;
use util::ResultExt; use util::ResultExt;
pub struct Img< pub struct Img<
V: 'static, V: 'static,
I: ElementInteraction<V> = StatelessInteraction<V>, I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled, F: ElementFocus<V> = FocusDisabled,
> { > {
base: Div<V, I, F>, base: Div<V, I, F>,
@ -19,7 +17,7 @@ pub struct Img<
grayscale: bool, grayscale: bool,
} }
pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> { pub fn img<V: 'static>() -> Img<V, StatelessInteractivity<V>, FocusDisabled> {
Img { Img {
base: div(), base: div(),
uri: None, uri: None,
@ -30,7 +28,7 @@ pub fn img<V: 'static>() -> Img<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I, F> Img<V, I, F> impl<V, I, F> Img<V, I, F>
where where
V: 'static, V: 'static,
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn uri(mut self, uri: impl Into<SharedString>) -> Self { pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
@ -44,11 +42,11 @@ where
} }
} }
impl<V, F> Img<V, StatelessInteraction<V>, F> impl<V, F> Img<V, StatelessInteractivity<V>, F>
where where
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteraction<V>, F> { pub fn id(self, id: impl Into<ElementId>) -> Img<V, StatefulInteractivity<V>, F> {
Img { Img {
base: self.base.id(id), base: self.base.id(id),
uri: self.uri, uri: self.uri,
@ -59,7 +57,7 @@ where
impl<V, I, F> Component<V> for Img<V, I, F> impl<V, I, F> Component<V> for Img<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn render(self) -> AnyElement<V> { fn render(self) -> AnyElement<V> {
@ -69,7 +67,7 @@ where
impl<V, I, F> Element<V> for Img<V, I, F> impl<V, I, F> Element<V> for Img<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
type ElementState = DivState; type ElementState = DivState;
@ -103,7 +101,7 @@ where
element_state: &mut Self::ElementState, element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) { ) {
cx.stack(0, |cx| { cx.with_z_index(0, |cx| {
self.base.paint(bounds, view, element_state, cx); self.base.paint(bounds, view, element_state, cx);
}); });
@ -120,7 +118,7 @@ where
.and_then(ResultExt::log_err) .and_then(ResultExt::log_err)
{ {
let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size()); let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
cx.stack(1, |cx| { cx.with_z_index(1, |cx| {
cx.paint_image(bounds, corner_radii, data, self.grayscale) cx.paint_image(bounds, corner_radii, data, self.grayscale)
.log_err() .log_err()
}); });
@ -138,7 +136,7 @@ where
impl<V, I, F> Styled for Img<V, I, F> impl<V, I, F> Styled for Img<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn style(&mut self) -> &mut StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
@ -148,27 +146,27 @@ where
impl<V, I, F> StatelessInteractive<V> for Img<V, I, F> impl<V, I, F> StatelessInteractive<V> for Img<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interaction() self.base.stateless_interactivity()
} }
} }
impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteraction<V>, F> impl<V, F> StatefulInteractive<V> for Img<V, StatefulInteractivity<V>, F>
where where
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
self.base.stateful_interaction() self.base.stateful_interactivity()
} }
} }
impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>> impl<V, I> Focusable<V> for Img<V, I, FocusEnabled<V>>
where where
V: 'static, V: 'static,
I: ElementInteraction<V>, I: ElementInteractivity<V>,
{ {
fn focus_listeners(&mut self) -> &mut FocusListeners<V> { fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
self.base.focus_listeners() self.base.focus_listeners()

View File

@ -1,15 +1,12 @@
use std::{cmp, ops::Range};
use smallvec::SmallVec;
use crate::{ use crate::{
point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId, point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
LayoutId, Pixels, Size, StyleRefinement, Styled, ViewContext, ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Size, StatefulInteractive,
StatefulInteractivity, StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled,
ViewContext,
}; };
use smallvec::SmallVec;
// We want to support uniform and non-uniform height use std::{cmp, ops::Range};
// We need to make the ID mandatory, to replace the 'state' field use taffy::style::Overflow;
// Previous implementation measured the first element as early as possible
pub fn list<Id, V, C>( pub fn list<Id, V, C>(
id: Id, id: Id,
@ -21,8 +18,9 @@ where
V: 'static, V: 'static,
C: Component<V>, C: Component<V>,
{ {
let id = id.into();
List { List {
id: id.into(), id: id.clone(),
style: Default::default(), style: Default::default(),
item_count, item_count,
render_items: Box::new(move |view, visible_range, cx| { render_items: Box::new(move |view, visible_range, cx| {
@ -31,10 +29,11 @@ where
.map(|component| component.render()) .map(|component| component.render())
.collect() .collect()
}), }),
interactivity: id.into(),
} }
} }
pub struct List<V> { pub struct List<V: 'static> {
id: ElementId, id: ElementId,
style: StyleRefinement, style: StyleRefinement,
item_count: usize, item_count: usize,
@ -45,19 +44,12 @@ pub struct List<V> {
&'a mut ViewContext<V>, &'a mut ViewContext<V>,
) -> SmallVec<[AnyElement<V>; 64]>, ) -> SmallVec<[AnyElement<V>; 64]>,
>, >,
interactivity: StatefulInteractivity<V>,
} }
// #[derive(Debug)]
// pub enum ScrollTarget {
// Show(usize),
// Center(usize),
// }
#[derive(Default)] #[derive(Default)]
pub struct ListState { pub struct ListState {
scroll_top: f32, interactive: InteractiveElementState,
// todo
// scroll_to: Option<ScrollTarget>,
} }
impl<V: 'static> Styled for List<V> { impl<V: 'static> Styled for List<V> {
@ -111,30 +103,66 @@ impl<V: 'static> Element<V> for List<V> {
- point(border.right + padding.right, border.bottom + padding.bottom), - point(border.right + padding.right, border.bottom + padding.bottom),
); );
if self.item_count > 0 { cx.with_z_index(style.z_index.unwrap_or(0), |cx| {
let item_height = self.measure_item_height(view_state, padded_bounds, cx); let content_size;
let visible_item_count = (padded_bounds.size.height / item_height).ceil() as usize; if self.item_count > 0 {
let visible_range = 0..cmp::min(visible_item_count, self.item_count); let item_height = self.measure_item_height(view_state, padded_bounds, cx);
let visible_item_count =
(padded_bounds.size.height / item_height).ceil() as usize + 1;
let scroll_offset = element_state
.interactive
.scroll_offset()
.map_or((0.0).into(), |offset| offset.y);
let first_visible_element_ix = (-scroll_offset / item_height).floor() as usize;
let visible_range = first_visible_element_ix
..cmp::min(
first_visible_element_ix + visible_item_count,
self.item_count,
);
let mut items = (self.render_items)(view_state, visible_range, cx); let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
dbg!(items.len(), self.item_count, visible_item_count); content_size = Size {
width: padded_bounds.size.width,
height: item_height * self.item_count,
};
for (ix, item) in items.iter_mut().enumerate() { cx.with_z_index(1, |cx| {
item.initialize(view_state, cx); for (item, ix) in items.iter_mut().zip(visible_range) {
item.initialize(view_state, cx);
let layout_id = item.layout(view_state, cx); let layout_id = item.layout(view_state, cx);
cx.compute_layout( cx.compute_layout(
layout_id, layout_id,
Size { Size {
width: AvailableSpace::Definite(bounds.size.width), width: AvailableSpace::Definite(bounds.size.width),
height: AvailableSpace::Definite(item_height), height: AvailableSpace::Definite(item_height),
}, },
); );
let offset = padded_bounds.origin + point(px(0.), item_height * ix); let offset =
cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx)) padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset);
cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx))
}
});
} else {
content_size = Size {
width: bounds.size.width,
height: px(0.),
};
} }
}
let overflow = point(style.overflow.x, Overflow::Scroll);
cx.with_z_index(0, |cx| {
self.interactivity.paint(
bounds,
content_size,
overflow,
&mut element_state.interactive,
cx,
);
});
})
} }
} }
@ -161,6 +189,18 @@ impl<V> List<V> {
} }
} }
impl<V: 'static> StatelessInteractive<V> for List<V> {
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.interactivity.as_stateless_mut()
}
}
impl<V: 'static> StatefulInteractive<V> for List<V> {
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
&mut self.interactivity
}
}
impl<V: 'static> Component<V> for List<V> { impl<V: 'static> Component<V> for List<V> {
fn render(self) -> AnyElement<V> { fn render(self) -> AnyElement<V> {
AnyElement::new(self) AnyElement::new(self)

View File

@ -1,21 +1,21 @@
use crate::{ use crate::{
div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId, div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId,
ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels,
SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive,
StatelessInteractive, StyleRefinement, Styled, ViewContext, StatelessInteractivity, StyleRefinement, Styled, ViewContext,
}; };
use util::ResultExt; use util::ResultExt;
pub struct Svg< pub struct Svg<
V: 'static, V: 'static,
I: ElementInteraction<V> = StatelessInteraction<V>, I: ElementInteractivity<V> = StatelessInteractivity<V>,
F: ElementFocus<V> = FocusDisabled, F: ElementFocus<V> = FocusDisabled,
> { > {
base: Div<V, I, F>, base: Div<V, I, F>,
path: Option<SharedString>, path: Option<SharedString>,
} }
pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> { pub fn svg<V: 'static>() -> Svg<V, StatelessInteractivity<V>, FocusDisabled> {
Svg { Svg {
base: div(), base: div(),
path: None, path: None,
@ -24,7 +24,7 @@ pub fn svg<V: 'static>() -> Svg<V, StatelessInteraction<V>, FocusDisabled> {
impl<V, I, F> Svg<V, I, F> impl<V, I, F> Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn path(mut self, path: impl Into<SharedString>) -> Self { pub fn path(mut self, path: impl Into<SharedString>) -> Self {
@ -33,11 +33,11 @@ where
} }
} }
impl<V, F> Svg<V, StatelessInteraction<V>, F> impl<V, F> Svg<V, StatelessInteractivity<V>, F>
where where
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteraction<V>, F> { pub fn id(self, id: impl Into<ElementId>) -> Svg<V, StatefulInteractivity<V>, F> {
Svg { Svg {
base: self.base.id(id), base: self.base.id(id),
path: self.path, path: self.path,
@ -47,7 +47,7 @@ where
impl<V, I, F> Component<V> for Svg<V, I, F> impl<V, I, F> Component<V> for Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn render(self) -> AnyElement<V> { fn render(self) -> AnyElement<V> {
@ -57,7 +57,7 @@ where
impl<V, I, F> Element<V> for Svg<V, I, F> impl<V, I, F> Element<V> for Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
type ElementState = DivState; type ElementState = DivState;
@ -107,7 +107,7 @@ where
impl<V, I, F> Styled for Svg<V, I, F> impl<V, I, F> Styled for Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn style(&mut self) -> &mut StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
@ -117,27 +117,27 @@ where
impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F> impl<V, I, F> StatelessInteractive<V> for Svg<V, I, F>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> { fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.base.stateless_interaction() self.base.stateless_interactivity()
} }
} }
impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteraction<V>, F> impl<V, F> StatefulInteractive<V> for Svg<V, StatefulInteractivity<V>, F>
where where
V: 'static, V: 'static,
F: ElementFocus<V>, F: ElementFocus<V>,
{ {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V> { fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
self.base.stateful_interaction() self.base.stateful_interactivity()
} }
} }
impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>> impl<V: 'static, I> Focusable<V> for Svg<V, I, FocusEnabled<V>>
where where
I: ElementInteraction<V>, I: ElementInteractivity<V>,
{ {
fn focus_listeners(&mut self) -> &mut FocusListeners<V> { fn focus_listeners(&mut self) -> &mut FocusListeners<V> {
self.base.focus_listeners() self.base.focus_listeners()

View File

@ -25,13 +25,13 @@ const TOOLTIP_DELAY: Duration = Duration::from_millis(500);
const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0)); const TOOLTIP_OFFSET: Point<Pixels> = Point::new(px(10.0), px(8.0));
pub trait StatelessInteractive<V: 'static>: Element<V> { pub trait StatelessInteractive<V: 'static>: Element<V> {
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V>; fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V>;
fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().hover_style = f(StyleRefinement::default()); self.stateless_interactivity().hover_style = f(StyleRefinement::default());
self self
} }
@ -43,7 +43,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().group_hover_style = Some(GroupStyle { self.stateless_interactivity().group_hover_style = Some(GroupStyle {
group: group_name.into(), group: group_name.into(),
style: f(StyleRefinement::default()), style: f(StyleRefinement::default()),
}); });
@ -58,7 +58,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_down_listeners .mouse_down_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
@ -79,7 +79,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_up_listeners .mouse_up_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
@ -100,7 +100,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_down_listeners .mouse_down_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture if phase == DispatchPhase::Capture
@ -121,7 +121,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_up_listeners .mouse_up_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Capture if phase == DispatchPhase::Capture
@ -141,7 +141,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.mouse_move_listeners .mouse_move_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
@ -158,7 +158,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.scroll_wheel_listeners .scroll_wheel_listeners
.push(Box::new(move |view, event, bounds, phase, cx| { .push(Box::new(move |view, event, bounds, phase, cx| {
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
@ -174,23 +174,48 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
C: TryInto<DispatchContext>, C: TryInto<DispatchContext>,
C::Error: Debug, C::Error: Debug,
{ {
self.stateless_interaction().dispatch_context = self.stateless_interactivity().dispatch_context =
context.try_into().expect("invalid dispatch context"); context.try_into().expect("invalid dispatch context");
self self
} }
fn on_action<A: 'static>( /// Capture the given action, fires during the capture phase
fn capture_action<A: 'static>(
mut self, mut self,
listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext<V>) + 'static, listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self ) -> Self
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().key_listeners.push(( self.stateless_interactivity().key_listeners.push((
TypeId::of::<A>(), TypeId::of::<A>(),
Box::new(move |view, event, _, phase, cx| { Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap(); let event = event.downcast_ref().unwrap();
listener(view, event, phase, cx); if phase == DispatchPhase::Capture {
listener(view, event, cx)
}
None
}),
));
self
}
/// Add a listener for the given action, fires during the bubble event phase
fn on_action<A: 'static>(
mut self,
listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
) -> Self
where
Self: Sized,
{
self.stateless_interactivity().key_listeners.push((
TypeId::of::<A>(),
Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap();
if phase == DispatchPhase::Bubble {
listener(view, event, cx)
}
None None
}), }),
)); ));
@ -204,7 +229,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().key_listeners.push(( self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyDownEvent>(), TypeId::of::<KeyDownEvent>(),
Box::new(move |view, event, _, phase, cx| { Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap(); let event = event.downcast_ref().unwrap();
@ -222,7 +247,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().key_listeners.push(( self.stateless_interactivity().key_listeners.push((
TypeId::of::<KeyUpEvent>(), TypeId::of::<KeyUpEvent>(),
Box::new(move |view, event, _, phase, cx| { Box::new(move |view, event, _, phase, cx| {
let event = event.downcast_ref().unwrap(); let event = event.downcast_ref().unwrap();
@ -237,7 +262,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction() self.stateless_interactivity()
.drag_over_styles .drag_over_styles
.push((TypeId::of::<S>(), f(StyleRefinement::default()))); .push((TypeId::of::<S>(), f(StyleRefinement::default())));
self self
@ -251,7 +276,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().group_drag_over_styles.push(( self.stateless_interactivity().group_drag_over_styles.push((
TypeId::of::<S>(), TypeId::of::<S>(),
GroupStyle { GroupStyle {
group: group_name.into(), group: group_name.into(),
@ -268,7 +293,7 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateless_interaction().drop_listeners.push(( self.stateless_interactivity().drop_listeners.push((
TypeId::of::<W>(), TypeId::of::<W>(),
Box::new(move |view, dragged_view, cx| { Box::new(move |view, dragged_view, cx| {
listener(view, dragged_view.downcast().unwrap(), cx); listener(view, dragged_view.downcast().unwrap(), cx);
@ -279,13 +304,13 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
} }
pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> { pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<V>; fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V>;
fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where where
Self: Sized, Self: Sized,
{ {
self.stateful_interaction().active_style = f(StyleRefinement::default()); self.stateful_interactivity().active_style = f(StyleRefinement::default());
self self
} }
@ -297,7 +322,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateful_interaction().group_active_style = Some(GroupStyle { self.stateful_interactivity().group_active_style = Some(GroupStyle {
group: group_name.into(), group: group_name.into(),
style: f(StyleRefinement::default()), style: f(StyleRefinement::default()),
}); });
@ -311,7 +336,7 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
where where
Self: Sized, Self: Sized,
{ {
self.stateful_interaction() self.stateful_interactivity()
.click_listeners .click_listeners
.push(Box::new(move |view, event, cx| listener(view, event, cx))); .push(Box::new(move |view, event, cx| listener(view, event, cx)));
self self
@ -326,10 +351,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
W: 'static + Render, W: 'static + Render,
{ {
debug_assert!( debug_assert!(
self.stateful_interaction().drag_listener.is_none(), self.stateful_interactivity().drag_listener.is_none(),
"calling on_drag more than once on the same element is not supported" "calling on_drag more than once on the same element is not supported"
); );
self.stateful_interaction().drag_listener = self.stateful_interactivity().drag_listener =
Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag { Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
view: listener(view_state, cx).into(), view: listener(view_state, cx).into(),
cursor_offset, cursor_offset,
@ -342,10 +367,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
Self: Sized, Self: Sized,
{ {
debug_assert!( debug_assert!(
self.stateful_interaction().hover_listener.is_none(), self.stateful_interactivity().hover_listener.is_none(),
"calling on_hover more than once on the same element is not supported" "calling on_hover more than once on the same element is not supported"
); );
self.stateful_interaction().hover_listener = Some(Box::new(listener)); self.stateful_interactivity().hover_listener = Some(Box::new(listener));
self self
} }
@ -358,10 +383,10 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
W: 'static + Render, W: 'static + Render,
{ {
debug_assert!( debug_assert!(
self.stateful_interaction().tooltip_builder.is_none(), self.stateful_interactivity().tooltip_builder.is_none(),
"calling tooltip more than once on the same element is not supported" "calling tooltip more than once on the same element is not supported"
); );
self.stateful_interaction().tooltip_builder = Some(Arc::new(move |view_state, cx| { self.stateful_interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
build_tooltip(view_state, cx).into() build_tooltip(view_state, cx).into()
})); }));
@ -369,11 +394,11 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
} }
} }
pub trait ElementInteraction<V: 'static>: 'static { pub trait ElementInteractivity<V: 'static>: 'static {
fn as_stateless(&self) -> &StatelessInteraction<V>; fn as_stateless(&self) -> &StatelessInteractivity<V>;
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V>; fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V>;
fn as_stateful(&self) -> Option<&StatefulInteraction<V>>; fn as_stateful(&self) -> Option<&StatefulInteractivity<V>>;
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>>; fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>>;
fn initialize<R>( fn initialize<R>(
&mut self, &mut self,
@ -735,11 +760,11 @@ pub trait ElementInteraction<V: 'static>: 'static {
} }
#[derive(Deref, DerefMut)] #[derive(Deref, DerefMut)]
pub struct StatefulInteraction<V> { pub struct StatefulInteractivity<V> {
pub id: ElementId, pub id: ElementId,
#[deref] #[deref]
#[deref_mut] #[deref_mut]
stateless: StatelessInteraction<V>, stateless: StatelessInteractivity<V>,
click_listeners: SmallVec<[ClickListener<V>; 2]>, click_listeners: SmallVec<[ClickListener<V>; 2]>,
active_style: StyleRefinement, active_style: StyleRefinement,
group_active_style: Option<GroupStyle>, group_active_style: Option<GroupStyle>,
@ -748,29 +773,29 @@ pub struct StatefulInteraction<V> {
tooltip_builder: Option<TooltipBuilder<V>>, tooltip_builder: Option<TooltipBuilder<V>>,
} }
impl<V: 'static> ElementInteraction<V> for StatefulInteraction<V> { impl<V: 'static> ElementInteractivity<V> for StatefulInteractivity<V> {
fn as_stateful(&self) -> Option<&StatefulInteraction<V>> { fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
Some(self) Some(self)
} }
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> { fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
Some(self) Some(self)
} }
fn as_stateless(&self) -> &StatelessInteraction<V> { fn as_stateless(&self) -> &StatelessInteractivity<V> {
&self.stateless &self.stateless
} }
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> { fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
&mut self.stateless &mut self.stateless
} }
} }
impl<V> From<ElementId> for StatefulInteraction<V> { impl<V> From<ElementId> for StatefulInteractivity<V> {
fn from(id: ElementId) -> Self { fn from(id: ElementId) -> Self {
Self { Self {
id, id,
stateless: StatelessInteraction::default(), stateless: StatelessInteractivity::default(),
click_listeners: SmallVec::new(), click_listeners: SmallVec::new(),
drag_listener: None, drag_listener: None,
hover_listener: None, hover_listener: None,
@ -783,7 +808,7 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static; type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
pub struct StatelessInteraction<V> { pub struct StatelessInteractivity<V> {
pub dispatch_context: DispatchContext, pub dispatch_context: DispatchContext,
pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>, pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>, pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
@ -797,9 +822,9 @@ pub struct StatelessInteraction<V> {
drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>, drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
} }
impl<V> StatelessInteraction<V> { impl<V> StatelessInteractivity<V> {
pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteraction<V> { pub fn into_stateful(self, id: impl Into<ElementId>) -> StatefulInteractivity<V> {
StatefulInteraction { StatefulInteractivity {
id: id.into(), id: id.into(),
stateless: self, stateless: self,
click_listeners: SmallVec::new(), click_listeners: SmallVec::new(),
@ -877,7 +902,7 @@ impl InteractiveElementState {
} }
} }
impl<V> Default for StatelessInteraction<V> { impl<V> Default for StatelessInteractivity<V> {
fn default() -> Self { fn default() -> Self {
Self { Self {
dispatch_context: DispatchContext::default(), dispatch_context: DispatchContext::default(),
@ -895,20 +920,20 @@ impl<V> Default for StatelessInteraction<V> {
} }
} }
impl<V: 'static> ElementInteraction<V> for StatelessInteraction<V> { impl<V: 'static> ElementInteractivity<V> for StatelessInteractivity<V> {
fn as_stateful(&self) -> Option<&StatefulInteraction<V>> { fn as_stateful(&self) -> Option<&StatefulInteractivity<V>> {
None None
} }
fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction<V>> { fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity<V>> {
None None
} }
fn as_stateless(&self) -> &StatelessInteraction<V> { fn as_stateless(&self) -> &StatelessInteractivity<V> {
self self
} }
fn as_stateless_mut(&mut self) -> &mut StatelessInteraction<V> { fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity<V> {
self self
} }
} }

View File

@ -277,7 +277,7 @@ impl Style {
pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) { pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
let rem_size = cx.rem_size(); let rem_size = cx.rem_size();
cx.stack(0, |cx| { cx.with_z_index(0, |cx| {
cx.paint_shadows( cx.paint_shadows(
bounds, bounds,
self.corner_radii.to_pixels(bounds.size, rem_size), self.corner_radii.to_pixels(bounds.size, rem_size),
@ -287,7 +287,7 @@ impl Style {
let background_color = self.background.as_ref().and_then(Fill::color); let background_color = self.background.as_ref().and_then(Fill::color);
if background_color.is_some() || self.is_border_visible() { if background_color.is_some() || self.is_border_visible() {
cx.stack(1, |cx| { cx.with_z_index(1, |cx| {
cx.paint_quad( cx.paint_quad(
bounds, bounds,
self.corner_radii.to_pixels(bounds.size, rem_size), self.corner_radii.to_pixels(bounds.size, rem_size),

View File

@ -142,8 +142,6 @@ impl Line {
color, color,
)?; )?;
} }
} else {
dbg!(content_mask.bounds, max_glyph_bounds);
} }
} }
} }

View File

@ -1698,8 +1698,8 @@ impl<'a, V: 'static> ViewContext<'a, V> {
&mut self.window_cx &mut self.window_cx
} }
pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R { pub fn with_z_index<R>(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R {
self.window.z_index_stack.push(order); self.window.z_index_stack.push(z_index);
let result = f(self); let result = f(self);
self.window.z_index_stack.pop(); self.window.z_index_stack.pop();
result result

View File

@ -9,4 +9,5 @@ path = "src/menu2.rs"
doctest = false doctest = false
[dependencies] [dependencies]
gpui = { package = "gpui2", path = "../gpui2" } serde.workspace = true
serde_derive.workspace = true

View File

@ -1,25 +1,25 @@
// todo!(use actions! macro) use serde_derive::Deserialize;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct Cancel; pub struct Cancel;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct Confirm; pub struct Confirm;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct SecondaryConfirm; pub struct SecondaryConfirm;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct SelectPrev; pub struct SelectPrev;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct SelectNext; pub struct SelectNext;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct SelectFirst; pub struct SelectFirst;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct SelectLast; pub struct SelectLast;
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, Deserialize, PartialEq)]
pub struct ShowContextMenu; pub struct ShowContextMenu;

View File

@ -18,11 +18,11 @@
// Dismiss, // Dismiss,
// } // }
use std::ops::Range; use std::cmp;
use gpui::{ use gpui::{
div, list, red, AppContext, Component, Div, Element, ElementId, ParentElement, Render, Styled, div, list, Component, ElementId, FocusHandle, Focusable, ParentElement, StatelessInteractive,
ViewContext, Styled, ViewContext,
}; };
// pub struct Picker<D> { // pub struct Picker<D> {
@ -43,8 +43,9 @@ pub trait PickerDelegate: Sized + 'static {
fn match_count(&self, picker_id: ElementId) -> usize; fn match_count(&self, picker_id: ElementId) -> usize;
// fn selected_index(&self) -> usize; fn selected_index(&self, picker_id: ElementId) -> usize;
// fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>); fn set_selected_index(&mut self, ix: usize, picker_id: ElementId, cx: &mut ViewContext<Self>);
// fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>; // fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
// fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>); // fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
// fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>); // fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
@ -53,8 +54,6 @@ pub trait PickerDelegate: Sized + 'static {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
active: bool,
hovered: bool,
selected: bool, selected: bool,
picker_id: ElementId, picker_id: ElementId,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
@ -84,32 +83,72 @@ pub trait PickerDelegate: Sized + 'static {
#[derive(Component)] #[derive(Component)]
pub struct Picker<V: PickerDelegate> { pub struct Picker<V: PickerDelegate> {
id: ElementId, id: ElementId,
focus_handle: FocusHandle,
phantom: std::marker::PhantomData<V>, phantom: std::marker::PhantomData<V>,
} }
impl<V: PickerDelegate> Picker<V> { impl<V: PickerDelegate> Picker<V> {
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>, focus_handle: FocusHandle) -> Self {
Self { Self {
id: id.into(), id: id.into(),
focus_handle,
phantom: std::marker::PhantomData, phantom: std::marker::PhantomData,
} }
} }
} }
impl<V: 'static + PickerDelegate> Picker<V> { impl<V: 'static + PickerDelegate> Picker<V> {
pub fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { pub fn render(self, view: &mut V, _cx: &mut ViewContext<V>) -> impl Component<V> {
div().size_full().id(self.id.clone()).child( let id = self.id.clone();
list( div()
"candidates", .size_full()
view.match_count(self.id.clone()), .id(self.id.clone())
move |this: &mut V, visible_range, cx| { .track_focus(&self.focus_handle)
visible_range .context("picker")
.map(|ix| this.render_match(ix, false, false, false, self.id.clone(), cx)) .on_focus(|v, e, cx| {
.collect() dbg!("FOCUSED!");
}, })
.on_blur(|v, e, cx| {
dbg!("BLURRED!");
})
.on_action({
let id = id.clone();
move |view: &mut V, _: &menu::SelectNext, cx| {
let index = view.selected_index(id.clone());
let count = view.match_count(id.clone());
if count > 0 {
view.set_selected_index(cmp::min(index + 1, count - 1), id.clone(), cx);
}
}
})
.on_action({
let id = id.clone();
move |view, _: &menu::SelectPrev, cx| {
let index = view.selected_index(id.clone());
let count = view.match_count(id.clone());
if count > 0 {
view.set_selected_index((index + 1) % count, id.clone(), cx);
}
}
})
.on_action(|view, _: &menu::SelectFirst, cx| {})
.on_action(|view, _: &menu::SelectLast, cx| {})
.on_action(|view, _: &menu::Cancel, cx| {})
.on_action(|view, _: &menu::Confirm, cx| {})
.on_action(|view, _: &menu::SecondaryConfirm, cx| {})
.child(
list(
"candidates",
view.match_count(self.id.clone()),
move |view: &mut V, visible_range, cx| {
let selected_ix = view.selected_index(self.id.clone());
visible_range
.map(|ix| view.render_match(ix, ix == selected_ix, self.id.clone(), cx))
.collect()
},
)
.size_full(),
) )
.size_full(),
)
} }
} }

View File

@ -25,6 +25,7 @@ smallvec.workspace = true
strum = { version = "0.25.0", features = ["derive"] } strum = { version = "0.25.0", features = ["derive"] }
theme = { path = "../theme" } theme = { path = "../theme" }
theme2 = { path = "../theme2" } theme2 = { path = "../theme2" }
menu = { package = "menu2", path = "../menu2" }
ui = { package = "ui2", path = "../ui2", features = ["stories"] } ui = { package = "ui2", path = "../ui2", features = ["stories"] }
util = { path = "../util" } util = { path = "../util" }
picker = { package = "picker2", path = "../picker2" } picker = { package = "picker2", path = "../picker2" }

View File

@ -1,5 +1,5 @@
use gpui::{ use gpui::{
div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, StatefulInteraction, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, StatefulInteractivity,
StatelessInteractive, Styled, View, VisualContext, WindowContext, StatelessInteractive, Styled, View, VisualContext, WindowContext,
}; };
use serde::Deserialize; use serde::Deserialize;
@ -31,7 +31,7 @@ impl FocusStory {
} }
impl Render for FocusStory { impl Render for FocusStory {
type Element = Div<Self, StatefulInteraction<Self>, FocusEnabled<Self>>; type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme(); let theme = cx.theme();
@ -48,20 +48,18 @@ impl Render for FocusStory {
.id("parent") .id("parent")
.focusable() .focusable()
.context("parent") .context("parent")
.on_action(|_, action: &ActionA, phase, cx| { .on_action(|_, action: &ActionA, cx| {
println!("Action A dispatched on parent during {:?}", phase); println!("Action A dispatched on parent during");
}) })
.on_action(|_, action: &ActionB, phase, cx| { .on_action(|_, action: &ActionB, cx| {
println!("Action B dispatched on parent during {:?}", phase); println!("Action B dispatched on parent during");
}) })
.on_focus(|_, _, _| println!("Parent focused")) .on_focus(|_, _, _| println!("Parent focused"))
.on_blur(|_, _, _| println!("Parent blurred")) .on_blur(|_, _, _| println!("Parent blurred"))
.on_focus_in(|_, _, _| println!("Parent focus_in")) .on_focus_in(|_, _, _| println!("Parent focus_in"))
.on_focus_out(|_, _, _| println!("Parent focus_out")) .on_focus_out(|_, _, _| println!("Parent focus_out"))
.on_key_down(|_, event, phase, _| { .on_key_down(|_, event, phase, _| println!("Key down on parent {:?}", event))
println!("Key down on parent {:?} {:?}", phase, event) .on_key_up(|_, event, phase, _| println!("Key up on parent {:?}", event))
})
.on_key_up(|_, event, phase, _| println!("Key up on parent {:?} {:?}", phase, event))
.size_full() .size_full()
.bg(color_1) .bg(color_1)
.focus(|style| style.bg(color_2)) .focus(|style| style.bg(color_2))
@ -70,8 +68,8 @@ impl Render for FocusStory {
div() div()
.track_focus(&child_1) .track_focus(&child_1)
.context("child-1") .context("child-1")
.on_action(|_, action: &ActionB, phase, cx| { .on_action(|_, action: &ActionB, cx| {
println!("Action B dispatched on child 1 during {:?}", phase); println!("Action B dispatched on child 1 during");
}) })
.w_full() .w_full()
.h_6() .h_6()
@ -82,20 +80,16 @@ impl Render for FocusStory {
.on_blur(|_, _, _| println!("Child 1 blurred")) .on_blur(|_, _, _| println!("Child 1 blurred"))
.on_focus_in(|_, _, _| println!("Child 1 focus_in")) .on_focus_in(|_, _, _| println!("Child 1 focus_in"))
.on_focus_out(|_, _, _| println!("Child 1 focus_out")) .on_focus_out(|_, _, _| println!("Child 1 focus_out"))
.on_key_down(|_, event, phase, _| { .on_key_down(|_, event, phase, _| println!("Key down on child 1 {:?}", event))
println!("Key down on child 1 {:?} {:?}", phase, event) .on_key_up(|_, event, phase, _| println!("Key up on child 1 {:?}", event))
})
.on_key_up(|_, event, phase, _| {
println!("Key up on child 1 {:?} {:?}", phase, event)
})
.child("Child 1"), .child("Child 1"),
) )
.child( .child(
div() div()
.track_focus(&child_2) .track_focus(&child_2)
.context("child-2") .context("child-2")
.on_action(|_, action: &ActionC, phase, cx| { .on_action(|_, action: &ActionC, cx| {
println!("Action C dispatched on child 2 during {:?}", phase); println!("Action C dispatched on child 2 during");
}) })
.w_full() .w_full()
.h_6() .h_6()
@ -104,12 +98,8 @@ impl Render for FocusStory {
.on_blur(|_, _, _| println!("Child 2 blurred")) .on_blur(|_, _, _| println!("Child 2 blurred"))
.on_focus_in(|_, _, _| println!("Child 2 focus_in")) .on_focus_in(|_, _, _| println!("Child 2 focus_in"))
.on_focus_out(|_, _, _| println!("Child 2 focus_out")) .on_focus_out(|_, _, _| println!("Child 2 focus_out"))
.on_key_down(|_, event, phase, _| { .on_key_down(|_, event, phase, _| println!("Key down on child 2 {:?}", event))
println!("Key down on child 2 {:?} {:?}", phase, event) .on_key_up(|_, event, phase, _| println!("Key up on child 2 {:?}", event))
})
.on_key_up(|_, event, phase, _| {
println!("Key up on child 2 {:?} {:?}", phase, event)
})
.child("Child 2"), .child("Child 2"),
) )
} }

View File

@ -1,5 +1,5 @@
use crate::{story::Story, story_selector::ComponentStory}; use crate::{story::Story, story_selector::ComponentStory};
use gpui::{Div, Render, StatefulInteraction, View, VisualContext}; use gpui::{Div, Render, StatefulInteractivity, View, VisualContext};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use ui::prelude::*; use ui::prelude::*;
@ -12,7 +12,7 @@ impl KitchenSinkStory {
} }
impl Render for KitchenSinkStory { impl Render for KitchenSinkStory {
type Element = Div<Self, StatefulInteraction<Self>>; type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let component_stories = ComponentStory::iter() let component_stories = ComponentStory::iter()

View File

@ -1,15 +1,18 @@
use gpui::{ use gpui::{
black, div, red, Div, Fill, ParentElement, Render, SharedString, Styled, View, VisualContext, div, Component, Div, FocusHandle, KeyBinding, ParentElement, Render, SharedString,
WindowContext, StatelessInteractive, Styled, View, VisualContext, WindowContext,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use theme2::ActiveTheme;
pub struct PickerStory { pub struct PickerStory {
selected_ix: usize,
candidates: Vec<SharedString>, candidates: Vec<SharedString>,
focus_handle: FocusHandle,
} }
impl PickerDelegate for PickerStory { impl PickerDelegate for PickerStory {
type ListItem = SharedString; type ListItem = Div<Self>;
fn match_count(&self, _picker_id: gpui::ElementId) -> usize { fn match_count(&self, _picker_id: gpui::ElementId) -> usize {
self.candidates.len() self.candidates.len()
@ -18,46 +21,118 @@ impl PickerDelegate for PickerStory {
fn render_match( fn render_match(
&self, &self,
ix: usize, ix: usize,
_active: bool, selected: bool,
_hovered: bool,
_selected: bool,
_picker_id: gpui::ElementId, _picker_id: gpui::ElementId,
cx: &mut gpui::ViewContext<Self>, cx: &mut gpui::ViewContext<Self>,
) -> Self::ListItem { ) -> Self::ListItem {
self.candidates[ix].clone() let colors = cx.theme().colors();
div()
.text_color(colors.text)
.when(selected, |s| {
s.border_l_10().border_color(colors.terminal_ansi_yellow)
})
.hover(|style| {
style
.bg(colors.element_active)
.text_color(colors.text_accent)
})
.child(self.candidates[ix].clone())
}
fn selected_index(&self, picker_id: gpui::ElementId) -> usize {
self.selected_ix
}
fn set_selected_index(
&mut self,
ix: usize,
_picker_id: gpui::ElementId,
_cx: &mut gpui::ViewContext<Self>,
) {
self.selected_ix = ix;
} }
} }
impl PickerStory { impl PickerStory {
pub fn new(cx: &mut WindowContext) -> View<Self> { pub fn new(cx: &mut WindowContext) -> View<Self> {
cx.build_view(|cx| PickerStory { cx.build_view(|cx| {
candidates: vec![ cx.bind_keys([
"Pizza (Italy)".into(), KeyBinding::new("up", menu::SelectPrev, Some("picker")),
"Sushi (Japan)".into(), KeyBinding::new("pageup", menu::SelectFirst, Some("picker")),
"Paella (Spain)".into(), KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")),
"Tacos (Mexico)".into(), KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")),
"Peking Duck (China)".into(), KeyBinding::new("down", menu::SelectNext, Some("picker")),
"Fish and Chips (UK)".into(), KeyBinding::new("pagedown", menu::SelectLast, Some("picker")),
"Croissant (France)".into(), KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")),
"Bratwurst (Germany)".into(), KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")),
"Poutine (Canada)".into(), KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")),
"Chicken Tikka Masala (India)".into(), KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")),
"Feijoada (Brazil)".into(), KeyBinding::new("enter", menu::Confirm, Some("picker")),
"Kimchi (Korea)".into(), KeyBinding::new("ctrl-enter", menu::ShowContextMenu, Some("picker")),
"Borscht (Ukraine)".into(), KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")),
"Falafel (Middle East)".into(), KeyBinding::new("escape", menu::Cancel, Some("picker")),
"Baklava (Turkey)".into(), KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")),
"Shepherd's Pie (Ireland)".into(), ]);
"Rendang (Indonesia)".into(),
"Kebab (Middle East)".into(), let fh = cx.focus_handle();
"Ceviche (Peru)".into(), cx.focus(&fh);
"Pierogi (Poland)".into(),
"Churrasco (Brazil)".into(), PickerStory {
"Moussaka (Greece)".into(), focus_handle: fh,
"Lasagna (Italy)".into(), candidates: vec![
"Pad Thai (Thailand)".into(), "Baguette (France)".into(),
"Pho (Vietnam)".into(), "Baklava (Turkey)".into(),
], "Beef Wellington (UK)".into(),
"Biryani (India)".into(),
"Borscht (Ukraine)".into(),
"Bratwurst (Germany)".into(),
"Bulgogi (Korea)".into(),
"Burrito (USA)".into(),
"Ceviche (Peru)".into(),
"Chicken Tikka Masala (India)".into(),
"Churrasco (Brazil)".into(),
"Couscous (North Africa)".into(),
"Croissant (France)".into(),
"Dim Sum (China)".into(),
"Empanada (Argentina)".into(),
"Fajitas (Mexico)".into(),
"Falafel (Middle East)".into(),
"Feijoada (Brazil)".into(),
"Fish and Chips (UK)".into(),
"Fondue (Switzerland)".into(),
"Goulash (Hungary)".into(),
"Haggis (Scotland)".into(),
"Kebab (Middle East)".into(),
"Kimchi (Korea)".into(),
"Lasagna (Italy)".into(),
"Maple Syrup Pancakes (Canada)".into(),
"Moussaka (Greece)".into(),
"Pad Thai (Thailand)".into(),
"Paella (Spain)".into(),
"Pancakes (USA)".into(),
"Pasta Carbonara (Italy)".into(),
"Pavlova (Australia)".into(),
"Peking Duck (China)".into(),
"Pho (Vietnam)".into(),
"Pierogi (Poland)".into(),
"Pizza (Italy)".into(),
"Poutine (Canada)".into(),
"Pretzel (Germany)".into(),
"Ramen (Japan)".into(),
"Rendang (Indonesia)".into(),
"Sashimi (Japan)".into(),
"Satay (Indonesia)".into(),
"Shepherd's Pie (Ireland)".into(),
"Sushi (Japan)".into(),
"Tacos (Mexico)".into(),
"Tandoori Chicken (India)".into(),
"Tortilla (Spain)".into(),
"Tzatziki (Greece)".into(),
"Wiener Schnitzel (Austria)".into(),
],
selected_ix: 0,
}
}) })
} }
} }
@ -66,9 +141,11 @@ impl Render for PickerStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
div() div()
.text_color(red()) .bg(theme.styles.colors.background)
.size_full() .size_full()
.child(Picker::new("picker_story")) .child(Picker::new("picker_story", self.focus_handle.clone()))
} }
} }

View File

@ -1,5 +1,5 @@
use gpui::{ use gpui::{
div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled, div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteractivity, Styled,
View, VisualContext, WindowContext, View, VisualContext, WindowContext,
}; };
use theme2::ActiveTheme; use theme2::ActiveTheme;
@ -13,7 +13,7 @@ impl ScrollStory {
} }
impl Render for ScrollStory { impl Render for ScrollStory {
type Element = Div<Self, StatefulInteraction<Self>>; type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
let theme = cx.theme(); let theme = cx.theme();

View File

@ -128,7 +128,7 @@ impl<V: 'static> Checkbox<V> {
// click area for the checkbox. // click area for the checkbox.
.size_5() .size_5()
// Because we've enlarged the click area, we need to create a // Because we've enlarged the click area, we need to create a
// `group` to pass down interaction events to the checkbox. // `group` to pass down interactivity events to the checkbox.
.group(group_id.clone()) .group(group_id.clone())
.child( .child(
div() div()
@ -148,7 +148,7 @@ impl<V: 'static> Checkbox<V> {
.bg(bg_color) .bg(bg_color)
.border() .border()
.border_color(border_color) .border_color(border_color)
// We only want the interaction states to fire when we // We only want the interactivity states to fire when we
// are in a checkbox that isn't disabled. // are in a checkbox that isn't disabled.
.when(!self.disabled, |this| { .when(!self.disabled, |this| {
// Here instead of `hover()` we use `group_hover()` // Here instead of `hover()` we use `group_hover()`

View File

@ -37,4 +37,3 @@
((symbol) @comment ((symbol) @comment
(#match? @comment "^#[cC][iIsS]$")) (#match? @comment "^#[cC][iIsS]$"))

View File

@ -37,4 +37,3 @@
((symbol) @comment ((symbol) @comment
(#match? @comment "^#[cC][iIsS]$")) (#match? @comment "^#[cC][iIsS]$"))