Add basic inspector

This commit is contained in:
Nathan Sobo 2023-09-07 22:24:02 -06:00
parent e7760e1a3f
commit d311bd04ff
8 changed files with 240 additions and 75 deletions

View file

@ -51,6 +51,7 @@ pub struct Window {
pub(crate) parents: HashMap<usize, usize>, pub(crate) parents: HashMap<usize, usize>,
pub(crate) is_active: bool, pub(crate) is_active: bool,
pub(crate) is_fullscreen: bool, pub(crate) is_fullscreen: bool,
inspector_enabled: bool,
pub(crate) invalidation: Option<WindowInvalidation>, pub(crate) invalidation: Option<WindowInvalidation>,
pub(crate) platform_window: Box<dyn platform::Window>, pub(crate) platform_window: Box<dyn platform::Window>,
pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>, pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
@ -65,6 +66,7 @@ pub struct Window {
event_handlers: Vec<EventHandler>, event_handlers: Vec<EventHandler>,
last_mouse_moved_event: Option<Event>, last_mouse_moved_event: Option<Event>,
last_mouse_position: Vector2F, last_mouse_position: Vector2F,
pressed_buttons: HashSet<MouseButton>,
pub(crate) hovered_region_ids: Vec<MouseRegionId>, pub(crate) hovered_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region_ids: Vec<MouseRegionId>, pub(crate) clicked_region_ids: Vec<MouseRegionId>,
pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>, pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
@ -92,6 +94,7 @@ impl Window {
is_active: false, is_active: false,
invalidation: None, invalidation: None,
is_fullscreen: false, is_fullscreen: false,
inspector_enabled: false,
platform_window, platform_window,
rendered_views: Default::default(), rendered_views: Default::default(),
text_style_stack: Vec::new(), text_style_stack: Vec::new(),
@ -104,6 +107,7 @@ impl Window {
text_layout_cache: TextLayoutCache::new(cx.font_system.clone()), text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
last_mouse_moved_event: None, last_mouse_moved_event: None,
last_mouse_position: Vector2F::zero(), last_mouse_position: Vector2F::zero(),
pressed_buttons: Default::default(),
hovered_region_ids: Default::default(), hovered_region_ids: Default::default(),
clicked_region_ids: Default::default(), clicked_region_ids: Default::default(),
clicked_region: None, clicked_region: None,
@ -235,6 +239,18 @@ impl<'a> WindowContext<'a> {
.push_back(Effect::RepaintWindow { window }); .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 { pub fn rem_size(&self) -> f32 {
16. 16.
} }
@ -521,7 +537,7 @@ impl<'a> WindowContext<'a> {
pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool { pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
if !event_reused { if !event_reused {
self.dispatch_to_new_event_handlers(&event); self.dispatch_event_2(&event);
} }
let mut mouse_events = SmallVec::<[_; 2]>::new(); let mut mouse_events = SmallVec::<[_; 2]>::new();
@ -898,12 +914,24 @@ impl<'a> WindowContext<'a> {
any_event_handled 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() { if let Some(mouse_event) = event.mouse_event() {
let event_handlers = self.window.take_event_handlers(); let event_handlers = self.window.take_event_handlers();
for event_handler in event_handlers.iter().rev() { for event_handler in event_handlers.iter().rev() {
if event_handler.event_type == mouse_event.type_id() { 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; self.window.event_handlers = event_handlers;
@ -1394,7 +1422,7 @@ pub struct MeasureParams {
pub available_space: Size<AvailableSpace>, pub available_space: Size<AvailableSpace>,
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum AvailableSpace { pub enum AvailableSpace {
/// The amount of space available is the specified number of pixels /// The amount of space available is the specified number of pixels
Pixels(f32), Pixels(f32),

View file

@ -1,3 +1,5 @@
use std::fmt::Debug;
use super::scene::{Path, PathVertex}; use super::scene::{Path, PathVertex};
use crate::{color::Color, json::ToJson}; use crate::{color::Color, json::ToJson};
pub use pathfinder_geometry::*; pub use pathfinder_geometry::*;
@ -133,13 +135,14 @@ impl ToJson for RectF {
} }
} }
#[derive(Refineable)] #[derive(Refineable, Debug)]
pub struct Point<T: Clone + Default> { #[refineable(debug)]
pub struct Point<T: Clone + Default + Debug> {
pub x: T, pub x: T,
pub y: T, pub y: T,
} }
impl<T: Clone + Default> Clone for Point<T> { impl<T: Clone + Default + Debug> Clone for Point<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
x: self.x.clone(), x: self.x.clone(),
@ -148,7 +151,7 @@ impl<T: Clone + Default> Clone for Point<T> {
} }
} }
impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> { impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
fn into(self) -> taffy::geometry::Point<T> { fn into(self) -> taffy::geometry::Point<T> {
taffy::geometry::Point { taffy::geometry::Point {
x: self.x, x: self.x,
@ -157,13 +160,14 @@ impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
} }
} }
#[derive(Clone, Refineable, Debug)] #[derive(Refineable, Clone, Debug)]
pub struct Size<T: Clone + Default> { #[refineable(debug)]
pub struct Size<T: Clone + Default + Debug> {
pub width: T, pub width: T,
pub height: T, pub height: T,
} }
impl<S, T: Clone + Default> From<taffy::geometry::Size<S>> for Size<T> impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
where where
S: Into<T>, S: Into<T>,
{ {
@ -175,7 +179,7 @@ where
} }
} }
impl<S, T: Clone + Default> Into<taffy::geometry::Size<S>> for Size<T> impl<S, T: Clone + Default + Debug> Into<taffy::geometry::Size<S>> for Size<T>
where where
T: Into<S>, T: Into<S>,
{ {
@ -222,8 +226,9 @@ impl Size<Length> {
} }
} }
#[derive(Clone, Default, Refineable)] #[derive(Clone, Default, Refineable, Debug)]
pub struct Edges<T: Clone + Default> { #[refineable(debug)]
pub struct Edges<T: Clone + Default + Debug> {
pub top: T, pub top: T,
pub right: T, pub right: T,
pub bottom: T, pub bottom: T,
@ -323,6 +328,15 @@ pub enum AbsoluteLength {
Rems(f32), 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 { impl AbsoluteLength {
pub fn to_pixels(&self, rem_size: f32) -> f32 { pub fn to_pixels(&self, rem_size: f32) -> f32 {
match self { match self {
@ -349,7 +363,7 @@ impl Default for AbsoluteLength {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum DefiniteLength { pub enum DefiniteLength {
Absolute(AbsoluteLength), Absolute(AbsoluteLength),
Relative(f32), // Percent, from 0 to 100. Relative(f32), // 0. to 1.
} }
impl DefiniteLength { 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<AbsoluteLength> for DefiniteLength { impl From<AbsoluteLength> for DefiniteLength {
fn from(length: AbsoluteLength) -> Self { fn from(length: AbsoluteLength) -> Self {
Self::Absolute(length) Self::Absolute(length)
@ -387,6 +410,15 @@ pub enum Length {
Auto, 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<T: From<DefiniteLength>>(fraction: f32) -> T { pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
DefiniteLength::Relative(fraction).into() DefiniteLength::Relative(fraction).into()
} }

View file

@ -1,12 +1,19 @@
use std::cell::Cell;
use crate::{ use crate::{
element::{AnyElement, Element, IntoElement, Layout, ParentElement}, element::{AnyElement, Element, IntoElement, Layout, ParentElement},
hsla,
layout_context::LayoutContext, layout_context::LayoutContext,
paint_context::PaintContext, paint_context::PaintContext,
style::{Style, StyleHelpers, Styleable}, style::{CornerRadii, Style, StyleHelpers, Styleable},
InteractionHandlers, Interactive, InteractionHandlers, Interactive,
}; };
use anyhow::Result; use anyhow::Result;
use gpui::LayoutId; use gpui::{
platform::{MouseButton, MouseButtonEvent, MouseMovedEvent},
scene::{self},
LayoutId,
};
use refineable::{Refineable, RefinementCascade}; use refineable::{Refineable, RefinementCascade};
use smallvec::SmallVec; use smallvec::SmallVec;
use util::ResultExt; use util::ResultExt;
@ -63,7 +70,7 @@ impl<V: 'static> Element<V> for Div<V> {
) where ) where
Self: Sized, Self: Sized,
{ {
let style = &self.computed_style(); let style = self.computed_style();
let pop_text_style = style.text_style(cx).map_or(false, |style| { let pop_text_style = style.text_style(cx).map_or(false, |style| {
cx.push_text_style(&style).log_err().is_some() cx.push_text_style(&style).log_err().is_some()
}); });
@ -77,6 +84,58 @@ impl<V: 'static> Element<V> for Div<V> {
if pop_text_style { if pop_text_style {
cx.pop_text_style(); cx.pop_text_style();
} }
if cx.is_inspector_enabled() {
self.paint_inspector(layout, cx);
}
}
}
impl<V: 'static> Div<V> {
fn paint_inspector(&self, layout: &Layout, cx: &mut PaintContext<V>) {
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();
}
});
} }
} }

View file

@ -72,6 +72,7 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
let hovered = self.hovered.clone(); let hovered = self.hovered.clone();
let bounds = layout.bounds; let bounds = layout.bounds;
cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| { cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
cx.bubble_event();
if bounds.contains_point(cx.mouse_position()) != hovered.get() { if bounds.contains_point(cx.mouse_position()) != hovered.get() {
cx.repaint(); cx.repaint();
} }

View file

@ -22,7 +22,8 @@ use gpui2_macros::styleable_helpers;
use refineable::{Refineable, RefinementCascade}; use refineable::{Refineable, RefinementCascade};
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone, Refineable)] #[derive(Clone, Refineable, Debug)]
#[refineable(debug)]
pub struct Style { pub struct Style {
/// What layout strategy should be used? /// What layout strategy should be used?
pub display: Display, pub display: Display,
@ -266,7 +267,8 @@ impl From<Hsla> for Fill {
} }
} }
#[derive(Clone, Refineable, Default)] #[derive(Clone, Refineable, Default, Debug)]
#[refineable(debug)]
pub struct CornerRadii { pub struct CornerRadii {
top_left: AbsoluteLength, top_left: AbsoluteLength,
top_right: AbsoluteLength, top_right: AbsoluteLength,

View file

@ -12,9 +12,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
ident, ident,
data, data,
generics, generics,
attrs,
.. ..
} = parse_macro_input!(input); } = 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 refinement_ident = format_ident!("{}Refinement", ident);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
@ -120,6 +125,41 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
}) })
.collect(); .collect();
let debug_impl = if impl_debug_on_refinement {
let refinement_field_debugs: Vec<TokenStream2> = 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! { let gen = quote! {
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct #refinement_ident #impl_generics { pub struct #refinement_ident #impl_generics {
@ -145,8 +185,22 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
#( #refinement_field_assignments )* #( #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() gen.into()
} }

View file

@ -15,6 +15,11 @@ mod element_ext;
mod theme; mod theme;
mod workspace; mod workspace;
gpui2::actions! {
storybook,
[ToggleInspector]
}
fn main() { fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
@ -33,7 +38,12 @@ fn main() {
center: true, center: true,
..Default::default() ..Default::default()
}, },
|_| view(|cx| storybook(cx)), |cx| {
view(|cx| {
cx.enable_inspector();
storybook(cx)
})
},
); );
cx.platform().activate(true); cx.platform().activate(true);
}); });

View file

@ -406,62 +406,41 @@ pub fn workspace<V: 'static>() -> impl Element<V> {
impl WorkspaceElement { impl WorkspaceElement {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> { fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx); let theme = theme(cx);
div() div()
.size_full() .size_full()
.flex() .flex()
.flex_col() .flex_row()
.font("Zed Sans Extended") .child(collab_panel())
.gap_0() .child(collab_panel())
.justify_start()
.items_start() // div()
.text_color(theme.lowest.base.default.foreground) // .size_full()
.fill(theme.middle.warning.default.background) // .flex()
.child( // .flex_col()
div() // .font("Zed Sans Extended")
.w_full() // .gap_0()
.h_8() // .justify_start()
.fill(theme.lowest.negative.default.background) // .items_start()
.child(titlebar()), // .text_color(theme.lowest.base.default.foreground)
) // // .fill(theme.middle.warning.default.background)
.child( // .child(titlebar())
div() // .child(
.flex() // div()
.flex_1() // .flex_1()
.child(collab_panel()) // .w_full()
.child(div().flex_1().fill(theme.lowest.accent.default.background)) // .flex()
.child(div().w_64().fill(theme.lowest.positive.default.background)), // .flex_row()
) // .child(collab_panel())
.child( // // .child(
div() // // div()
.w_full() // // .h_full()
.h_9() // // .flex_1()
.fill(theme.lowest.positive.default.background) // // .fill(theme.highest.accent.default.background),
.child(statusbar()) // // )
.child( // .child(collab_panel()),
div() // )
.h_px() // .child(statusbar())
.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),
),
)
} }
} }