From 044d9679abc53c1dc0865a322ce0029fd7361cfe Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 13 Nov 2023 21:40:02 -0700 Subject: [PATCH] Checkpoint --- crates/editor2/src/element.rs | 4 +- crates/gpui2/src/element.rs | 2 +- crates/gpui2/src/elements/div.rs | 6 +- crates/gpui2/src/elements/node.rs | 582 ++++++++++++++++++++++++------ crates/gpui2/src/gpui2.rs | 16 +- crates/gpui2/src/style.rs | 44 ++- crates/gpui2/src/window.rs | 47 +-- 7 files changed, 548 insertions(+), 153 deletions(-) diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index f8386ee271e658148d7edb9b32fec5c67a044443..630b062d1fb9188b8c1eb3798e95086b278891f4 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -616,7 +616,7 @@ impl EditorElement { let line_end_overshoot = 0.15 * layout.position_map.line_height; let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces; - cx.with_content_mask(ContentMask { bounds }, |cx| { + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { // todo!("cursor region") // cx.scene().push_cursor_region(CursorRegion { // bounds, @@ -2659,7 +2659,7 @@ impl Element for EditorElement { // We call with_z_index to establish a new stacking context. cx.with_z_index(0, |cx| { - cx.with_content_mask(ContentMask { bounds }, |cx| { + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { self.paint_mouse_listeners( bounds, gutter_bounds, diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index 9ee9eaa7c335960f3e3c6974b0a8798c3d13f9c4..80ab0abc90fcd36446a2cc244e88c113d28da9bc 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -255,7 +255,7 @@ where // Ignore the element offset when drawing this element, as the origin is already specified // in absolute terms. origin -= cx.element_offset(); - cx.with_element_offset(Some(origin), |cx| self.paint(view_state, cx)) + cx.with_element_offset(origin, |cx| self.paint(view_state, cx)) } } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 25c13d6980f3c8b3e3cdfd80570e603c574e795d..2b411f76091598e941343b1cbbdc7afab3d192a6 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -252,7 +252,7 @@ where cx: &mut ViewContext, ) -> LayoutId { let style = self.compute_style(Bounds::default(), element_state, cx); - style.with_text_style(cx, |cx| { + style.apply_text_style(cx, |cx| { self.with_element_id(cx, |this, _global_id, cx| { let layout_ids = this .children @@ -318,10 +318,10 @@ where ); }); cx.with_z_index(1, |cx| { - style.with_text_style(cx, |cx| { + style.apply_text_style(cx, |cx| { style.apply_overflow(bounds, cx, |cx| { let scroll_offset = element_state.interactive.scroll_offset(); - cx.with_element_offset(scroll_offset, |cx| { + cx.with_element_offset(scroll_offset.unwrap_or_default(), |cx| { for child in &mut this.children { child.paint(view_state, cx); } diff --git a/crates/gpui2/src/elements/node.rs b/crates/gpui2/src/elements/node.rs index 7133997c48e95f415d2d4901fcd0744d9c05a301..57f825994f20bbebd0e4c03151eea266d71d94fa 100644 --- a/crates/gpui2/src/elements/node.rs +++ b/crates/gpui2/src/elements/node.rs @@ -1,10 +1,12 @@ use crate::{ - point, Action, AnyDrag, AnyElement, AnyView, AppContext, BorrowWindow, Bounds, ClickEvent, - DispatchPhase, Element, FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Render, - ScrollWheelEvent, SharedString, Style, StyleRefinement, Styled, View, ViewContext, Visibility, + point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, + BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, FocusHandle, KeyContext, + KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, + Task, View, ViewContext, Visibility, }; use collections::HashMap; +use parking_lot::Mutex; use refineable::Refineable; use smallvec::SmallVec; use std::{ @@ -12,14 +14,20 @@ use std::{ marker::PhantomData, mem, sync::Arc, + time::Duration, }; +use taffy::style::Overflow; + +const DRAG_THRESHOLD: f64 = 2.; +const TOOLTIP_DELAY: Duration = Duration::from_millis(500); +const TOOLTIP_OFFSET: Point = Point::new(px(10.0), px(8.0)); pub struct GroupStyle { pub group: SharedString, pub style: StyleRefinement, } -pub trait InteractiveComponent { +pub trait InteractiveComponent: Sized + Element { fn interactivity(&mut self) -> &mut Interactivity; fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self @@ -274,7 +282,7 @@ pub trait InteractiveComponent { } } -pub trait StatefulInteractiveComponent: InteractiveComponent { +pub trait StatefulInteractiveComponent>: InteractiveComponent { fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, @@ -541,6 +549,13 @@ impl InteractiveComponent for Node { pub struct NodeState { child_layout_ids: SmallVec<[LayoutId; 4]>, + interactive_state: InteractiveElementState, +} + +impl AsMut for InteractiveElementState { + fn as_mut(&mut self) -> &mut InteractiveElementState { + self + } } impl Element for Node { @@ -553,7 +568,7 @@ impl Element for Node { fn initialize( &mut self, view_state: &mut V, - _: Option, + previous_element_state: Option, cx: &mut ViewContext, ) -> Self::ElementState { for child in &mut self.children { @@ -561,6 +576,9 @@ impl Element for Node { } NodeState { child_layout_ids: SmallVec::new(), + interactive_state: previous_element_state + .map(|s| s.interactive_state) + .unwrap_or_default(), } } @@ -570,15 +588,20 @@ impl Element for Node { element_state: &mut Self::ElementState, cx: &mut ViewContext, ) -> crate::LayoutId { - let style = self.interactivity().compute_style(None, cx); - style.with_text_style(cx, |cx| { - element_state.child_layout_ids = self - .children - .iter_mut() - .map(|child| child.layout(view_state, cx)) - .collect::>(); - cx.request_layout(&style, element_state.child_layout_ids.iter().copied()) - }) + let mut interactivity = mem::take(&mut self.interactivity); + let layout_id = + interactivity.layout(&mut element_state.interactive_state, cx, |style, cx| { + cx.with_text_style(style.text_style().cloned(), |cx| { + element_state.child_layout_ids = self + .children + .iter_mut() + .map(|child| child.layout(view_state, cx)) + .collect::>(); + cx.request_layout(&style, element_state.child_layout_ids.iter().copied()) + }) + }); + self.interactivity = interactivity; + layout_id } fn paint( @@ -588,27 +611,10 @@ impl Element for Node { element_state: &mut Self::ElementState, cx: &mut ViewContext, ) { - let style = self.interactivity.compute_style(Some(bounds), cx); - if style.visibility == Visibility::Hidden { - return; - } - - if let Some(mouse_cursor) = style.mouse_cursor { - let hovered = bounds.contains_point(&cx.mouse_position()); - if hovered { - cx.set_cursor_style(mouse_cursor); - } - } - - if let Some(group) = self.interactivity.group.clone() { - GroupBounds::push(group, bounds, cx); - } - - let z_index = style.z_index.unwrap_or(0); + let mut interactivity = mem::take(&mut self.interactivity); let mut child_min = point(Pixels::MAX, Pixels::MAX); let mut child_max = Point::default(); - let content_size = if element_state.child_layout_ids.is_empty() { bounds.size } else { @@ -620,35 +626,41 @@ impl Element for Node { (child_max - child_min).into() }; - let mut interactivity = mem::take(&mut self.interactivity); - interactivity.paint(bounds, cx, |cx| { - cx.with_z_index(z_index, |cx| { - cx.with_z_index(0, |cx| { - style.paint(bounds, cx); - }); - cx.with_z_index(1, |cx| { - style.with_text_style(cx, |cx| { - style.apply_overflow(bounds, cx, |cx| { - let scroll_offset = self.interactivity.scroll_offset; - cx.with_element_offset2(scroll_offset, |cx| { - for child in &mut self.children { - child.paint(view_state, cx); - } - }); + interactivity.paint( + bounds, + content_size, + &mut element_state.interactive_state, + cx, + |style, scroll_offset, cx| { + if style.visibility == Visibility::Hidden { + return; + } + + let z_index = style.z_index.unwrap_or(0); + + cx.with_z_index(z_index, |cx| { + cx.with_z_index(0, |cx| { + style.paint(bounds, cx); + }); + cx.with_z_index(1, |cx| { + cx.with_text_style(style.text_style().cloned(), |cx| { + cx.with_content_mask(style.overflow_mask(bounds), |cx| { + cx.with_element_offset(scroll_offset, |cx| { + for child in &mut self.children { + child.paint(view_state, cx); + } + }) + }) }) }) - }); - }); - }); + }) + }, + ); self.interactivity = interactivity; - - if let Some(group) = self.interactivity.group.as_ref() { - GroupBounds::pop(group, cx); - } } } -pub enum FocusState { +pub enum FocusStatus { /// The current element is not focused, and does not contain or descend from the focused element. None, /// The current element is focused. @@ -660,56 +672,87 @@ pub enum FocusState { } pub struct Interactivity { - pub active: bool, - pub group_active: bool, - pub hovered: bool, - pub group_hovered: bool, - pub focus: FocusState, - pub key_context: KeyContext, - pub focus_handle: Option, - pub scroll_offset: Point, - pub base_style: StyleRefinement, - pub focus_style: StyleRefinement, - pub focus_in_style: StyleRefinement, - pub in_focus_style: StyleRefinement, - pub hover_style: StyleRefinement, - pub group_hover_style: Option, - pub active_style: StyleRefinement, - pub group_active_style: Option, - pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, - pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, - pub group: Option, - pub dispatch_context: KeyContext, - pub mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, - pub mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, - pub mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, - pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>, - pub key_down_listeners: SmallVec<[KeyDownListener; 2]>, - pub key_up_listeners: SmallVec<[KeyUpListener; 2]>, - pub action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, - pub drop_listeners: SmallVec<[(TypeId, Box>); 2]>, - pub click_listeners: SmallVec<[ClickListener; 2]>, - pub drag_listener: Option>, - pub hover_listener: Option>, - pub tooltip_builder: Option>, + active: Option, + group_active: bool, + hovered: bool, + group_hovered: bool, + focus_status: FocusStatus, + key_context: KeyContext, + focus_handle: Option, + scroll_offset: Point, + base_style: StyleRefinement, + focus_style: StyleRefinement, + focus_in_style: StyleRefinement, + in_focus_style: StyleRefinement, + hover_style: StyleRefinement, + group_hover_style: Option, + active_style: StyleRefinement, + group_active_style: Option, + drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>, + group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>, + group: Option, + dispatch_context: KeyContext, + mouse_down_listeners: SmallVec<[MouseDownListener; 2]>, + mouse_up_listeners: SmallVec<[MouseUpListener; 2]>, + mouse_move_listeners: SmallVec<[MouseMoveListener; 2]>, + scroll_wheel_listeners: SmallVec<[ScrollWheelListener; 2]>, + key_down_listeners: SmallVec<[KeyDownListener; 2]>, + key_up_listeners: SmallVec<[KeyUpListener; 2]>, + action_listeners: SmallVec<[(TypeId, ActionListener); 8]>, + drop_listeners: SmallVec<[(TypeId, Box>); 2]>, + click_listeners: SmallVec<[ClickListener; 2]>, + drag_listener: Option>, + hover_listener: Option>, + tooltip_builder: Option>, +} + +#[derive(Default)] +pub struct InteractiveElementState { + clicked_state: Arc>, + hover_state: Arc>, + pending_mouse_down: Arc>>, + scroll_offset: Option>>>, + active_tooltip: Arc>>, +} + +struct ActiveTooltip { + #[allow(unused)] // used to drop the task + waiting: Option>, + tooltip: Option, } -impl Interactivity { +/// Whether or not the element or a group that contains it is clicked by the mouse. +#[derive(Copy, Clone, Default, Eq, PartialEq)] +struct ElementClickedState { + pub group: bool, + pub element: bool, +} + +impl ElementClickedState { + fn is_clicked(&self) -> bool { + self.group || self.element + } +} + +impl Interactivity +where + V: 'static, +{ fn compute_style(&self, bounds: Option>, cx: &mut ViewContext) -> Style { let mut style = Style::default(); style.refine(&self.base_style); - match self.focus { - FocusState::None => {} - FocusState::Focus => { + match self.focus_status { + FocusStatus::None => {} + FocusStatus::Focus => { style.refine(&self.focus_style); style.refine(&self.focus_in_style); style.refine(&self.in_focus_style); } - FocusState::FocusIn => { + FocusStatus::FocusIn => { style.refine(&self.focus_in_style); } - FocusState::InFocus => { + FocusStatus::InFocus => { style.refine(&self.in_focus_style); } } @@ -756,19 +799,66 @@ impl Interactivity { } } - if self.active { + if self.active.is_some() { style.refine(&self.active_style) } style } + fn layout( + &mut self, + element_state: &mut InteractiveElementState, + cx: &mut ViewContext, + f: impl FnOnce(Style, &mut ViewContext) -> LayoutId, + ) -> LayoutId { + let mut style = Style::default(); + style.refine(&self.base_style); + + if let Some(focus_handle) = self.focus_handle.as_ref() { + if focus_handle.contains_focused(cx) { + style.refine(&self.focus_in_style); + } + + if focus_handle.within_focused(cx) { + style.refine(&self.in_focus_style); + } + + if focus_handle.is_focused(cx) { + style.refine(&self.focus_style); + } + } + + let clicked_state = element_state.clicked_state.lock(); + if clicked_state.group { + if let Some(group_style) = self.group_active_style.as_ref() { + style.refine(&group_style.style); + } + } + if clicked_state.element { + style.refine(&self.active_style); + } + + f(style, cx) + } + fn paint( &mut self, bounds: Bounds, + content_size: Size, + element_state: &mut InteractiveElementState, cx: &mut ViewContext, - f: impl FnOnce(&mut ViewContext), + f: impl FnOnce(Style, Point, &mut ViewContext), ) { + let style = self.compute_style(Some(bounds), cx); + + if let Some(mouse_cursor) = style.mouse_cursor { + let hovered = bounds.contains_point(&cx.mouse_position()); + if hovered { + cx.set_cursor_style(mouse_cursor); + } + } + for listener in self.mouse_down_listeners.drain(..) { cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| { listener(state, event, &bounds, phase, cx); @@ -845,22 +935,212 @@ impl Interactivity { }); } + let mut element_state: &mut InteractiveElementState = element_state.as_mut(); + + let click_listeners = mem::take(&mut self.click_listeners); + let drag_listener = mem::take(&mut self.drag_listener); + + 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.clicked_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() = ElementClickedState::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()); + } + }); + } + } + + if let Some(hover_listener) = self.hover_listener.take() { + let was_hovered = element_state.hover_state.clone(); + let has_mouse_down = element_state.pending_mouse_down.clone(); + + cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + let is_hovered = + bounds.contains_point(&event.position) && has_mouse_down.lock().is_none(); + let mut was_hovered = was_hovered.lock(); + + if is_hovered != was_hovered.clone() { + *was_hovered = is_hovered; + drop(was_hovered); + + hover_listener(view_state, is_hovered, cx); + } + }); + } + + if let Some(tooltip_builder) = self.tooltip_builder.take() { + let active_tooltip = element_state.active_tooltip.clone(); + let pending_mouse_down = element_state.pending_mouse_down.clone(); + + cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble { + return; + } + + let is_hovered = + bounds.contains_point(&event.position) && pending_mouse_down.lock().is_none(); + if !is_hovered { + active_tooltip.lock().take(); + return; + } + + if active_tooltip.lock().is_none() { + let task = cx.spawn({ + let active_tooltip = active_tooltip.clone(); + let tooltip_builder = tooltip_builder.clone(); + + move |view, mut cx| async move { + cx.background_executor().timer(TOOLTIP_DELAY).await; + view.update(&mut cx, move |view_state, cx| { + active_tooltip.lock().replace(ActiveTooltip { + waiting: None, + tooltip: Some(AnyTooltip { + view: tooltip_builder(view_state, cx), + cursor_offset: cx.mouse_position() + TOOLTIP_OFFSET, + }), + }); + cx.notify(); + }) + .ok(); + } + }); + active_tooltip.lock().replace(ActiveTooltip { + waiting: Some(task), + tooltip: None, + }); + } + }); + + if let Some(active_tooltip) = element_state.active_tooltip.lock().as_ref() { + if active_tooltip.tooltip.is_some() { + cx.active_tooltip = active_tooltip.tooltip.clone() + } + } + } + + let active_state = element_state.clicked_state.clone(); + if !active_state.lock().is_clicked() { + cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| { + if phase == DispatchPhase::Capture { + *active_state.lock() = ElementClickedState::default(); + cx.notify(); + } + }); + } else { + let active_group_bounds = self + .group_active_style + .as_ref() + .and_then(|group_active| GroupBounds::get(&group_active.group, cx)); + cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| { + if phase == DispatchPhase::Bubble { + let group = active_group_bounds + .map_or(false, |bounds| bounds.contains_point(&down.position)); + let element = bounds.contains_point(&down.position); + if group || element { + *active_state.lock() = ElementClickedState { group, element }; + cx.notify(); + } + } + }); + } + + let overflow = style.overflow; + if overflow.x == Overflow::Scroll || overflow.y == Overflow::Scroll { + let scroll_offset = element_state + .scroll_offset + .get_or_insert_with(Arc::default) + .clone(); + let line_height = cx.line_height(); + let scroll_max = (content_size - bounds.size).max(&Size::default()); + + cx.on_mouse_event(move |_, event: &ScrollWheelEvent, phase, cx| { + if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { + let mut scroll_offset = scroll_offset.lock(); + let old_scroll_offset = *scroll_offset; + let delta = event.delta.pixel_delta(line_height); + + if overflow.x == Overflow::Scroll { + scroll_offset.x = + (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(-scroll_max.height, px(0.)); + } + + if *scroll_offset != old_scroll_offset { + cx.notify(); + cx.stop_propagation(); + } + } + }); + } + + if let Some(group) = self.group.clone() { + GroupBounds::push(group, bounds, cx); + } + cx.with_key_dispatch( self.key_context.clone(), self.focus_handle.clone(), - |_, cx| f(cx), + |_, cx| f(style, self.scroll_offset, cx), ); + + if let Some(group) = self.group.as_ref() { + GroupBounds::pop(group, cx); + } } } impl Default for Interactivity { fn default() -> Self { Self { - active: false, + active: None, group_active: false, hovered: false, group_hovered: false, - focus: FocusState::None, + focus_status: FocusStatus::None, key_context: KeyContext::default(), focus_handle: None, scroll_offset: Point::default(), @@ -937,26 +1217,80 @@ impl FocusableComponent for Focusable { } } -impl> InteractiveComponent for Focusable { +impl InteractiveComponent for Focusable +where + V: 'static, + E: InteractiveComponent, +{ fn interactivity(&mut self) -> &mut Interactivity { self.element.interactivity() } } -impl> StatefulInteractiveComponent +impl> StatefulInteractiveComponent for Focusable { } +impl Element for Focusable +where + V: 'static, + E: Element, +{ + type ElementState = E::ElementState; + + fn id(&self) -> Option { + todo!() + } + + fn initialize( + &mut self, + view_state: &mut V, + element_state: Option, + cx: &mut ViewContext, + ) -> Self::ElementState { + todo!() + } + + fn layout( + &mut self, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + todo!() + } + + fn paint( + &mut self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + todo!() + } +} + pub struct Stateful { id: SharedString, view_type: PhantomData, element: E, } -impl> StatefulInteractiveComponent for Stateful {} +impl StatefulInteractiveComponent for Stateful +where + V: 'static, + E: Element, + Self: InteractiveComponent, +{ +} -impl> InteractiveComponent for Stateful { +impl InteractiveComponent for Stateful +where + V: 'static, + E: InteractiveComponent, +{ fn interactivity(&mut self) -> &mut Interactivity { self.element.interactivity() } @@ -967,3 +1301,43 @@ impl> FocusableComponent for Stateful { self.element.focusability() } } + +impl Element for Stateful +where + V: 'static, + E: Element, +{ + type ElementState = InteractiveElementState; + + fn id(&self) -> Option { + todo!() + } + + fn initialize( + &mut self, + view_state: &mut V, + element_state: Option, + cx: &mut ViewContext, + ) -> Self::ElementState { + todo!() + } + + fn layout( + &mut self, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) -> LayoutId { + todo!() + } + + fn paint( + &mut self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + todo!() + } +} diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 87de7998a88f9223386b05308871585eeb779040..323ca5851ba09981a68e65d4ae56171326494429 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -148,7 +148,7 @@ pub enum GlobalKey { } pub trait BorrowAppContext { - fn with_text_style(&mut self, style: TextStyleRefinement, f: F) -> R + fn with_text_style(&mut self, style: Option, f: F) -> R where F: FnOnce(&mut Self) -> R; @@ -159,14 +159,18 @@ impl BorrowAppContext for C where C: BorrowMut, { - fn with_text_style(&mut self, style: TextStyleRefinement, f: F) -> R + fn with_text_style(&mut self, style: Option, f: F) -> R where F: FnOnce(&mut Self) -> R, { - self.borrow_mut().push_text_style(style); - let result = f(self); - self.borrow_mut().pop_text_style(); - result + if let Some(style) = style { + self.borrow_mut().push_text_style(style); + let result = f(self); + self.borrow_mut().pop_text_style(); + result + } else { + f(self) + } } fn set_global(&mut self, global: G) { diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 18b92c0b8b7ae8f0bfe83a7f45d56e5b92fe5e51..92959ef05795d441d395c6445f6ba0af0f745c9c 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -2,7 +2,7 @@ use crate::{ black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, CornersRefinement, CursorStyle, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Result, - Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext, + Rgba, SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, }; use refineable::{Cascade, Refineable}; use smallvec::SmallVec; @@ -220,7 +220,7 @@ pub struct HighlightStyle { impl Eq for HighlightStyle {} impl Style { - pub fn text_style(&self, _cx: &WindowContext) -> Option<&TextStyleRefinement> { + pub fn text_style(&self) -> Option<&TextStyleRefinement> { if self.text.is_some() { Some(&self.text) } else { @@ -228,13 +228,47 @@ impl Style { } } - pub fn with_text_style(&self, cx: &mut C, f: F) -> R + pub fn overflow_mask(&self, bounds: Bounds) -> Option> { + match self.overflow { + Point { + x: Overflow::Visible, + y: Overflow::Visible, + } => None, + _ => { + let current_mask = bounds; + let min = current_mask.origin; + let max = current_mask.lower_right(); + let bounds = match ( + self.overflow.x == Overflow::Visible, + self.overflow.y == Overflow::Visible, + ) { + // x and y both visible + (true, true) => return None, + // x visible, y hidden + (true, false) => Bounds::from_corners( + point(min.x, bounds.origin.y), + point(max.x, bounds.lower_right().y), + ), + // x hidden, y visible + (false, true) => Bounds::from_corners( + point(bounds.origin.x, min.y), + point(bounds.lower_right().x, max.y), + ), + // both hidden + (false, false) => bounds, + }; + Some(ContentMask { bounds }) + } + } + } + + pub fn apply_text_style(&self, cx: &mut C, f: F) -> R where C: BorrowAppContext, F: FnOnce(&mut C) -> R, { if self.text.is_some() { - cx.with_text_style(self.text.clone(), f) + cx.with_text_style(Some(self.text.clone()), f) } else { f(cx) } @@ -274,7 +308,7 @@ impl Style { bounds: mask_bounds, }; - cx.with_content_mask(mask, f) + cx.with_content_mask(Some(mask), f) } /// Paints the background of an element styled with this style. diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 52c5464e4fe77b5a76a0de922208744e06c5be00..edda71f13fe28136031479f89f1faad10c2ed10f 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1067,7 +1067,7 @@ impl<'a> WindowContext<'a> { if let Some(active_drag) = self.app.active_drag.take() { self.with_z_index(1, |cx| { let offset = cx.mouse_position() - active_drag.cursor_offset; - cx.with_element_offset(Some(offset), |cx| { + cx.with_element_offset(offset, |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); active_drag.view.draw(available_space, cx); @@ -1076,7 +1076,7 @@ impl<'a> WindowContext<'a> { }); } else if let Some(active_tooltip) = self.app.active_tooltip.take() { self.with_z_index(1, |cx| { - cx.with_element_offset(Some(active_tooltip.cursor_offset), |cx| { + cx.with_element_offset(active_tooltip.cursor_offset, |cx| { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); active_tooltip.view.draw(available_space, cx); @@ -1553,43 +1553,26 @@ pub trait BorrowWindow: BorrowMut + BorrowMut { /// with the current mask. fn with_content_mask( &mut self, - mask: ContentMask, + mask: Option>, f: impl FnOnce(&mut Self) -> R, ) -> R { - let mask = mask.intersect(&self.content_mask()); - self.window_mut() - .current_frame - .content_mask_stack - .push(mask); - let result = f(self); - self.window_mut().current_frame.content_mask_stack.pop(); - result + if let Some(mask) = mask { + let mask = mask.intersect(&self.content_mask()); + self.window_mut() + .current_frame + .content_mask_stack + .push(mask); + let result = f(self); + self.window_mut().current_frame.content_mask_stack.pop(); + result + } else { + f(self) + } } /// Update the global element offset based on the given offset. This is used to implement /// scrolling and position drag handles. fn with_element_offset( - &mut self, - offset: Option>, - f: impl FnOnce(&mut Self) -> R, - ) -> R { - let Some(offset) = offset else { - return f(self); - }; - - let offset = self.element_offset() + offset; - self.window_mut() - .current_frame - .element_offset_stack - .push(offset); - let result = f(self); - self.window_mut().current_frame.element_offset_stack.pop(); - result - } - - /// Update the global element offset based on the given offset. This is used to implement - /// scrolling and position drag handles. - fn with_element_offset2( &mut self, offset: Point, f: impl FnOnce(&mut Self) -> R,