From 85000eba819e0fd375fed0e7cf6dfcd3c2ba888a Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 6 Nov 2023 17:09:38 -0800 Subject: [PATCH] wip: picker co-authored-by: nathan co-authored-by: max --- Cargo.lock | 4 +- crates/gpui2/src/color.rs | 9 ++ crates/gpui2/src/elements/div.rs | 80 +++++----- crates/gpui2/src/elements/img.rs | 42 +++-- crates/gpui2/src/elements/list.rs | 118 +++++++++----- crates/gpui2/src/elements/svg.rs | 36 ++--- crates/gpui2/src/interactive.rs | 131 +++++++++------ crates/gpui2/src/style.rs | 4 +- crates/gpui2/src/text_system/line.rs | 2 - crates/gpui2/src/window.rs | 4 +- crates/menu2/Cargo.toml | 3 +- crates/menu2/src/menu2.rs | 18 +-- crates/picker2/src/picker2.rs | 79 ++++++--- crates/storybook2/Cargo.toml | 1 + crates/storybook2/src/stories/focus.rs | 42 ++--- crates/storybook2/src/stories/kitchen_sink.rs | 4 +- crates/storybook2/src/stories/picker.rs | 151 +++++++++++++----- crates/storybook2/src/stories/scroll.rs | 4 +- crates/ui2/src/components/checkbox.rs | 4 +- .../zed/src/languages/racket/highlights.scm | 1 - .../zed2/src/languages/racket/highlights.scm | 1 - 21 files changed, 460 insertions(+), 278 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98feaf9f61..50475da143 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4970,7 +4970,8 @@ dependencies = [ name = "menu2" version = "0.1.0" dependencies = [ - "gpui2", + "serde", + "serde_derive", ] [[package]] @@ -8542,6 +8543,7 @@ dependencies = [ "gpui2", "itertools 0.11.0", "log", + "menu2", "picker2", "rust-embed", "serde", diff --git a/crates/gpui2/src/color.rs b/crates/gpui2/src/color.rs index db07259476..d5ff160321 100644 --- a/crates/gpui2/src/color.rs +++ b/crates/gpui2/src/color.rs @@ -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 { /// Returns true if the HSLA color is fully transparent, false otherwise. pub fn is_transparent(&self) -> bool { diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index e011041bae..77d066729e 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,28 +1,28 @@ use crate::{ 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, - Pixels, Point, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, - StatelessInteractive, Style, StyleRefinement, Styled, ViewContext, Visibility, + Pixels, Point, SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive, + StatelessInteractivity, Style, StyleRefinement, Styled, ViewContext, Visibility, }; use refineable::Refineable; use smallvec::SmallVec; pub struct Div< V: 'static, - I: ElementInteraction = StatelessInteraction, + I: ElementInteractivity = StatelessInteractivity, F: ElementFocus = FocusDisabled, > { - interaction: I, + interactivity: I, focus: F, children: SmallVec<[AnyElement; 2]>, group: Option, base_style: StyleRefinement, } -pub fn div() -> Div, FocusDisabled> { +pub fn div() -> Div, FocusDisabled> { Div { - interaction: StatelessInteraction::default(), + interactivity: StatelessInteractivity::default(), focus: FocusDisabled, children: SmallVec::new(), group: None, @@ -30,14 +30,14 @@ pub fn div() -> Div, FocusDisabled> { } } -impl Div, F> +impl Div, F> where V: 'static, F: ElementFocus, { - pub fn id(self, id: impl Into) -> Div, F> { + pub fn id(self, id: impl Into) -> Div, F> { Div { - interaction: id.into().into(), + interactivity: id.into().into(), focus: self.focus, children: self.children, group: self.group, @@ -48,7 +48,7 @@ where impl Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { pub fn group(mut self, group: impl Into) -> Self { @@ -98,16 +98,20 @@ where let mut computed_style = Style::default(); computed_style.refine(&self.base_style); self.focus.refine_style(&mut computed_style, cx); - self.interaction - .refine_style(&mut computed_style, bounds, &element_state.interactive, cx); + self.interactivity.refine_style( + &mut computed_style, + bounds, + &element_state.interactive, + cx, + ); computed_style } } -impl Div, FocusDisabled> { - pub fn focusable(self) -> Div, FocusEnabled> { +impl Div, FocusDisabled> { + pub fn focusable(self) -> Div, FocusEnabled> { Div { - interaction: self.interaction, + interactivity: self.interactivity, focus: FocusEnabled::new(), children: self.children, group: self.group, @@ -118,9 +122,9 @@ impl Div, FocusDisabled> { pub fn track_focus( self, handle: &FocusHandle, - ) -> Div, FocusEnabled> { + ) -> Div, FocusEnabled> { Div { - interaction: self.interaction, + interactivity: self.interactivity, focus: FocusEnabled::tracked(handle), children: self.children, group: self.group, @@ -145,13 +149,13 @@ impl Div, FocusDisabled> { } } -impl Div, FocusDisabled> { +impl Div, FocusDisabled> { pub fn track_focus( self, handle: &FocusHandle, - ) -> Div, FocusEnabled> { + ) -> Div, FocusEnabled> { Div { - interaction: self.interaction.into_stateful(handle), + interactivity: self.interactivity.into_stateful(handle), focus: handle.clone().into(), children: self.children, group: self.group, @@ -163,7 +167,7 @@ impl Div, FocusDisabled> { impl Focusable for Div> where V: 'static, - I: ElementInteraction, + I: ElementInteractivity, { fn focus_listeners(&mut self) -> &mut FocusListeners { &mut self.focus.focus_listeners @@ -191,13 +195,13 @@ pub struct DivState { impl Element for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { type ElementState = DivState; fn id(&self) -> Option { - self.interaction + self.interactivity .as_stateful() .map(|identified| identified.id.clone()) } @@ -212,7 +216,7 @@ where self.focus .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| { element_state.focus_handle = focus_handle; - self.interaction.initialize(cx, |cx| { + self.interactivity.initialize(cx, |cx| { for child in &mut self.children { child.initialize(view_state, cx); } @@ -281,11 +285,11 @@ where (child_max - child_min).into() }; - cx.stack(z_index, |cx| { - cx.stack(0, |cx| { + cx.with_z_index(z_index, |cx| { + cx.with_z_index(0, |cx| { style.paint(bounds, cx); this.focus.paint(bounds, cx); - this.interaction.paint( + this.interactivity.paint( bounds, content_size, style.overflow, @@ -293,7 +297,7 @@ where cx, ); }); - cx.stack(1, |cx| { + cx.with_z_index(1, |cx| { style.apply_text_style(cx, |cx| { style.apply_overflow(bounds, cx, |cx| { let scroll_offset = element_state.interactive.scroll_offset(); @@ -316,7 +320,7 @@ where impl Component for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn render(self) -> AnyElement { @@ -326,7 +330,7 @@ where impl ParentElement for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { @@ -336,7 +340,7 @@ where impl Styled for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn style(&mut self) -> &mut StyleRefinement { @@ -346,19 +350,19 @@ where impl StatelessInteractive for Div where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { - fn stateless_interaction(&mut self) -> &mut StatelessInteraction { - self.interaction.as_stateless_mut() + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { + self.interactivity.as_stateless_mut() } } -impl StatefulInteractive for Div, F> +impl StatefulInteractive for Div, F> where F: ElementFocus, { - fn stateful_interaction(&mut self) -> &mut StatefulInteraction { - &mut self.interaction + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { + &mut self.interactivity } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 637bcd78e6..638665d414 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,17 +1,15 @@ -use std::sync::Arc; - use crate::{ div, AnyElement, BorrowWindow, Bounds, Component, Div, DivState, Element, ElementFocus, - ElementId, ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, - LayoutId, Pixels, SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, - StatelessInteractive, StyleRefinement, Styled, ViewContext, + ElementId, ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, + LayoutId, Pixels, SharedString, StatefulInteractive, StatefulInteractivity, + StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, ViewContext, }; use futures::FutureExt; use util::ResultExt; pub struct Img< V: 'static, - I: ElementInteraction = StatelessInteraction, + I: ElementInteractivity = StatelessInteractivity, F: ElementFocus = FocusDisabled, > { base: Div, @@ -19,7 +17,7 @@ pub struct Img< grayscale: bool, } -pub fn img() -> Img, FocusDisabled> { +pub fn img() -> Img, FocusDisabled> { Img { base: div(), uri: None, @@ -30,7 +28,7 @@ pub fn img() -> Img, FocusDisabled> { impl Img where V: 'static, - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { pub fn uri(mut self, uri: impl Into) -> Self { @@ -44,11 +42,11 @@ where } } -impl Img, F> +impl Img, F> where F: ElementFocus, { - pub fn id(self, id: impl Into) -> Img, F> { + pub fn id(self, id: impl Into) -> Img, F> { Img { base: self.base.id(id), uri: self.uri, @@ -59,7 +57,7 @@ where impl Component for Img where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn render(self) -> AnyElement { @@ -69,7 +67,7 @@ where impl Element for Img where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { type ElementState = DivState; @@ -103,7 +101,7 @@ where element_state: &mut Self::ElementState, cx: &mut ViewContext, ) { - cx.stack(0, |cx| { + cx.with_z_index(0, |cx| { self.base.paint(bounds, view, element_state, cx); }); @@ -120,7 +118,7 @@ where .and_then(ResultExt::log_err) { 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) .log_err() }); @@ -138,7 +136,7 @@ where impl Styled for Img where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn style(&mut self) -> &mut StyleRefinement { @@ -148,27 +146,27 @@ where impl StatelessInteractive for Img where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { - fn stateless_interaction(&mut self) -> &mut StatelessInteraction { - self.base.stateless_interaction() + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { + self.base.stateless_interactivity() } } -impl StatefulInteractive for Img, F> +impl StatefulInteractive for Img, F> where F: ElementFocus, { - fn stateful_interaction(&mut self) -> &mut StatefulInteraction { - self.base.stateful_interaction() + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { + self.base.stateful_interactivity() } } impl Focusable for Img> where V: 'static, - I: ElementInteraction, + I: ElementInteractivity, { fn focus_listeners(&mut self) -> &mut FocusListeners { self.base.focus_listeners() diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index 7f15af66c4..0719f2a92e 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/crates/gpui2/src/elements/list.rs @@ -1,15 +1,12 @@ -use std::{cmp, ops::Range}; - -use smallvec::SmallVec; - use crate::{ 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, }; - -// We want to support uniform and non-uniform height -// We need to make the ID mandatory, to replace the 'state' field -// Previous implementation measured the first element as early as possible +use smallvec::SmallVec; +use std::{cmp, ops::Range}; +use taffy::style::Overflow; pub fn list( id: Id, @@ -21,8 +18,9 @@ where V: 'static, C: Component, { + let id = id.into(); List { - id: id.into(), + id: id.clone(), style: Default::default(), item_count, render_items: Box::new(move |view, visible_range, cx| { @@ -31,10 +29,11 @@ where .map(|component| component.render()) .collect() }), + interactivity: id.into(), } } -pub struct List { +pub struct List { id: ElementId, style: StyleRefinement, item_count: usize, @@ -45,19 +44,12 @@ pub struct List { &'a mut ViewContext, ) -> SmallVec<[AnyElement; 64]>, >, + interactivity: StatefulInteractivity, } -// #[derive(Debug)] -// pub enum ScrollTarget { -// Show(usize), -// Center(usize), -// } - #[derive(Default)] pub struct ListState { - scroll_top: f32, - // todo - // scroll_to: Option, + interactive: InteractiveElementState, } impl Styled for List { @@ -111,30 +103,66 @@ impl Element for List { - point(border.right + padding.right, border.bottom + padding.bottom), ); - if self.item_count > 0 { - 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; - let visible_range = 0..cmp::min(visible_item_count, self.item_count); + cx.with_z_index(style.z_index.unwrap_or(0), |cx| { + let content_size; + if self.item_count > 0 { + 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() { - item.initialize(view_state, cx); + cx.with_z_index(1, |cx| { + for (item, ix) in items.iter_mut().zip(visible_range) { + item.initialize(view_state, cx); - let layout_id = item.layout(view_state, cx); - cx.compute_layout( - layout_id, - Size { - width: AvailableSpace::Definite(bounds.size.width), - height: AvailableSpace::Definite(item_height), - }, - ); - let offset = padded_bounds.origin + point(px(0.), item_height * ix); - cx.with_element_offset(Some(offset), |cx| item.paint(view_state, cx)) + let layout_id = item.layout(view_state, cx); + cx.compute_layout( + layout_id, + Size { + width: AvailableSpace::Definite(bounds.size.width), + height: AvailableSpace::Definite(item_height), + }, + ); + let offset = + 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 List { } } +impl StatelessInteractive for List { + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { + self.interactivity.as_stateless_mut() + } +} + +impl StatefulInteractive for List { + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { + &mut self.interactivity + } +} + impl Component for List { fn render(self) -> AnyElement { AnyElement::new(self) diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index 7db4c5cf6d..8e2ba9d8a1 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -1,21 +1,21 @@ use crate::{ div, AnyElement, Bounds, Component, Div, DivState, Element, ElementFocus, ElementId, - ElementInteraction, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels, - SharedString, StatefulInteraction, StatefulInteractive, StatelessInteraction, - StatelessInteractive, StyleRefinement, Styled, ViewContext, + ElementInteractivity, FocusDisabled, FocusEnabled, FocusListeners, Focusable, LayoutId, Pixels, + SharedString, StatefulInteractive, StatefulInteractivity, StatelessInteractive, + StatelessInteractivity, StyleRefinement, Styled, ViewContext, }; use util::ResultExt; pub struct Svg< V: 'static, - I: ElementInteraction = StatelessInteraction, + I: ElementInteractivity = StatelessInteractivity, F: ElementFocus = FocusDisabled, > { base: Div, path: Option, } -pub fn svg() -> Svg, FocusDisabled> { +pub fn svg() -> Svg, FocusDisabled> { Svg { base: div(), path: None, @@ -24,7 +24,7 @@ pub fn svg() -> Svg, FocusDisabled> { impl Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { pub fn path(mut self, path: impl Into) -> Self { @@ -33,11 +33,11 @@ where } } -impl Svg, F> +impl Svg, F> where F: ElementFocus, { - pub fn id(self, id: impl Into) -> Svg, F> { + pub fn id(self, id: impl Into) -> Svg, F> { Svg { base: self.base.id(id), path: self.path, @@ -47,7 +47,7 @@ where impl Component for Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn render(self) -> AnyElement { @@ -57,7 +57,7 @@ where impl Element for Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { type ElementState = DivState; @@ -107,7 +107,7 @@ where impl Styled for Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { fn style(&mut self) -> &mut StyleRefinement { @@ -117,27 +117,27 @@ where impl StatelessInteractive for Svg where - I: ElementInteraction, + I: ElementInteractivity, F: ElementFocus, { - fn stateless_interaction(&mut self) -> &mut StatelessInteraction { - self.base.stateless_interaction() + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity { + self.base.stateless_interactivity() } } -impl StatefulInteractive for Svg, F> +impl StatefulInteractive for Svg, F> where V: 'static, F: ElementFocus, { - fn stateful_interaction(&mut self) -> &mut StatefulInteraction { - self.base.stateful_interaction() + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity { + self.base.stateful_interactivity() } } impl Focusable for Svg> where - I: ElementInteraction, + I: ElementInteractivity, { fn focus_listeners(&mut self) -> &mut FocusListeners { self.base.focus_listeners() diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index da208b3813..9d0b896673 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -25,13 +25,13 @@ const TOOLTIP_DELAY: Duration = Duration::from_millis(500); const TOOLTIP_OFFSET: Point = Point::new(px(10.0), px(8.0)); pub trait StatelessInteractive: Element { - fn stateless_interaction(&mut self) -> &mut StatelessInteraction; + fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity; fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { - self.stateless_interaction().hover_style = f(StyleRefinement::default()); + self.stateless_interactivity().hover_style = f(StyleRefinement::default()); self } @@ -43,7 +43,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().group_hover_style = Some(GroupStyle { + self.stateless_interactivity().group_hover_style = Some(GroupStyle { group: group_name.into(), style: f(StyleRefinement::default()), }); @@ -58,7 +58,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_down_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble @@ -79,7 +79,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_up_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble @@ -100,7 +100,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_down_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture @@ -121,7 +121,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_up_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture @@ -141,7 +141,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .mouse_move_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { @@ -158,7 +158,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .scroll_wheel_listeners .push(Box::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { @@ -174,23 +174,48 @@ pub trait StatelessInteractive: Element { C: TryInto, C::Error: Debug, { - self.stateless_interaction().dispatch_context = + self.stateless_interactivity().dispatch_context = context.try_into().expect("invalid dispatch context"); self } - fn on_action( + /// Capture the given action, fires during the capture phase + fn capture_action( mut self, - listener: impl Fn(&mut V, &A, DispatchPhase, &mut ViewContext) + 'static, + listener: impl Fn(&mut V, &A, &mut ViewContext) + 'static, ) -> Self where Self: Sized, { - self.stateless_interaction().key_listeners.push(( + self.stateless_interactivity().key_listeners.push(( TypeId::of::(), Box::new(move |view, event, _, phase, cx| { 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( + mut self, + listener: impl Fn(&mut V, &A, &mut ViewContext) + 'static, + ) -> Self + where + Self: Sized, + { + self.stateless_interactivity().key_listeners.push(( + TypeId::of::(), + Box::new(move |view, event, _, phase, cx| { + let event = event.downcast_ref().unwrap(); + if phase == DispatchPhase::Bubble { + listener(view, event, cx) + } + None }), )); @@ -204,7 +229,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().key_listeners.push(( + self.stateless_interactivity().key_listeners.push(( TypeId::of::(), Box::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -222,7 +247,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().key_listeners.push(( + self.stateless_interactivity().key_listeners.push(( TypeId::of::(), Box::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -237,7 +262,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction() + self.stateless_interactivity() .drag_over_styles .push((TypeId::of::(), f(StyleRefinement::default()))); self @@ -251,7 +276,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().group_drag_over_styles.push(( + self.stateless_interactivity().group_drag_over_styles.push(( TypeId::of::(), GroupStyle { group: group_name.into(), @@ -268,7 +293,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interaction().drop_listeners.push(( + self.stateless_interactivity().drop_listeners.push(( TypeId::of::(), Box::new(move |view, dragged_view, cx| { listener(view, dragged_view.downcast().unwrap(), cx); @@ -279,13 +304,13 @@ pub trait StatelessInteractive: Element { } pub trait StatefulInteractive: StatelessInteractive { - fn stateful_interaction(&mut self) -> &mut StatefulInteraction; + fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity; fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { - self.stateful_interaction().active_style = f(StyleRefinement::default()); + self.stateful_interactivity().active_style = f(StyleRefinement::default()); self } @@ -297,7 +322,7 @@ pub trait StatefulInteractive: StatelessInteractive { where Self: Sized, { - self.stateful_interaction().group_active_style = Some(GroupStyle { + self.stateful_interactivity().group_active_style = Some(GroupStyle { group: group_name.into(), style: f(StyleRefinement::default()), }); @@ -311,7 +336,7 @@ pub trait StatefulInteractive: StatelessInteractive { where Self: Sized, { - self.stateful_interaction() + self.stateful_interactivity() .click_listeners .push(Box::new(move |view, event, cx| listener(view, event, cx))); self @@ -326,10 +351,10 @@ pub trait StatefulInteractive: StatelessInteractive { W: 'static + Render, { 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" ); - self.stateful_interaction().drag_listener = + self.stateful_interactivity().drag_listener = Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag { view: listener(view_state, cx).into(), cursor_offset, @@ -342,10 +367,10 @@ pub trait StatefulInteractive: StatelessInteractive { Self: Sized, { 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" ); - self.stateful_interaction().hover_listener = Some(Box::new(listener)); + self.stateful_interactivity().hover_listener = Some(Box::new(listener)); self } @@ -358,10 +383,10 @@ pub trait StatefulInteractive: StatelessInteractive { W: 'static + Render, { 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" ); - 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() })); @@ -369,11 +394,11 @@ pub trait StatefulInteractive: StatelessInteractive { } } -pub trait ElementInteraction: 'static { - fn as_stateless(&self) -> &StatelessInteraction; - fn as_stateless_mut(&mut self) -> &mut StatelessInteraction; - fn as_stateful(&self) -> Option<&StatefulInteraction>; - fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction>; +pub trait ElementInteractivity: 'static { + fn as_stateless(&self) -> &StatelessInteractivity; + fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity; + fn as_stateful(&self) -> Option<&StatefulInteractivity>; + fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity>; fn initialize( &mut self, @@ -735,11 +760,11 @@ pub trait ElementInteraction: 'static { } #[derive(Deref, DerefMut)] -pub struct StatefulInteraction { +pub struct StatefulInteractivity { pub id: ElementId, #[deref] #[deref_mut] - stateless: StatelessInteraction, + stateless: StatelessInteractivity, click_listeners: SmallVec<[ClickListener; 2]>, active_style: StyleRefinement, group_active_style: Option, @@ -748,29 +773,29 @@ pub struct StatefulInteraction { tooltip_builder: Option>, } -impl ElementInteraction for StatefulInteraction { - fn as_stateful(&self) -> Option<&StatefulInteraction> { +impl ElementInteractivity for StatefulInteractivity { + fn as_stateful(&self) -> Option<&StatefulInteractivity> { Some(self) } - fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction> { + fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity> { Some(self) } - fn as_stateless(&self) -> &StatelessInteraction { + fn as_stateless(&self) -> &StatelessInteractivity { &self.stateless } - fn as_stateless_mut(&mut self) -> &mut StatelessInteraction { + fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity { &mut self.stateless } } -impl From for StatefulInteraction { +impl From for StatefulInteractivity { fn from(id: ElementId) -> Self { Self { id, - stateless: StatelessInteraction::default(), + stateless: StatelessInteractivity::default(), click_listeners: SmallVec::new(), drag_listener: None, hover_listener: None, @@ -783,7 +808,7 @@ impl From for StatefulInteraction { type DropListener = dyn Fn(&mut V, AnyView, &mut ViewContext) + 'static; -pub struct StatelessInteraction { +pub struct StatelessInteractivity { pub dispatch_context: DispatchContext, pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, @@ -797,9 +822,9 @@ pub struct StatelessInteraction { drop_listeners: SmallVec<[(TypeId, Box>); 2]>, } -impl StatelessInteraction { - pub fn into_stateful(self, id: impl Into) -> StatefulInteraction { - StatefulInteraction { +impl StatelessInteractivity { + pub fn into_stateful(self, id: impl Into) -> StatefulInteractivity { + StatefulInteractivity { id: id.into(), stateless: self, click_listeners: SmallVec::new(), @@ -877,7 +902,7 @@ impl InteractiveElementState { } } -impl Default for StatelessInteraction { +impl Default for StatelessInteractivity { fn default() -> Self { Self { dispatch_context: DispatchContext::default(), @@ -895,20 +920,20 @@ impl Default for StatelessInteraction { } } -impl ElementInteraction for StatelessInteraction { - fn as_stateful(&self) -> Option<&StatefulInteraction> { +impl ElementInteractivity for StatelessInteractivity { + fn as_stateful(&self) -> Option<&StatefulInteractivity> { None } - fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteraction> { + fn as_stateful_mut(&mut self) -> Option<&mut StatefulInteractivity> { None } - fn as_stateless(&self) -> &StatelessInteraction { + fn as_stateless(&self) -> &StatelessInteractivity { self } - fn as_stateless_mut(&mut self) -> &mut StatelessInteraction { + fn as_stateless_mut(&mut self) -> &mut StatelessInteractivity { self } } diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index d2571a3253..0fe3660d7c 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -277,7 +277,7 @@ impl Style { pub fn paint(&self, bounds: Bounds, cx: &mut ViewContext) { let rem_size = cx.rem_size(); - cx.stack(0, |cx| { + cx.with_z_index(0, |cx| { cx.paint_shadows( bounds, 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); if background_color.is_some() || self.is_border_visible() { - cx.stack(1, |cx| { + cx.with_z_index(1, |cx| { cx.paint_quad( bounds, self.corner_radii.to_pixels(bounds.size, rem_size), diff --git a/crates/gpui2/src/text_system/line.rs b/crates/gpui2/src/text_system/line.rs index 21a7dcea6f..29956b70f2 100644 --- a/crates/gpui2/src/text_system/line.rs +++ b/crates/gpui2/src/text_system/line.rs @@ -142,8 +142,6 @@ impl Line { color, )?; } - } else { - dbg!(content_mask.bounds, max_glyph_bounds); } } } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 7c05018aa1..8811c845a6 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1698,8 +1698,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self.window_cx } - pub fn stack(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R { - self.window.z_index_stack.push(order); + pub fn with_z_index(&mut self, z_index: u32, f: impl FnOnce(&mut Self) -> R) -> R { + self.window.z_index_stack.push(z_index); let result = f(self); self.window.z_index_stack.pop(); result diff --git a/crates/menu2/Cargo.toml b/crates/menu2/Cargo.toml index 9bf61db82c..5fc33ddb11 100644 --- a/crates/menu2/Cargo.toml +++ b/crates/menu2/Cargo.toml @@ -9,4 +9,5 @@ path = "src/menu2.rs" doctest = false [dependencies] -gpui = { package = "gpui2", path = "../gpui2" } +serde.workspace = true +serde_derive.workspace = true diff --git a/crates/menu2/src/menu2.rs b/crates/menu2/src/menu2.rs index decd4aca22..da21bdcd22 100644 --- a/crates/menu2/src/menu2.rs +++ b/crates/menu2/src/menu2.rs @@ -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; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct Confirm; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct SecondaryConfirm; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct SelectPrev; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct SelectNext; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct SelectFirst; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct SelectLast; -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct ShowContextMenu; diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 3bcf5b9203..26ca810b0c 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -18,11 +18,11 @@ // Dismiss, // } -use std::ops::Range; +use std::cmp; use gpui::{ - div, list, red, AppContext, Component, Div, Element, ElementId, ParentElement, Render, Styled, - ViewContext, + div, list, Component, ElementId, FocusHandle, Focusable, ParentElement, StatelessInteractive, + Styled, ViewContext, }; // pub struct Picker { @@ -43,8 +43,9 @@ pub trait PickerDelegate: Sized + 'static { fn match_count(&self, picker_id: ElementId) -> usize; - // fn selected_index(&self) -> usize; - // fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext>); + fn selected_index(&self, picker_id: ElementId) -> usize; + fn set_selected_index(&mut self, ix: usize, picker_id: ElementId, cx: &mut ViewContext); + // fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()>; // fn confirm(&mut self, secondary: bool, cx: &mut ViewContext>); // fn dismissed(&mut self, cx: &mut ViewContext>); @@ -53,8 +54,6 @@ pub trait PickerDelegate: Sized + 'static { fn render_match( &self, ix: usize, - active: bool, - hovered: bool, selected: bool, picker_id: ElementId, cx: &mut ViewContext, @@ -84,32 +83,72 @@ pub trait PickerDelegate: Sized + 'static { #[derive(Component)] pub struct Picker { id: ElementId, + focus_handle: FocusHandle, phantom: std::marker::PhantomData, } impl Picker { - pub fn new(id: impl Into) -> Self { + pub fn new(id: impl Into, focus_handle: FocusHandle) -> Self { Self { id: id.into(), + focus_handle, phantom: std::marker::PhantomData, } } } impl Picker { - pub fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Component { - div().size_full().id(self.id.clone()).child( - list( - "candidates", - view.match_count(self.id.clone()), - move |this: &mut V, visible_range, cx| { - visible_range - .map(|ix| this.render_match(ix, false, false, false, self.id.clone(), cx)) - .collect() - }, + pub fn render(self, view: &mut V, _cx: &mut ViewContext) -> impl Component { + let id = self.id.clone(); + div() + .size_full() + .id(self.id.clone()) + .track_focus(&self.focus_handle) + .context("picker") + .on_focus(|v, e, cx| { + 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(), - ) } } diff --git a/crates/storybook2/Cargo.toml b/crates/storybook2/Cargo.toml index 0e35a7a66f..7a59dda6df 100644 --- a/crates/storybook2/Cargo.toml +++ b/crates/storybook2/Cargo.toml @@ -25,6 +25,7 @@ smallvec.workspace = true strum = { version = "0.25.0", features = ["derive"] } theme = { path = "../theme" } theme2 = { path = "../theme2" } +menu = { package = "menu2", path = "../menu2" } ui = { package = "ui2", path = "../ui2", features = ["stories"] } util = { path = "../util" } picker = { package = "picker2", path = "../picker2" } diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 16c03f87d5..bfd7d5fa1c 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -1,5 +1,5 @@ use gpui::{ - div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, StatefulInteraction, + div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext, }; use serde::Deserialize; @@ -31,7 +31,7 @@ impl FocusStory { } impl Render for FocusStory { - type Element = Div, FocusEnabled>; + type Element = Div, FocusEnabled>; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { let theme = cx.theme(); @@ -48,20 +48,18 @@ impl Render for FocusStory { .id("parent") .focusable() .context("parent") - .on_action(|_, action: &ActionA, phase, cx| { - println!("Action A dispatched on parent during {:?}", phase); + .on_action(|_, action: &ActionA, cx| { + println!("Action A dispatched on parent during"); }) - .on_action(|_, action: &ActionB, phase, cx| { - println!("Action B dispatched on parent during {:?}", phase); + .on_action(|_, action: &ActionB, cx| { + println!("Action B dispatched on parent during"); }) .on_focus(|_, _, _| println!("Parent focused")) .on_blur(|_, _, _| println!("Parent blurred")) .on_focus_in(|_, _, _| println!("Parent focus_in")) .on_focus_out(|_, _, _| println!("Parent focus_out")) - .on_key_down(|_, event, phase, _| { - println!("Key down on parent {:?} {:?}", phase, event) - }) - .on_key_up(|_, event, phase, _| println!("Key up on parent {:?} {:?}", phase, event)) + .on_key_down(|_, event, phase, _| println!("Key down on parent {:?}", event)) + .on_key_up(|_, event, phase, _| println!("Key up on parent {:?}", event)) .size_full() .bg(color_1) .focus(|style| style.bg(color_2)) @@ -70,8 +68,8 @@ impl Render for FocusStory { div() .track_focus(&child_1) .context("child-1") - .on_action(|_, action: &ActionB, phase, cx| { - println!("Action B dispatched on child 1 during {:?}", phase); + .on_action(|_, action: &ActionB, cx| { + println!("Action B dispatched on child 1 during"); }) .w_full() .h_6() @@ -82,20 +80,16 @@ impl Render for FocusStory { .on_blur(|_, _, _| println!("Child 1 blurred")) .on_focus_in(|_, _, _| println!("Child 1 focus_in")) .on_focus_out(|_, _, _| println!("Child 1 focus_out")) - .on_key_down(|_, event, phase, _| { - println!("Key down on child 1 {:?} {:?}", phase, event) - }) - .on_key_up(|_, event, phase, _| { - println!("Key up on child 1 {:?} {:?}", phase, event) - }) + .on_key_down(|_, event, phase, _| println!("Key down on child 1 {:?}", event)) + .on_key_up(|_, event, phase, _| println!("Key up on child 1 {:?}", event)) .child("Child 1"), ) .child( div() .track_focus(&child_2) .context("child-2") - .on_action(|_, action: &ActionC, phase, cx| { - println!("Action C dispatched on child 2 during {:?}", phase); + .on_action(|_, action: &ActionC, cx| { + println!("Action C dispatched on child 2 during"); }) .w_full() .h_6() @@ -104,12 +98,8 @@ impl Render for FocusStory { .on_blur(|_, _, _| println!("Child 2 blurred")) .on_focus_in(|_, _, _| println!("Child 2 focus_in")) .on_focus_out(|_, _, _| println!("Child 2 focus_out")) - .on_key_down(|_, event, phase, _| { - println!("Key down on child 2 {:?} {:?}", phase, event) - }) - .on_key_up(|_, event, phase, _| { - println!("Key up on child 2 {:?} {:?}", phase, event) - }) + .on_key_down(|_, event, phase, _| println!("Key down on child 2 {:?}", event)) + .on_key_up(|_, event, phase, _| println!("Key up on child 2 {:?}", event)) .child("Child 2"), ) } diff --git a/crates/storybook2/src/stories/kitchen_sink.rs b/crates/storybook2/src/stories/kitchen_sink.rs index 54d6f2a3a9..6831ae2722 100644 --- a/crates/storybook2/src/stories/kitchen_sink.rs +++ b/crates/storybook2/src/stories/kitchen_sink.rs @@ -1,5 +1,5 @@ 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 ui::prelude::*; @@ -12,7 +12,7 @@ impl KitchenSinkStory { } impl Render for KitchenSinkStory { - type Element = Div>; + type Element = Div>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { let component_stories = ComponentStory::iter() diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs index 23d13b83fc..13c3957979 100644 --- a/crates/storybook2/src/stories/picker.rs +++ b/crates/storybook2/src/stories/picker.rs @@ -1,15 +1,18 @@ use gpui::{ - black, div, red, Div, Fill, ParentElement, Render, SharedString, Styled, View, VisualContext, - WindowContext, + div, Component, Div, FocusHandle, KeyBinding, ParentElement, Render, SharedString, + StatelessInteractive, Styled, View, VisualContext, WindowContext, }; use picker::{Picker, PickerDelegate}; +use theme2::ActiveTheme; pub struct PickerStory { + selected_ix: usize, candidates: Vec, + focus_handle: FocusHandle, } impl PickerDelegate for PickerStory { - type ListItem = SharedString; + type ListItem = Div; fn match_count(&self, _picker_id: gpui::ElementId) -> usize { self.candidates.len() @@ -18,46 +21,118 @@ impl PickerDelegate for PickerStory { fn render_match( &self, ix: usize, - _active: bool, - _hovered: bool, - _selected: bool, + selected: bool, _picker_id: gpui::ElementId, cx: &mut gpui::ViewContext, ) -> 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.selected_ix = ix; } } impl PickerStory { pub fn new(cx: &mut WindowContext) -> View { - cx.build_view(|cx| PickerStory { - candidates: vec![ - "Pizza (Italy)".into(), - "Sushi (Japan)".into(), - "Paella (Spain)".into(), - "Tacos (Mexico)".into(), - "Peking Duck (China)".into(), - "Fish and Chips (UK)".into(), - "Croissant (France)".into(), - "Bratwurst (Germany)".into(), - "Poutine (Canada)".into(), - "Chicken Tikka Masala (India)".into(), - "Feijoada (Brazil)".into(), - "Kimchi (Korea)".into(), - "Borscht (Ukraine)".into(), - "Falafel (Middle East)".into(), - "Baklava (Turkey)".into(), - "Shepherd's Pie (Ireland)".into(), - "Rendang (Indonesia)".into(), - "Kebab (Middle East)".into(), - "Ceviche (Peru)".into(), - "Pierogi (Poland)".into(), - "Churrasco (Brazil)".into(), - "Moussaka (Greece)".into(), - "Lasagna (Italy)".into(), - "Pad Thai (Thailand)".into(), - "Pho (Vietnam)".into(), - ], + cx.build_view(|cx| { + cx.bind_keys([ + KeyBinding::new("up", menu::SelectPrev, Some("picker")), + KeyBinding::new("pageup", menu::SelectFirst, Some("picker")), + KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")), + KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")), + KeyBinding::new("down", menu::SelectNext, Some("picker")), + KeyBinding::new("pagedown", menu::SelectLast, Some("picker")), + KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")), + KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")), + KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")), + KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")), + KeyBinding::new("enter", menu::Confirm, Some("picker")), + KeyBinding::new("ctrl-enter", menu::ShowContextMenu, Some("picker")), + KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")), + KeyBinding::new("escape", menu::Cancel, Some("picker")), + KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")), + ]); + + let fh = cx.focus_handle(); + cx.focus(&fh); + + PickerStory { + focus_handle: fh, + candidates: vec![ + "Baguette (France)".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; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { + let theme = cx.theme(); + div() - .text_color(red()) + .bg(theme.styles.colors.background) .size_full() - .child(Picker::new("picker_story")) + .child(Picker::new("picker_story", self.focus_handle.clone())) } } diff --git a/crates/storybook2/src/stories/scroll.rs b/crates/storybook2/src/stories/scroll.rs index cdb48603e0..31928bb9e4 100644 --- a/crates/storybook2/src/stories/scroll.rs +++ b/crates/storybook2/src/stories/scroll.rs @@ -1,5 +1,5 @@ use gpui::{ - div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled, + div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteractivity, Styled, View, VisualContext, WindowContext, }; use theme2::ActiveTheme; @@ -13,7 +13,7 @@ impl ScrollStory { } impl Render for ScrollStory { - type Element = Div>; + type Element = Div>; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { let theme = cx.theme(); diff --git a/crates/ui2/src/components/checkbox.rs b/crates/ui2/src/components/checkbox.rs index 9f7c10a104..20dad74712 100644 --- a/crates/ui2/src/components/checkbox.rs +++ b/crates/ui2/src/components/checkbox.rs @@ -128,7 +128,7 @@ impl Checkbox { // click area for the checkbox. .size_5() // 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()) .child( div() @@ -148,7 +148,7 @@ impl Checkbox { .bg(bg_color) .border() .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. .when(!self.disabled, |this| { // Here instead of `hover()` we use `group_hover()` diff --git a/crates/zed/src/languages/racket/highlights.scm b/crates/zed/src/languages/racket/highlights.scm index 2c0caf8935..3caf1d88e9 100644 --- a/crates/zed/src/languages/racket/highlights.scm +++ b/crates/zed/src/languages/racket/highlights.scm @@ -37,4 +37,3 @@ ((symbol) @comment (#match? @comment "^#[cC][iIsS]$")) - diff --git a/crates/zed2/src/languages/racket/highlights.scm b/crates/zed2/src/languages/racket/highlights.scm index 2c0caf8935..3caf1d88e9 100644 --- a/crates/zed2/src/languages/racket/highlights.scm +++ b/crates/zed2/src/languages/racket/highlights.scm @@ -37,4 +37,3 @@ ((symbol) @comment (#match? @comment "^#[cC][iIsS]$")) -