Port the picker and uniform list (#3248)

This adds a `UniformList` element and partially implements `Picker` as a
component, using `UniformList`. Because editor2 isn't fully implemented
yet, the picker doesn't have filtering logic yet. We want to merge this
for now though, to make the UniformList element available for other
crates.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2023-11-08 17:45:23 +01:00 committed by GitHub
commit 761d4fcd49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 966 additions and 227 deletions

25
Cargo.lock generated
View file

@ -4987,7 +4987,8 @@ dependencies = [
name = "menu2" name = "menu2"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"gpui2", "serde",
"serde_derive",
] ]
[[package]] [[package]]
@ -6023,6 +6024,23 @@ dependencies = [
"workspace", "workspace",
] ]
[[package]]
name = "picker2"
version = "0.1.0"
dependencies = [
"ctor",
"editor2",
"env_logger 0.9.3",
"gpui2",
"menu2",
"parking_lot 0.11.2",
"serde_json",
"settings2",
"theme2",
"util",
"workspace2",
]
[[package]] [[package]]
name = "pico-args" name = "pico-args"
version = "0.4.2" version = "0.4.2"
@ -8539,9 +8557,14 @@ dependencies = [
"backtrace-on-stack-overflow", "backtrace-on-stack-overflow",
"chrono", "chrono",
"clap 4.4.4", "clap 4.4.4",
"editor2",
"fuzzy2",
"gpui2", "gpui2",
"itertools 0.11.0", "itertools 0.11.0",
"language2",
"log", "log",
"menu2",
"picker2",
"rust-embed", "rust-embed",
"serde", "serde",
"settings2", "settings2",

View file

@ -68,6 +68,7 @@ members = [
"crates/notifications", "crates/notifications",
"crates/outline", "crates/outline",
"crates/picker", "crates/picker",
"crates/picker2",
"crates/plugin", "crates/plugin",
"crates/plugin_macros", "crates/plugin_macros",
"crates/plugin_runtime", "crates/plugin_runtime",

View file

@ -9192,7 +9192,7 @@ impl Editor {
supports supports
} }
fn focus(&self, cx: &mut WindowContext) { pub fn focus(&self, cx: &mut WindowContext) {
cx.focus(&self.focus_handle) cx.focus(&self.focus_handle)
} }
} }

View file

@ -19,8 +19,8 @@ use gpui::{
BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, DispatchContext, DispatchPhase,
Edges, Element, ElementId, Entity, GlobalElementId, Hsla, KeyDownEvent, KeyListener, KeyMatch, Edges, Element, ElementId, Entity, GlobalElementId, Hsla, KeyDownEvent, KeyListener, KeyMatch,
Line, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Line, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
ScrollWheelEvent, ShapedGlyph, Size, StatefulInteraction, Style, TextRun, TextStyle, ScrollWheelEvent, ShapedGlyph, Size, Style, TextRun, TextStyle, TextSystem, ViewContext,
TextSystem, ViewContext, WindowContext, WindowContext,
}; };
use itertools::Itertools; use itertools::Itertools;
use language::language_settings::ShowWhitespaceSetting; use language::language_settings::ShowWhitespaceSetting;
@ -3220,7 +3220,7 @@ impl PositionMap {
let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left); let previous_valid = self.snapshot.clip_point(exact_unclipped, Bias::Left);
let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right); let next_valid = self.snapshot.clip_point(exact_unclipped, Bias::Right);
let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance).into(); let column_overshoot_after_line_end = (x_overshoot_after_line_end / self.em_advance) as u32;
*exact_unclipped.column_mut() += column_overshoot_after_line_end; *exact_unclipped.column_mut() += column_overshoot_after_line_end;
PointForPosition { PointForPosition {
previous_valid, previous_valid,

View file

@ -13,6 +13,7 @@ use std::{
atomic::{AtomicUsize, Ordering::SeqCst}, atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Weak, Arc, Weak,
}, },
thread::panicking,
}; };
slotmap::new_key_type! { pub struct EntityId; } slotmap::new_key_type! { pub struct EntityId; }
@ -140,9 +141,8 @@ impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> {
impl<'a, T> Drop for Lease<'a, T> { impl<'a, T> Drop for Lease<'a, T> {
fn drop(&mut self) { fn drop(&mut self) {
if self.entity.is_some() { if self.entity.is_some() && !panicking() {
// We don't panic here, because other panics can cause us to drop the lease without ending it cleanly. panic!("Leases must be ended with EntityMap::end_lease")
log::error!("Leases must be ended with EntityMap::end_lease")
} }
} }
} }

View file

@ -203,6 +203,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

@ -2,8 +2,10 @@ mod div;
mod img; mod img;
mod svg; mod svg;
mod text; mod text;
mod uniform_list;
pub use div::*; pub use div::*;
pub use img::*; pub use img::*;
pub use svg::*; pub use svg::*;
pub use text::*; pub use text::*;
pub use uniform_list::*;

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())
} }
@ -209,7 +213,7 @@ where
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
) -> Self::ElementState { ) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default(); let mut element_state = element_state.unwrap_or_default();
self.interaction.initialize(cx, |cx| { self.interactivity.initialize(cx, |cx| {
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;
@ -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,15 +1,15 @@
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>,
@ -17,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,
@ -28,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 {
@ -42,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,
@ -57,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> {
@ -67,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;
@ -101,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);
}); });
@ -118,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()
}); });
@ -136,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 {
@ -146,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,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

@ -127,6 +127,7 @@ impl<V: 'static> Element<V> for Text<V> {
let element_state = element_state let element_state = element_state
.as_ref() .as_ref()
.expect("measurement has not been performed"); .expect("measurement has not been performed");
let line_height = element_state.line_height; let line_height = element_state.line_height;
let mut line_origin = bounds.origin; let mut line_origin = bounds.origin;
for line in &element_state.lines { for line in &element_state.lines {

View file

@ -0,0 +1,244 @@
use crate::{
point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size,
StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity,
StyleRefinement, Styled, ViewContext,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::{cmp, ops::Range, sync::Arc};
use taffy::style::Overflow;
pub fn uniform_list<Id, V, C>(
id: Id,
item_count: usize,
f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> SmallVec<[C; 64]>,
) -> UniformList<V>
where
Id: Into<ElementId>,
V: 'static,
C: Component<V>,
{
let id = id.into();
UniformList {
id: id.clone(),
style: Default::default(),
item_count,
render_items: Box::new(move |view, visible_range, cx| {
f(view, visible_range, cx)
.into_iter()
.map(|component| component.render())
.collect()
}),
interactivity: id.into(),
scroll_handle: None,
}
}
pub struct UniformList<V: 'static> {
id: ElementId,
style: StyleRefinement,
item_count: usize,
render_items: Box<
dyn for<'a> Fn(
&'a mut V,
Range<usize>,
&'a mut ViewContext<V>,
) -> SmallVec<[AnyElement<V>; 64]>,
>,
interactivity: StatefulInteractivity<V>,
scroll_handle: Option<UniformListScrollHandle>,
}
#[derive(Clone)]
pub struct UniformListScrollHandle(Arc<Mutex<Option<ScrollHandleState>>>);
#[derive(Clone, Debug)]
struct ScrollHandleState {
item_height: Pixels,
list_height: Pixels,
scroll_offset: Arc<Mutex<Point<Pixels>>>,
}
impl UniformListScrollHandle {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(None)))
}
pub fn scroll_to_item(&self, ix: usize) {
if let Some(state) = &*self.0.lock() {
let mut scroll_offset = state.scroll_offset.lock();
let item_top = state.item_height * ix;
let item_bottom = item_top + state.item_height;
let scroll_top = -scroll_offset.y;
if item_top < scroll_top {
scroll_offset.y = -item_top;
} else if item_bottom > scroll_top + state.list_height {
scroll_offset.y = -(item_bottom - state.list_height);
}
}
}
}
impl<V: 'static> Styled for UniformList<V> {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl<V: 'static> Element<V> for UniformList<V> {
type ElementState = InteractiveElementState;
fn id(&self) -> Option<crate::ElementId> {
Some(self.id.clone())
}
fn initialize(
&mut self,
_: &mut V,
element_state: Option<Self::ElementState>,
_: &mut ViewContext<V>,
) -> Self::ElementState {
element_state.unwrap_or_default()
}
fn layout(
&mut self,
_view_state: &mut V,
_element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) -> LayoutId {
cx.request_layout(&self.computed_style(), None)
}
fn paint(
&mut self,
bounds: crate::Bounds<crate::Pixels>,
view_state: &mut V,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<V>,
) {
let style = self.computed_style();
style.paint(bounds, cx);
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
let padded_bounds = Bounds::from_corners(
bounds.origin + point(border.left + padding.left, border.top + padding.top),
bounds.lower_right()
- point(border.right + padding.right, border.bottom + padding.bottom),
);
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);
if let Some(scroll_handle) = self.scroll_handle.clone() {
scroll_handle.0.lock().replace(ScrollHandleState {
item_height,
list_height: padded_bounds.size.height,
scroll_offset: element_state.track_scroll_offset(),
});
}
let visible_item_count = if item_height > px(0.) {
(padded_bounds.size.height / item_height).ceil() as usize + 1
} else {
0
};
let scroll_offset = element_state
.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.clone(), cx);
content_size = Size {
width: padded_bounds.size.width,
height: item_height * self.item_count,
};
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 + 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, element_state, cx);
});
})
}
}
impl<V> UniformList<V> {
fn measure_item_height(
&self,
view_state: &mut V,
list_bounds: Bounds<Pixels>,
cx: &mut ViewContext<V>,
) -> Pixels {
let mut items = (self.render_items)(view_state, 0..1, cx);
debug_assert!(items.len() == 1);
let mut item_to_measure = items.pop().unwrap();
item_to_measure.initialize(view_state, cx);
let layout_id = item_to_measure.layout(view_state, cx);
cx.compute_layout(
layout_id,
Size {
width: AvailableSpace::Definite(list_bounds.size.width),
height: AvailableSpace::MinContent,
},
);
cx.layout_bounds(layout_id).size.height
}
pub fn track_scroll(mut self, handle: UniformListScrollHandle) -> Self {
self.scroll_handle = Some(handle);
self
}
}
impl<V: 'static> StatelessInteractive<V> for UniformList<V> {
fn stateless_interactivity(&mut self) -> &mut StatelessInteractivity<V> {
self.interactivity.as_stateless_mut()
}
}
impl<V: 'static> StatefulInteractive<V> for UniformList<V> {
fn stateful_interactivity(&mut self) -> &mut StatefulInteractivity<V> {
&mut self.interactivity
}
}
impl<V: 'static> Component<V> for UniformList<V> {
fn render(self) -> AnyElement<V> {
AnyElement::new(self)
}
}

View file

@ -267,6 +267,24 @@ impl From<Size<Pixels>> for Size<GlobalPixels> {
} }
} }
impl From<Size<Pixels>> for Size<DefiniteLength> {
fn from(size: Size<Pixels>) -> Self {
Size {
width: size.width.into(),
height: size.height.into(),
}
}
}
impl From<Size<Pixels>> for Size<AbsoluteLength> {
fn from(size: Size<Pixels>) -> Self {
Size {
width: size.width.into(),
height: size.height.into(),
}
}
}
impl Size<Length> { impl Size<Length> {
pub fn full() -> Self { pub fn full() -> Self {
Self { Self {
@ -558,6 +576,15 @@ impl Edges<DefiniteLength> {
left: px(0.).into(), left: px(0.).into(),
} }
} }
pub fn to_pixels(&self, parent_size: Size<AbsoluteLength>, rem_size: Pixels) -> Edges<Pixels> {
Edges {
top: self.top.to_pixels(parent_size.height, rem_size),
right: self.right.to_pixels(parent_size.width, rem_size),
bottom: self.bottom.to_pixels(parent_size.height, rem_size),
left: self.left.to_pixels(parent_size.width, rem_size),
}
}
} }
impl Edges<AbsoluteLength> { impl Edges<AbsoluteLength> {
@ -689,16 +716,16 @@ impl<T> Copy for Corners<T> where T: Copy + Clone + Default + Debug {}
pub struct Pixels(pub(crate) f32); pub struct Pixels(pub(crate) f32);
impl std::ops::Div for Pixels { impl std::ops::Div for Pixels {
type Output = Self; type Output = f32;
fn div(self, rhs: Self) -> Self::Output { fn div(self, rhs: Self) -> Self::Output {
Self(self.0 / rhs.0) self.0 / rhs.0
} }
} }
impl std::ops::DivAssign for Pixels { impl std::ops::DivAssign for Pixels {
fn div_assign(&mut self, rhs: Self) { fn div_assign(&mut self, rhs: Self) {
self.0 /= rhs.0; *self = Self(self.0 / rhs.0);
} }
} }
@ -750,14 +777,6 @@ impl Pixels {
pub const ZERO: Pixels = Pixels(0.0); pub const ZERO: Pixels = Pixels(0.0);
pub const MAX: Pixels = Pixels(f32::MAX); pub const MAX: Pixels = Pixels(f32::MAX);
pub fn as_usize(&self) -> usize {
self.0 as usize
}
pub fn as_isize(&self) -> isize {
self.0 as isize
}
pub fn floor(&self) -> Self { pub fn floor(&self) -> Self {
Self(self.0.floor()) Self(self.0.floor())
} }

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,
@ -736,11 +761,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>,
@ -749,29 +774,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,
@ -784,7 +809,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]>,
@ -798,9 +823,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(),
@ -876,9 +901,15 @@ impl InteractiveElementState {
.as_ref() .as_ref()
.map(|offset| offset.lock().clone()) .map(|offset| offset.lock().clone())
} }
pub fn track_scroll_offset(&mut self) -> Arc<Mutex<Point<Pixels>>> {
self.scroll_offset
.get_or_insert_with(|| Arc::new(Mutex::new(Default::default())))
.clone()
}
} }
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(),
@ -896,20 +927,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
} }
} }
@ -1236,7 +1267,7 @@ pub type KeyListener<V> = Box<
mod test { mod test {
use crate::{ use crate::{
self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render, self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render,
StatefulInteraction, StatelessInteractive, TestAppContext, VisualContext, StatefulInteractivity, StatelessInteractive, TestAppContext, VisualContext,
}; };
struct TestView { struct TestView {
@ -1248,7 +1279,7 @@ mod test {
actions!(TestAction); actions!(TestAction);
impl Render for TestView { impl Render for TestView {
type Element = Div<Self, StatefulInteraction<Self>>; type Element = Div<Self, StatefulInteractivity<Self>>;
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
div().id("testview").child( div().id("testview").child(
@ -1257,7 +1288,7 @@ mod test {
dbg!("ola!"); dbg!("ola!");
this.saw_key_down = true this.saw_key_down = true
}) })
.on_action(|this: &mut TestView, _: &TestAction, _, _| { .on_action(|this: &mut TestView, _: &TestAction, _| {
dbg!("ola!"); dbg!("ola!");
this.saw_action = true this.saw_action = true
}) })

View file

@ -281,7 +281,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),
@ -291,7 +291,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

@ -1,14 +1,19 @@
use crate::{ use crate::{
self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle,
DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position, DefiniteLength, Display, Fill, FlexDirection, Hsla, JustifyContent, Length, Position,
SharedString, StyleRefinement, Visibility, SharedString, Style, StyleRefinement, Visibility,
}; };
use crate::{BoxShadow, TextStyleRefinement}; use crate::{BoxShadow, TextStyleRefinement};
use refineable::Refineable;
use smallvec::smallvec; use smallvec::smallvec;
pub trait Styled { pub trait Styled {
fn style(&mut self) -> &mut StyleRefinement; fn style(&mut self) -> &mut StyleRefinement;
fn computed_style(&mut self) -> Style {
Style::default().refined(self.style().clone())
}
gpui2_macros::style_helpers!(); gpui2_macros::style_helpers!();
/// Sets the size of the element to the full width and height. /// Sets the size of the element to the full width and height.

View file

@ -74,7 +74,6 @@ impl Line {
glyph_origin.y += line_height; glyph_origin.y += line_height;
} }
prev_glyph_position = glyph.position; prev_glyph_position = glyph.position;
let glyph_origin = glyph_origin + baseline_offset;
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None; let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end { if glyph.index >= run_end {
@ -125,14 +124,14 @@ impl Line {
if max_glyph_bounds.intersects(&content_mask.bounds) { if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji { if glyph.is_emoji {
cx.paint_emoji( cx.paint_emoji(
glyph_origin, glyph_origin + baseline_offset,
run.font_id, run.font_id,
glyph.id, glyph.id,
self.layout.layout.font_size, self.layout.layout.font_size,
)?; )?;
} else { } else {
cx.paint_glyph( cx.paint_glyph(
glyph_origin, glyph_origin + baseline_offset,
run.font_id, run.font_id,
glyph.id, glyph.id,
self.layout.layout.font_size, self.layout.layout.font_size,

View file

@ -563,6 +563,12 @@ impl<'a> WindowContext<'a> {
.request_measured_layout(style, rem_size, measure) .request_measured_layout(style, rem_size, measure)
} }
pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
self.window
.layout_engine
.compute_layout(layout_id, available_space)
}
/// Obtain the bounds computed for the given LayoutId relative to the window. This method should not /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
/// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically /// be invoked until the paint phase begins, and will usually be invoked by GPUI itself automatically
/// in order to pass your element its `Bounds` automatically. /// in order to pass your element its `Bounds` automatically.
@ -794,6 +800,7 @@ impl<'a> WindowContext<'a> {
} }
/// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index. /// Paint a monochrome (non-emoji) glyph into the scene for the current frame at the current z-index.
/// The y component of the origin is the baseline of the glyph.
pub fn paint_glyph( pub fn paint_glyph(
&mut self, &mut self,
origin: Point<Pixels>, origin: Point<Pixels>,
@ -847,6 +854,7 @@ impl<'a> WindowContext<'a> {
} }
/// Paint an emoji glyph into the scene for the current frame at the current z-index. /// Paint an emoji glyph into the scene for the current frame at the current z-index.
/// The y component of the origin is the baseline of the glyph.
pub fn paint_emoji( pub fn paint_emoji(
&mut self, &mut self,
origin: Point<Pixels>, origin: Point<Pixels>,
@ -1707,8 +1715,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;

28
crates/picker2/Cargo.toml Normal file
View file

@ -0,0 +1,28 @@
[package]
name = "picker2"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
path = "src/picker2.rs"
doctest = false
[dependencies]
editor = { package = "editor2", path = "../editor2" }
gpui = { package = "gpui2", path = "../gpui2" }
menu = { package = "menu2", path = "../menu2" }
settings = { package = "settings2", path = "../settings2" }
util = { path = "../util" }
theme = { package = "theme2", path = "../theme2" }
workspace = { package = "workspace2", path = "../workspace2" }
parking_lot.workspace = true
[dev-dependencies]
editor = { package = "editor2", path = "../editor2", features = ["test-support"] }
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
serde_json.workspace = true
workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
ctor.workspace = true
env_logger.workspace = true

View file

@ -0,0 +1,163 @@
use editor::Editor;
use gpui::{
div, uniform_list, Component, Div, FocusEnabled, ParentElement, Render, StatefulInteractivity,
StatelessInteractive, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
WindowContext,
};
use std::cmp;
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
scroll_handle: UniformListScrollHandle,
editor: View<Editor>,
pending_update_matches: Option<Task<Option<()>>>,
}
pub trait PickerDelegate: Sized + 'static {
type ListItem: Component<Picker<Self>>;
fn match_count(&self) -> usize;
fn selected_index(&self) -> usize;
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
// fn placeholder_text(&self) -> Arc<str>;
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 dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Self::ListItem;
}
impl<D: PickerDelegate> Picker<D> {
pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
let editor = cx.build_view(|cx| Editor::single_line(cx));
cx.subscribe(&editor, Self::on_input_editor_event).detach();
Self {
delegate,
scroll_handle: UniformListScrollHandle::new(),
pending_update_matches: None,
editor,
}
}
pub fn focus(&self, cx: &mut WindowContext) {
self.editor.update(cx, |editor, cx| editor.focus(cx));
}
fn select_next(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
let count = self.delegate.match_count();
if count > 0 {
let index = self.delegate.selected_index();
let ix = cmp::min(index + 1, count - 1);
self.delegate.set_selected_index(ix, cx);
self.scroll_handle.scroll_to_item(ix);
}
}
fn select_prev(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
let count = self.delegate.match_count();
if count > 0 {
let index = self.delegate.selected_index();
let ix = index.saturating_sub(1);
self.delegate.set_selected_index(ix, cx);
self.scroll_handle.scroll_to_item(ix);
}
}
fn select_first(&mut self, _: &menu::SelectFirst, cx: &mut ViewContext<Self>) {
let count = self.delegate.match_count();
if count > 0 {
self.delegate.set_selected_index(0, cx);
self.scroll_handle.scroll_to_item(0);
}
}
fn select_last(&mut self, _: &menu::SelectLast, cx: &mut ViewContext<Self>) {
let count = self.delegate.match_count();
if count > 0 {
self.delegate.set_selected_index(count - 1, cx);
self.scroll_handle.scroll_to_item(count - 1);
}
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
self.delegate.dismissed(cx);
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
self.delegate.confirm(false, cx);
}
fn secondary_confirm(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
self.delegate.confirm(true, cx);
}
fn on_input_editor_event(
&mut self,
_: View<Editor>,
event: &editor::Event,
cx: &mut ViewContext<Self>,
) {
if let editor::Event::BufferEdited = event {
let query = self.editor.read(cx).text(cx);
self.update_matches(query, cx);
}
}
pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
let update = self.delegate.update_matches(query, cx);
self.matches_updated(cx);
self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move {
update.await;
this.update(&mut cx, |this, cx| {
this.matches_updated(cx);
})
.ok()
}));
}
fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
let index = self.delegate.selected_index();
self.scroll_handle.scroll_to_item(index);
self.pending_update_matches = None;
cx.notify();
}
}
impl<D: PickerDelegate> Render for Picker<D> {
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
div()
.context("picker")
.id("picker-container")
.focusable()
.size_full()
.on_action(Self::select_next)
.on_action(Self::select_prev)
.on_action(Self::select_first)
.on_action(Self::select_last)
.on_action(Self::cancel)
.on_action(Self::confirm)
.on_action(Self::secondary_confirm)
.child(self.editor.clone())
.child(
uniform_list("candidates", self.delegate.match_count(), {
move |this: &mut Self, visible_range, cx| {
let selected_ix = this.delegate.selected_index();
visible_range
.map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
.collect()
}
})
.track_scroll(self.scroll_handle.clone())
.size_full(),
)
}
}

View file

@ -13,9 +13,12 @@ anyhow.workspace = true
# TODO: Remove after diagnosing stack overflow. # TODO: Remove after diagnosing stack overflow.
backtrace-on-stack-overflow = "0.3.0" backtrace-on-stack-overflow = "0.3.0"
clap = { version = "4.4", features = ["derive", "string"] } clap = { version = "4.4", features = ["derive", "string"] }
editor = { package = "editor2", path = "../editor2" }
chrono = "0.4" chrono = "0.4"
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
gpui = { package = "gpui2", path = "../gpui2" } gpui = { package = "gpui2", path = "../gpui2" }
itertools = "0.11.0" itertools = "0.11.0"
language = { package = "language2", path = "../language2" }
log.workspace = true log.workspace = true
rust-embed.workspace = true rust-embed.workspace = true
serde.workspace = true serde.workspace = true
@ -25,8 +28,10 @@ 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" }
[dev-dependencies] [dev-dependencies]
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] } gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

View file

@ -1,6 +1,7 @@
mod colors; mod colors;
mod focus; mod focus;
mod kitchen_sink; mod kitchen_sink;
mod picker;
mod scroll; mod scroll;
mod text; mod text;
mod z_index; mod z_index;
@ -8,6 +9,7 @@ mod z_index;
pub use colors::*; pub use colors::*;
pub use focus::*; pub use focus::*;
pub use kitchen_sink::*; pub use kitchen_sink::*;
pub use picker::*;
pub use scroll::*; pub use scroll::*;
pub use text::*; pub use text::*;
pub use z_index::*; pub use z_index::*;

View file

@ -1,6 +1,6 @@
use gpui::{ use gpui::{
actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, actions, div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render,
StatefulInteraction, StatelessInteractive, Styled, View, VisualContext, WindowContext, StatefulInteractivity, StatelessInteractive, Styled, View, VisualContext, WindowContext,
}; };
use theme2::ActiveTheme; use theme2::ActiveTheme;
@ -21,7 +21,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();
@ -38,20 +38,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))
@ -60,8 +58,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()
@ -72,20 +70,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()
@ -94,12 +88,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

@ -0,0 +1,214 @@
use std::sync::Arc;
use fuzzy::StringMatchCandidate;
use gpui::{
div, Component, Div, KeyBinding, ParentElement, Render, StatelessInteractive, Styled, Task,
View, VisualContext, WindowContext,
};
use picker::{Picker, PickerDelegate};
use theme2::ActiveTheme;
pub struct PickerStory {
picker: View<Picker<Delegate>>,
}
struct Delegate {
candidates: Arc<[StringMatchCandidate]>,
matches: Vec<usize>,
selected_ix: usize,
}
impl Delegate {
fn new(strings: &[&str]) -> Self {
Self {
candidates: strings
.iter()
.copied()
.enumerate()
.map(|(id, string)| StringMatchCandidate {
id,
char_bag: string.into(),
string: string.into(),
})
.collect(),
matches: vec![],
selected_ix: 0,
}
}
}
impl PickerDelegate for Delegate {
type ListItem = Div<Picker<Self>>;
fn match_count(&self) -> usize {
self.candidates.len()
}
fn render_match(
&self,
ix: usize,
selected: bool,
cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Self::ListItem {
let colors = cx.theme().colors();
let Some(candidate_ix) = self.matches.get(ix) else {
return div();
};
let candidate = self.candidates[*candidate_ix].string.clone();
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(candidate)
}
fn selected_index(&self) -> usize {
self.selected_ix
}
fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
self.selected_ix = ix;
cx.notify();
}
fn confirm(&mut self, secondary: bool, cx: &mut gpui::ViewContext<Picker<Self>>) {
let candidate_ix = self.matches[self.selected_ix];
let candidate = self.candidates[candidate_ix].string.clone();
if secondary {
eprintln!("Secondary confirmed {}", candidate)
} else {
eprintln!("Confirmed {}", candidate)
}
}
fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
cx.quit();
}
fn update_matches(
&mut self,
query: String,
cx: &mut gpui::ViewContext<Picker<Self>>,
) -> Task<()> {
let candidates = self.candidates.clone();
self.matches = cx
.background_executor()
.block(fuzzy::match_strings(
&candidates,
&query,
true,
100,
&Default::default(),
cx.background_executor().clone(),
))
.into_iter()
.map(|r| r.candidate_id)
.collect();
self.selected_ix = 0;
Task::ready(())
}
}
impl PickerStory {
pub fn new(cx: &mut WindowContext) -> View<Self> {
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")),
]);
PickerStory {
picker: cx.build_view(|cx| {
let mut delegate = Delegate::new(&[
"Baguette (France)",
"Baklava (Turkey)",
"Beef Wellington (UK)",
"Biryani (India)",
"Borscht (Ukraine)",
"Bratwurst (Germany)",
"Bulgogi (Korea)",
"Burrito (USA)",
"Ceviche (Peru)",
"Chicken Tikka Masala (India)",
"Churrasco (Brazil)",
"Couscous (North Africa)",
"Croissant (France)",
"Dim Sum (China)",
"Empanada (Argentina)",
"Fajitas (Mexico)",
"Falafel (Middle East)",
"Feijoada (Brazil)",
"Fish and Chips (UK)",
"Fondue (Switzerland)",
"Goulash (Hungary)",
"Haggis (Scotland)",
"Kebab (Middle East)",
"Kimchi (Korea)",
"Lasagna (Italy)",
"Maple Syrup Pancakes (Canada)",
"Moussaka (Greece)",
"Pad Thai (Thailand)",
"Paella (Spain)",
"Pancakes (USA)",
"Pasta Carbonara (Italy)",
"Pavlova (Australia)",
"Peking Duck (China)",
"Pho (Vietnam)",
"Pierogi (Poland)",
"Pizza (Italy)",
"Poutine (Canada)",
"Pretzel (Germany)",
"Ramen (Japan)",
"Rendang (Indonesia)",
"Sashimi (Japan)",
"Satay (Indonesia)",
"Shepherd's Pie (Ireland)",
"Sushi (Japan)",
"Tacos (Mexico)",
"Tandoori Chicken (India)",
"Tortilla (Spain)",
"Tzatziki (Greece)",
"Wiener Schnitzel (Austria)",
]);
delegate.update_matches("".into(), cx).detach();
let picker = Picker::new(delegate, cx);
picker.focus(cx);
picker
}),
}
})
}
}
impl Render for PickerStory {
type Element = Div<Self>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
div()
.bg(cx.theme().styles.colors.background)
.size_full()
.child(self.picker.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

@ -52,6 +52,7 @@ pub enum ComponentStory {
TrafficLights, TrafficLights,
Workspace, Workspace,
ZIndex, ZIndex,
Picker,
} }
impl ComponentStory { impl ComponentStory {
@ -96,6 +97,7 @@ impl ComponentStory {
Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(), Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(),
Self::Workspace => ui::WorkspaceStory::view(cx).into(), Self::Workspace => ui::WorkspaceStory::view(cx).into(),
Self::ZIndex => cx.build_view(|_| ZIndexStory).into(), Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
Self::Picker => PickerStory::new(cx).into(),
} }
} }
} }

View file

@ -72,6 +72,8 @@ fn main() {
ThemeSettings::override_global(theme_settings, cx); ThemeSettings::override_global(theme_settings, cx);
ui::settings::init(cx); ui::settings::init(cx);
language::init(cx);
editor::init(cx);
let window = cx.open_window( let window = cx.open_window(
WindowOptions { WindowOptions {

View file

@ -186,9 +186,9 @@ pub fn mouse_side(
} }
pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { pub fn grid_point(pos: Point<Pixels>, cur_size: TerminalSize, display_offset: usize) -> AlacPoint {
let col = GridCol((pos.x / cur_size.cell_width).as_usize()); let col = GridCol((cur_size.cell_width / pos.x) as usize);
let col = min(col, cur_size.last_column()); let col = min(col, cur_size.last_column());
let line = (pos.y / cur_size.line_height).as_isize() as i32; let line = (cur_size.line_height / pos.y) as i32;
let line = min(line, cur_size.bottommost_line().0); let line = min(line, cur_size.bottommost_line().0);
AlacPoint::new(GridLine(line - display_offset as i32), col) AlacPoint::new(GridLine(line - display_offset as i32), col)
} }

View file

@ -1121,8 +1121,7 @@ impl Terminal {
None => return, None => return,
}; };
let scroll_lines = let scroll_lines = (scroll_delta / self.last_content.size.line_height) as i32;
(scroll_delta / self.last_content.size.line_height).as_isize() as i32;
self.events self.events
.push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines))); .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines)));
@ -1280,11 +1279,11 @@ impl Terminal {
} }
/* Calculate the appropriate scroll lines */ /* Calculate the appropriate scroll lines */
TouchPhase::Moved => { TouchPhase::Moved => {
let old_offset = (self.scroll_px / line_height).as_isize() as i32; let old_offset = (self.scroll_px / line_height) as i32;
self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier; self.scroll_px += e.delta.pixel_delta(line_height).y * scroll_multiplier;
let new_offset = (self.scroll_px / line_height).as_isize() as i32; let new_offset = (self.scroll_px / line_height) as i32;
// Whenever we hit the edges, reset our stored scroll to 0 // Whenever we hit the edges, reset our stored scroll to 0
// so we can respond to changes in direction quickly // so we can respond to changes in direction quickly
@ -1396,9 +1395,9 @@ fn all_search_matches<'a, T>(
} }
fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize { fn content_index_for_mouse(pos: Point<Pixels>, size: &TerminalSize) -> usize {
let col = (pos.x / size.cell_width()).round().as_usize(); let col = (pos.x / size.cell_width()).round() as usize;
let clamped_col = min(col, size.columns() - 1); let clamped_col = min(col, size.columns() - 1);
let row = (pos.y / size.line_height()).round().as_usize(); let row = (pos.y / size.line_height()).round() as usize;
let clamped_row = min(row, size.screen_lines() - 1); let clamped_row = min(row, size.screen_lines() - 1);
clamped_row * size.columns() + clamped_col clamped_row * size.columns() + clamped_col
} }

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

@ -1,11 +1,8 @@
use std::{any::TypeId, sync::Arc};
use gpui::{
div, AnyView, AppContext, DispatchPhase, Div, ParentElement, Render, StatelessInteractive,
View, ViewContext,
};
use crate::Workspace; use crate::Workspace;
use gpui::{
div, AnyView, AppContext, Div, ParentElement, Render, StatelessInteractive, View, ViewContext,
};
use std::{any::TypeId, sync::Arc};
pub struct ModalRegistry { pub struct ModalRegistry {
registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>, registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
@ -42,15 +39,7 @@ impl ModalRegistry {
let build_view = build_view.clone(); let build_view = build_view.clone();
div.on_action( div.on_action(
move |workspace: &mut Workspace, move |workspace: &mut Workspace, event: &A, cx: &mut ViewContext<Workspace>| {
event: &A,
phase: DispatchPhase,
cx: &mut ViewContext<Workspace>| {
dbg!("GOT HERE");
if phase == DispatchPhase::Capture {
return;
}
let new_modal = (build_view)(workspace, cx); let new_modal = (build_view)(workspace, cx);
workspace.modal_layer.update(cx, |modal_layer, _| { workspace.modal_layer.update(cx, |modal_layer, _| {
modal_layer.open_modal = Some(new_modal.into()); modal_layer.open_modal = Some(new_modal.into());

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]$"))