diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index b1dbf70357ab43887378aa4ea14a44838aa1e856..9649f4745f5b83cc02587549ef8c905c6db8df51 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -9,11 +9,11 @@ use refineable::Refineable; use smallvec::SmallVec; use crate::{ - current_platform, image_cache::ImageCache, Action, AppMetadata, AssetSource, ClipboardItem, - Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, - Keymap, LayoutId, MainThread, MainThreadOnly, Platform, SharedString, SubscriberSet, - Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, - WindowContext, WindowHandle, WindowId, + current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource, + ClipboardItem, Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, + KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, + SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, + TextSystem, View, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, HashSet, VecDeque}; @@ -93,6 +93,7 @@ impl App { quit_observers: SubscriberSet::new(), layout_id_buffer: Default::default(), propagate_event: true, + active_drag: None, }) })) } @@ -171,6 +172,7 @@ pub struct AppContext { text_system: Arc, flushing_effects: bool, pending_updates: usize, + pub(crate) active_drag: Option, pub(crate) next_frame_callbacks: HashMap>, pub(crate) executor: Executor, pub(crate) svg_renderer: SvgRenderer, @@ -766,6 +768,13 @@ pub(crate) enum Effect { }, } +pub(crate) struct AnyDrag { + pub drag_handle_view: AnyView, + pub cursor_offset: Point, + pub state: AnyBox, + pub state_type: TypeId, +} + #[cfg(test)] mod tests { use super::AppContext; diff --git a/crates/gpui2/src/color.rs b/crates/gpui2/src/color.rs index d05caba1a6e9bf923c02680d2e634f64ec0f9b92..f6ea2549ec94db1379d19d8066d1f3ade8a3250f 100644 --- a/crates/gpui2/src/color.rs +++ b/crates/gpui2/src/color.rs @@ -155,6 +155,15 @@ pub fn white() -> Hsla { } } +pub fn red() -> Hsla { + Hsla { + h: 0., + 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 e955309112071a863ff1386bba9857b48b6442ab..2753d5f8fb290453ea4b8741b74249d6f2e0c5de 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -298,7 +298,7 @@ where style.apply_text_style(cx, |cx| { style.apply_overflow(bounds, cx, |cx| { let scroll_offset = element_state.interactive.scroll_offset(); - cx.with_scroll_offset(scroll_offset, |cx| { + cx.with_element_offset(scroll_offset, |cx| { for child in &mut this.children { child.paint(view_state, cx); } @@ -354,7 +354,7 @@ where F: ElementFocus, V: 'static + Send + Sync, { - fn stateless_interactivity(&mut self) -> &mut StatelessInteraction { + fn stateless_interaction(&mut self) -> &mut StatelessInteraction { self.interaction.as_stateless_mut() } } @@ -364,7 +364,7 @@ where F: ElementFocus, V: 'static + Send + Sync, { - fn stateful_interactivity(&mut self) -> &mut StatefulInteraction { + fn stateful_interaction(&mut self) -> &mut StatefulInteraction { &mut self.interaction } } diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index 50e9e29fc58f2fb79ba6ca43d1be8cf752d8b0ae..713bc8b98dbf667eb75957a382a07d1e0961a634 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -156,8 +156,8 @@ where I: ElementInteraction, F: ElementFocus, { - fn stateless_interactivity(&mut self) -> &mut StatelessInteraction { - self.base.stateless_interactivity() + fn stateless_interaction(&mut self) -> &mut StatelessInteraction { + self.base.stateless_interaction() } } @@ -166,8 +166,8 @@ where V: 'static + Send + Sync, F: ElementFocus, { - fn stateful_interactivity(&mut self) -> &mut StatefulInteraction { - self.base.stateful_interactivity() + fn stateful_interaction(&mut self) -> &mut StatefulInteraction { + self.base.stateful_interaction() } } diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index fd55296a3a6aff6ac221614685154c16273b949d..3785ce480e16a2979addd3604ecc1ec66403b8d5 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -130,8 +130,8 @@ where I: ElementInteraction, F: ElementFocus, { - fn stateless_interactivity(&mut self) -> &mut StatelessInteraction { - self.base.stateless_interactivity() + fn stateless_interaction(&mut self) -> &mut StatelessInteraction { + self.base.stateless_interaction() } } @@ -140,8 +140,8 @@ where V: 'static + Send + Sync, F: ElementFocus, { - fn stateful_interactivity(&mut self) -> &mut StatefulInteraction { - self.base.stateful_interactivity() + fn stateful_interaction(&mut self) -> &mut StatefulInteraction { + self.base.stateful_interaction() } } diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index 41b3b751f810c87e4d5be245b98907dad688c579..45b6af9444b3819530de94f8b6f7865fd974e60e 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -40,6 +40,10 @@ impl Point { y: self.y.scale(factor), } } + + pub fn magnitude(&self) -> f64 { + ((self.x.0.powi(2) + self.y.0.powi(2)) as f64).sqrt() + } } impl Mul for Point diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 475cbb75a470427b5c33f3356c6f6f0cbb59364f..aa8cbf4826e9f9218e3fe6ba814e477a70e8d65e 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,7 +1,7 @@ use crate::{ - point, px, Action, AppContext, BorrowWindow, Bounds, DispatchContext, DispatchPhase, Element, - ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, Pixels, Point, SharedString, - Size, Style, StyleRefinement, ViewContext, + point, px, view, Action, AnyBox, AnyDrag, AppContext, BorrowWindow, Bounds, DispatchContext, + DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, + Pixels, Point, SharedString, Size, Style, StyleRefinement, ViewContext, }; use collections::HashMap; use derive_more::{Deref, DerefMut}; @@ -11,18 +11,21 @@ use smallvec::SmallVec; use std::{ any::{Any, TypeId}, fmt::Debug, + marker::PhantomData, ops::Deref, sync::Arc, }; +const DRAG_THRESHOLD: f64 = 2.; + pub trait StatelessInteractive: Element { - fn stateless_interactivity(&mut self) -> &mut StatelessInteraction; + fn stateless_interaction(&mut self) -> &mut StatelessInteraction; fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { - self.stateless_interactivity().hover_style = f(StyleRefinement::default()); + self.stateless_interaction().hover_style = f(StyleRefinement::default()); self } @@ -34,7 +37,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().group_hover_style = Some(GroupStyle { + self.stateless_interaction().group_hover_style = Some(GroupStyle { group: group_name.into(), style: f(StyleRefinement::default()), }); @@ -52,7 +55,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_down_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble @@ -76,7 +79,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_up_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble @@ -100,7 +103,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_down_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture @@ -124,7 +127,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_up_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Capture @@ -147,7 +150,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .mouse_move_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { @@ -167,7 +170,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity() + self.stateless_interaction() .scroll_wheel_listeners .push(Arc::new(move |view, event, bounds, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { @@ -183,7 +186,7 @@ pub trait StatelessInteractive: Element { C: TryInto, C::Error: Debug, { - self.stateless_interactivity().dispatch_context = + self.stateless_interaction().dispatch_context = context.try_into().expect("invalid dispatch context"); self } @@ -198,7 +201,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interaction().key_listeners.push(( TypeId::of::(), Arc::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -223,7 +226,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interaction().key_listeners.push(( TypeId::of::(), Arc::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -244,7 +247,7 @@ pub trait StatelessInteractive: Element { where Self: Sized, { - self.stateless_interactivity().key_listeners.push(( + self.stateless_interaction().key_listeners.push(( TypeId::of::(), Arc::new(move |view, event, _, phase, cx| { let event = event.downcast_ref().unwrap(); @@ -254,16 +257,63 @@ pub trait StatelessInteractive: Element { )); self } + + fn drag_over(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self + where + Self: Sized, + { + self.stateless_interaction() + .drag_over_styles + .push((TypeId::of::(), f(StyleRefinement::default()))); + self + } + + fn group_drag_over( + mut self, + group_name: impl Into, + f: impl FnOnce(StyleRefinement) -> StyleRefinement, + ) -> Self + where + Self: Sized, + { + self.stateless_interaction().group_drag_over_styles.push(( + TypeId::of::(), + GroupStyle { + group: group_name.into(), + style: f(StyleRefinement::default()), + }, + )); + self + } + + fn on_drop( + mut self, + listener: impl Fn(&mut Self::ViewState, S, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + self.stateless_interaction().drop_listeners.push(( + TypeId::of::(), + Arc::new(move |view, drag_state, cx| { + listener(view, *drag_state.downcast().unwrap(), cx); + }), + )); + self + } } pub trait StatefulInteractive: StatelessInteractive { - fn stateful_interactivity(&mut self) -> &mut StatefulInteraction; + fn stateful_interaction(&mut self) -> &mut StatefulInteraction; fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { - self.stateful_interactivity().active_style = f(StyleRefinement::default()); + self.stateful_interaction().active_style = f(StyleRefinement::default()); self } @@ -275,7 +325,7 @@ pub trait StatefulInteractive: StatelessInteractive { where Self: Sized, { - self.stateful_interactivity().group_active_style = Some(GroupStyle { + self.stateful_interaction().group_active_style = Some(GroupStyle { group: group_name.into(), style: f(StyleRefinement::default()), }); @@ -284,17 +334,55 @@ pub trait StatefulInteractive: StatelessInteractive { fn on_click( mut self, - handler: impl Fn(&mut Self::ViewState, &MouseClickEvent, &mut ViewContext) + listener: impl Fn(&mut Self::ViewState, &ClickEvent, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + self.stateful_interaction() + .click_listeners + .push(Arc::new(move |view, event, cx| listener(view, event, cx))); + self + } + + fn on_drag( + mut self, + listener: impl Fn( + &mut Self::ViewState, + &mut ViewContext, + ) -> Drag + Send + Sync + 'static, ) -> Self where Self: Sized, + S: 'static + Send + Sync, + R: 'static + Fn(&mut Self::ViewState, &mut ViewContext) -> E + Send + Sync, + E: Element, { - self.stateful_interactivity() - .mouse_click_listeners - .push(Arc::new(move |view, event, cx| handler(view, event, cx))); + debug_assert!( + self.stateful_interaction().drag_listener.is_none(), + "calling on_drag more than once on the same element is not supported" + ); + self.stateful_interaction().drag_listener = + Some(Arc::new(move |view_state, cursor_offset, cx| { + let drag = listener(view_state, cx); + let view_handle = cx.handle().upgrade().unwrap(); + let drag_handle_view = view(view_handle, move |view_state, cx| { + (drag.render_drag_handle)(view_state, cx) + }) + .into_any(); + AnyDrag { + drag_handle_view, + cursor_offset, + state: Box::new(drag.state), + state_type: TypeId::of::(), + } + })); self } } @@ -359,6 +447,26 @@ pub trait ElementInteraction: 'static + Send + Sync { style.refine(&stateless.hover_style); } + if let Some(drag) = cx.active_drag.take() { + for (state_type, group_drag_style) in &self.as_stateless().group_drag_over_styles { + if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) { + if *state_type == drag.state_type + && group_bounds.contains_point(&mouse_position) + { + style.refine(&group_drag_style.style); + } + } + } + + for (state_type, drag_over_style) in &self.as_stateless().drag_over_styles { + if *state_type == drag.state_type && bounds.contains_point(&mouse_position) { + style.refine(drag_over_style); + } + } + + cx.active_drag = Some(drag); + } + if let Some(stateful) = self.as_stateful() { let active_state = element_state.active_state.lock(); if active_state.group { @@ -411,38 +519,104 @@ pub trait ElementInteraction: 'static + Send + Sync { .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx)); if let Some(group_bounds) = hover_group_bounds { - paint_hover_listener(group_bounds, cx); + let hovered = group_bounds.contains_point(&cx.mouse_position()); + cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture { + if group_bounds.contains_point(&event.position) != hovered { + cx.notify(); + } + } + }); } - if stateless.hover_style.is_some() { - paint_hover_listener(bounds, cx); + if stateless.hover_style.is_some() + || (cx.active_drag.is_some() && !stateless.drag_over_styles.is_empty()) + { + let hovered = bounds.contains_point(&cx.mouse_position()); + cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture { + if bounds.contains_point(&event.position) != hovered { + cx.notify(); + } + } + }); } - if let Some(stateful) = self.as_stateful() { - let click_listeners = stateful.mouse_click_listeners.clone(); - - let pending_click = element_state.pending_click.clone(); - let mouse_down = pending_click.lock().clone(); - if let Some(mouse_down) = mouse_down { - cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { - let mouse_click = MouseClickEvent { - down: mouse_down.clone(), - up: event.clone(), - }; - for listener in &click_listeners { - listener(state, &mouse_click, cx); + if cx.active_drag.is_some() { + let drop_listeners = stateless.drop_listeners.clone(); + cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + if let Some(drag_state_type) = + cx.active_drag.as_ref().map(|drag| drag.state_type) + { + for (drop_state_type, listener) in &drop_listeners { + if *drop_state_type == drag_state_type { + let drag = cx + .active_drag + .take() + .expect("checked for type drag state type above"); + listener(view, drag.state, cx); + cx.notify(); + cx.stop_propagation(); + } } } + } + }); + } - *pending_click.lock() = None; - }); - } else { - cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| { - if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { - *pending_click.lock() = Some(event.clone()); + if let Some(stateful) = self.as_stateful() { + let click_listeners = stateful.click_listeners.clone(); + let drag_listener = stateful.drag_listener.clone(); + + if !click_listeners.is_empty() || drag_listener.is_some() { + let pending_mouse_down = element_state.pending_mouse_down.clone(); + let mouse_down = pending_mouse_down.lock().clone(); + if let Some(mouse_down) = mouse_down { + if let Some(drag_listener) = drag_listener { + let active_state = element_state.active_state.clone(); + + cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { + if cx.active_drag.is_some() { + if phase == DispatchPhase::Capture { + cx.notify(); + } + } else if phase == DispatchPhase::Bubble + && bounds.contains_point(&event.position) + && (event.position - mouse_down.position).magnitude() + > DRAG_THRESHOLD + { + *active_state.lock() = ActiveState::default(); + let cursor_offset = event.position - bounds.origin; + let drag = drag_listener(view_state, cursor_offset, cx); + cx.active_drag = Some(drag); + cx.notify(); + cx.stop_propagation(); + } + }); } - }); + + cx.on_mouse_event(move |view_state, event: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) + { + let mouse_click = ClickEvent { + down: mouse_down.clone(), + up: event.clone(), + }; + for listener in &click_listeners { + listener(view_state, &mouse_click, cx); + } + } + *pending_mouse_down.lock() = None; + }); + } else { + cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) + { + *pending_mouse_down.lock() = Some(event.clone()); + } + }); + } } let active_state = element_state.active_state.clone(); @@ -487,12 +661,12 @@ pub trait ElementInteraction: 'static + Send + Sync { if overflow.x == Overflow::Scroll { scroll_offset.x = - (scroll_offset.x - delta.x).clamp(px(0.), scroll_max.width); + (scroll_offset.x + delta.x).clamp(-scroll_max.width, px(0.)); } if overflow.y == Overflow::Scroll { scroll_offset.y = - (scroll_offset.y - delta.y).clamp(px(0.), scroll_max.height); + (scroll_offset.y + delta.y).clamp(-scroll_max.height, px(0.)); } if *scroll_offset != old_scroll_offset { @@ -506,29 +680,16 @@ pub trait ElementInteraction: 'static + Send + Sync { } } -fn paint_hover_listener(bounds: Bounds, cx: &mut ViewContext) -where - V: 'static + Send + Sync, -{ - let hovered = bounds.contains_point(&cx.mouse_position()); - cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture { - if bounds.contains_point(&event.position) != hovered { - cx.notify(); - } - } - }); -} - #[derive(Deref, DerefMut)] pub struct StatefulInteraction { pub id: ElementId, #[deref] #[deref_mut] stateless: StatelessInteraction, - pub mouse_click_listeners: SmallVec<[MouseClickListener; 2]>, - pub active_style: StyleRefinement, - pub group_active_style: Option, + click_listeners: SmallVec<[ClickListener; 2]>, + active_style: StyleRefinement, + group_active_style: Option, + drag_listener: Option>, } impl ElementInteraction for StatefulInteraction @@ -560,13 +721,16 @@ where Self { id, stateless: StatelessInteraction::default(), - mouse_click_listeners: SmallVec::new(), + click_listeners: SmallVec::new(), + drag_listener: None, active_style: StyleRefinement::default(), group_active_style: None, } } } +type DropListener = dyn Fn(&mut V, AnyBox, &mut ViewContext) + Send + Sync; + pub struct StatelessInteraction { pub dispatch_context: DispatchContext, pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, @@ -576,6 +740,9 @@ pub struct StatelessInteraction { pub key_listeners: SmallVec<[(TypeId, KeyListener); 32]>, pub hover_style: StyleRefinement, pub group_hover_style: Option, + drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, + group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, + drop_listeners: SmallVec<[(TypeId, Arc>); 2]>, } impl StatelessInteraction @@ -586,7 +753,8 @@ where StatefulInteraction { id: id.into(), stateless: self, - mouse_click_listeners: SmallVec::new(), + click_listeners: SmallVec::new(), + drag_listener: None, active_style: StyleRefinement::default(), group_active_style: None, } @@ -642,7 +810,7 @@ impl ActiveState { #[derive(Default)] pub struct InteractiveElementState { active_state: Arc>, - pending_click: Arc>>, + pending_mouse_down: Arc>>, scroll_offset: Option>>>, } @@ -665,6 +833,9 @@ impl Default for StatelessInteraction { key_listeners: SmallVec::new(), hover_style: StyleRefinement::default(), group_hover_style: None, + drag_over_styles: SmallVec::new(), + group_drag_over_styles: SmallVec::new(), + drop_listeners: SmallVec::new(), } } } @@ -740,11 +911,39 @@ pub struct MouseUpEvent { } #[derive(Clone, Debug, Default)] -pub struct MouseClickEvent { +pub struct ClickEvent { pub down: MouseDownEvent, pub up: MouseUpEvent, } +pub struct Drag +where + S: 'static + Send + Sync, + R: Fn(&mut V, &mut ViewContext) -> E, + V: 'static + Send + Sync, + E: Element, +{ + pub state: S, + pub render_drag_handle: R, + view_type: PhantomData, +} + +impl Drag +where + S: 'static + Send + Sync, + R: Fn(&mut V, &mut ViewContext) -> E + Send + Sync, + V: 'static + Send + Sync, + E: Element, +{ + pub fn new(state: S, render_drag_handle: R) -> Self { + Drag { + state, + render_drag_handle, + view_type: PhantomData, + } + } +} + #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] pub enum MouseButton { Left, @@ -919,8 +1118,6 @@ pub type MouseUpListener = Arc< + Sync + 'static, >; -pub type MouseClickListener = - Arc) + Send + Sync + 'static>; pub type MouseMoveListener = Arc< dyn Fn(&mut V, &MouseMoveEvent, &Bounds, DispatchPhase, &mut ViewContext) @@ -936,6 +1133,12 @@ pub type ScrollWheelListener = Arc< + 'static, >; +pub type ClickListener = + Arc) + Send + Sync + 'static>; + +pub(crate) type DragListener = + Arc, &mut ViewContext) -> AnyDrag + Send + Sync + 'static>; + pub type KeyListener = Arc< dyn Fn( &mut V, diff --git a/crates/gpui2/src/taffy.rs b/crates/gpui2/src/taffy.rs index 71844bb8a631bb0820ebbcadaf8e4131aef0eeb3..ca8b43aa70c20578849626870655c8bb05c90f51 100644 --- a/crates/gpui2/src/taffy.rs +++ b/crates/gpui2/src/taffy.rs @@ -125,11 +125,11 @@ impl TaffyLayoutEngine { // } // println!(""); - let started_at = std::time::Instant::now(); + // let started_at = std::time::Instant::now(); self.taffy .compute_layout(id.into(), available_space.into()) .expect(EXPECT_MESSAGE); - println!("compute_layout took {:?}", started_at.elapsed()); + // println!("compute_layout took {:?}", started_at.elapsed()); } pub fn layout_bounds(&mut self, id: LayoutId) -> Bounds { diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index cb2460798f2b1bf44ee94a89e02591841197eae3..f803bb7bb667068489434f9db6a0dc05b91d5a0c 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -3,11 +3,11 @@ use crate::{ BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, - Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, - Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, - RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, - SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, - WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, + Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, + MouseUpEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, + Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, + SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, + Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; use collections::HashMap; @@ -159,7 +159,7 @@ pub struct Window { key_matchers: HashMap, z_index_stack: StackingOrder, content_mask_stack: Vec>, - scroll_offset_stack: Vec>, + element_offset_stack: Vec>, mouse_listeners: HashMap>, key_dispatch_stack: Vec, freeze_key_dispatch_stack: bool, @@ -177,7 +177,7 @@ pub struct Window { } impl Window { - pub fn new( + pub(crate) fn new( handle: AnyWindowHandle, options: WindowOptions, cx: &mut MainThread, @@ -234,7 +234,7 @@ impl Window { key_matchers: HashMap::default(), z_index_stack: StackingOrder(SmallVec::new()), content_mask_stack: Vec::new(), - scroll_offset_stack: Vec::new(), + element_offset_stack: Vec::new(), mouse_listeners: HashMap::default(), key_dispatch_stack: Vec::new(), freeze_key_dispatch_stack: false, @@ -469,7 +469,7 @@ impl<'a, 'w> WindowContext<'a, 'w> { .layout_engine .layout_bounds(layout_id) .map(Into::into); - bounds.origin -= self.scroll_offset(); + bounds.origin += self.element_offset(); bounds } @@ -805,14 +805,22 @@ impl<'a, 'w> WindowContext<'a, 'w> { let mut root_view = cx.window.root_view.take().unwrap(); - if let Some(element_id) = root_view.id() { - cx.with_element_state(element_id, |element_state, cx| { - let element_state = draw_with_element_state(&mut root_view, element_state, cx); - ((), element_state) + cx.stack(0, |cx| { + let available_space = cx.window.content_size.map(Into::into); + draw_any_view(&mut root_view, available_space, cx); + }); + + if let Some(mut active_drag) = cx.active_drag.take() { + cx.stack(1, |cx| { + let offset = cx.mouse_position() - active_drag.cursor_offset; + cx.with_element_offset(Some(offset), |cx| { + let available_space = + size(AvailableSpace::MinContent, AvailableSpace::MinContent); + draw_any_view(&mut active_drag.drag_handle_view, available_space, cx); + cx.active_drag = Some(active_drag); + }); }); - } else { - draw_with_element_state(&mut root_view, None, cx); - }; + } cx.window.root_view = Some(root_view); let scene = cx.window.scene_builder.build(); @@ -827,20 +835,21 @@ impl<'a, 'w> WindowContext<'a, 'w> { .detach(); }); - fn draw_with_element_state( - root_view: &mut AnyView, - element_state: Option, + fn draw_any_view( + view: &mut AnyView, + available_space: Size, cx: &mut ViewContext<()>, - ) -> AnyBox { - let mut element_state = root_view.initialize(&mut (), element_state, cx); - let layout_id = root_view.layout(&mut (), &mut element_state, cx); - let available_space = cx.window.content_size.map(Into::into); - cx.window - .layout_engine - .compute_layout(layout_id, available_space); - let bounds = cx.window.layout_engine.layout_bounds(layout_id); - root_view.paint(bounds, &mut (), &mut element_state, cx); - element_state + ) { + cx.with_optional_element_state(view.id(), |element_state, cx| { + let mut element_state = view.initialize(&mut (), element_state, cx); + let layout_id = view.layout(&mut (), &mut element_state, cx); + cx.window + .layout_engine + .compute_layout(layout_id, available_space); + let bounds = cx.window.layout_engine.layout_bounds(layout_id); + view.paint(bounds, &mut (), &mut element_state, cx); + ((), element_state) + }); } } @@ -916,6 +925,12 @@ impl<'a, 'w> WindowContext<'a, 'w> { } } + if self.app.propagate_event + && any_mouse_event.downcast_ref::().is_some() + { + self.active_drag = None; + } + // Just in case any handlers added new handlers, which is weird, but possible. handlers.extend( self.window @@ -1206,7 +1221,7 @@ pub trait BorrowWindow: BorrowAppContext { result } - fn with_scroll_offset( + fn with_element_offset( &mut self, offset: Option>, f: impl FnOnce(&mut Self) -> R, @@ -1215,16 +1230,16 @@ pub trait BorrowWindow: BorrowAppContext { return f(self); }; - let offset = self.scroll_offset() + offset; - self.window_mut().scroll_offset_stack.push(offset); + let offset = self.element_offset() + offset; + self.window_mut().element_offset_stack.push(offset); let result = f(self); - self.window_mut().scroll_offset_stack.pop(); + self.window_mut().element_offset_stack.pop(); result } - fn scroll_offset(&self) -> Point { + fn element_offset(&self) -> Point { self.window() - .scroll_offset_stack + .element_offset_stack .last() .copied() .unwrap_or_default() @@ -1263,6 +1278,18 @@ pub trait BorrowWindow: BorrowAppContext { }) } + fn with_optional_element_state( + &mut self, + element_id: Option, + f: impl FnOnce(Option, &mut Self) -> (R, S), + ) -> R { + if let Some(element_id) = element_id { + self.with_element_state(element_id, f) + } else { + f(None, self).0 + } + } + fn content_mask(&self) -> ContentMask { self.window() .content_mask_stack diff --git a/crates/ui2/src/color.rs b/crates/ui2/src/color.rs new file mode 100644 index 0000000000000000000000000000000000000000..7e7453dc72cb924e5bddd5c193a64d961c6deabd --- /dev/null +++ b/crates/ui2/src/color.rs @@ -0,0 +1,253 @@ +pub use crate::{theme, ButtonVariant, ElementExt, Theme}; +use gpui2::{hsla, rgb, Hsla, WindowContext}; +use strum::EnumIter; + +#[derive(Clone, Copy)] +pub struct PlayerThemeColors { + pub cursor: Hsla, + pub selection: Hsla, +} + +impl PlayerThemeColors { + pub fn new(cx: &WindowContext, ix: usize) -> Self { + let theme = theme(cx); + + if ix < theme.players.len() { + Self { + cursor: theme.players[ix].cursor, + selection: theme.players[ix].selection, + } + } else { + Self { + cursor: rgb::(0xff00ff), + selection: rgb::(0xff00ff), + } + } + } +} + +#[derive(Clone, Copy)] +pub struct SyntaxColor { + pub comment: Hsla, + pub string: Hsla, + pub function: Hsla, + pub keyword: Hsla, +} + +impl SyntaxColor { + pub fn new(cx: &WindowContext) -> Self { + let theme = theme(cx); + + Self { + comment: theme + .syntax + .get("comment") + .cloned() + .unwrap_or_else(|| rgb::(0xff00ff)), + string: theme + .syntax + .get("string") + .cloned() + .unwrap_or_else(|| rgb::(0xff00ff)), + function: theme + .syntax + .get("function") + .cloned() + .unwrap_or_else(|| rgb::(0xff00ff)), + keyword: theme + .syntax + .get("keyword") + .cloned() + .unwrap_or_else(|| rgb::(0xff00ff)), + } + } +} + +/// ThemeColor is the primary interface for coloring elements in the UI. +/// +/// It is a mapping layer between semantic theme colors and colors from the reference library. +/// +/// While we are between zed and zed2 we use this to map semantic colors to the old theme. +#[derive(Clone, Copy)] +pub struct ThemeColor { + pub transparent: Hsla, + pub mac_os_traffic_light_red: Hsla, + pub mac_os_traffic_light_yellow: Hsla, + pub mac_os_traffic_light_green: Hsla, + pub border: Hsla, + pub border_variant: Hsla, + pub border_focused: Hsla, + pub border_transparent: Hsla, + /// The background color of an elevated surface, like a modal, tooltip or toast. + pub elevated_surface: Hsla, + pub surface: Hsla, + /// Window background color of the base app + pub background: Hsla, + /// Default background for elements like filled buttons, + /// text fields, checkboxes, radio buttons, etc. + /// - TODO: Map to step 3. + pub filled_element: Hsla, + /// The background color of a hovered element, like a button being hovered + /// with a mouse, or hovered on a touch screen. + /// - TODO: Map to step 4. + pub filled_element_hover: Hsla, + /// The background color of an active element, like a button being pressed, + /// or tapped on a touch screen. + /// - TODO: Map to step 5. + pub filled_element_active: Hsla, + /// The background color of a selected element, like a selected tab, + /// a button toggled on, or a checkbox that is checked. + pub filled_element_selected: Hsla, + pub filled_element_disabled: Hsla, + pub ghost_element: Hsla, + /// The background color of a hovered element with no default background, + /// like a ghost-style button or an interactable list item. + /// - TODO: Map to step 3. + pub ghost_element_hover: Hsla, + /// - TODO: Map to step 4. + pub ghost_element_active: Hsla, + pub ghost_element_selected: Hsla, + pub ghost_element_disabled: Hsla, + pub text: Hsla, + pub text_muted: Hsla, + pub text_placeholder: Hsla, + pub text_disabled: Hsla, + pub text_accent: Hsla, + pub icon_muted: Hsla, + pub syntax: SyntaxColor, + + pub status_bar: Hsla, + pub title_bar: Hsla, + pub toolbar: Hsla, + pub tab_bar: Hsla, + /// The background of the editor + pub editor: Hsla, + pub editor_subheader: Hsla, + pub editor_active_line: Hsla, + pub terminal: Hsla, + pub image_fallback_background: Hsla, + + pub git_created: Hsla, + pub git_modified: Hsla, + pub git_deleted: Hsla, + pub git_conflict: Hsla, + pub git_ignored: Hsla, + pub git_renamed: Hsla, + + pub player: [PlayerThemeColors; 8], +} + +impl ThemeColor { + pub fn new(cx: &WindowContext) -> Self { + let theme = theme(cx); + let transparent = hsla(0.0, 0.0, 0.0, 0.0); + + let players = [ + PlayerThemeColors::new(cx, 0), + PlayerThemeColors::new(cx, 1), + PlayerThemeColors::new(cx, 2), + PlayerThemeColors::new(cx, 3), + PlayerThemeColors::new(cx, 4), + PlayerThemeColors::new(cx, 5), + PlayerThemeColors::new(cx, 6), + PlayerThemeColors::new(cx, 7), + ]; + + Self { + transparent, + mac_os_traffic_light_red: rgb::(0xEC695E), + mac_os_traffic_light_yellow: rgb::(0xF4BF4F), + mac_os_traffic_light_green: rgb::(0x62C554), + border: theme.lowest.base.default.border, + border_variant: theme.lowest.variant.default.border, + border_focused: theme.lowest.accent.default.border, + border_transparent: transparent, + elevated_surface: theme.lowest.base.default.background, + surface: theme.middle.base.default.background, + background: theme.lowest.base.default.background, + filled_element: theme.lowest.base.default.background, + filled_element_hover: theme.lowest.base.hovered.background, + filled_element_active: theme.lowest.base.active.background, + filled_element_selected: theme.lowest.accent.default.background, + filled_element_disabled: transparent, + ghost_element: transparent, + ghost_element_hover: theme.lowest.base.default.background, + ghost_element_active: theme.lowest.base.hovered.background, + ghost_element_selected: theme.lowest.accent.default.background, + ghost_element_disabled: transparent, + text: theme.lowest.base.default.foreground, + text_muted: theme.lowest.variant.default.foreground, + /// TODO: map this to a real value + text_placeholder: theme.lowest.negative.default.foreground, + text_disabled: theme.lowest.base.disabled.foreground, + text_accent: theme.lowest.accent.default.foreground, + icon_muted: theme.lowest.variant.default.foreground, + syntax: SyntaxColor::new(cx), + + status_bar: theme.lowest.base.default.background, + title_bar: theme.lowest.base.default.background, + toolbar: theme.highest.base.default.background, + tab_bar: theme.middle.base.default.background, + editor: theme.highest.base.default.background, + editor_subheader: theme.middle.base.default.background, + terminal: theme.highest.base.default.background, + editor_active_line: theme.highest.on.default.background, + image_fallback_background: theme.lowest.base.default.background, + + git_created: theme.lowest.positive.default.foreground, + git_modified: theme.lowest.accent.default.foreground, + git_deleted: theme.lowest.negative.default.foreground, + git_conflict: theme.lowest.warning.default.foreground, + git_ignored: theme.lowest.base.disabled.foreground, + git_renamed: theme.lowest.warning.default.foreground, + + player: players, + } + } +} + +/// Colors used exclusively for syntax highlighting. +/// +/// For now we deserialize these from a theme. +/// These will be defined statically in the new theme. +#[derive(Default, PartialEq, EnumIter, Clone, Copy)] +pub enum HighlightColor { + #[default] + Default, + Comment, + String, + Function, + Keyword, +} + +impl HighlightColor { + pub fn hsla(&self, theme: &Theme) -> Hsla { + match self { + Self::Default => theme + .syntax + .get("primary") + .cloned() + .expect("Couldn't find `primary` in theme.syntax"), + Self::Comment => theme + .syntax + .get("comment") + .cloned() + .expect("Couldn't find `comment` in theme.syntax"), + Self::String => theme + .syntax + .get("string") + .cloned() + .expect("Couldn't find `string` in theme.syntax"), + Self::Function => theme + .syntax + .get("function") + .cloned() + .expect("Couldn't find `function` in theme.syntax"), + Self::Keyword => theme + .syntax + .get("keyword") + .cloned() + .expect("Couldn't find `keyword` in theme.syntax"), + } + } +} diff --git a/crates/ui2/src/components/breadcrumb.rs b/crates/ui2/src/components/breadcrumb.rs index 4a8a375bf64613645f7715722c9846a98da4f5d5..7d63425a7c0f75d9846c12b7e264669fbc82f62d 100644 --- a/crates/ui2/src/components/breadcrumb.rs +++ b/crates/ui2/src/components/breadcrumb.rs @@ -36,7 +36,6 @@ impl Breadcrumb { cx: &mut ViewContext, ) -> impl Element { let color = ThemeColor::new(cx); - let color = ThemeColor::new(cx); let symbols_len = self.symbols.len(); diff --git a/crates/ui2/src/components/buffer.rs b/crates/ui2/src/components/buffer.rs index 84390653170d54d4994d4a1ac5086bb1ba308e1b..c684d2e24d9aa1a01ac73b5a33ca19a7f05691d2 100644 --- a/crates/ui2/src/components/buffer.rs +++ b/crates/ui2/src/components/buffer.rs @@ -159,13 +159,12 @@ impl Buffer { } fn render_row(row: BufferRow, cx: &WindowContext) -> impl Element { - let system_color = SystemColor::new(); let color = ThemeColor::new(cx); let line_background = if row.current { color.editor_active_line } else { - system_color.transparent + color.transparent }; let line_number_color = if row.current { diff --git a/crates/ui2/src/components/collab_panel.rs b/crates/ui2/src/components/collab_panel.rs index 8e3c99a8332af4dc7e06a96beddcc17fac699823..7c53891506b7eba644f432753a2268dd77002579 100644 --- a/crates/ui2/src/components/collab_panel.rs +++ b/crates/ui2/src/components/collab_panel.rs @@ -22,7 +22,6 @@ impl CollabPanel { fn render(&mut self, _view: &mut S, cx: &mut ViewContext) -> impl Element { let color = ThemeColor::new(cx); - let color = ThemeColor::new(cx); v_stack() .id(self.id.clone()) diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index dea77d871886248598b7efb06f0281a7c4f5a6ef..114e73e4a2d1155aec952d86f1e23d9b0e71e6df 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -94,8 +94,6 @@ impl ListHeader { fn render(&mut self, _view: &mut S, cx: &mut ViewContext) -> impl Element { let color = ThemeColor::new(cx); - let system_color = SystemColor::new(); - let color = ThemeColor::new(cx); let is_toggleable = self.toggleable != Toggleable::NotToggleable; let is_toggled = self.toggleable.is_toggled(); @@ -373,7 +371,6 @@ impl ListEntry { fn render(&mut self, _view: &mut S, cx: &mut ViewContext) -> impl Element { let color = ThemeColor::new(cx); - let system_color = SystemColor::new(); let color = ThemeColor::new(cx); let settings = user_settings(cx); diff --git a/crates/ui2/src/components/panes.rs b/crates/ui2/src/components/panes.rs index 4cf2e28f058a143018b33ae4f884df3fdca5413e..48d4327cabe3ee0cc0f09f9e25600e0514a8567d 100644 --- a/crates/ui2/src/components/panes.rs +++ b/crates/ui2/src/components/panes.rs @@ -24,7 +24,6 @@ pub struct Pane { impl Pane { pub fn new(id: impl Into, size: Size) -> Self { // Fill is only here for debugging purposes, remove before release - let system_color = SystemColor::new(); Self { id: id.into(), diff --git a/crates/ui2/src/components/player_stack.rs b/crates/ui2/src/components/player_stack.rs index a5f76f4ab915d96c39c255120c6aa179ebdbe4c7..c7b0d52eefa22a96ab49c8b4d96891ec7ea49142 100644 --- a/crates/ui2/src/components/player_stack.rs +++ b/crates/ui2/src/components/player_stack.rs @@ -18,7 +18,7 @@ impl PlayerStack { } fn render(&mut self, _view: &mut S, cx: &mut ViewContext) -> impl Element { - let system_color = SystemColor::new(); + let color = ThemeColor::new(cx); let player = self.player_with_call_status.get_player(); self.player_with_call_status.get_call_status(); @@ -54,7 +54,7 @@ impl PlayerStack { .pl_1() .rounded_lg() .bg(if followers.is_none() { - system_color.transparent + color.transparent } else { player.selection_color(cx) }) diff --git a/crates/ui2/src/components/project_panel.rs b/crates/ui2/src/components/project_panel.rs index d8f5626d1805f3116117cc397b87c272d0e80215..a6b755d67ec2b867bd942367bf4603989292d54e 100644 --- a/crates/ui2/src/components/project_panel.rs +++ b/crates/ui2/src/components/project_panel.rs @@ -21,7 +21,6 @@ impl ProjectPanel { fn render(&mut self, _view: &mut S, cx: &mut ViewContext) -> impl Element { let color = ThemeColor::new(cx); - let color = ThemeColor::new(cx); div() .id(self.id.clone()) diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 2d787e6f025347f1498f629edce5dc4b4652e211..c13d4e5637cd4254caf26f57f38dc3de2a371e5b 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -17,6 +17,11 @@ pub struct Tab { close_side: IconSide, } +#[derive(Clone, Debug)] +struct TabDragState { + title: String, +} + impl Tab { pub fn new(id: impl Into) -> Self { Self { @@ -111,8 +116,19 @@ impl Tab { ), }; + let drag_state = TabDragState { + title: self.title.clone(), + }; + div() .id(self.id.clone()) + .on_drag(move |_view, _cx| { + Drag::new(drag_state.clone(), |view, cx| div().w_8().h_4().bg(red())) + }) + .drag_over::(|d| d.bg(black())) + .on_drop(|_view, state: TabDragState, cx| { + dbg!(state); + }) .px_2() .py_0p5() .flex() @@ -148,7 +164,7 @@ impl Tab { } } -use gpui2::ElementId; +use gpui2::{black, red, Drag, ElementId}; #[cfg(feature = "stories")] pub use stories::*; diff --git a/crates/ui2/src/components/title_bar.rs b/crates/ui2/src/components/title_bar.rs index db33a9de85670fa965589a0d2c5c37b7e8040532..487a4717917a14cb16a00a921d0bdebc96b7b49d 100644 --- a/crates/ui2/src/components/title_bar.rs +++ b/crates/ui2/src/components/title_bar.rs @@ -88,7 +88,6 @@ impl TitleBar { } fn render(&mut self, cx: &mut ViewContext) -> impl Element { - let color = ThemeColor::new(cx); let color = ThemeColor::new(cx); let settings = user_settings(cx); diff --git a/crates/ui2/src/components/traffic_lights.rs b/crates/ui2/src/components/traffic_lights.rs index cf8d2b47d149bbda5a0353fb338562adfe826a74..58f252628dd9d304b0af98aa5d4cd0fc4043c9e0 100644 --- a/crates/ui2/src/components/traffic_lights.rs +++ b/crates/ui2/src/components/traffic_lights.rs @@ -1,7 +1,6 @@ use std::marker::PhantomData; use crate::prelude::*; -use crate::SystemColor; #[derive(Clone, Copy)] enum TrafficLightColor { @@ -28,12 +27,11 @@ impl TrafficLight { fn render(&mut self, _view: &mut S, cx: &mut ViewContext) -> impl Element { let color = ThemeColor::new(cx); - let system_color = SystemColor::new(); let fill = match (self.window_has_focus, self.color) { - (true, TrafficLightColor::Red) => system_color.mac_os_traffic_light_red, - (true, TrafficLightColor::Yellow) => system_color.mac_os_traffic_light_yellow, - (true, TrafficLightColor::Green) => system_color.mac_os_traffic_light_green, + (true, TrafficLightColor::Red) => color.mac_os_traffic_light_red, + (true, TrafficLightColor::Yellow) => color.mac_os_traffic_light_yellow, + (true, TrafficLightColor::Green) => color.mac_os_traffic_light_green, (false, _) => color.filled_element, }; diff --git a/crates/ui2/src/elements/input.rs b/crates/ui2/src/elements/input.rs index dc2a8b8d6346623cc295e1c8004c5a76ade0a066..eb2bdcdc25c2c23be1aae5ba663f4f5e3a46d53c 100644 --- a/crates/ui2/src/elements/input.rs +++ b/crates/ui2/src/elements/input.rs @@ -62,7 +62,6 @@ impl Input { fn render(&mut self, _view: &mut S, cx: &mut ViewContext) -> impl Element { let color = ThemeColor::new(cx); - let system_color = SystemColor::new(); let (input_bg, input_hover_bg, input_active_bg) = match self.variant { InputVariant::Ghost => ( @@ -95,7 +94,7 @@ impl Input { .w_full() .px_2() .border() - .border_color(system_color.transparent) + .border_color(color.transparent) .bg(input_bg) .hover(|style| style.bg(input_hover_bg)) .active(|style| style.bg(input_active_bg)) diff --git a/crates/ui2/src/lib.rs b/crates/ui2/src/lib.rs index 8bc7bfa810927e9477e6c52ac67b23bb40dba980..841599c4b49d3071f326845b7805ee966d32a06b 100644 --- a/crates/ui2/src/lib.rs +++ b/crates/ui2/src/lib.rs @@ -1,5 +1,26 @@ +//! # UI – Zed UI Primitives & Components +//! +//! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI. +//! +//! ## Work in Progress +//! +//! This crate is still a work in progress. The initial primitives and components are built for getting all the UI on the screen, +//! much of the state and functionality is mocked or hard codeded, and performance has not been a focus. +//! +//! Expect some inconsistencies from component to component as we work out the best way to build these components. +//! +//! ## Getting Started +//! +//! - [ThemeColor](crate::color::ThemeColor) is your one stop shop for all colors in the UI. +//! +//! ## Design Philosophy +//! +//! Work in Progress! +//! + #![allow(dead_code, unused_variables)] +mod color; mod components; mod element_ext; mod elements; diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index 2b282400343a0ebc6f044bce64d78416633faf68..c79c30ee6892866d6919527e604375aed8d2ea4f 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -3,282 +3,13 @@ pub use gpui2::{ StatelessInteractive, Styled, ViewContext, WindowContext, }; +pub use crate::color::*; use crate::settings::user_settings; pub use crate::{theme, ButtonVariant, ElementExt, Theme}; -use gpui2::{hsla, rems, rgb, Hsla, Rems}; +use gpui2::{rems, Hsla, Rems}; use strum::EnumIter; -// TODO Remove uses in favor of ThemeColor -#[derive(Default)] -pub struct SystemColor { - pub transparent: Hsla, - pub mac_os_traffic_light_red: Hsla, - pub mac_os_traffic_light_yellow: Hsla, - pub mac_os_traffic_light_green: Hsla, - pub state_hover_background: Hsla, - pub state_active_background: Hsla, -} - -impl SystemColor { - pub fn new() -> SystemColor { - SystemColor { - transparent: hsla(0.0, 0.0, 0.0, 0.0), - mac_os_traffic_light_red: rgb::(0xEC695E), - mac_os_traffic_light_yellow: rgb::(0xF4BF4F), - mac_os_traffic_light_green: rgb::(0x62C554), - state_hover_background: hsla(0.0, 0.0, 0.0, 0.08), - state_active_background: hsla(0.0, 0.0, 0.0, 0.16), - } - } - pub fn color(&self) -> Hsla { - self.transparent - } -} - -#[derive(Clone, Copy)] -pub struct PlayerThemeColors { - pub cursor: Hsla, - pub selection: Hsla, -} - -impl PlayerThemeColors { - pub fn new(cx: &WindowContext, ix: usize) -> Self { - let theme = theme(cx); - - if ix < theme.players.len() { - Self { - cursor: theme.players[ix].cursor, - selection: theme.players[ix].selection, - } - } else { - Self { - cursor: rgb::(0xff00ff), - selection: rgb::(0xff00ff), - } - } - } -} - -#[derive(Clone, Copy)] -pub struct SyntaxColor { - pub comment: Hsla, - pub string: Hsla, - pub function: Hsla, - pub keyword: Hsla, -} - -impl SyntaxColor { - pub fn new(cx: &WindowContext) -> Self { - let theme = theme(cx); - - Self { - comment: theme - .syntax - .get("comment") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - string: theme - .syntax - .get("string") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - function: theme - .syntax - .get("function") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - keyword: theme - .syntax - .get("keyword") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - } - } -} - -#[derive(Clone, Copy)] -pub struct ThemeColor { - pub transparent: Hsla, - pub mac_os_traffic_light_red: Hsla, - pub mac_os_traffic_light_yellow: Hsla, - pub mac_os_traffic_light_green: Hsla, - pub border: Hsla, - pub border_variant: Hsla, - pub border_focused: Hsla, - pub border_transparent: Hsla, - /// The background color of an elevated surface, like a modal, tooltip or toast. - pub elevated_surface: Hsla, - pub surface: Hsla, - /// Window background color of the base app - pub background: Hsla, - /// Default background for elements like filled buttons, - /// text fields, checkboxes, radio buttons, etc. - /// - TODO: Map to step 3. - pub filled_element: Hsla, - /// The background color of a hovered element, like a button being hovered - /// with a mouse, or hovered on a touch screen. - /// - TODO: Map to step 4. - pub filled_element_hover: Hsla, - /// The background color of an active element, like a button being pressed, - /// or tapped on a touch screen. - /// - TODO: Map to step 5. - pub filled_element_active: Hsla, - /// The background color of a selected element, like a selected tab, - /// a button toggled on, or a checkbox that is checked. - pub filled_element_selected: Hsla, - pub filled_element_disabled: Hsla, - pub ghost_element: Hsla, - /// The background color of a hovered element with no default background, - /// like a ghost-style button or an interactable list item. - /// - TODO: Map to step 3. - pub ghost_element_hover: Hsla, - /// - TODO: Map to step 4. - pub ghost_element_active: Hsla, - pub ghost_element_selected: Hsla, - pub ghost_element_disabled: Hsla, - pub text: Hsla, - pub text_muted: Hsla, - pub text_placeholder: Hsla, - pub text_disabled: Hsla, - pub text_accent: Hsla, - pub icon_muted: Hsla, - pub syntax: SyntaxColor, - - pub status_bar: Hsla, - pub title_bar: Hsla, - pub toolbar: Hsla, - pub tab_bar: Hsla, - /// The background of the editor - pub editor: Hsla, - pub editor_subheader: Hsla, - pub editor_active_line: Hsla, - pub terminal: Hsla, - pub image_fallback_background: Hsla, - - pub git_created: Hsla, - pub git_modified: Hsla, - pub git_deleted: Hsla, - pub git_conflict: Hsla, - pub git_ignored: Hsla, - pub git_renamed: Hsla, - - pub player: [PlayerThemeColors; 8], -} - -impl ThemeColor { - pub fn new(cx: &WindowContext) -> Self { - let theme = theme(cx); - let system_color = SystemColor::new(); - - let players = [ - PlayerThemeColors::new(cx, 0), - PlayerThemeColors::new(cx, 1), - PlayerThemeColors::new(cx, 2), - PlayerThemeColors::new(cx, 3), - PlayerThemeColors::new(cx, 4), - PlayerThemeColors::new(cx, 5), - PlayerThemeColors::new(cx, 6), - PlayerThemeColors::new(cx, 7), - ]; - - Self { - transparent: hsla(0.0, 0.0, 0.0, 0.0), - mac_os_traffic_light_red: rgb::(0xEC695E), - mac_os_traffic_light_yellow: rgb::(0xF4BF4F), - mac_os_traffic_light_green: rgb::(0x62C554), - border: theme.lowest.base.default.border, - border_variant: theme.lowest.variant.default.border, - border_focused: theme.lowest.accent.default.border, - border_transparent: system_color.transparent, - elevated_surface: theme.lowest.base.default.background, - surface: theme.middle.base.default.background, - background: theme.lowest.base.default.background, - filled_element: theme.lowest.base.default.background, - filled_element_hover: theme.lowest.base.hovered.background, - filled_element_active: theme.lowest.base.active.background, - filled_element_selected: theme.lowest.accent.default.background, - filled_element_disabled: system_color.transparent, - ghost_element: system_color.transparent, - ghost_element_hover: theme.lowest.base.default.background, - ghost_element_active: theme.lowest.base.hovered.background, - ghost_element_selected: theme.lowest.accent.default.background, - ghost_element_disabled: system_color.transparent, - text: theme.lowest.base.default.foreground, - text_muted: theme.lowest.variant.default.foreground, - /// TODO: map this to a real value - text_placeholder: theme.lowest.negative.default.foreground, - text_disabled: theme.lowest.base.disabled.foreground, - text_accent: theme.lowest.accent.default.foreground, - icon_muted: theme.lowest.variant.default.foreground, - syntax: SyntaxColor::new(cx), - - status_bar: theme.lowest.base.default.background, - title_bar: theme.lowest.base.default.background, - toolbar: theme.highest.base.default.background, - tab_bar: theme.middle.base.default.background, - editor: theme.highest.base.default.background, - editor_subheader: theme.middle.base.default.background, - terminal: theme.highest.base.default.background, - editor_active_line: theme.highest.on.default.background, - image_fallback_background: theme.lowest.base.default.background, - - git_created: theme.lowest.positive.default.foreground, - git_modified: theme.lowest.accent.default.foreground, - git_deleted: theme.lowest.negative.default.foreground, - git_conflict: theme.lowest.warning.default.foreground, - git_ignored: theme.lowest.base.disabled.foreground, - git_renamed: theme.lowest.warning.default.foreground, - - player: players, - } - } -} - -#[derive(Default, PartialEq, EnumIter, Clone, Copy)] -pub enum HighlightColor { - #[default] - Default, - Comment, - String, - Function, - Keyword, -} - -impl HighlightColor { - pub fn hsla(&self, theme: &Theme) -> Hsla { - let system_color = SystemColor::new(); - - match self { - Self::Default => theme - .syntax - .get("primary") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - Self::Comment => theme - .syntax - .get("comment") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - Self::String => theme - .syntax - .get("string") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - Self::Function => theme - .syntax - .get("function") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - Self::Keyword => theme - .syntax - .get("keyword") - .cloned() - .unwrap_or_else(|| rgb::(0xff00ff)), - } - } -} - pub fn ui_size(cx: &mut WindowContext, size: f32) -> Rems { const UI_SCALE_RATIO: f32 = 0.875; @@ -330,10 +61,9 @@ impl GitStatus { pub fn hsla(&self, cx: &WindowContext) -> Hsla { let color = ThemeColor::new(cx); - let system_color = SystemColor::new(); match self { - Self::None => system_color.transparent, + Self::None => color.transparent, Self::Created => color.git_created, Self::Modified => color.git_modified, Self::Deleted => color.git_deleted,