Merge branch 'gpui2-drag-drop' into zed2
This commit is contained in:
commit
192b3512fd
10 changed files with 395 additions and 127 deletions
|
@ -9,11 +9,11 @@ use refineable::Refineable;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
current_platform, image_cache::ImageCache, Action, AppMetadata, AssetSource, Context,
|
current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource,
|
||||||
DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap,
|
Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId, KeyBinding,
|
||||||
LayoutId, MainThread, MainThreadOnly, Platform, SharedString, SubscriberSet, Subscription,
|
Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, SharedString,
|
||||||
SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext,
|
SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem,
|
||||||
WindowHandle, WindowId,
|
View, Window, WindowContext, WindowHandle, WindowId,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use collections::{HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque};
|
||||||
|
@ -93,6 +93,7 @@ impl App {
|
||||||
quit_observers: SubscriberSet::new(),
|
quit_observers: SubscriberSet::new(),
|
||||||
layout_id_buffer: Default::default(),
|
layout_id_buffer: Default::default(),
|
||||||
propagate_event: true,
|
propagate_event: true,
|
||||||
|
active_drag: None,
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -171,6 +172,7 @@ pub struct AppContext {
|
||||||
text_system: Arc<TextSystem>,
|
text_system: Arc<TextSystem>,
|
||||||
flushing_effects: bool,
|
flushing_effects: bool,
|
||||||
pending_updates: usize,
|
pending_updates: usize,
|
||||||
|
pub(crate) active_drag: Option<AnyDrag>,
|
||||||
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
|
||||||
pub(crate) executor: Executor,
|
pub(crate) executor: Executor,
|
||||||
pub(crate) svg_renderer: SvgRenderer,
|
pub(crate) svg_renderer: SvgRenderer,
|
||||||
|
@ -758,6 +760,13 @@ pub(crate) enum Effect {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct AnyDrag {
|
||||||
|
pub drag_handle_view: AnyView,
|
||||||
|
pub cursor_offset: Point<Pixels>,
|
||||||
|
pub state: AnyBox,
|
||||||
|
pub state_type: TypeId,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::AppContext;
|
use super::AppContext;
|
||||||
|
|
|
@ -155,6 +155,15 @@ pub fn white() -> Hsla {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn red() -> Hsla {
|
||||||
|
Hsla {
|
||||||
|
h: 0.,
|
||||||
|
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 {
|
||||||
|
|
|
@ -298,7 +298,7 @@ where
|
||||||
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();
|
||||||
cx.with_scroll_offset(scroll_offset, |cx| {
|
cx.with_element_offset(scroll_offset, |cx| {
|
||||||
for child in &mut this.children {
|
for child in &mut this.children {
|
||||||
child.paint(view_state, cx);
|
child.paint(view_state, cx);
|
||||||
}
|
}
|
||||||
|
@ -354,7 +354,7 @@ where
|
||||||
F: ElementFocus<V>,
|
F: ElementFocus<V>,
|
||||||
V: 'static + Send + Sync,
|
V: 'static + Send + Sync,
|
||||||
{
|
{
|
||||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
|
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
|
||||||
self.interaction.as_stateless_mut()
|
self.interaction.as_stateless_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -364,7 +364,7 @@ where
|
||||||
F: ElementFocus<V>,
|
F: ElementFocus<V>,
|
||||||
V: 'static + Send + Sync,
|
V: 'static + Send + Sync,
|
||||||
{
|
{
|
||||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
|
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
|
||||||
&mut self.interaction
|
&mut self.interaction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,8 +156,8 @@ where
|
||||||
I: ElementInteraction<V>,
|
I: ElementInteraction<V>,
|
||||||
F: ElementFocus<V>,
|
F: ElementFocus<V>,
|
||||||
{
|
{
|
||||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
|
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
|
||||||
self.base.stateless_interactivity()
|
self.base.stateless_interaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,8 +166,8 @@ where
|
||||||
V: 'static + Send + Sync,
|
V: 'static + Send + Sync,
|
||||||
F: ElementFocus<V>,
|
F: ElementFocus<V>,
|
||||||
{
|
{
|
||||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
|
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
|
||||||
self.base.stateful_interactivity()
|
self.base.stateful_interaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,8 +130,8 @@ where
|
||||||
I: ElementInteraction<V>,
|
I: ElementInteraction<V>,
|
||||||
F: ElementFocus<V>,
|
F: ElementFocus<V>,
|
||||||
{
|
{
|
||||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<V> {
|
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<V> {
|
||||||
self.base.stateless_interactivity()
|
self.base.stateless_interaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,8 +140,8 @@ where
|
||||||
V: 'static + Send + Sync,
|
V: 'static + Send + Sync,
|
||||||
F: ElementFocus<V>,
|
F: ElementFocus<V>,
|
||||||
{
|
{
|
||||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
|
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState> {
|
||||||
self.base.stateful_interactivity()
|
self.base.stateful_interaction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,10 @@ impl Point<Pixels> {
|
||||||
y: self.y.scale(factor),
|
y: self.y.scale(factor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn magnitude(&self) -> f64 {
|
||||||
|
((self.x.0.powi(2) + self.y.0.powi(2)) as f64).sqrt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Rhs> Mul<Rhs> for Point<T>
|
impl<T, Rhs> Mul<Rhs> for Point<T>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
point, px, Action, AppContext, BorrowWindow, Bounds, DispatchContext, DispatchPhase, Element,
|
point, px, view, Action, AnyBox, AnyDrag, AppContext, BorrowWindow, Bounds, DispatchContext,
|
||||||
ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow, Pixels, Point, SharedString,
|
DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke, Modifiers, Overflow,
|
||||||
Size, Style, StyleRefinement, ViewContext,
|
Pixels, Point, SharedString, Size, Style, StyleRefinement, ViewContext,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
@ -11,18 +11,21 @@ use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
|
marker::PhantomData,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DRAG_THRESHOLD: f64 = 2.;
|
||||||
|
|
||||||
pub trait StatelessInteractive: Element {
|
pub trait StatelessInteractive: Element {
|
||||||
fn stateless_interactivity(&mut self) -> &mut StatelessInteraction<Self::ViewState>;
|
fn stateless_interaction(&mut self) -> &mut StatelessInteraction<Self::ViewState>;
|
||||||
|
|
||||||
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_interactivity().hover_style = f(StyleRefinement::default());
|
self.stateless_interaction().hover_style = f(StyleRefinement::default());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +37,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().group_hover_style = Some(GroupStyle {
|
self.stateless_interaction().group_hover_style = Some(GroupStyle {
|
||||||
group: group_name.into(),
|
group: group_name.into(),
|
||||||
style: f(StyleRefinement::default()),
|
style: f(StyleRefinement::default()),
|
||||||
});
|
});
|
||||||
|
@ -52,7 +55,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity()
|
self.stateless_interaction()
|
||||||
.mouse_down_listeners
|
.mouse_down_listeners
|
||||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble
|
if phase == DispatchPhase::Bubble
|
||||||
|
@ -76,7 +79,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity()
|
self.stateless_interaction()
|
||||||
.mouse_up_listeners
|
.mouse_up_listeners
|
||||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble
|
if phase == DispatchPhase::Bubble
|
||||||
|
@ -100,7 +103,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity()
|
self.stateless_interaction()
|
||||||
.mouse_down_listeners
|
.mouse_down_listeners
|
||||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Capture
|
if phase == DispatchPhase::Capture
|
||||||
|
@ -124,7 +127,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity()
|
self.stateless_interaction()
|
||||||
.mouse_up_listeners
|
.mouse_up_listeners
|
||||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
||||||
if phase == DispatchPhase::Capture
|
if phase == DispatchPhase::Capture
|
||||||
|
@ -147,7 +150,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity()
|
self.stateless_interaction()
|
||||||
.mouse_move_listeners
|
.mouse_move_listeners
|
||||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
.push(Arc::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) {
|
||||||
|
@ -167,7 +170,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity()
|
self.stateless_interaction()
|
||||||
.scroll_wheel_listeners
|
.scroll_wheel_listeners
|
||||||
.push(Arc::new(move |view, event, bounds, phase, cx| {
|
.push(Arc::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) {
|
||||||
|
@ -183,7 +186,7 @@ pub trait StatelessInteractive: Element {
|
||||||
C: TryInto<DispatchContext>,
|
C: TryInto<DispatchContext>,
|
||||||
C::Error: Debug,
|
C::Error: Debug,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().dispatch_context =
|
self.stateless_interaction().dispatch_context =
|
||||||
context.try_into().expect("invalid dispatch context");
|
context.try_into().expect("invalid dispatch context");
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -198,7 +201,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().key_listeners.push((
|
self.stateless_interaction().key_listeners.push((
|
||||||
TypeId::of::<A>(),
|
TypeId::of::<A>(),
|
||||||
Arc::new(move |view, event, _, phase, cx| {
|
Arc::new(move |view, event, _, phase, cx| {
|
||||||
let event = event.downcast_ref().unwrap();
|
let event = event.downcast_ref().unwrap();
|
||||||
|
@ -223,7 +226,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().key_listeners.push((
|
self.stateless_interaction().key_listeners.push((
|
||||||
TypeId::of::<KeyDownEvent>(),
|
TypeId::of::<KeyDownEvent>(),
|
||||||
Arc::new(move |view, event, _, phase, cx| {
|
Arc::new(move |view, event, _, phase, cx| {
|
||||||
let event = event.downcast_ref().unwrap();
|
let event = event.downcast_ref().unwrap();
|
||||||
|
@ -244,7 +247,7 @@ pub trait StatelessInteractive: Element {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateless_interactivity().key_listeners.push((
|
self.stateless_interaction().key_listeners.push((
|
||||||
TypeId::of::<KeyUpEvent>(),
|
TypeId::of::<KeyUpEvent>(),
|
||||||
Arc::new(move |view, event, _, phase, cx| {
|
Arc::new(move |view, event, _, phase, cx| {
|
||||||
let event = event.downcast_ref().unwrap();
|
let event = event.downcast_ref().unwrap();
|
||||||
|
@ -254,16 +257,63 @@ pub trait StatelessInteractive: Element {
|
||||||
));
|
));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.stateless_interaction()
|
||||||
|
.drag_over_styles
|
||||||
|
.push((TypeId::of::<S>(), f(StyleRefinement::default())));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_drag_over<S: 'static>(
|
||||||
|
mut self,
|
||||||
|
group_name: impl Into<SharedString>,
|
||||||
|
f: impl FnOnce(StyleRefinement) -> StyleRefinement,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.stateless_interaction().group_drag_over_styles.push((
|
||||||
|
TypeId::of::<S>(),
|
||||||
|
GroupStyle {
|
||||||
|
group: group_name.into(),
|
||||||
|
style: f(StyleRefinement::default()),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_drop<S: 'static>(
|
||||||
|
mut self,
|
||||||
|
listener: impl Fn(&mut Self::ViewState, S, &mut ViewContext<Self::ViewState>)
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.stateless_interaction().drop_listeners.push((
|
||||||
|
TypeId::of::<S>(),
|
||||||
|
Arc::new(move |view, drag_state, cx| {
|
||||||
|
listener(view, *drag_state.downcast().unwrap(), cx);
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait StatefulInteractive: StatelessInteractive {
|
pub trait StatefulInteractive: StatelessInteractive {
|
||||||
fn stateful_interactivity(&mut self) -> &mut StatefulInteraction<Self::ViewState>;
|
fn stateful_interaction(&mut self) -> &mut StatefulInteraction<Self::ViewState>;
|
||||||
|
|
||||||
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_interactivity().active_style = f(StyleRefinement::default());
|
self.stateful_interaction().active_style = f(StyleRefinement::default());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +325,7 @@ pub trait StatefulInteractive: StatelessInteractive {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateful_interactivity().group_active_style = Some(GroupStyle {
|
self.stateful_interaction().group_active_style = Some(GroupStyle {
|
||||||
group: group_name.into(),
|
group: group_name.into(),
|
||||||
style: f(StyleRefinement::default()),
|
style: f(StyleRefinement::default()),
|
||||||
});
|
});
|
||||||
|
@ -284,7 +334,7 @@ pub trait StatefulInteractive: StatelessInteractive {
|
||||||
|
|
||||||
fn on_click(
|
fn on_click(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(&mut Self::ViewState, &MouseClickEvent, &mut ViewContext<Self::ViewState>)
|
listener: impl Fn(&mut Self::ViewState, &ClickEvent, &mut ViewContext<Self::ViewState>)
|
||||||
+ Send
|
+ Send
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
|
@ -292,9 +342,47 @@ pub trait StatefulInteractive: StatelessInteractive {
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
self.stateful_interactivity()
|
self.stateful_interaction()
|
||||||
.mouse_click_listeners
|
.click_listeners
|
||||||
.push(Arc::new(move |view, event, cx| handler(view, event, cx)));
|
.push(Arc::new(move |view, event, cx| listener(view, event, cx)));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_drag<S, R, E>(
|
||||||
|
mut self,
|
||||||
|
listener: impl Fn(
|
||||||
|
&mut Self::ViewState,
|
||||||
|
&mut ViewContext<Self::ViewState>,
|
||||||
|
) -> Drag<S, R, Self::ViewState, E>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
S: 'static + Send + Sync,
|
||||||
|
R: 'static + Fn(&mut Self::ViewState, &mut ViewContext<Self::ViewState>) -> E + Send + Sync,
|
||||||
|
E: Element<ViewState = Self::ViewState>,
|
||||||
|
{
|
||||||
|
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::<S>(),
|
||||||
|
}
|
||||||
|
}));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -359,6 +447,26 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
|
||||||
style.refine(&stateless.hover_style);
|
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() {
|
if let Some(stateful) = self.as_stateful() {
|
||||||
let active_state = element_state.active_state.lock();
|
let active_state = element_state.active_state.lock();
|
||||||
if active_state.group {
|
if active_state.group {
|
||||||
|
@ -411,38 +519,104 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
|
||||||
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
|
.and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
|
||||||
|
|
||||||
if let Some(group_bounds) = hover_group_bounds {
|
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() {
|
if stateless.hover_style.is_some()
|
||||||
paint_hover_listener(bounds, cx);
|
|| (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 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(stateful) = self.as_stateful() {
|
if let Some(stateful) = self.as_stateful() {
|
||||||
let click_listeners = stateful.mouse_click_listeners.clone();
|
let click_listeners = stateful.click_listeners.clone();
|
||||||
|
let drag_listener = stateful.drag_listener.clone();
|
||||||
|
|
||||||
let pending_click = element_state.pending_click.clone();
|
if !click_listeners.is_empty() || drag_listener.is_some() {
|
||||||
let mouse_down = pending_click.lock().clone();
|
let pending_mouse_down = element_state.pending_mouse_down.clone();
|
||||||
if let Some(mouse_down) = mouse_down {
|
let mouse_down = pending_mouse_down.lock().clone();
|
||||||
cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
|
if let Some(mouse_down) = mouse_down {
|
||||||
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
|
if let Some(drag_listener) = drag_listener {
|
||||||
let mouse_click = MouseClickEvent {
|
let active_state = element_state.active_state.clone();
|
||||||
down: mouse_down.clone(),
|
|
||||||
up: event.clone(),
|
cx.on_mouse_event(move |view_state, event: &MouseMoveEvent, phase, cx| {
|
||||||
};
|
if cx.active_drag.is_some() {
|
||||||
for listener in &click_listeners {
|
if phase == DispatchPhase::Capture {
|
||||||
listener(state, &mouse_click, cx);
|
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;
|
||||||
|
});
|
||||||
*pending_click.lock() = None;
|
} else {
|
||||||
});
|
cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
|
||||||
} else {
|
if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position)
|
||||||
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());
|
||||||
*pending_click.lock() = Some(event.clone());
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_state = element_state.active_state.clone();
|
let active_state = element_state.active_state.clone();
|
||||||
|
@ -487,12 +661,12 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
|
||||||
|
|
||||||
if overflow.x == Overflow::Scroll {
|
if overflow.x == Overflow::Scroll {
|
||||||
scroll_offset.x =
|
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 {
|
if overflow.y == Overflow::Scroll {
|
||||||
scroll_offset.y =
|
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 {
|
if *scroll_offset != old_scroll_offset {
|
||||||
|
@ -506,29 +680,16 @@ pub trait ElementInteraction<V: 'static + Send + Sync>: 'static + Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_hover_listener<V>(bounds: Bounds<Pixels>, cx: &mut ViewContext<V>)
|
|
||||||
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)]
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct StatefulInteraction<V: 'static + Send + Sync> {
|
pub struct StatefulInteraction<V: 'static + Send + Sync> {
|
||||||
pub id: ElementId,
|
pub id: ElementId,
|
||||||
#[deref]
|
#[deref]
|
||||||
#[deref_mut]
|
#[deref_mut]
|
||||||
stateless: StatelessInteraction<V>,
|
stateless: StatelessInteraction<V>,
|
||||||
pub mouse_click_listeners: SmallVec<[MouseClickListener<V>; 2]>,
|
click_listeners: SmallVec<[ClickListener<V>; 2]>,
|
||||||
pub active_style: StyleRefinement,
|
active_style: StyleRefinement,
|
||||||
pub group_active_style: Option<GroupStyle>,
|
group_active_style: Option<GroupStyle>,
|
||||||
|
drag_listener: Option<DragListener<V>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> ElementInteraction<V> for StatefulInteraction<V>
|
impl<V> ElementInteraction<V> for StatefulInteraction<V>
|
||||||
|
@ -560,13 +721,16 @@ where
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
stateless: StatelessInteraction::default(),
|
stateless: StatelessInteraction::default(),
|
||||||
mouse_click_listeners: SmallVec::new(),
|
click_listeners: SmallVec::new(),
|
||||||
|
drag_listener: None,
|
||||||
active_style: StyleRefinement::default(),
|
active_style: StyleRefinement::default(),
|
||||||
group_active_style: None,
|
group_active_style: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DropListener<V> = dyn Fn(&mut V, AnyBox, &mut ViewContext<V>) + Send + Sync;
|
||||||
|
|
||||||
pub struct StatelessInteraction<V> {
|
pub struct StatelessInteraction<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]>,
|
||||||
|
@ -576,6 +740,9 @@ pub struct StatelessInteraction<V> {
|
||||||
pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
|
pub key_listeners: SmallVec<[(TypeId, KeyListener<V>); 32]>,
|
||||||
pub hover_style: StyleRefinement,
|
pub hover_style: StyleRefinement,
|
||||||
pub group_hover_style: Option<GroupStyle>,
|
pub group_hover_style: Option<GroupStyle>,
|
||||||
|
drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
|
||||||
|
group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
|
||||||
|
drop_listeners: SmallVec<[(TypeId, Arc<DropListener<V>>); 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> StatelessInteraction<V>
|
impl<V> StatelessInteraction<V>
|
||||||
|
@ -586,7 +753,8 @@ where
|
||||||
StatefulInteraction {
|
StatefulInteraction {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
stateless: self,
|
stateless: self,
|
||||||
mouse_click_listeners: SmallVec::new(),
|
click_listeners: SmallVec::new(),
|
||||||
|
drag_listener: None,
|
||||||
active_style: StyleRefinement::default(),
|
active_style: StyleRefinement::default(),
|
||||||
group_active_style: None,
|
group_active_style: None,
|
||||||
}
|
}
|
||||||
|
@ -642,7 +810,7 @@ impl ActiveState {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct InteractiveElementState {
|
pub struct InteractiveElementState {
|
||||||
active_state: Arc<Mutex<ActiveState>>,
|
active_state: Arc<Mutex<ActiveState>>,
|
||||||
pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
|
pending_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
|
||||||
scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
|
scroll_offset: Option<Arc<Mutex<Point<Pixels>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -665,6 +833,9 @@ impl<V> Default for StatelessInteraction<V> {
|
||||||
key_listeners: SmallVec::new(),
|
key_listeners: SmallVec::new(),
|
||||||
hover_style: StyleRefinement::default(),
|
hover_style: StyleRefinement::default(),
|
||||||
group_hover_style: None,
|
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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct MouseClickEvent {
|
pub struct ClickEvent {
|
||||||
pub down: MouseDownEvent,
|
pub down: MouseDownEvent,
|
||||||
pub up: MouseUpEvent,
|
pub up: MouseUpEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Drag<S, R, V, E>
|
||||||
|
where
|
||||||
|
S: 'static + Send + Sync,
|
||||||
|
R: Fn(&mut V, &mut ViewContext<V>) -> E,
|
||||||
|
V: 'static + Send + Sync,
|
||||||
|
E: Element<ViewState = V>,
|
||||||
|
{
|
||||||
|
pub state: S,
|
||||||
|
pub render_drag_handle: R,
|
||||||
|
view_type: PhantomData<V>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, R, V, E> Drag<S, R, V, E>
|
||||||
|
where
|
||||||
|
S: 'static + Send + Sync,
|
||||||
|
R: Fn(&mut V, &mut ViewContext<V>) -> E + Send + Sync,
|
||||||
|
V: 'static + Send + Sync,
|
||||||
|
E: Element<ViewState = V>,
|
||||||
|
{
|
||||||
|
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)]
|
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
pub enum MouseButton {
|
pub enum MouseButton {
|
||||||
Left,
|
Left,
|
||||||
|
@ -919,8 +1118,6 @@ pub type MouseUpListener<V> = Arc<
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
>;
|
>;
|
||||||
pub type MouseClickListener<V> =
|
|
||||||
Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
|
|
||||||
|
|
||||||
pub type MouseMoveListener<V> = Arc<
|
pub type MouseMoveListener<V> = Arc<
|
||||||
dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
|
||||||
|
@ -936,6 +1133,12 @@ pub type ScrollWheelListener<V> = Arc<
|
||||||
+ 'static,
|
+ 'static,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
pub type ClickListener<V> =
|
||||||
|
Arc<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
pub(crate) type DragListener<V> =
|
||||||
|
Arc<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + Send + Sync + 'static>;
|
||||||
|
|
||||||
pub type KeyListener<V> = Arc<
|
pub type KeyListener<V> = Arc<
|
||||||
dyn Fn(
|
dyn Fn(
|
||||||
&mut V,
|
&mut V,
|
||||||
|
|
|
@ -129,7 +129,7 @@ impl TaffyLayoutEngine {
|
||||||
self.taffy
|
self.taffy
|
||||||
.compute_layout(id.into(), available_space.into())
|
.compute_layout(id.into(), available_space.into())
|
||||||
.expect(EXPECT_MESSAGE);
|
.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<Pixels> {
|
pub fn layout_bounds(&mut self, id: LayoutId) -> Bounds<Pixels> {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
|
||||||
BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext,
|
BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext,
|
||||||
DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId,
|
DisplayId, Edges, Effect, Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId,
|
||||||
GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher,
|
GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher,
|
||||||
Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path,
|
Keystroke, LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent,
|
||||||
Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference,
|
MouseUpEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite,
|
||||||
RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow,
|
Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
|
||||||
SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
|
SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task,
|
||||||
WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
|
Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -159,7 +159,7 @@ pub struct Window {
|
||||||
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
|
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
|
||||||
z_index_stack: StackingOrder,
|
z_index_stack: StackingOrder,
|
||||||
content_mask_stack: Vec<ContentMask<Pixels>>,
|
content_mask_stack: Vec<ContentMask<Pixels>>,
|
||||||
scroll_offset_stack: Vec<Point<Pixels>>,
|
element_offset_stack: Vec<Point<Pixels>>,
|
||||||
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
|
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
|
||||||
key_dispatch_stack: Vec<KeyDispatchStackFrame>,
|
key_dispatch_stack: Vec<KeyDispatchStackFrame>,
|
||||||
freeze_key_dispatch_stack: bool,
|
freeze_key_dispatch_stack: bool,
|
||||||
|
@ -177,7 +177,7 @@ pub struct Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
pub fn new(
|
pub(crate) fn new(
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
options: WindowOptions,
|
options: WindowOptions,
|
||||||
cx: &mut MainThread<AppContext>,
|
cx: &mut MainThread<AppContext>,
|
||||||
|
@ -234,7 +234,7 @@ impl Window {
|
||||||
key_matchers: HashMap::default(),
|
key_matchers: HashMap::default(),
|
||||||
z_index_stack: StackingOrder(SmallVec::new()),
|
z_index_stack: StackingOrder(SmallVec::new()),
|
||||||
content_mask_stack: Vec::new(),
|
content_mask_stack: Vec::new(),
|
||||||
scroll_offset_stack: Vec::new(),
|
element_offset_stack: Vec::new(),
|
||||||
mouse_listeners: HashMap::default(),
|
mouse_listeners: HashMap::default(),
|
||||||
key_dispatch_stack: Vec::new(),
|
key_dispatch_stack: Vec::new(),
|
||||||
freeze_key_dispatch_stack: false,
|
freeze_key_dispatch_stack: false,
|
||||||
|
@ -469,7 +469,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
.layout_engine
|
.layout_engine
|
||||||
.layout_bounds(layout_id)
|
.layout_bounds(layout_id)
|
||||||
.map(Into::into);
|
.map(Into::into);
|
||||||
bounds.origin -= self.scroll_offset();
|
bounds.origin += self.element_offset();
|
||||||
bounds
|
bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -805,14 +805,22 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
|
|
||||||
let mut root_view = cx.window.root_view.take().unwrap();
|
let mut root_view = cx.window.root_view.take().unwrap();
|
||||||
|
|
||||||
if let Some(element_id) = root_view.id() {
|
cx.stack(0, |cx| {
|
||||||
cx.with_element_state(element_id, |element_state, cx| {
|
let available_space = cx.window.content_size.map(Into::into);
|
||||||
let element_state = draw_with_element_state(&mut root_view, element_state, cx);
|
draw_any_view(&mut root_view, available_space, cx);
|
||||||
((), element_state)
|
});
|
||||||
|
|
||||||
|
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);
|
cx.window.root_view = Some(root_view);
|
||||||
let scene = cx.window.scene_builder.build();
|
let scene = cx.window.scene_builder.build();
|
||||||
|
@ -827,20 +835,21 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
.detach();
|
.detach();
|
||||||
});
|
});
|
||||||
|
|
||||||
fn draw_with_element_state(
|
fn draw_any_view(
|
||||||
root_view: &mut AnyView,
|
view: &mut AnyView,
|
||||||
element_state: Option<AnyBox>,
|
available_space: Size<AvailableSpace>,
|
||||||
cx: &mut ViewContext<()>,
|
cx: &mut ViewContext<()>,
|
||||||
) -> AnyBox {
|
) {
|
||||||
let mut element_state = root_view.initialize(&mut (), element_state, cx);
|
cx.with_optional_element_state(view.id(), |element_state, cx| {
|
||||||
let layout_id = root_view.layout(&mut (), &mut element_state, cx);
|
let mut element_state = view.initialize(&mut (), element_state, cx);
|
||||||
let available_space = cx.window.content_size.map(Into::into);
|
let layout_id = view.layout(&mut (), &mut element_state, cx);
|
||||||
cx.window
|
cx.window
|
||||||
.layout_engine
|
.layout_engine
|
||||||
.compute_layout(layout_id, available_space);
|
.compute_layout(layout_id, available_space);
|
||||||
let bounds = cx.window.layout_engine.layout_bounds(layout_id);
|
let bounds = cx.window.layout_engine.layout_bounds(layout_id);
|
||||||
root_view.paint(bounds, &mut (), &mut element_state, cx);
|
view.paint(bounds, &mut (), &mut element_state, cx);
|
||||||
element_state
|
((), element_state)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -916,6 +925,12 @@ impl<'a, 'w> WindowContext<'a, 'w> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.app.propagate_event
|
||||||
|
&& any_mouse_event.downcast_ref::<MouseUpEvent>().is_some()
|
||||||
|
{
|
||||||
|
self.active_drag = None;
|
||||||
|
}
|
||||||
|
|
||||||
// Just in case any handlers added new handlers, which is weird, but possible.
|
// Just in case any handlers added new handlers, which is weird, but possible.
|
||||||
handlers.extend(
|
handlers.extend(
|
||||||
self.window
|
self.window
|
||||||
|
@ -1206,7 +1221,7 @@ pub trait BorrowWindow: BorrowAppContext {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_scroll_offset<R>(
|
fn with_element_offset<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: Option<Point<Pixels>>,
|
offset: Option<Point<Pixels>>,
|
||||||
f: impl FnOnce(&mut Self) -> R,
|
f: impl FnOnce(&mut Self) -> R,
|
||||||
|
@ -1215,16 +1230,16 @@ pub trait BorrowWindow: BorrowAppContext {
|
||||||
return f(self);
|
return f(self);
|
||||||
};
|
};
|
||||||
|
|
||||||
let offset = self.scroll_offset() + offset;
|
let offset = self.element_offset() + offset;
|
||||||
self.window_mut().scroll_offset_stack.push(offset);
|
self.window_mut().element_offset_stack.push(offset);
|
||||||
let result = f(self);
|
let result = f(self);
|
||||||
self.window_mut().scroll_offset_stack.pop();
|
self.window_mut().element_offset_stack.pop();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_offset(&self) -> Point<Pixels> {
|
fn element_offset(&self) -> Point<Pixels> {
|
||||||
self.window()
|
self.window()
|
||||||
.scroll_offset_stack
|
.element_offset_stack
|
||||||
.last()
|
.last()
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
@ -1263,6 +1278,18 @@ pub trait BorrowWindow: BorrowAppContext {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_optional_element_state<S: 'static + Send + Sync, R>(
|
||||||
|
&mut self,
|
||||||
|
element_id: Option<ElementId>,
|
||||||
|
f: impl FnOnce(Option<S>, &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<Pixels> {
|
fn content_mask(&self) -> ContentMask<Pixels> {
|
||||||
self.window()
|
self.window()
|
||||||
.content_mask_stack
|
.content_mask_stack
|
||||||
|
|
|
@ -17,6 +17,11 @@ pub struct Tab<S: 'static + Send + Sync + Clone> {
|
||||||
close_side: IconSide,
|
close_side: IconSide,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct TabDragState {
|
||||||
|
title: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl<S: 'static + Send + Sync + Clone> Tab<S> {
|
impl<S: 'static + Send + Sync + Clone> Tab<S> {
|
||||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -111,8 +116,19 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let drag_state = TabDragState {
|
||||||
|
title: self.title.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id(self.id.clone())
|
.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::<TabDragState>(|d| d.bg(black()))
|
||||||
|
.on_drop(|_view, state: TabDragState, cx| {
|
||||||
|
dbg!(state);
|
||||||
|
})
|
||||||
.px_2()
|
.px_2()
|
||||||
.py_0p5()
|
.py_0p5()
|
||||||
.flex()
|
.flex()
|
||||||
|
@ -148,7 +164,7 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use gpui2::ElementId;
|
use gpui2::{black, red, Drag, ElementId};
|
||||||
#[cfg(feature = "stories")]
|
#[cfg(feature = "stories")]
|
||||||
pub use stories::*;
|
pub use stories::*;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue