diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 2c4a758c7f45a433e6ff3d6366eae14ab5c7627e..e0f29fea51b414a50d792f915fd0e8934d53fe0f 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -51,6 +51,7 @@ pub struct Window { pub(crate) parents: HashMap, pub(crate) is_active: bool, pub(crate) is_fullscreen: bool, + inspector_enabled: bool, pub(crate) invalidation: Option, pub(crate) platform_window: Box, pub(crate) rendered_views: HashMap>, @@ -65,6 +66,7 @@ pub struct Window { event_handlers: Vec, last_mouse_moved_event: Option, last_mouse_position: Vector2F, + pressed_buttons: HashSet, pub(crate) hovered_region_ids: Vec, pub(crate) clicked_region_ids: Vec, pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>, @@ -92,6 +94,7 @@ impl Window { is_active: false, invalidation: None, is_fullscreen: false, + inspector_enabled: false, platform_window, rendered_views: Default::default(), text_style_stack: Vec::new(), @@ -104,6 +107,7 @@ impl Window { text_layout_cache: TextLayoutCache::new(cx.font_system.clone()), last_mouse_moved_event: None, last_mouse_position: Vector2F::zero(), + pressed_buttons: Default::default(), hovered_region_ids: Default::default(), clicked_region_ids: Default::default(), clicked_region: None, @@ -235,6 +239,18 @@ impl<'a> WindowContext<'a> { .push_back(Effect::RepaintWindow { window }); } + pub fn enable_inspector(&mut self) { + self.window.inspector_enabled = true; + } + + pub fn is_inspector_enabled(&self) -> bool { + self.window.inspector_enabled + } + + pub fn is_mouse_down(&self, button: MouseButton) -> bool { + self.window.pressed_buttons.contains(&button) + } + pub fn rem_size(&self) -> f32 { 16. } @@ -521,7 +537,7 @@ impl<'a> WindowContext<'a> { pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool { if !event_reused { - self.dispatch_to_new_event_handlers(&event); + self.dispatch_event_2(&event); } let mut mouse_events = SmallVec::<[_; 2]>::new(); @@ -898,12 +914,24 @@ impl<'a> WindowContext<'a> { any_event_handled } - fn dispatch_to_new_event_handlers(&mut self, event: &Event) { + fn dispatch_event_2(&mut self, event: &Event) { + match event { + Event::MouseDown(event) => { + self.window.pressed_buttons.insert(event.button); + } + Event::MouseUp(event) => { + self.window.pressed_buttons.remove(&event.button); + } + _ => {} + } + if let Some(mouse_event) = event.mouse_event() { let event_handlers = self.window.take_event_handlers(); for event_handler in event_handlers.iter().rev() { if event_handler.event_type == mouse_event.type_id() { - (event_handler.handler)(mouse_event, self); + if !(event_handler.handler)(mouse_event, self) { + break; + } } } self.window.event_handlers = event_handlers; @@ -1394,7 +1422,7 @@ pub struct MeasureParams { pub available_space: Size, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum AvailableSpace { /// The amount of space available is the specified number of pixels Pixels(f32), diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index b05ec63f9714534c3b6bacc0c6b9421cc3e6cfac..c83ad73b5db16a457a94d0494743e7ee3f352f4a 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -1,3 +1,5 @@ +use std::fmt::Debug; + use super::scene::{Path, PathVertex}; use crate::{color::Color, json::ToJson}; pub use pathfinder_geometry::*; @@ -133,13 +135,14 @@ impl ToJson for RectF { } } -#[derive(Refineable)] -pub struct Point { +#[derive(Refineable, Debug)] +#[refineable(debug)] +pub struct Point { pub x: T, pub y: T, } -impl Clone for Point { +impl Clone for Point { fn clone(&self) -> Self { Self { x: self.x.clone(), @@ -148,7 +151,7 @@ impl Clone for Point { } } -impl Into> for Point { +impl Into> for Point { fn into(self) -> taffy::geometry::Point { taffy::geometry::Point { x: self.x, @@ -157,13 +160,14 @@ impl Into> for Point { } } -#[derive(Clone, Refineable, Debug)] -pub struct Size { +#[derive(Refineable, Clone, Debug)] +#[refineable(debug)] +pub struct Size { pub width: T, pub height: T, } -impl From> for Size +impl From> for Size where S: Into, { @@ -175,7 +179,7 @@ where } } -impl Into> for Size +impl Into> for Size where T: Into, { @@ -222,8 +226,9 @@ impl Size { } } -#[derive(Clone, Default, Refineable)] -pub struct Edges { +#[derive(Clone, Default, Refineable, Debug)] +#[refineable(debug)] +pub struct Edges { pub top: T, pub right: T, pub bottom: T, @@ -323,6 +328,15 @@ pub enum AbsoluteLength { Rems(f32), } +impl std::fmt::Debug for AbsoluteLength { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AbsoluteLength::Pixels(pixels) => write!(f, "{}px", pixels), + AbsoluteLength::Rems(rems) => write!(f, "{}rems", rems), + } + } +} + impl AbsoluteLength { pub fn to_pixels(&self, rem_size: f32) -> f32 { match self { @@ -349,7 +363,7 @@ impl Default for AbsoluteLength { #[derive(Clone, Copy)] pub enum DefiniteLength { Absolute(AbsoluteLength), - Relative(f32), // Percent, from 0 to 100. + Relative(f32), // 0. to 1. } impl DefiniteLength { @@ -368,6 +382,15 @@ impl DefiniteLength { } } +impl std::fmt::Debug for DefiniteLength { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DefiniteLength::Absolute(length) => std::fmt::Debug::fmt(length, f), + DefiniteLength::Relative(fract) => write!(f, "{}%", (fract * 100.0) as i32), + } + } +} + impl From for DefiniteLength { fn from(length: AbsoluteLength) -> Self { Self::Absolute(length) @@ -387,6 +410,15 @@ pub enum Length { Auto, } +impl std::fmt::Debug for Length { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Length::Definite(definite_length) => write!(f, "{:?}", definite_length), + Length::Auto => write!(f, "auto"), + } + } +} + pub fn relative>(fraction: f32) -> T { DefiniteLength::Relative(fraction).into() } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 081b4f65a3ae1f09e303877688eb66d898355ca1..06c40af11a2b5ceb5ccc7f648bf8a8484b12d094 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,12 +1,19 @@ +use std::cell::Cell; + use crate::{ element::{AnyElement, Element, IntoElement, Layout, ParentElement}, + hsla, layout_context::LayoutContext, paint_context::PaintContext, - style::{Style, StyleHelpers, Styleable}, + style::{CornerRadii, Style, StyleHelpers, Styleable}, InteractionHandlers, Interactive, }; use anyhow::Result; -use gpui::LayoutId; +use gpui::{ + platform::{MouseButton, MouseButtonEvent, MouseMovedEvent}, + scene::{self}, + LayoutId, +}; use refineable::{Refineable, RefinementCascade}; use smallvec::SmallVec; use util::ResultExt; @@ -63,7 +70,7 @@ impl Element for Div { ) where Self: Sized, { - let style = &self.computed_style(); + let style = self.computed_style(); let pop_text_style = style.text_style(cx).map_or(false, |style| { cx.push_text_style(&style).log_err().is_some() }); @@ -77,6 +84,58 @@ impl Element for Div { if pop_text_style { cx.pop_text_style(); } + + if cx.is_inspector_enabled() { + self.paint_inspector(layout, cx); + } + } +} + +impl Div { + fn paint_inspector(&self, layout: &Layout, cx: &mut PaintContext) { + let style = self.styles.merged(); + + let hovered = layout.bounds.contains_point(cx.mouse_position()); + if hovered { + let rem_size = cx.rem_size(); + cx.scene.push_quad(scene::Quad { + bounds: layout.bounds, + background: Some(hsla(0., 0., 1., 0.05).into()), + border: gpui::Border { + color: hsla(0., 0., 1., 0.2).into(), + top: 1., + right: 1., + bottom: 1., + left: 1., + }, + corner_radii: CornerRadii::default() + .refined(&style.corner_radii) + .to_gpui(layout.bounds.size(), rem_size), + }) + } + + let bounds = layout.bounds; + let pressed = Cell::new(hovered && cx.is_mouse_down(MouseButton::Left)); + cx.on_event(layout.order, move |_, event: &MouseButtonEvent, _| { + if bounds.contains_point(event.position) { + if event.is_down { + pressed.set(true); + } else if pressed.get() { + pressed.set(false); + eprintln!("clicked div {:?} {:#?}", bounds, style); + } + } + }); + + let hovered = Cell::new(hovered); + cx.on_event(layout.order, move |_, event: &MouseMovedEvent, cx| { + cx.bubble_event(); + let hovered_now = bounds.contains_point(event.position); + if hovered.get() != hovered_now { + hovered.set(hovered_now); + cx.repaint(); + } + }); } } diff --git a/crates/gpui2/src/elements/hoverable.rs b/crates/gpui2/src/elements/hoverable.rs index e97c66113cf5688ea4fc440778b0f8cf8b124b15..d70b6f994d8cfeedcab67a307dacabc95dbabac6 100644 --- a/crates/gpui2/src/elements/hoverable.rs +++ b/crates/gpui2/src/elements/hoverable.rs @@ -72,6 +72,7 @@ impl + Styleable> Element for Hoverable { let hovered = self.hovered.clone(); let bounds = layout.bounds; cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| { + cx.bubble_event(); if bounds.contains_point(cx.mouse_position()) != hovered.get() { cx.repaint(); } diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 591fb54576355e14a888ca23bf12fa4cb863bec9..42f91b102cc85f202e5d4c8a94aa6ae68ee0bc9d 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -22,7 +22,8 @@ use gpui2_macros::styleable_helpers; use refineable::{Refineable, RefinementCascade}; use std::sync::Arc; -#[derive(Clone, Refineable)] +#[derive(Clone, Refineable, Debug)] +#[refineable(debug)] pub struct Style { /// What layout strategy should be used? pub display: Display, @@ -266,7 +267,8 @@ impl From for Fill { } } -#[derive(Clone, Refineable, Default)] +#[derive(Clone, Refineable, Default, Debug)] +#[refineable(debug)] pub struct CornerRadii { top_left: AbsoluteLength, top_right: AbsoluteLength, diff --git a/crates/refineable/derive_refineable/src/derive_refineable.rs b/crates/refineable/derive_refineable/src/derive_refineable.rs index b5ba4124820c8a775974a427542b2d6e35f10914..547af0e757d0f3a7467ddfbc510f52750dbfb9a8 100644 --- a/crates/refineable/derive_refineable/src/derive_refineable.rs +++ b/crates/refineable/derive_refineable/src/derive_refineable.rs @@ -12,9 +12,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { ident, data, generics, + attrs, .. } = parse_macro_input!(input); + let impl_debug_on_refinement = attrs + .iter() + .any(|attr| attr.path.is_ident("refineable") && attr.tokens.to_string().contains("debug")); + let refinement_ident = format_ident!("{}Refinement", ident); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); @@ -120,6 +125,41 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { }) .collect(); + let debug_impl = if impl_debug_on_refinement { + let refinement_field_debugs: Vec = fields + .iter() + .map(|field| { + let name = &field.ident; + quote! { + if self.#name.is_some() { + debug_struct.field(stringify!(#name), &self.#name); + } else { + all_some = false; + } + } + }) + .collect(); + + quote! { + impl #impl_generics std::fmt::Debug for #refinement_ident #ty_generics + #where_clause + { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut debug_struct = f.debug_struct(stringify!(#refinement_ident)); + let mut all_some = true; + #( #refinement_field_debugs )* + if all_some { + debug_struct.finish() + } else { + debug_struct.finish_non_exhaustive() + } + } + } + } + } else { + quote! {} + }; + let gen = quote! { #[derive(Default, Clone)] pub struct #refinement_ident #impl_generics { @@ -145,8 +185,22 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream { #( #refinement_field_assignments )* } } - }; + impl #impl_generics #refinement_ident #ty_generics + #where_clause + { + pub fn is_some(&self) -> bool { + #( + if self.#field_names.is_some() { + return true; + } + )* + false + } + } + + #debug_impl + }; gen.into() } diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index c13e5e0bc323c51c563018f886ea6b1e3b4358a5..cbfc9681f6e892f2ccdbf58b6c3a7e61c5e801d2 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -15,6 +15,11 @@ mod element_ext; mod theme; mod workspace; +gpui2::actions! { + storybook, + [ToggleInspector] +} + fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); @@ -33,7 +38,12 @@ fn main() { center: true, ..Default::default() }, - |_| view(|cx| storybook(cx)), + |cx| { + view(|cx| { + cx.enable_inspector(); + storybook(cx) + }) + }, ); cx.platform().activate(true); }); diff --git a/crates/storybook/src/workspace.rs b/crates/storybook/src/workspace.rs index ecdfcd4def8632a4c92f49f895e00ac4e1eede5b..f66b1ba14078393414668eb5be5c8bc481c5bb12 100644 --- a/crates/storybook/src/workspace.rs +++ b/crates/storybook/src/workspace.rs @@ -406,62 +406,41 @@ pub fn workspace() -> impl Element { impl WorkspaceElement { fn render(&mut self, _: &mut V, cx: &mut ViewContext) -> impl IntoElement { let theme = theme(cx); + div() .size_full() .flex() - .flex_col() - .font("Zed Sans Extended") - .gap_0() - .justify_start() - .items_start() - .text_color(theme.lowest.base.default.foreground) - .fill(theme.middle.warning.default.background) - .child( - div() - .w_full() - .h_8() - .fill(theme.lowest.negative.default.background) - .child(titlebar()), - ) - .child( - div() - .flex() - .flex_1() - .child(collab_panel()) - .child(div().flex_1().fill(theme.lowest.accent.default.background)) - .child(div().w_64().fill(theme.lowest.positive.default.background)), - ) - .child( - div() - .w_full() - .h_9() - .fill(theme.lowest.positive.default.background) - .child(statusbar()) - .child( - div() - .h_px() - .w_full() - .fill(theme.lowest.negative.default.background), - ) - .child( - div() - .h_px() - .w_full() - .fill(theme.lowest.positive.default.background), - ) - .child( - div() - .h_px() - .w_full() - .fill(theme.lowest.accent.default.background), - ) - .child( - div() - .h_px() - .w_full() - .fill(theme.lowest.warning.default.background), - ), - ) + .flex_row() + .child(collab_panel()) + .child(collab_panel()) + + // div() + // .size_full() + // .flex() + // .flex_col() + // .font("Zed Sans Extended") + // .gap_0() + // .justify_start() + // .items_start() + // .text_color(theme.lowest.base.default.foreground) + // // .fill(theme.middle.warning.default.background) + // .child(titlebar()) + // .child( + // div() + // .flex_1() + // .w_full() + // .flex() + // .flex_row() + // .child(collab_panel()) + // // .child( + // // div() + // // .h_full() + // // .flex_1() + // // .fill(theme.highest.accent.default.background), + // // ) + // .child(collab_panel()), + // ) + // .child(statusbar()) } }