Detailed changes
@@ -112,7 +112,6 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
- "smallvec",
"smol",
"streaming_diff",
"telemetry",
@@ -8159,6 +8158,26 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "inspector_ui"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "command_palette_hooks",
+ "editor",
+ "gpui",
+ "language",
+ "project",
+ "serde_json",
+ "serde_json_lenient",
+ "theme",
+ "ui",
+ "util",
+ "workspace",
+ "workspace-hack",
+ "zed_actions",
+]
+
[[package]]
name = "install_cli"
version = "0.1.0"
@@ -8931,8 +8950,10 @@ dependencies = [
"regex",
"rope",
"rust-embed",
+ "schemars",
"serde",
"serde_json",
+ "serde_json_lenient",
"settings",
"smol",
"snippet_provider",
@@ -19750,6 +19771,7 @@ dependencies = [
"image_viewer",
"indoc",
"inline_completion_button",
+ "inspector_ui",
"install_cli",
"jj_ui",
"journal",
@@ -73,6 +73,7 @@ members = [
"crates/indexed_docs",
"crates/inline_completion",
"crates/inline_completion_button",
+ "crates/inspector_ui",
"crates/install_cli",
"crates/jj",
"crates/jj_ui",
@@ -279,6 +280,7 @@ image_viewer = { path = "crates/image_viewer" }
indexed_docs = { path = "crates/indexed_docs" }
inline_completion = { path = "crates/inline_completion" }
inline_completion_button = { path = "crates/inline_completion_button" }
+inspector_ui = { path = "crates/inspector_ui" }
install_cli = { path = "crates/install_cli" }
jj = { path = "crates/jj" }
jj_ui = { path = "crates/jj_ui" }
@@ -447,6 +449,7 @@ futures-batch = "0.6.1"
futures-lite = "1.13"
git2 = { version = "0.20.1", default-features = false }
globset = "0.4"
+hashbrown = "0.15.3"
handlebars = "4.3"
heck = "0.5"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
@@ -675,7 +675,7 @@
{
"bindings": {
"ctrl-alt-shift-f": "workspace::FollowNextCollaborator",
- "ctrl-alt-i": "zed::DebugElements"
+ "ctrl-alt-i": "dev::ToggleInspector"
}
},
{
@@ -735,7 +735,7 @@
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
// TODO: Move this to a dock open action
"cmd-shift-c": "collab_panel::ToggleFocus",
- "cmd-alt-i": "zed::DebugElements"
+ "cmd-alt-i": "dev::ToggleInspector"
}
},
{
@@ -76,7 +76,6 @@ serde.workspace = true
serde_json.workspace = true
serde_json_lenient.workspace = true
settings.workspace = true
-smallvec.workspace = true
smol.workspace = true
streaming_diff.workspace = true
telemetry.workspace = true
@@ -842,7 +842,7 @@ impl MessageEditor {
.border_b_0()
.border_color(border_color)
.rounded_t_md()
- .shadow(smallvec::smallvec![gpui::BoxShadow {
+ .shadow(vec![gpui::BoxShadow {
color: gpui::black().opacity(0.15),
offset: point(px(1.), px(-1.)),
blur_radius: px(3.),
@@ -7698,7 +7698,7 @@ impl Editor {
.gap_1()
// Workaround: For some reason, there's a gap if we don't do this
.ml(-BORDER_WIDTH)
- .shadow(smallvec![gpui::BoxShadow {
+ .shadow(vec![gpui::BoxShadow {
color: gpui::black().opacity(0.05),
offset: point(px(1.), px(1.)),
blur_radius: px(2.),
@@ -138,7 +138,6 @@ pub use git::blame::BlameRenderer;
pub use proposed_changes_editor::{
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
};
-use smallvec::smallvec;
use std::{cell::OnceCell, iter::Peekable, ops::Not};
use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
@@ -176,7 +175,7 @@ use selections_collection::{
};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
-use smallvec::SmallVec;
+use smallvec::{SmallVec, smallvec};
use snippet::Snippet;
use std::sync::Arc;
use std::{
@@ -7993,7 +7992,7 @@ impl Editor {
.gap_1()
// Workaround: For some reason, there's a gap if we don't do this
.ml(-BORDER_WIDTH)
- .shadow(smallvec![gpui::BoxShadow {
+ .shadow(vec![gpui::BoxShadow {
color: gpui::black().opacity(0.05),
offset: point(px(1.), px(1.)),
blur_radius: px(2.),
@@ -16708,7 +16707,7 @@ impl Editor {
}
pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
- let mut wrap_guides = smallvec::smallvec![];
+ let mut wrap_guides = smallvec![];
if self.show_wrap_guides == Some(false) {
return wrap_guides;
@@ -7181,9 +7181,14 @@ impl Element for EditorElement {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
+ __inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, ()) {
@@ -7290,6 +7295,7 @@ impl Element for EditorElement {
fn prepaint(
&mut self,
_: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -7761,7 +7767,7 @@ impl Element for EditorElement {
// If the fold widths have changed, we need to prepaint
// the element again to account for any changes in
// wrapping.
- return self.prepaint(None, bounds, &mut (), window, cx);
+ return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
}
let longest_line_blame_width = self
@@ -7846,7 +7852,7 @@ impl Element for EditorElement {
self.editor.update(cx, |editor, cx| {
editor.resize_blocks(resized_blocks, autoscroll_request, cx)
});
- return self.prepaint(None, bounds, &mut (), window, cx);
+ return self.prepaint(None, _inspector_id, bounds, &mut (), window, cx);
}
};
@@ -8345,6 +8351,7 @@ impl Element for EditorElement {
fn paint(
&mut self,
_: Option<&GlobalElementId>,
+ __inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<gpui::Pixels>,
_: &mut Self::RequestLayoutState,
layout: &mut Self::PrepaintState,
@@ -1135,7 +1135,7 @@ impl SerializableItem for Editor {
mtime,
..
} => {
- let project_item = project.update(cx, |project, cx| {
+ let opened_buffer = project.update(cx, |project, cx| {
let (worktree, path) = project.find_worktree(&abs_path, cx)?;
let project_path = ProjectPath {
worktree_id: worktree.read(cx).id(),
@@ -1144,13 +1144,10 @@ impl SerializableItem for Editor {
Some(project.open_path(project_path, cx))
});
- match project_item {
- Some(project_item) => {
+ match opened_buffer {
+ Some(opened_buffer) => {
window.spawn(cx, async move |cx| {
- let (_, project_item) = project_item.await?;
- let buffer = project_item.downcast::<Buffer>().map_err(|_| {
- anyhow!("Project item at stored path was not a buffer")
- })?;
+ let (_, buffer) = opened_buffer.await?;
// This is a bit wasteful: we're loading the whole buffer from
// disk and then overwrite the content.
@@ -22,6 +22,7 @@ test-support = [
"wayland",
"x11",
]
+inspector = []
leak-detection = ["backtrace"]
runtime_shaders = []
macos-blade = [
@@ -404,16 +404,20 @@ impl IntoElement for TextElement {
impl Element for TextElement {
type RequestLayoutState = ();
-
type PrepaintState = PrepaintState;
fn id(&self) -> Option<ElementId> {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -426,6 +430,7 @@ impl Element for TextElement {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -523,6 +528,7 @@ impl Element for TextElement {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
@@ -121,7 +121,7 @@ impl Render for HelloWorld {
.bg(gpui::blue())
.border_3()
.border_color(gpui::black())
- .shadow(smallvec::smallvec![BoxShadow {
+ .shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.5),
blur_radius: px(1.0),
spread_radius: px(5.0),
@@ -3,8 +3,6 @@ use gpui::{
WindowOptions, div, hsla, point, prelude::*, px, relative, rgb, size,
};
-use smallvec::smallvec;
-
struct Shadow {}
impl Shadow {
@@ -103,7 +101,7 @@ impl Render for Shadow {
example(
"Square",
Shadow::square()
- .shadow(smallvec![BoxShadow {
+ .shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -113,7 +111,7 @@ impl Render for Shadow {
example(
"Rounded 4",
Shadow::rounded_small()
- .shadow(smallvec![BoxShadow {
+ .shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -123,7 +121,7 @@ impl Render for Shadow {
example(
"Rounded 8",
Shadow::rounded_medium()
- .shadow(smallvec![BoxShadow {
+ .shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -133,7 +131,7 @@ impl Render for Shadow {
example(
"Rounded 16",
Shadow::rounded_large()
- .shadow(smallvec![BoxShadow {
+ .shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -143,7 +141,7 @@ impl Render for Shadow {
example(
"Circle",
Shadow::base()
- .shadow(smallvec![BoxShadow {
+ .shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -175,7 +173,7 @@ impl Render for Shadow {
.children(vec![
example(
"Blur 0",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(0.),
@@ -184,7 +182,7 @@ impl Render for Shadow {
),
example(
"Blur 2",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(2.),
@@ -193,7 +191,7 @@ impl Render for Shadow {
),
example(
"Blur 4",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(4.),
@@ -202,7 +200,7 @@ impl Render for Shadow {
),
example(
"Blur 8",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -211,7 +209,7 @@ impl Render for Shadow {
),
example(
"Blur 16",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(16.),
@@ -227,7 +225,7 @@ impl Render for Shadow {
.children(vec![
example(
"Spread 0",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -236,7 +234,7 @@ impl Render for Shadow {
),
example(
"Spread 2",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -245,7 +243,7 @@ impl Render for Shadow {
),
example(
"Spread 4",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -254,7 +252,7 @@ impl Render for Shadow {
),
example(
"Spread 8",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -263,7 +261,7 @@ impl Render for Shadow {
),
example(
"Spread 16",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -279,7 +277,7 @@ impl Render for Shadow {
.children(vec![
example(
"Square Spread 0",
- Shadow::square().shadow(smallvec![BoxShadow {
+ Shadow::square().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -288,7 +286,7 @@ impl Render for Shadow {
),
example(
"Square Spread 8",
- Shadow::square().shadow(smallvec![BoxShadow {
+ Shadow::square().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -297,7 +295,7 @@ impl Render for Shadow {
),
example(
"Square Spread 16",
- Shadow::square().shadow(smallvec![BoxShadow {
+ Shadow::square().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -313,7 +311,7 @@ impl Render for Shadow {
.children(vec![
example(
"Rounded Large Spread 0",
- Shadow::rounded_large().shadow(smallvec![BoxShadow {
+ Shadow::rounded_large().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -322,7 +320,7 @@ impl Render for Shadow {
),
example(
"Rounded Large Spread 8",
- Shadow::rounded_large().shadow(smallvec![BoxShadow {
+ Shadow::rounded_large().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -331,7 +329,7 @@ impl Render for Shadow {
),
example(
"Rounded Large Spread 16",
- Shadow::rounded_large().shadow(smallvec![BoxShadow {
+ Shadow::rounded_large().shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -347,7 +345,7 @@ impl Render for Shadow {
.children(vec![
example(
"Left",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
@@ -356,7 +354,7 @@ impl Render for Shadow {
),
example(
"Right",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
@@ -365,7 +363,7 @@ impl Render for Shadow {
),
example(
"Top",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
@@ -374,7 +372,7 @@ impl Render for Shadow {
),
example(
"Bottom",
- Shadow::base().shadow(smallvec![BoxShadow {
+ Shadow::base().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -390,7 +388,7 @@ impl Render for Shadow {
.children(vec![
example(
"Square Left",
- Shadow::square().shadow(smallvec![BoxShadow {
+ Shadow::square().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
@@ -399,7 +397,7 @@ impl Render for Shadow {
),
example(
"Square Right",
- Shadow::square().shadow(smallvec![BoxShadow {
+ Shadow::square().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
@@ -408,7 +406,7 @@ impl Render for Shadow {
),
example(
"Square Top",
- Shadow::square().shadow(smallvec![BoxShadow {
+ Shadow::square().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
@@ -417,7 +415,7 @@ impl Render for Shadow {
),
example(
"Square Bottom",
- Shadow::square().shadow(smallvec![BoxShadow {
+ Shadow::square().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -433,7 +431,7 @@ impl Render for Shadow {
.children(vec![
example(
"Rounded Large Left",
- Shadow::rounded_large().shadow(smallvec![BoxShadow {
+ Shadow::rounded_large().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(-8.), px(0.)),
blur_radius: px(8.),
@@ -442,7 +440,7 @@ impl Render for Shadow {
),
example(
"Rounded Large Right",
- Shadow::rounded_large().shadow(smallvec![BoxShadow {
+ Shadow::rounded_large().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(8.), px(0.)),
blur_radius: px(8.),
@@ -451,7 +449,7 @@ impl Render for Shadow {
),
example(
"Rounded Large Top",
- Shadow::rounded_large().shadow(smallvec![BoxShadow {
+ Shadow::rounded_large().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(-8.)),
blur_radius: px(8.),
@@ -460,7 +458,7 @@ impl Render for Shadow {
),
example(
"Rounded Large Bottom",
- Shadow::rounded_large().shadow(smallvec![BoxShadow {
+ Shadow::rounded_large().shadow(vec![BoxShadow {
color: hsla(0.0, 0.5, 0.5, 0.3),
offset: point(px(0.), px(8.)),
blur_radius: px(8.),
@@ -476,7 +474,7 @@ impl Render for Shadow {
.children(vec![
example(
"Circle Multiple",
- Shadow::base().shadow(smallvec![
+ Shadow::base().shadow(vec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
@@ -505,7 +503,7 @@ impl Render for Shadow {
),
example(
"Square Multiple",
- Shadow::square().shadow(smallvec![
+ Shadow::square().shadow(vec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
@@ -534,7 +532,7 @@ impl Render for Shadow {
),
example(
"Rounded Large Multiple",
- Shadow::rounded_large().shadow(smallvec![
+ Shadow::rounded_large().shadow(vec![
BoxShadow {
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
offset: point(px(0.), px(-12.)),
@@ -73,7 +73,7 @@ impl Render for HelloWorld {
.flex_shrink_0()
.text_xl()
.overflow_hidden()
- .text_overflow(TextOverflow::Ellipsis(""))
+ .text_overflow(TextOverflow::Truncate("".into()))
.border_1()
.border_color(gpui::green())
.child("TRUNCATE: ".to_owned() + text),
@@ -83,7 +83,7 @@ impl Render for HelloWorld {
.flex_shrink_0()
.text_xl()
.overflow_hidden()
- .text_overflow(TextOverflow::Ellipsis(""))
+ .text_overflow(TextOverflow::Truncate("".into()))
.line_clamp(3)
.border_1()
.border_color(gpui::green())
@@ -104,7 +104,7 @@ impl Render for WindowShadow {
.when(!tiling.left, |div| div.border_l(border_size))
.when(!tiling.right, |div| div.border_r(border_size))
.when(!tiling.is_tiled(), |div| {
- div.shadow(smallvec::smallvec![gpui::BoxShadow {
+ div.shadow(vec![gpui::BoxShadow {
color: Hsla {
h: 0.,
s: 0.,
@@ -144,7 +144,7 @@ impl Render for WindowShadow {
.w(px(200.0))
.h(px(100.0))
.bg(green())
- .shadow(smallvec::smallvec![gpui::BoxShadow {
+ .shadow(vec![gpui::BoxShadow {
color: Hsla {
h: 0.,
s: 0.,
@@ -30,6 +30,8 @@ use smallvec::SmallVec;
pub use test_context::*;
use util::{ResultExt, debug_panic};
+#[cfg(any(feature = "inspector", debug_assertions))]
+use crate::InspectorElementRegistry;
use crate::{
Action, ActionBuildError, ActionRegistry, Any, AnyView, AnyWindowHandle, AppContext, Asset,
AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId,
@@ -281,6 +283,10 @@ pub struct App {
pub(crate) window_invalidators_by_entity:
FxHashMap<EntityId, FxHashMap<WindowId, WindowInvalidator>>,
pub(crate) tracked_entities: FxHashMap<WindowId, FxHashSet<EntityId>>,
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub(crate) inspector_renderer: Option<crate::InspectorRenderer>,
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub(crate) inspector_element_registry: InspectorElementRegistry,
#[cfg(any(test, feature = "test-support", debug_assertions))]
pub(crate) name: Option<&'static str>,
quitting: bool,
@@ -345,6 +351,10 @@ impl App {
layout_id_buffer: Default::default(),
propagate_event: true,
prompt_builder: Some(PromptBuilder::Default),
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ inspector_renderer: None,
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ inspector_element_registry: InspectorElementRegistry::default(),
quitting: false,
#[cfg(any(test, feature = "test-support", debug_assertions))]
@@ -1669,6 +1679,21 @@ impl App {
}
}
+ /// Sets the renderer for the inspector.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub fn set_inspector_renderer(&mut self, f: crate::InspectorRenderer) {
+ self.inspector_renderer = Some(f);
+ }
+
+ /// Registers a renderer specific to an inspector state.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub fn register_inspector_element<T: 'static, R: crate::IntoElement>(
+ &mut self,
+ f: impl 'static + Fn(crate::InspectorElementId, &T, &mut Window, &mut App) -> R,
+ ) {
+ self.inspector_element_registry.register(f);
+ }
+
/// Initializes gpui's default colors for the application.
///
/// These colors can be accessed through `cx.default_colors()`.
@@ -1,5 +1,9 @@
use anyhow::{Context as _, bail};
-use serde::de::{self, Deserialize, Deserializer, Visitor};
+use schemars::{JsonSchema, SchemaGenerator, schema::Schema};
+use serde::{
+ Deserialize, Deserializer, Serialize, Serializer,
+ de::{self, Visitor},
+};
use std::{
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
@@ -94,12 +98,48 @@ impl Visitor<'_> for RgbaVisitor {
}
}
+impl JsonSchema for Rgba {
+ fn schema_name() -> String {
+ "Rgba".to_string()
+ }
+
+ fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
+ use schemars::schema::{InstanceType, SchemaObject, StringValidation};
+
+ Schema::Object(SchemaObject {
+ instance_type: Some(InstanceType::String.into()),
+ string: Some(Box::new(StringValidation {
+ pattern: Some(
+ r"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$".to_string(),
+ ),
+ ..Default::default()
+ })),
+ ..Default::default()
+ })
+ }
+}
+
impl<'de> Deserialize<'de> for Rgba {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_str(RgbaVisitor)
}
}
+impl Serialize for Rgba {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let r = (self.r * 255.0).round() as u8;
+ let g = (self.g * 255.0).round() as u8;
+ let b = (self.b * 255.0).round() as u8;
+ let a = (self.a * 255.0).round() as u8;
+
+ let s = format!("#{r:02x}{g:02x}{b:02x}{a:02x}");
+ serializer.serialize_str(&s)
+ }
+}
+
impl From<Hsla> for Rgba {
fn from(color: Hsla) -> Self {
let h = color.h;
@@ -588,20 +628,35 @@ impl From<Rgba> for Hsla {
}
}
+impl JsonSchema for Hsla {
+ fn schema_name() -> String {
+ Rgba::schema_name()
+ }
+
+ fn json_schema(generator: &mut SchemaGenerator) -> Schema {
+ Rgba::json_schema(generator)
+ }
+}
+
+impl Serialize for Hsla {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ Rgba::from(*self).serialize(serializer)
+ }
+}
+
impl<'de> Deserialize<'de> for Hsla {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
- // First, deserialize it into Rgba
- let rgba = Rgba::deserialize(deserializer)?;
-
- // Then, use the From<Rgba> for Hsla implementation to convert it
- Ok(Hsla::from(rgba))
+ Ok(Rgba::deserialize(deserializer)?.into())
}
}
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub(crate) enum BackgroundTag {
Solid = 0,
@@ -614,7 +669,7 @@ pub(crate) enum BackgroundTag {
/// References:
/// - <https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method>
/// - <https://www.w3.org/TR/css-color-4/#typedef-color-space>
-#[derive(Debug, Clone, Copy, PartialEq, Default)]
+#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub enum ColorSpace {
#[default]
@@ -634,7 +689,7 @@ impl Display for ColorSpace {
}
/// A background color, which can be either a solid color or a linear gradient.
-#[derive(Clone, Copy, PartialEq)]
+#[derive(Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Background {
pub(crate) tag: BackgroundTag,
@@ -727,7 +782,7 @@ pub fn linear_gradient(
/// A color stop in a linear gradient.
///
/// <https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/linear-gradient#linear-color-stop>
-#[derive(Debug, Clone, Copy, Default, PartialEq)]
+#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct LinearColorStop {
/// The color of the color stop.
@@ -33,11 +33,16 @@
use crate::{
App, ArenaBox, AvailableSpace, Bounds, Context, DispatchNodeId, ELEMENT_ARENA, ElementId,
- FocusHandle, LayoutId, Pixels, Point, Size, Style, Window, util::FluentBuilder,
+ FocusHandle, InspectorElementId, LayoutId, Pixels, Point, Size, Style, Window,
+ util::FluentBuilder,
};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
-use std::{any::Any, fmt::Debug, mem};
+use std::{
+ any::Any,
+ fmt::{self, Debug, Display},
+ mem, panic,
+};
/// Implemented by types that participate in laying out and painting the contents of a window.
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
@@ -59,11 +64,16 @@ pub trait Element: 'static + IntoElement {
/// frames. This id must be unique among children of the first containing element with an id.
fn id(&self) -> Option<ElementId>;
+ /// Source location where this element was constructed, used to disambiguate elements in the
+ /// inspector and navigate to their source code.
+ fn source_location(&self) -> Option<&'static panic::Location<'static>>;
+
/// Before an element can be painted, we need to know where it's going to be and how big it is.
/// Use this method to request a layout from Taffy and initialize the element's state.
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState);
@@ -73,6 +83,7 @@ pub trait Element: 'static + IntoElement {
fn prepaint(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -84,6 +95,7 @@ pub trait Element: 'static + IntoElement {
fn paint(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
@@ -167,12 +179,21 @@ pub trait ParentElement {
/// An element for rendering components. An implementation detail of the [`IntoElement`] derive macro
/// for [`RenderOnce`]
#[doc(hidden)]
-pub struct Component<C: RenderOnce>(Option<C>);
+pub struct Component<C: RenderOnce> {
+ component: Option<C>,
+ #[cfg(debug_assertions)]
+ source: &'static core::panic::Location<'static>,
+}
impl<C: RenderOnce> Component<C> {
/// Create a new component from the given RenderOnce type.
+ #[track_caller]
pub fn new(component: C) -> Self {
- Component(Some(component))
+ Component {
+ component: Some(component),
+ #[cfg(debug_assertions)]
+ source: core::panic::Location::caller(),
+ }
}
}
@@ -184,13 +205,27 @@ impl<C: RenderOnce> Element for Component<C> {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ #[cfg(debug_assertions)]
+ return Some(self.source);
+
+ #[cfg(not(debug_assertions))]
+ return None;
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
- let mut element = self.0.take().unwrap().render(window, cx).into_any_element();
+ let mut element = self
+ .component
+ .take()
+ .unwrap()
+ .render(window, cx)
+ .into_any_element();
let layout_id = element.request_layout(window, cx);
(layout_id, element)
}
@@ -198,6 +233,7 @@ impl<C: RenderOnce> Element for Component<C> {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
element: &mut AnyElement,
window: &mut Window,
@@ -209,6 +245,7 @@ impl<C: RenderOnce> Element for Component<C> {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
@@ -231,6 +268,18 @@ impl<C: RenderOnce> IntoElement for Component<C> {
#[derive(Deref, DerefMut, Default, Debug, Eq, PartialEq, Hash)]
pub struct GlobalElementId(pub(crate) SmallVec<[ElementId; 32]>);
+impl Display for GlobalElementId {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ for (i, element_id) in self.0.iter().enumerate() {
+ if i > 0 {
+ write!(f, ".")?;
+ }
+ write!(f, "{}", element_id)?;
+ }
+ Ok(())
+ }
+}
+
trait ElementObject {
fn inner_element(&mut self) -> &mut dyn Any;
@@ -262,17 +311,20 @@ enum ElementDrawPhase<RequestLayoutState, PrepaintState> {
RequestLayout {
layout_id: LayoutId,
global_id: Option<GlobalElementId>,
+ inspector_id: Option<InspectorElementId>,
request_layout: RequestLayoutState,
},
LayoutComputed {
layout_id: LayoutId,
global_id: Option<GlobalElementId>,
+ inspector_id: Option<InspectorElementId>,
available_space: Size<AvailableSpace>,
request_layout: RequestLayoutState,
},
Prepaint {
node_id: DispatchNodeId,
global_id: Option<GlobalElementId>,
+ inspector_id: Option<InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: RequestLayoutState,
prepaint: PrepaintState,
@@ -297,8 +349,28 @@ impl<E: Element> Drawable<E> {
GlobalElementId(window.element_id_stack.clone())
});
- let (layout_id, request_layout) =
- self.element.request_layout(global_id.as_ref(), window, cx);
+ let inspector_id;
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ {
+ inspector_id = self.element.source_location().map(|source| {
+ let path = crate::InspectorElementPath {
+ global_id: GlobalElementId(window.element_id_stack.clone()),
+ source_location: source,
+ };
+ window.build_inspector_element_id(path)
+ });
+ }
+ #[cfg(not(any(feature = "inspector", debug_assertions)))]
+ {
+ inspector_id = None;
+ }
+
+ let (layout_id, request_layout) = self.element.request_layout(
+ global_id.as_ref(),
+ inspector_id.as_ref(),
+ window,
+ cx,
+ );
if global_id.is_some() {
window.element_id_stack.pop();
@@ -307,6 +379,7 @@ impl<E: Element> Drawable<E> {
self.phase = ElementDrawPhase::RequestLayout {
layout_id,
global_id,
+ inspector_id,
request_layout,
};
layout_id
@@ -320,11 +393,13 @@ impl<E: Element> Drawable<E> {
ElementDrawPhase::RequestLayout {
layout_id,
global_id,
+ inspector_id,
mut request_layout,
}
| ElementDrawPhase::LayoutComputed {
layout_id,
global_id,
+ inspector_id,
mut request_layout,
..
} => {
@@ -337,6 +412,7 @@ impl<E: Element> Drawable<E> {
let node_id = window.next_frame.dispatch_tree.push_node();
let prepaint = self.element.prepaint(
global_id.as_ref(),
+ inspector_id.as_ref(),
bounds,
&mut request_layout,
window,
@@ -351,6 +427,7 @@ impl<E: Element> Drawable<E> {
self.phase = ElementDrawPhase::Prepaint {
node_id,
global_id,
+ inspector_id,
bounds,
request_layout,
prepaint,
@@ -369,6 +446,7 @@ impl<E: Element> Drawable<E> {
ElementDrawPhase::Prepaint {
node_id,
global_id,
+ inspector_id,
bounds,
mut request_layout,
mut prepaint,
@@ -382,6 +460,7 @@ impl<E: Element> Drawable<E> {
window.next_frame.dispatch_tree.set_active_node(node_id);
self.element.paint(
global_id.as_ref(),
+ inspector_id.as_ref(),
bounds,
&mut request_layout,
&mut prepaint,
@@ -414,12 +493,14 @@ impl<E: Element> Drawable<E> {
ElementDrawPhase::RequestLayout {
layout_id,
global_id,
+ inspector_id,
request_layout,
} => {
window.compute_layout(layout_id, available_space, cx);
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
global_id,
+ inspector_id,
available_space,
request_layout,
};
@@ -428,6 +509,7 @@ impl<E: Element> Drawable<E> {
ElementDrawPhase::LayoutComputed {
layout_id,
global_id,
+ inspector_id,
available_space: prev_available_space,
request_layout,
} => {
@@ -437,6 +519,7 @@ impl<E: Element> Drawable<E> {
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
global_id,
+ inspector_id,
available_space,
request_layout,
};
@@ -570,9 +653,14 @@ impl Element for AnyElement {
None
}
+ fn source_location(&self) -> Option<&'static panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -583,6 +671,7 @@ impl Element for AnyElement {
fn prepaint(
&mut self,
_: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -594,6 +683,7 @@ impl Element for AnyElement {
fn paint(
&mut self,
_: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
@@ -635,9 +725,14 @@ impl Element for Empty {
None
}
+ fn source_location(&self) -> Option<&'static panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -647,6 +742,7 @@ impl Element for Empty {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_state: &mut Self::RequestLayoutState,
_window: &mut Window,
@@ -657,6 +753,7 @@ impl Element for Empty {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
@@ -1,9 +1,9 @@
use smallvec::SmallVec;
-use taffy::style::{Display, Position};
use crate::{
- AnyElement, App, Axis, Bounds, Corner, Edges, Element, GlobalElementId, IntoElement, LayoutId,
- ParentElement, Pixels, Point, Size, Style, Window, point, px,
+ AnyElement, App, Axis, Bounds, Corner, Display, Edges, Element, GlobalElementId,
+ InspectorElementId, IntoElement, LayoutId, ParentElement, Pixels, Point, Position, Size, Style,
+ Window, point, px,
};
/// The state that the anchored element element uses to track its children.
@@ -91,9 +91,14 @@ impl Element for Anchored {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (crate::LayoutId, Self::RequestLayoutState) {
@@ -117,6 +122,7 @@ impl Element for Anchored {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -213,6 +219,7 @@ impl Element for Anchored {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: crate::Bounds<crate::Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
@@ -1,6 +1,8 @@
use std::time::{Duration, Instant};
-use crate::{AnyElement, App, Element, ElementId, GlobalElementId, IntoElement, Window};
+use crate::{
+ AnyElement, App, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, Window,
+};
pub use easing::*;
use smallvec::SmallVec;
@@ -121,9 +123,14 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
Some(self.id.clone())
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (crate::LayoutId, Self::RequestLayoutState) {
@@ -172,6 +179,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: crate::Bounds<crate::Pixels>,
element: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -183,6 +191,7 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: crate::Bounds<crate::Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
@@ -1,8 +1,8 @@
use refineable::Refineable as _;
use crate::{
- App, Bounds, Element, ElementId, GlobalElementId, IntoElement, Pixels, Style, StyleRefinement,
- Styled, Window,
+ App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, Pixels,
+ Style, StyleRefinement, Styled, Window,
};
/// Construct a canvas element with the given paint callback.
@@ -42,9 +42,14 @@ impl<T: 'static> Element for Canvas<T> {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (crate::LayoutId, Self::RequestLayoutState) {
@@ -57,6 +62,7 @@ impl<T: 'static> Element for Canvas<T> {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Style,
window: &mut Window,
@@ -68,6 +74,7 @@ impl<T: 'static> Element for Canvas<T> {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
style: &mut Style,
prepaint: &mut Self::PrepaintState,
@@ -1,5 +1,6 @@
use crate::{
- AnyElement, App, Bounds, Element, GlobalElementId, IntoElement, LayoutId, Pixels, Window,
+ AnyElement, App, Bounds, Element, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
+ Pixels, Window,
};
/// Builds a `Deferred` element, which delays the layout and paint of its child.
@@ -35,9 +36,14 @@ impl Element for Deferred {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) {
@@ -48,6 +54,7 @@ impl Element for Deferred {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -61,6 +68,7 @@ impl Element for Deferred {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
@@ -18,10 +18,10 @@
use crate::{
Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, HitboxId,
- IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, ModifiersChangedEvent,
- MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
- Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, TooltipId,
- Visibility, Window, point, px, size,
+ InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
+ ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Overflow,
+ ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
+ StyleRefinement, Styled, Task, TooltipId, Visibility, Window, point, px, size,
};
use collections::HashMap;
use refineable::Refineable;
@@ -37,7 +37,6 @@ use std::{
sync::Arc,
time::Duration,
};
-use taffy::style::Overflow;
use util::ResultExt;
use super::ImageCacheProvider;
@@ -83,6 +82,35 @@ impl<T: 'static> DragMoveEvent<T> {
}
impl Interactivity {
+ /// Create an `Interactivity`, capturing the caller location in debug mode.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ #[track_caller]
+ pub fn new() -> Interactivity {
+ Interactivity {
+ source_location: Some(core::panic::Location::caller()),
+ ..Default::default()
+ }
+ }
+
+ /// Create an `Interactivity`, capturing the caller location in debug mode.
+ #[cfg(not(any(feature = "inspector", debug_assertions)))]
+ pub fn new() -> Interactivity {
+ Interactivity::default()
+ }
+
+ /// Gets the source location of construction. Returns `None` when not in debug mode.
+ pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ {
+ self.source_location
+ }
+
+ #[cfg(not(any(feature = "inspector", debug_assertions)))]
+ {
+ None
+ }
+ }
+
/// Bind the given callback to the mouse down event for the given mouse button, during the bubble phase
/// The imperative API equivalent of [`InteractiveElement::on_mouse_down`]
///
@@ -1138,17 +1166,8 @@ pub(crate) type ActionListener =
/// Construct a new [`Div`] element
#[track_caller]
pub fn div() -> Div {
- #[cfg(debug_assertions)]
- let interactivity = Interactivity {
- location: Some(*core::panic::Location::caller()),
- ..Default::default()
- };
-
- #[cfg(not(debug_assertions))]
- let interactivity = Interactivity::default();
-
Div {
- interactivity,
+ interactivity: Interactivity::new(),
children: SmallVec::default(),
prepaint_listener: None,
image_cache: None,
@@ -1191,6 +1210,20 @@ pub struct DivFrameState {
child_layout_ids: SmallVec<[LayoutId; 2]>,
}
+/// Interactivity state displayed an manipulated in the inspector.
+#[derive(Clone)]
+pub struct DivInspectorState {
+ /// The inspected element's base style. This is used for both inspecting and modifying the
+ /// state. In the future it will make sense to separate the read and write, possibly tracking
+ /// the modifications.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub base_style: Box<StyleRefinement>,
+ /// Inspects the bounds of the element.
+ pub bounds: Bounds<Pixels>,
+ /// Size of the children of the element, or `bounds.size` if it has no children.
+ pub content_size: Size<Pixels>,
+}
+
impl Styled for Div {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style
@@ -1217,9 +1250,14 @@ impl Element for Div {
self.interactivity.element_id.clone()
}
+ fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
+ self.interactivity.source_location()
+ }
+
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -1230,8 +1268,12 @@ impl Element for Div {
.map(|provider| provider.provide(window, cx));
let layout_id = window.with_image_cache(image_cache, |window| {
- self.interactivity
- .request_layout(global_id, window, cx, |style, window, cx| {
+ self.interactivity.request_layout(
+ global_id,
+ inspector_id,
+ window,
+ cx,
+ |style, window, cx| {
window.with_text_style(style.text_style().cloned(), |window| {
child_layout_ids = self
.children
@@ -1240,7 +1282,8 @@ impl Element for Div {
.collect::<SmallVec<_>>();
window.request_layout(style, child_layout_ids.iter().copied(), cx)
})
- })
+ },
+ )
});
(layout_id, DivFrameState { child_layout_ids })
@@ -1249,6 +1292,7 @@ impl Element for Div {
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -1294,6 +1338,7 @@ impl Element for Div {
self.interactivity.prepaint(
global_id,
+ inspector_id,
bounds,
content_size,
window,
@@ -1317,6 +1362,7 @@ impl Element for Div {
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Option<Hitbox>,
@@ -1331,6 +1377,7 @@ impl Element for Div {
window.with_image_cache(image_cache, |window| {
self.interactivity.paint(
global_id,
+ inspector_id,
bounds,
hitbox.as_ref(),
window,
@@ -1403,8 +1450,8 @@ pub struct Interactivity {
pub(crate) tooltip_builder: Option<TooltipBuilder>,
pub(crate) occlude_mouse: bool,
- #[cfg(debug_assertions)]
- pub(crate) location: Option<core::panic::Location<'static>>,
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub(crate) source_location: Option<&'static core::panic::Location<'static>>,
#[cfg(any(test, feature = "test-support"))]
pub(crate) debug_selector: Option<String>,
@@ -1415,10 +1462,28 @@ impl Interactivity {
pub fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId,
) -> LayoutId {
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ window.with_inspector_state(
+ _inspector_id,
+ cx,
+ |inspector_state: &mut Option<DivInspectorState>, _window| {
+ if let Some(inspector_state) = inspector_state {
+ self.base_style = inspector_state.base_style.clone();
+ } else {
+ *inspector_state = Some(DivInspectorState {
+ base_style: self.base_style.clone(),
+ bounds: Default::default(),
+ content_size: Default::default(),
+ })
+ }
+ },
+ );
+
window.with_optional_element_state::<InteractiveElementState, _>(
global_id,
|element_state, window| {
@@ -1478,6 +1543,7 @@ impl Interactivity {
pub fn prepaint<R>(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
content_size: Size<Pixels>,
window: &mut Window,
@@ -1485,6 +1551,19 @@ impl Interactivity {
f: impl FnOnce(&Style, Point<Pixels>, Option<Hitbox>, &mut Window, &mut App) -> R,
) -> R {
self.content_size = content_size;
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ window.with_inspector_state(
+ _inspector_id,
+ cx,
+ |inspector_state: &mut Option<DivInspectorState>, _window| {
+ if let Some(inspector_state) = inspector_state {
+ inspector_state.bounds = bounds;
+ inspector_state.content_size = content_size;
+ }
+ },
+ );
+
if let Some(focus_handle) = self.tracked_focus_handle.as_ref() {
window.set_focus_handle(focus_handle, cx);
}
@@ -1514,7 +1593,7 @@ impl Interactivity {
window.with_content_mask(
style.overflow_mask(bounds, window.rem_size()),
|window| {
- let hitbox = if self.should_insert_hitbox(&style) {
+ let hitbox = if self.should_insert_hitbox(&style, window, cx) {
Some(window.insert_hitbox(bounds, self.occlude_mouse))
} else {
None
@@ -1531,7 +1610,7 @@ impl Interactivity {
)
}
- fn should_insert_hitbox(&self, style: &Style) -> bool {
+ fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
self.occlude_mouse
|| style.mouse_cursor.is_some()
|| self.group.is_some()
@@ -1548,6 +1627,7 @@ impl Interactivity {
|| self.drag_listener.is_some()
|| !self.drop_listeners.is_empty()
|| self.tooltip_builder.is_some()
+ || window.is_inspector_picking(cx)
}
fn clamp_scroll_position(
@@ -1605,6 +1685,7 @@ impl Interactivity {
pub fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
hitbox: Option<&Hitbox>,
window: &mut Window,
@@ -1672,7 +1753,14 @@ impl Interactivity {
self.paint_keyboard_listeners(window, cx);
f(&style, window, cx);
- if hitbox.is_some() {
+ if let Some(_hitbox) = hitbox {
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ window.insert_inspector_hitbox(
+ _hitbox.id,
+ _inspector_id,
+ cx,
+ );
+
if let Some(group) = self.group.as_ref() {
GroupHitboxes::pop(group, cx);
}
@@ -1727,7 +1815,7 @@ impl Interactivity {
origin: hitbox.origin,
size: text.size(FONT_SIZE),
};
- if self.location.is_some()
+ if self.source_location.is_some()
&& text_bounds.contains(&window.mouse_position())
&& window.modifiers().secondary()
{
@@ -1758,7 +1846,7 @@ impl Interactivity {
window.on_mouse_event({
let hitbox = hitbox.clone();
- let location = self.location.unwrap();
+ let location = self.source_location.unwrap();
move |e: &crate::MouseDownEvent, phase, window, cx| {
if text_bounds.contains(&e.position)
&& phase.capture()
@@ -2721,37 +2809,52 @@ where
self.element.id()
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ self.element.source_location()
+ }
+
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
- self.element.request_layout(id, window, cx)
+ self.element.request_layout(id, inspector_id, window, cx)
}
fn prepaint(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
state: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> E::PrepaintState {
- self.element.prepaint(id, bounds, state, window, cx)
+ self.element
+ .prepaint(id, inspector_id, bounds, state, window, cx)
}
fn paint(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
- self.element
- .paint(id, bounds, request_layout, prepaint, window, cx)
+ self.element.paint(
+ id,
+ inspector_id,
+ bounds,
+ request_layout,
+ prepaint,
+ window,
+ cx,
+ )
}
}
@@ -2818,37 +2921,52 @@ where
self.element.id()
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ self.element.source_location()
+ }
+
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
- self.element.request_layout(id, window, cx)
+ self.element.request_layout(id, inspector_id, window, cx)
}
fn prepaint(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
state: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> E::PrepaintState {
- self.element.prepaint(id, bounds, state, window, cx)
+ self.element
+ .prepaint(id, inspector_id, bounds, state, window, cx)
}
fn paint(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
- self.element
- .paint(id, bounds, request_layout, prepaint, window, cx);
+ self.element.paint(
+ id,
+ inspector_id,
+ bounds,
+ request_layout,
+ prepaint,
+ window,
+ cx,
+ );
}
}
@@ -1,7 +1,8 @@
use crate::{
AnyElement, AnyEntity, App, AppContext, Asset, AssetLogger, Bounds, Element, ElementId, Entity,
- GlobalElementId, ImageAssetLoader, ImageCacheError, IntoElement, LayoutId, ParentElement,
- Pixels, RenderImage, Resource, Style, StyleRefinement, Styled, Task, Window, hash,
+ GlobalElementId, ImageAssetLoader, ImageCacheError, InspectorElementId, IntoElement, LayoutId,
+ ParentElement, Pixels, RenderImage, Resource, Style, StyleRefinement, Styled, Task, Window,
+ hash,
};
use futures::{FutureExt, future::Shared};
@@ -102,9 +103,14 @@ impl Element for ImageCacheElement {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -125,6 +131,7 @@ impl Element for ImageCacheElement {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -138,6 +145,7 @@ impl Element for ImageCacheElement {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
@@ -1,9 +1,9 @@
use crate::{
AbsoluteLength, AnyElement, AnyImageCache, App, Asset, AssetLogger, Bounds, DefiniteLength,
- Element, ElementId, Entity, GlobalElementId, Hitbox, Image, ImageCache, InteractiveElement,
- Interactivity, IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource,
- SMOOTH_SVG_SCALE_FACTOR, SharedString, SharedUri, StyleRefinement, Styled, SvgSize, Task,
- Window, px, swap_rgba_pa_to_bgra,
+ Element, ElementId, Entity, GlobalElementId, Hitbox, Image, ImageCache, InspectorElementId,
+ InteractiveElement, Interactivity, IntoElement, LayoutId, Length, ObjectFit, Pixels,
+ RenderImage, Resource, SMOOTH_SVG_SCALE_FACTOR, SharedString, SharedUri, StyleRefinement,
+ Styled, SvgSize, Task, Window, px, swap_rgba_pa_to_bgra,
};
use anyhow::{Context as _, Result};
@@ -194,9 +194,10 @@ pub struct Img {
}
/// Create a new image element.
+#[track_caller]
pub fn img(source: impl Into<ImageSource>) -> Img {
Img {
- interactivity: Interactivity::default(),
+ interactivity: Interactivity::new(),
source: source.into(),
style: ImageStyle::default(),
image_cache: None,
@@ -266,9 +267,14 @@ impl Element for Img {
self.interactivity.element_id.clone()
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ self.interactivity.source_location()
+ }
+
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -290,6 +296,7 @@ impl Element for Img {
let layout_id = self.interactivity.request_layout(
global_id,
+ inspector_id,
window,
cx,
|mut style, window, cx| {
@@ -408,6 +415,7 @@ impl Element for Img {
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -415,6 +423,7 @@ impl Element for Img {
) -> Self::PrepaintState {
self.interactivity.prepaint(
global_id,
+ inspector_id,
bounds,
bounds.size,
window,
@@ -432,6 +441,7 @@ impl Element for Img {
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
layout_state: &mut Self::RequestLayoutState,
hitbox: &mut Self::PrepaintState,
@@ -441,6 +451,7 @@ impl Element for Img {
let source = self.source.clone();
self.interactivity.paint(
global_id,
+ inspector_id,
bounds,
hitbox.as_ref(),
window,
@@ -9,14 +9,13 @@
use crate::{
AnyElement, App, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges, Element, EntityId,
- FocusHandle, GlobalElementId, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size,
- Style, StyleRefinement, Styled, Window, point, px, size,
+ FocusHandle, GlobalElementId, Hitbox, InspectorElementId, IntoElement, Overflow, Pixels, Point,
+ ScrollWheelEvent, Size, Style, StyleRefinement, Styled, Window, point, px, size,
};
use collections::VecDeque;
use refineable::Refineable as _;
use std::{cell::RefCell, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
-use taffy::style::Overflow;
/// Construct a new list element
pub fn list(state: ListState) -> List {
@@ -820,9 +819,14 @@ impl Element for List {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (crate::LayoutId, Self::RequestLayoutState) {
@@ -890,6 +894,7 @@ impl Element for List {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -938,6 +943,7 @@ impl Element for List {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<crate::Pixels>,
_: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
@@ -1,6 +1,6 @@
use crate::{
- App, Bounds, Element, ElementId, GlobalElementId, IntoElement, LayoutId, ObjectFit, Pixels,
- Style, StyleRefinement, Styled, Window,
+ App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
+ ObjectFit, Pixels, Style, StyleRefinement, Styled, Window,
};
#[cfg(target_os = "macos")]
use core_video::pixel_buffer::CVPixelBuffer;
@@ -53,9 +53,14 @@ impl Element for Surface {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -68,6 +73,7 @@ impl Element for Surface {
fn prepaint(
&mut self,
_global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_window: &mut Window,
@@ -78,6 +84,7 @@ impl Element for Surface {
fn paint(
&mut self,
_global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
#[cfg_attr(not(target_os = "macos"), allow(unused_variables))] bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
@@ -1,7 +1,8 @@
use crate::{
- App, Bounds, Element, GlobalElementId, Hitbox, InteractiveElement, Interactivity, IntoElement,
- LayoutId, Pixels, Point, Radians, SharedString, Size, StyleRefinement, Styled,
- TransformationMatrix, Window, geometry::Negate as _, point, px, radians, size,
+ App, Bounds, Element, GlobalElementId, Hitbox, InspectorElementId, InteractiveElement,
+ Interactivity, IntoElement, LayoutId, Pixels, Point, Radians, SharedString, Size,
+ StyleRefinement, Styled, TransformationMatrix, Window, geometry::Negate as _, point, px,
+ radians, size,
};
use util::ResultExt;
@@ -13,9 +14,10 @@ pub struct Svg {
}
/// Create a new SVG element.
+#[track_caller]
pub fn svg() -> Svg {
Svg {
- interactivity: Interactivity::default(),
+ interactivity: Interactivity::new(),
transformation: None,
path: None,
}
@@ -44,23 +46,31 @@ impl Element for Svg {
self.interactivity.element_id.clone()
}
+ fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
+ self.interactivity.source_location()
+ }
+
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
- let layout_id =
- self.interactivity
- .request_layout(global_id, window, cx, |style, window, cx| {
- window.request_layout(style, None, cx)
- });
+ let layout_id = self.interactivity.request_layout(
+ global_id,
+ inspector_id,
+ window,
+ cx,
+ |style, window, cx| window.request_layout(style, None, cx),
+ );
(layout_id, ())
}
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -68,6 +78,7 @@ impl Element for Svg {
) -> Option<Hitbox> {
self.interactivity.prepaint(
global_id,
+ inspector_id,
bounds,
bounds.size,
window,
@@ -79,6 +90,7 @@ impl Element for Svg {
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Option<Hitbox>,
@@ -89,6 +101,7 @@ impl Element for Svg {
{
self.interactivity.paint(
global_id,
+ inspector_id,
bounds,
hitbox.as_ref(),
window,
@@ -1,8 +1,9 @@
use crate::{
ActiveTooltip, AnyView, App, Bounds, DispatchPhase, Element, ElementId, GlobalElementId,
- HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
- Pixels, Point, SharedString, Size, TextOverflow, TextRun, TextStyle, TooltipId, WhiteSpace,
- Window, WrappedLine, WrappedLineLayout, register_tooltip_mouse_handlers, set_tooltip_on_window,
+ HighlightStyle, Hitbox, InspectorElementId, IntoElement, LayoutId, MouseDownEvent,
+ MouseMoveEvent, MouseUpEvent, Pixels, Point, SharedString, Size, TextOverflow, TextRun,
+ TextStyle, TooltipId, WhiteSpace, Window, WrappedLine, WrappedLineLayout,
+ register_tooltip_mouse_handlers, set_tooltip_on_window,
};
use anyhow::Context as _;
use smallvec::SmallVec;
@@ -23,9 +24,14 @@ impl Element for &'static str {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -37,6 +43,7 @@ impl Element for &'static str {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
text_layout: &mut Self::RequestLayoutState,
_window: &mut Window,
@@ -48,6 +55,7 @@ impl Element for &'static str {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
text_layout: &mut TextLayout,
_: &mut (),
@@ -82,11 +90,14 @@ impl Element for SharedString {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
-
_id: Option<&GlobalElementId>,
-
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -98,6 +109,7 @@ impl Element for SharedString {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
text_layout: &mut Self::RequestLayoutState,
_window: &mut Window,
@@ -109,6 +121,7 @@ impl Element for SharedString {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
text_layout: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
@@ -225,9 +238,14 @@ impl Element for StyledText {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -244,6 +262,7 @@ impl Element for StyledText {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_window: &mut Window,
@@ -255,6 +274,7 @@ impl Element for StyledText {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
@@ -319,8 +339,8 @@ impl TextLayout {
None
};
- let (truncate_width, ellipsis) =
- if let Some(text_overflow) = text_style.text_overflow {
+ let (truncate_width, truncation_suffix) =
+ if let Some(text_overflow) = text_style.text_overflow.clone() {
let width = known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => match text_style.line_clamp {
Some(max_lines) => Some(x * max_lines),
@@ -330,10 +350,10 @@ impl TextLayout {
});
match text_overflow {
- TextOverflow::Ellipsis(s) => (width, Some(s)),
+ TextOverflow::Truncate(s) => (width, s),
}
} else {
- (None, None)
+ (None, "".into())
};
if let Some(text_layout) = element_state.0.borrow().as_ref() {
@@ -346,7 +366,12 @@ impl TextLayout {
let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size);
let text = if let Some(truncate_width) = truncate_width {
- line_wrapper.truncate_line(text.clone(), truncate_width, ellipsis, &mut runs)
+ line_wrapper.truncate_line(
+ text.clone(),
+ truncate_width,
+ &truncation_suffix,
+ &mut runs,
+ )
} else {
text.clone()
};
@@ -673,18 +698,24 @@ impl Element for InteractiveText {
Some(self.element_id.clone())
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
- self.text.request_layout(None, window, cx)
+ self.text.request_layout(None, inspector_id, window, cx)
}
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
state: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -706,7 +737,8 @@ impl Element for InteractiveText {
}
}
- self.text.prepaint(None, bounds, state, window, cx);
+ self.text
+ .prepaint(None, inspector_id, bounds, state, window, cx);
let hitbox = window.insert_hitbox(bounds, false);
(hitbox, interactive_state)
},
@@ -716,6 +748,7 @@ impl Element for InteractiveText {
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
hitbox: &mut Hitbox,
@@ -853,7 +886,8 @@ impl Element for InteractiveText {
);
}
- self.text.paint(None, bounds, &mut (), &mut (), window, cx);
+ self.text
+ .paint(None, inspector_id, bounds, &mut (), &mut (), window, cx);
((), interactive_state)
},
@@ -6,13 +6,12 @@
use crate::{
AnyElement, App, AvailableSpace, Bounds, ContentMask, Context, Element, ElementId, Entity,
- GlobalElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, IsZero, LayoutId,
- ListSizingBehavior, Pixels, Render, ScrollHandle, Size, StyleRefinement, Styled, Window, point,
- size,
+ GlobalElementId, Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement,
+ IsZero, LayoutId, ListSizingBehavior, Overflow, Pixels, Render, ScrollHandle, Size,
+ StyleRefinement, Styled, Window, point, size,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
-use taffy::style::Overflow;
use super::ListHorizontalSizingBehavior;
@@ -52,11 +51,7 @@ where
interactivity: Interactivity {
element_id: Some(id),
base_style: Box::new(base_style),
-
- #[cfg(debug_assertions)]
- location: Some(*core::panic::Location::caller()),
-
- ..Default::default()
+ ..Interactivity::new()
},
scroll_handle: None,
sizing_behavior: ListSizingBehavior::default(),
@@ -166,9 +161,14 @@ impl Element for UniformList {
self.interactivity.element_id.clone()
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -176,6 +176,7 @@ impl Element for UniformList {
let item_size = self.measure_item(None, window, cx);
let layout_id = self.interactivity.request_layout(
global_id,
+ inspector_id,
window,
cx,
|style, window, cx| match self.sizing_behavior {
@@ -223,6 +224,7 @@ impl Element for UniformList {
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
frame_state: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -271,6 +273,7 @@ impl Element for UniformList {
self.interactivity.prepaint(
global_id,
+ inspector_id,
bounds,
content_size,
window,
@@ -435,6 +438,7 @@ impl Element for UniformList {
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&InspectorElementId>,
bounds: Bounds<crate::Pixels>,
request_layout: &mut Self::RequestLayoutState,
hitbox: &mut Option<Hitbox>,
@@ -443,6 +447,7 @@ impl Element for UniformList {
) {
self.interactivity.paint(
global_id,
+ inspector_id,
bounds,
hitbox.as_ref(),
window,
@@ -2,13 +2,15 @@
//! can be used to describe common units, concepts, and the relationships
//! between them.
+use anyhow::{Context as _, anyhow};
use core::fmt::Debug;
use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
use refineable::Refineable;
-use serde_derive::{Deserialize, Serialize};
+use schemars::{JsonSchema, SchemaGenerator, schema::Schema};
+use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use std::{
cmp::{self, PartialOrd},
- fmt,
+ fmt::{self, Display},
hash::Hash,
ops::{Add, Div, Mul, MulAssign, Neg, Sub},
};
@@ -71,9 +73,10 @@ pub trait Along {
Eq,
Serialize,
Deserialize,
+ JsonSchema,
Hash,
)]
-#[refineable(Debug)]
+#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Point<T: Default + Clone + Debug> {
/// The x coordinate of the point.
@@ -375,12 +378,18 @@ impl<T: Clone + Default + Debug> Clone for Point<T> {
}
}
+impl<T: Default + Clone + Debug + Display> Display for Point<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "({}, {})", self.x, self.y)
+ }
+}
+
/// A structure representing a two-dimensional size with width and height in a given unit.
///
/// This struct is generic over the type `T`, which can be any type that implements `Clone`, `Default`, and `Debug`.
/// It is commonly used to specify dimensions for elements in a UI, such as a window or element.
#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
-#[refineable(Debug)]
+#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Size<T: Clone + Default + Debug> {
/// The width component of the size.
@@ -649,6 +658,12 @@ where
}
}
+impl<T: Default + Clone + Debug + Display> Display for Size<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{} × {}", self.width, self.height)
+ }
+}
+
impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
fn from(point: Point<T>) -> Self {
Self {
@@ -1541,6 +1556,18 @@ impl<T: PartialOrd + Default + Debug + Clone> Bounds<T> {
}
}
+impl<T: Default + Clone + Debug + Display + Add<T, Output = T>> Display for Bounds<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{} - {} (size {})",
+ self.origin,
+ self.bottom_right(),
+ self.size
+ )
+ }
+}
+
impl Size<DevicePixels> {
/// Converts the size from physical to logical pixels.
pub(crate) fn to_pixels(self, scale_factor: f32) -> Size<Pixels> {
@@ -1647,7 +1674,7 @@ impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
/// assert_eq!(edges.left, 40.0);
/// ```
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
-#[refineable(Debug)]
+#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Edges<T: Clone + Default + Debug> {
/// The size of the top edge.
@@ -2124,7 +2151,7 @@ impl Corner {
///
/// Each field represents the size of the corner on one side of the box: `top_left`, `top_right`, `bottom_right`, and `bottom_left`.
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
-#[refineable(Debug)]
+#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub struct Corners<T: Clone + Default + Debug> {
/// The value associated with the top left corner.
@@ -2508,16 +2535,11 @@ impl From<Percentage> for Radians {
PartialEq,
Serialize,
Deserialize,
+ JsonSchema,
)]
#[repr(transparent)]
pub struct Pixels(pub f32);
-impl std::fmt::Display for Pixels {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.write_fmt(format_args!("{}px", self.0))
- }
-}
-
impl Div for Pixels {
type Output = f32;
@@ -2584,6 +2606,30 @@ impl MulAssign<f32> for Pixels {
}
}
+impl Display for Pixels {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}px", self.0)
+ }
+}
+
+impl Debug for Pixels {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+impl TryFrom<&'_ str> for Pixels {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+ value
+ .strip_suffix("px")
+ .context("expected 'px' suffix")
+ .and_then(|number| Ok(number.parse()?))
+ .map(Self)
+ }
+}
+
impl Pixels {
/// Represents zero pixels.
pub const ZERO: Pixels = Pixels(0.0);
@@ -2706,12 +2752,6 @@ impl From<f32> for Pixels {
}
}
-impl Debug for Pixels {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} px", self.0)
- }
-}
-
impl From<Pixels> for f32 {
fn from(pixels: Pixels) -> Self {
pixels.0
@@ -2910,7 +2950,7 @@ impl Ord for ScaledPixels {
impl Debug for ScaledPixels {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} px (scaled)", self.0)
+ write!(f, "{}px (scaled)", self.0)
}
}
@@ -3032,9 +3072,27 @@ impl Mul<Pixels> for Rems {
}
}
+impl Display for Rems {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}rem", self.0)
+ }
+}
+
impl Debug for Rems {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "{} rem", self.0)
+ Display::fmt(self, f)
+ }
+}
+
+impl TryFrom<&'_ str> for Rems {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+ value
+ .strip_suffix("rem")
+ .context("expected 'rem' suffix")
+ .and_then(|number| Ok(number.parse()?))
+ .map(Self)
}
}
@@ -3044,7 +3102,7 @@ impl Debug for Rems {
/// affected by the current font size, or a number of rems, which is relative to the font size of
/// the root element. It is used for specifying dimensions that are either independent of or
/// related to the typographic scale.
-#[derive(Clone, Copy, Debug, Neg, PartialEq)]
+#[derive(Clone, Copy, Neg, PartialEq)]
pub enum AbsoluteLength {
/// A length in pixels.
Pixels(Pixels),
@@ -3126,6 +3184,87 @@ impl Default for AbsoluteLength {
}
}
+impl Display for AbsoluteLength {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Pixels(pixels) => write!(f, "{pixels}"),
+ Self::Rems(rems) => write!(f, "{rems}"),
+ }
+ }
+}
+
+impl Debug for AbsoluteLength {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+const EXPECTED_ABSOLUTE_LENGTH: &str = "number with 'px' or 'rem' suffix";
+
+impl TryFrom<&'_ str> for AbsoluteLength {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+ if let Ok(pixels) = value.try_into() {
+ Ok(Self::Pixels(pixels))
+ } else if let Ok(rems) = value.try_into() {
+ Ok(Self::Rems(rems))
+ } else {
+ Err(anyhow!(
+ "invalid AbsoluteLength '{value}', expected {EXPECTED_ABSOLUTE_LENGTH}"
+ ))
+ }
+ }
+}
+
+impl JsonSchema for AbsoluteLength {
+ fn schema_name() -> String {
+ "AbsoluteLength".to_string()
+ }
+
+ fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
+ use schemars::schema::{InstanceType, SchemaObject, StringValidation};
+
+ Schema::Object(SchemaObject {
+ instance_type: Some(InstanceType::String.into()),
+ string: Some(Box::new(StringValidation {
+ pattern: Some(r"^-?\d+(\.\d+)?(px|rem)$".to_string()),
+ ..Default::default()
+ })),
+ ..Default::default()
+ })
+ }
+}
+
+impl<'de> Deserialize<'de> for AbsoluteLength {
+ fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ struct StringVisitor;
+
+ impl de::Visitor<'_> for StringVisitor {
+ type Value = AbsoluteLength;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{EXPECTED_ABSOLUTE_LENGTH}")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
+ AbsoluteLength::try_from(value).map_err(E::custom)
+ }
+ }
+
+ deserializer.deserialize_str(StringVisitor)
+ }
+}
+
+impl Serialize for AbsoluteLength {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(&format!("{self}"))
+ }
+}
+
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
///
/// This enum represents lengths that have a specific value, as opposed to lengths that are automatically
@@ -3180,11 +3319,86 @@ impl DefiniteLength {
}
impl Debug for DefiniteLength {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+impl Display for DefiniteLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- DefiniteLength::Absolute(length) => Debug::fmt(length, f),
- DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
+ DefiniteLength::Absolute(length) => write!(f, "{length}"),
+ DefiniteLength::Fraction(fraction) => write!(f, "{}%", (fraction * 100.0) as i32),
+ }
+ }
+}
+
+const EXPECTED_DEFINITE_LENGTH: &str = "expected number with 'px', 'rem', or '%' suffix";
+
+impl TryFrom<&'_ str> for DefiniteLength {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+ if let Some(percentage) = value.strip_suffix('%') {
+ let fraction: f32 = percentage.parse::<f32>().with_context(|| {
+ format!("invalid DefiniteLength '{value}', expected {EXPECTED_DEFINITE_LENGTH}")
+ })?;
+ Ok(DefiniteLength::Fraction(fraction / 100.0))
+ } else if let Ok(absolute_length) = value.try_into() {
+ Ok(DefiniteLength::Absolute(absolute_length))
+ } else {
+ Err(anyhow!(
+ "invalid DefiniteLength '{value}', expected {EXPECTED_DEFINITE_LENGTH}"
+ ))
+ }
+ }
+}
+
+impl JsonSchema for DefiniteLength {
+ fn schema_name() -> String {
+ "DefiniteLength".to_string()
+ }
+
+ fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
+ use schemars::schema::{InstanceType, SchemaObject, StringValidation};
+
+ Schema::Object(SchemaObject {
+ instance_type: Some(InstanceType::String.into()),
+ string: Some(Box::new(StringValidation {
+ pattern: Some(r"^-?\d+(\.\d+)?(px|rem|%)$".to_string()),
+ ..Default::default()
+ })),
+ ..Default::default()
+ })
+ }
+}
+
+impl<'de> Deserialize<'de> for DefiniteLength {
+ fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ struct StringVisitor;
+
+ impl de::Visitor<'_> for StringVisitor {
+ type Value = DefiniteLength;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{EXPECTED_DEFINITE_LENGTH}")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
+ DefiniteLength::try_from(value).map_err(E::custom)
+ }
}
+
+ deserializer.deserialize_str(StringVisitor)
+ }
+}
+
+impl Serialize for DefiniteLength {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(&format!("{self}"))
}
}
@@ -3222,14 +3436,86 @@ pub enum Length {
}
impl Debug for Length {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ Display::fmt(self, f)
+ }
+}
+
+impl Display for Length {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
- Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
+ Length::Definite(definite_length) => write!(f, "{}", definite_length),
Length::Auto => write!(f, "auto"),
}
}
}
+const EXPECTED_LENGTH: &str = "expected 'auto' or number with 'px', 'rem', or '%' suffix";
+
+impl TryFrom<&'_ str> for Length {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+ if value == "auto" {
+ Ok(Length::Auto)
+ } else if let Ok(definite_length) = value.try_into() {
+ Ok(Length::Definite(definite_length))
+ } else {
+ Err(anyhow!(
+ "invalid Length '{value}', expected {EXPECTED_LENGTH}"
+ ))
+ }
+ }
+}
+
+impl JsonSchema for Length {
+ fn schema_name() -> String {
+ "Length".to_string()
+ }
+
+ fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
+ use schemars::schema::{InstanceType, SchemaObject, StringValidation};
+
+ Schema::Object(SchemaObject {
+ instance_type: Some(InstanceType::String.into()),
+ string: Some(Box::new(StringValidation {
+ pattern: Some(r"^(auto|-?\d+(\.\d+)?(px|rem|%))$".to_string()),
+ ..Default::default()
+ })),
+ ..Default::default()
+ })
+ }
+}
+
+impl<'de> Deserialize<'de> for Length {
+ fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+ struct StringVisitor;
+
+ impl de::Visitor<'_> for StringVisitor {
+ type Value = Length;
+
+ fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{EXPECTED_LENGTH}")
+ }
+
+ fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
+ Length::try_from(value).map_err(E::custom)
+ }
+ }
+
+ deserializer.deserialize_str(StringVisitor)
+ }
+}
+
+impl Serialize for Length {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(&format!("{self}"))
+ }
+}
+
/// Constructs a `DefiniteLength` representing a relative fraction of a parent size.
///
/// This function creates a `DefiniteLength` that is a specified fraction of a parent's dimension.
@@ -81,6 +81,7 @@ mod executor;
mod geometry;
mod global;
mod input;
+mod inspector;
mod interactive;
mod key_dispatch;
mod keymap;
@@ -135,6 +136,7 @@ pub use global::*;
pub use gpui_macros::{AppContext, IntoElement, Render, VisualContext, register_action, test};
pub use http_client;
pub use input::*;
+pub use inspector::*;
pub use interactive::*;
use key_dispatch::*;
pub use keymap::*;
@@ -0,0 +1,223 @@
+/// A unique identifier for an element that can be inspected.
+#[derive(Debug, Eq, PartialEq, Hash, Clone)]
+pub struct InspectorElementId {
+ /// Stable part of the ID.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub path: std::rc::Rc<InspectorElementPath>,
+ /// Disambiguates elements that have the same path.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub instance_id: usize,
+}
+
+impl Into<InspectorElementId> for &InspectorElementId {
+ fn into(self) -> InspectorElementId {
+ self.clone()
+ }
+}
+
+#[cfg(any(feature = "inspector", debug_assertions))]
+pub use conditional::*;
+
+#[cfg(any(feature = "inspector", debug_assertions))]
+mod conditional {
+ use super::*;
+ use crate::{AnyElement, App, Context, Empty, IntoElement, Render, Window};
+ use collections::FxHashMap;
+ use std::any::{Any, TypeId};
+
+ /// `GlobalElementId` qualified by source location of element construction.
+ #[derive(Debug, Eq, PartialEq, Hash)]
+ pub struct InspectorElementPath {
+ /// The path to the nearest ancestor element that has an `ElementId`.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub global_id: crate::GlobalElementId,
+ /// Source location where this element was constructed.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub source_location: &'static std::panic::Location<'static>,
+ }
+
+ impl Clone for InspectorElementPath {
+ fn clone(&self) -> Self {
+ Self {
+ global_id: crate::GlobalElementId(self.global_id.0.clone()),
+ source_location: self.source_location,
+ }
+ }
+ }
+
+ impl Into<InspectorElementPath> for &InspectorElementPath {
+ fn into(self) -> InspectorElementPath {
+ self.clone()
+ }
+ }
+
+ /// Function set on `App` to render the inspector UI.
+ pub type InspectorRenderer =
+ Box<dyn Fn(&mut Inspector, &mut Window, &mut Context<Inspector>) -> AnyElement>;
+
+ /// Manages inspector state - which element is currently selected and whether the inspector is
+ /// in picking mode.
+ pub struct Inspector {
+ active_element: Option<InspectedElement>,
+ pub(crate) pick_depth: Option<f32>,
+ }
+
+ struct InspectedElement {
+ id: InspectorElementId,
+ states: FxHashMap<TypeId, Box<dyn Any>>,
+ }
+
+ impl InspectedElement {
+ fn new(id: InspectorElementId) -> Self {
+ InspectedElement {
+ id,
+ states: FxHashMap::default(),
+ }
+ }
+ }
+
+ impl Inspector {
+ pub(crate) fn new() -> Self {
+ Self {
+ active_element: None,
+ pick_depth: Some(0.0),
+ }
+ }
+
+ pub(crate) fn select(&mut self, id: InspectorElementId, window: &mut Window) {
+ self.set_active_element_id(id, window);
+ self.pick_depth = None;
+ }
+
+ pub(crate) fn hover(&mut self, id: InspectorElementId, window: &mut Window) {
+ if self.is_picking() {
+ let changed = self.set_active_element_id(id, window);
+ if changed {
+ self.pick_depth = Some(0.0);
+ }
+ }
+ }
+
+ pub(crate) fn set_active_element_id(
+ &mut self,
+ id: InspectorElementId,
+ window: &mut Window,
+ ) -> bool {
+ let changed = Some(&id) != self.active_element_id();
+ if changed {
+ self.active_element = Some(InspectedElement::new(id));
+ window.refresh();
+ }
+ changed
+ }
+
+ /// ID of the currently hovered or selected element.
+ pub fn active_element_id(&self) -> Option<&InspectorElementId> {
+ self.active_element.as_ref().map(|e| &e.id)
+ }
+
+ pub(crate) fn with_active_element_state<T: 'static, R>(
+ &mut self,
+ window: &mut Window,
+ f: impl FnOnce(&mut Option<T>, &mut Window) -> R,
+ ) -> R {
+ let Some(active_element) = &mut self.active_element else {
+ return f(&mut None, window);
+ };
+
+ let type_id = TypeId::of::<T>();
+ let mut inspector_state = active_element
+ .states
+ .remove(&type_id)
+ .map(|state| *state.downcast().unwrap());
+
+ let result = f(&mut inspector_state, window);
+
+ if let Some(inspector_state) = inspector_state {
+ active_element
+ .states
+ .insert(type_id, Box::new(inspector_state));
+ }
+
+ result
+ }
+
+ /// Starts element picking mode, allowing the user to select elements by clicking.
+ pub fn start_picking(&mut self) {
+ self.pick_depth = Some(0.0);
+ }
+
+ /// Returns whether the inspector is currently in picking mode.
+ pub fn is_picking(&self) -> bool {
+ self.pick_depth.is_some()
+ }
+
+ /// Renders elements for all registered inspector states of the active inspector element.
+ pub fn render_inspector_states(
+ &mut self,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Vec<AnyElement> {
+ let mut elements = Vec::new();
+ if let Some(active_element) = self.active_element.take() {
+ for (type_id, state) in &active_element.states {
+ if let Some(render_inspector) = cx
+ .inspector_element_registry
+ .renderers_by_type_id
+ .remove(&type_id)
+ {
+ let mut element = (render_inspector)(
+ active_element.id.clone(),
+ state.as_ref(),
+ window,
+ cx,
+ );
+ elements.push(element);
+ cx.inspector_element_registry
+ .renderers_by_type_id
+ .insert(*type_id, render_inspector);
+ }
+ }
+
+ self.active_element = Some(active_element);
+ }
+
+ elements
+ }
+ }
+
+ impl Render for Inspector {
+ fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ if let Some(inspector_renderer) = cx.inspector_renderer.take() {
+ let result = inspector_renderer(self, window, cx);
+ cx.inspector_renderer = Some(inspector_renderer);
+ result
+ } else {
+ Empty.into_any_element()
+ }
+ }
+ }
+
+ #[derive(Default)]
+ pub(crate) struct InspectorElementRegistry {
+ renderers_by_type_id: FxHashMap<
+ TypeId,
+ Box<dyn Fn(InspectorElementId, &dyn Any, &mut Window, &mut App) -> AnyElement>,
+ >,
+ }
+
+ impl InspectorElementRegistry {
+ pub fn register<T: 'static, R: IntoElement>(
+ &mut self,
+ f: impl 'static + Fn(InspectorElementId, &T, &mut Window, &mut App) -> R,
+ ) {
+ self.renderers_by_type_id.insert(
+ TypeId::of::<T>(),
+ Box::new(move |id, value, window, cx| {
+ let value = value.downcast_ref().unwrap();
+ f(id, value, window, cx).into_any_element()
+ }),
+ );
+ }
+ }
+}
@@ -45,6 +45,7 @@ use image::codecs::gif::GifDecoder;
use image::{AnimationDecoder as _, Frame};
use parking::Unparker;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
+use schemars::JsonSchema;
use seahash::SeaHasher;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
@@ -1244,7 +1245,7 @@ pub enum PromptLevel {
}
/// The style of the cursor (pointer)
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
pub enum CursorStyle {
/// The default cursor
Arrow,
@@ -1,6 +1,9 @@
// todo("windows"): remove
#![cfg_attr(windows, allow(dead_code))]
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
use crate::{
AtlasTextureId, AtlasTile, Background, Bounds, ContentMask, Corners, Edges, Hsla, Pixels,
Point, Radians, ScaledPixels, Size, bounds_tree::BoundsTree, point,
@@ -506,7 +509,7 @@ impl From<Shadow> for Primitive {
}
/// The style of a border.
-#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[repr(C)]
pub enum BorderStyle {
/// A solid border.
@@ -13,11 +13,8 @@ use crate::{
};
use collections::HashSet;
use refineable::Refineable;
-use smallvec::SmallVec;
-pub use taffy::style::{
- AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
- Overflow, Position,
-};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
/// Use this struct for interfacing with the 'debug_below' styling from your own elements.
/// If a parent element has this style set on it, then this struct will be set as a global in
@@ -143,7 +140,7 @@ impl ObjectFit {
/// The CSS styling that can be applied to an element via the `Styled` trait
#[derive(Clone, Refineable, Debug)]
-#[refineable(Debug)]
+#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Style {
/// What layout strategy should be used?
pub display: Display,
@@ -252,7 +249,7 @@ pub struct Style {
pub corner_radii: Corners<AbsoluteLength>,
/// Box shadow of the element
- pub box_shadow: SmallVec<[BoxShadow; 2]>,
+ pub box_shadow: Vec<BoxShadow>,
/// The text style of this element
pub text: TextStyleRefinement,
@@ -279,7 +276,7 @@ impl Styled for StyleRefinement {
}
/// The value of the visibility property, similar to the CSS property `visibility`
-#[derive(Default, Clone, Copy, Debug, Eq, PartialEq)]
+#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum Visibility {
/// The element should be drawn as normal.
#[default]
@@ -289,7 +286,7 @@ pub enum Visibility {
}
/// The possible values of the box-shadow property
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct BoxShadow {
/// What color should the shadow have?
pub color: Hsla,
@@ -302,7 +299,7 @@ pub struct BoxShadow {
}
/// How to handle whitespace in text
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum WhiteSpace {
/// Normal line wrapping when text overflows the width of the element
#[default]
@@ -312,14 +309,15 @@ pub enum WhiteSpace {
}
/// How to truncate text that overflows the width of the element
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum TextOverflow {
- /// Truncate the text with an ellipsis, same as: `text-overflow: ellipsis;` in CSS
- Ellipsis(&'static str),
+ /// Truncate the text when it doesn't fit, and represent this truncation by displaying the
+ /// provided string.
+ Truncate(SharedString),
}
/// How to align text within the element
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum TextAlign {
/// Align the text to the left of the element
#[default]
@@ -334,7 +332,7 @@ pub enum TextAlign {
/// The properties that can be used to style text in GPUI
#[derive(Refineable, Clone, Debug, PartialEq)]
-#[refineable(Debug)]
+#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
pub struct TextStyle {
/// The color of the text
pub color: Hsla,
@@ -769,8 +767,9 @@ impl Default for Style {
}
/// The properties that can be applied to an underline.
-#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
-#[refineable(Debug)]
+#[derive(
+ Refineable, Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema,
+)]
pub struct UnderlineStyle {
/// The thickness of the underline.
pub thickness: Pixels,
@@ -783,8 +782,9 @@ pub struct UnderlineStyle {
}
/// The properties that can be applied to a strikethrough.
-#[derive(Refineable, Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
-#[refineable(Debug)]
+#[derive(
+ Refineable, Copy, Clone, Default, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema,
+)]
pub struct StrikethroughStyle {
/// The thickness of the strikethrough.
pub thickness: Pixels,
@@ -794,7 +794,7 @@ pub struct StrikethroughStyle {
}
/// The kinds of fill that can be applied to a shape.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum Fill {
/// A solid color fill.
Color(Background),
@@ -984,6 +984,305 @@ pub fn combine_highlights(
})
}
+/// Used to control how child nodes are aligned.
+/// For Flexbox it controls alignment in the cross axis
+/// For Grid it controls alignment in the block axis
+///
+/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items)
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)]
+// Copy of taffy::style type of the same name, to derive JsonSchema.
+pub enum AlignItems {
+ /// Items are packed toward the start of the axis
+ Start,
+ /// Items are packed toward the end of the axis
+ End,
+ /// Items are packed towards the flex-relative start of the axis.
+ ///
+ /// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent
+ /// to End. In all other cases it is equivalent to Start.
+ FlexStart,
+ /// Items are packed towards the flex-relative end of the axis.
+ ///
+ /// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent
+ /// to Start. In all other cases it is equivalent to End.
+ FlexEnd,
+ /// Items are packed along the center of the cross axis
+ Center,
+ /// Items are aligned such as their baselines align
+ Baseline,
+ /// Stretch to fill the container
+ Stretch,
+}
+/// Used to control how child nodes are aligned.
+/// Does not apply to Flexbox, and will be ignored if specified on a flex container
+/// For Grid it controls alignment in the inline axis
+///
+/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-items)
+pub type JustifyItems = AlignItems;
+/// Used to control how the specified nodes is aligned.
+/// Overrides the parent Node's `AlignItems` property.
+/// For Flexbox it controls alignment in the cross axis
+/// For Grid it controls alignment in the block axis
+///
+/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-self)
+pub type AlignSelf = AlignItems;
+/// Used to control how the specified nodes is aligned.
+/// Overrides the parent Node's `JustifyItems` property.
+/// Does not apply to Flexbox, and will be ignored if specified on a flex child
+/// For Grid it controls alignment in the inline axis
+///
+/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-self)
+pub type JustifySelf = AlignItems;
+
+/// Sets the distribution of space between and around content items
+/// For Flexbox it controls alignment in the cross axis
+/// For Grid it controls alignment in the block axis
+///
+/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content)
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)]
+// Copy of taffy::style type of the same name, to derive JsonSchema.
+pub enum AlignContent {
+ /// Items are packed toward the start of the axis
+ Start,
+ /// Items are packed toward the end of the axis
+ End,
+ /// Items are packed towards the flex-relative start of the axis.
+ ///
+ /// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent
+ /// to End. In all other cases it is equivalent to Start.
+ FlexStart,
+ /// Items are packed towards the flex-relative end of the axis.
+ ///
+ /// For flex containers with flex_direction RowReverse or ColumnReverse this is equivalent
+ /// to Start. In all other cases it is equivalent to End.
+ FlexEnd,
+ /// Items are centered around the middle of the axis
+ Center,
+ /// Items are stretched to fill the container
+ Stretch,
+ /// The first and last items are aligned flush with the edges of the container (no gap)
+ /// The gap between items is distributed evenly.
+ SpaceBetween,
+ /// The gap between the first and last items is exactly THE SAME as the gap between items.
+ /// The gaps are distributed evenly
+ SpaceEvenly,
+ /// The gap between the first and last items is exactly HALF the gap between items.
+ /// The gaps are distributed evenly in proportion to these ratios.
+ SpaceAround,
+}
+
+/// Sets the distribution of space between and around content items
+/// For Flexbox it controls alignment in the main axis
+/// For Grid it controls alignment in the inline axis
+///
+/// [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content)
+pub type JustifyContent = AlignContent;
+
+/// Sets the layout used for the children of this node
+///
+/// The default values depends on on which feature flags are enabled. The order of precedence is: Flex, Grid, Block, None.
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
+// Copy of taffy::style type of the same name, to derive JsonSchema.
+pub enum Display {
+ /// The children will follow the block layout algorithm
+ Block,
+ /// The children will follow the flexbox layout algorithm
+ #[default]
+ Flex,
+ /// The children will follow the CSS Grid layout algorithm
+ Grid,
+ /// The children will not be laid out, and will follow absolute positioning
+ None,
+}
+
+/// Controls whether flex items are forced onto one line or can wrap onto multiple lines.
+///
+/// Defaults to [`FlexWrap::NoWrap`]
+///
+/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-wrap-property)
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
+// Copy of taffy::style type of the same name, to derive JsonSchema.
+pub enum FlexWrap {
+ /// Items will not wrap and stay on a single line
+ #[default]
+ NoWrap,
+ /// Items will wrap according to this item's [`FlexDirection`]
+ Wrap,
+ /// Items will wrap in the opposite direction to this item's [`FlexDirection`]
+ WrapReverse,
+}
+
+/// The direction of the flexbox layout main axis.
+///
+/// There are always two perpendicular layout axes: main (or primary) and cross (or secondary).
+/// Adding items will cause them to be positioned adjacent to each other along the main axis.
+/// By varying this value throughout your tree, you can create complex axis-aligned layouts.
+///
+/// Items are always aligned relative to the cross axis, and justified relative to the main axis.
+///
+/// The default behavior is [`FlexDirection::Row`].
+///
+/// [Specification](https://www.w3.org/TR/css-flexbox-1/#flex-direction-property)
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
+// Copy of taffy::style type of the same name, to derive JsonSchema.
+pub enum FlexDirection {
+ /// Defines +x as the main axis
+ ///
+ /// Items will be added from left to right in a row.
+ #[default]
+ Row,
+ /// Defines +y as the main axis
+ ///
+ /// Items will be added from top to bottom in a column.
+ Column,
+ /// Defines -x as the main axis
+ ///
+ /// Items will be added from right to left in a row.
+ RowReverse,
+ /// Defines -y as the main axis
+ ///
+ /// Items will be added from bottom to top in a column.
+ ColumnReverse,
+}
+
+/// How children overflowing their container should affect layout
+///
+/// In CSS the primary effect of this property is to control whether contents of a parent container that overflow that container should
+/// be displayed anyway, be clipped, or trigger the container to become a scroll container. However it also has secondary effects on layout,
+/// the main ones being:
+///
+/// - The automatic minimum size Flexbox/CSS Grid items with non-`Visible` overflow is `0` rather than being content based
+/// - `Overflow::Scroll` nodes have space in the layout reserved for a scrollbar (width controlled by the `scrollbar_width` property)
+///
+/// In Taffy, we only implement the layout related secondary effects as we are not concerned with drawing/painting. The amount of space reserved for
+/// a scrollbar is controlled by the `scrollbar_width` property. If this is `0` then `Scroll` behaves identically to `Hidden`.
+///
+/// <https://developer.mozilla.org/en-US/docs/Web/CSS/overflow>
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
+// Copy of taffy::style type of the same name, to derive JsonSchema.
+pub enum Overflow {
+ /// The automatic minimum size of this node as a flexbox/grid item should be based on the size of its content.
+ /// Content that overflows this node *should* contribute to the scroll region of its parent.
+ #[default]
+ Visible,
+ /// The automatic minimum size of this node as a flexbox/grid item should be based on the size of its content.
+ /// Content that overflows this node should *not* contribute to the scroll region of its parent.
+ Clip,
+ /// The automatic minimum size of this node as a flexbox/grid item should be `0`.
+ /// Content that overflows this node should *not* contribute to the scroll region of its parent.
+ Hidden,
+ /// The automatic minimum size of this node as a flexbox/grid item should be `0`. Additionally, space should be reserved
+ /// for a scrollbar. The amount of space reserved is controlled by the `scrollbar_width` property.
+ /// Content that overflows this node should *not* contribute to the scroll region of its parent.
+ Scroll,
+}
+
+/// The positioning strategy for this item.
+///
+/// This controls both how the origin is determined for the [`Style::position`] field,
+/// and whether or not the item will be controlled by flexbox's layout algorithm.
+///
+/// WARNING: this enum follows the behavior of [CSS's `position` property](https://developer.mozilla.org/en-US/docs/Web/CSS/position),
+/// which can be unintuitive.
+///
+/// [`Position::Relative`] is the default value, in contrast to the default behavior in CSS.
+#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize, JsonSchema)]
+// Copy of taffy::style type of the same name, to derive JsonSchema.
+pub enum Position {
+ /// The offset is computed relative to the final position given by the layout algorithm.
+ /// Offsets do not affect the position of any other items; they are effectively a correction factor applied at the end.
+ #[default]
+ Relative,
+ /// The offset is computed relative to this item's closest positioned ancestor, if any.
+ /// Otherwise, it is placed relative to the origin.
+ /// No space is created for the item in the page layout, and its size will not be altered.
+ ///
+ /// WARNING: to opt-out of layouting entirely, you must use [`Display::None`] instead on your [`Style`] object.
+ Absolute,
+}
+
+impl From<AlignItems> for taffy::style::AlignItems {
+ fn from(value: AlignItems) -> Self {
+ match value {
+ AlignItems::Start => Self::Start,
+ AlignItems::End => Self::End,
+ AlignItems::FlexStart => Self::FlexStart,
+ AlignItems::FlexEnd => Self::FlexEnd,
+ AlignItems::Center => Self::Center,
+ AlignItems::Baseline => Self::Baseline,
+ AlignItems::Stretch => Self::Stretch,
+ }
+ }
+}
+
+impl From<AlignContent> for taffy::style::AlignContent {
+ fn from(value: AlignContent) -> Self {
+ match value {
+ AlignContent::Start => Self::Start,
+ AlignContent::End => Self::End,
+ AlignContent::FlexStart => Self::FlexStart,
+ AlignContent::FlexEnd => Self::FlexEnd,
+ AlignContent::Center => Self::Center,
+ AlignContent::Stretch => Self::Stretch,
+ AlignContent::SpaceBetween => Self::SpaceBetween,
+ AlignContent::SpaceEvenly => Self::SpaceEvenly,
+ AlignContent::SpaceAround => Self::SpaceAround,
+ }
+ }
+}
+
+impl From<Display> for taffy::style::Display {
+ fn from(value: Display) -> Self {
+ match value {
+ Display::Block => Self::Block,
+ Display::Flex => Self::Flex,
+ Display::Grid => Self::Grid,
+ Display::None => Self::None,
+ }
+ }
+}
+
+impl From<FlexWrap> for taffy::style::FlexWrap {
+ fn from(value: FlexWrap) -> Self {
+ match value {
+ FlexWrap::NoWrap => Self::NoWrap,
+ FlexWrap::Wrap => Self::Wrap,
+ FlexWrap::WrapReverse => Self::WrapReverse,
+ }
+ }
+}
+
+impl From<FlexDirection> for taffy::style::FlexDirection {
+ fn from(value: FlexDirection) -> Self {
+ match value {
+ FlexDirection::Row => Self::Row,
+ FlexDirection::Column => Self::Column,
+ FlexDirection::RowReverse => Self::RowReverse,
+ FlexDirection::ColumnReverse => Self::ColumnReverse,
+ }
+ }
+}
+
+impl From<Overflow> for taffy::style::Overflow {
+ fn from(value: Overflow) -> Self {
+ match value {
+ Overflow::Visible => Self::Visible,
+ Overflow::Clip => Self::Clip,
+ Overflow::Hidden => Self::Hidden,
+ Overflow::Scroll => Self::Scroll,
+ }
+ }
+}
+
+impl From<Position> for taffy::style::Position {
+ fn from(value: Position) -> Self {
+ match value {
+ Position::Relative => Self::Relative,
+ Position::Absolute => Self::Absolute,
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::{blue, green, red, yellow};
@@ -1,18 +1,16 @@
use crate::{
- self as gpui, AbsoluteLength, AlignItems, BorderStyle, CursorStyle, DefiniteLength, Fill,
- FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla, JustifyContent, Length,
- SharedString, StrikethroughStyle, StyleRefinement, TextOverflow, UnderlineStyle, WhiteSpace,
- px, relative, rems,
+ self as gpui, AbsoluteLength, AlignContent, AlignItems, BorderStyle, CursorStyle,
+ DefiniteLength, Display, Fill, FlexDirection, FlexWrap, Font, FontStyle, FontWeight, Hsla,
+ JustifyContent, Length, SharedString, StrikethroughStyle, StyleRefinement, TextAlign,
+ TextOverflow, TextStyleRefinement, UnderlineStyle, WhiteSpace, px, relative, rems,
};
-use crate::{TextAlign, TextStyleRefinement};
pub use gpui_macros::{
border_style_methods, box_shadow_style_methods, cursor_style_methods, margin_style_methods,
overflow_style_methods, padding_style_methods, position_style_methods,
visibility_style_methods,
};
-use taffy::style::{AlignContent, Display};
-const ELLIPSIS: &str = "…";
+const ELLIPSIS: SharedString = SharedString::new_static("…");
/// A trait for elements that can be styled.
/// Use this to opt-in to a utility CSS-like styling API.
@@ -67,7 +65,7 @@ pub trait Styled: Sized {
fn text_ellipsis(mut self) -> Self {
self.text_style()
.get_or_insert_with(Default::default)
- .text_overflow = Some(TextOverflow::Ellipsis(ELLIPSIS));
+ .text_overflow = Some(TextOverflow::Truncate(ELLIPSIS));
self
}
@@ -250,10 +250,10 @@ trait ToTaffy<Output> {
impl ToTaffy<taffy::style::Style> for Style {
fn to_taffy(&self, rem_size: Pixels) -> taffy::style::Style {
taffy::style::Style {
- display: self.display,
+ display: self.display.into(),
overflow: self.overflow.into(),
scrollbar_width: self.scrollbar_width,
- position: self.position,
+ position: self.position.into(),
inset: self.inset.to_taffy(rem_size),
size: self.size.to_taffy(rem_size),
min_size: self.min_size.to_taffy(rem_size),
@@ -262,13 +262,13 @@ impl ToTaffy<taffy::style::Style> for Style {
margin: self.margin.to_taffy(rem_size),
padding: self.padding.to_taffy(rem_size),
border: self.border_widths.to_taffy(rem_size),
- align_items: self.align_items,
- align_self: self.align_self,
- align_content: self.align_content,
- justify_content: self.justify_content,
+ align_items: self.align_items.map(|x| x.into()),
+ align_self: self.align_self.map(|x| x.into()),
+ align_content: self.align_content.map(|x| x.into()),
+ justify_content: self.justify_content.map(|x| x.into()),
gap: self.gap.to_taffy(rem_size),
- flex_direction: self.flex_direction,
- flex_wrap: self.flex_wrap,
+ flex_direction: self.flex_direction.into(),
+ flex_wrap: self.flex_wrap.into(),
flex_basis: self.flex_basis.to_taffy(rem_size),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
@@ -583,7 +583,7 @@ impl DerefMut for LineWrapperHandle {
/// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0,
/// with 400.0 as normal.
-#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Deserialize, Serialize, JsonSchema)]
+#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, JsonSchema)]
pub struct FontWeight(pub f32);
impl Default for FontWeight {
@@ -636,7 +636,7 @@ impl FontWeight {
}
/// Allows italic or oblique faces to be selected.
-#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default)]
+#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize, JsonSchema)]
pub enum FontStyle {
/// A face that is neither italic not obliqued.
#[default]
@@ -133,21 +133,18 @@ impl LineWrapper {
&mut self,
line: SharedString,
truncate_width: Pixels,
- ellipsis: Option<&str>,
+ truncation_suffix: &str,
runs: &mut Vec<TextRun>,
) -> SharedString {
let mut width = px(0.);
- let mut ellipsis_width = px(0.);
- if let Some(ellipsis) = ellipsis {
- for c in ellipsis.chars() {
- ellipsis_width += self.width_for_char(c);
- }
- }
-
+ let mut suffix_width = truncation_suffix
+ .chars()
+ .map(|c| self.width_for_char(c))
+ .fold(px(0.0), |a, x| a + x);
let mut char_indices = line.char_indices();
let mut truncate_ix = 0;
for (ix, c) in char_indices {
- if width + ellipsis_width < truncate_width {
+ if width + suffix_width < truncate_width {
truncate_ix = ix;
}
@@ -155,9 +152,9 @@ impl LineWrapper {
width += char_width;
if width.floor() > truncate_width {
- let ellipsis = ellipsis.unwrap_or("");
- let result = SharedString::from(format!("{}{}", &line[..truncate_ix], ellipsis));
- update_runs_after_truncation(&result, ellipsis, runs);
+ let result =
+ SharedString::from(format!("{}{}", &line[..truncate_ix], truncation_suffix));
+ update_runs_after_truncation(&result, truncation_suffix, runs);
return result;
}
@@ -500,7 +497,7 @@ mod tests {
wrapper: &mut LineWrapper,
text: &'static str,
result: &'static str,
- ellipsis: Option<&str>,
+ ellipsis: &str,
) {
let dummy_run_lens = vec![text.len()];
let mut dummy_runs = generate_test_runs(&dummy_run_lens);
@@ -515,19 +512,19 @@ mod tests {
&mut wrapper,
"aa bbb cccc ddddd eeee ffff gggg",
"aa bbb cccc ddddd eeee",
- None,
+ "",
);
perform_test(
&mut wrapper,
"aa bbb cccc ddddd eeee ffff gggg",
"aa bbb cccc ddddd eee…",
- Some("…"),
+ "…",
);
perform_test(
&mut wrapper,
"aa bbb cccc ddddd eeee ffff gggg",
"aa bbb cccc dddd......",
- Some("......"),
+ "......",
);
}
@@ -545,7 +542,7 @@ mod tests {
) {
let mut dummy_runs = generate_test_runs(run_lens);
assert_eq!(
- wrapper.truncate_line(text.into(), line_width, Some("…"), &mut dummy_runs),
+ wrapper.truncate_line(text.into(), line_width, "…", &mut dummy_runs),
result
);
for (run, result_len) in dummy_runs.iter().zip(result_run_len) {
@@ -1,7 +1,7 @@
use crate::{
AnyElement, AnyEntity, AnyWeakEntity, App, Bounds, ContentMask, Context, Element, ElementId,
- Entity, EntityId, GlobalElementId, IntoElement, LayoutId, PaintIndex, Pixels,
- PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, WeakEntity,
+ Entity, EntityId, GlobalElementId, InspectorElementId, IntoElement, LayoutId, PaintIndex,
+ Pixels, PrepaintStateIndex, Render, Style, StyleRefinement, TextStyle, WeakEntity,
};
use crate::{Empty, Window};
use anyhow::Result;
@@ -33,9 +33,14 @@ impl<V: Render> Element for Entity<V> {
Some(ElementId::View(self.entity_id()))
}
+ fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -49,6 +54,7 @@ impl<V: Render> Element for Entity<V> {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -61,6 +67,7 @@ impl<V: Render> Element for Entity<V> {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
_: &mut Self::PrepaintState,
@@ -146,22 +153,32 @@ impl Element for AnyView {
Some(ElementId::View(self.entity_id()))
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
window.with_rendered_view(self.entity_id(), |window| {
- if let Some(style) = self.cached_style.as_ref() {
- let mut root_style = Style::default();
- root_style.refine(style);
- let layout_id = window.request_layout(root_style, None, cx);
- (layout_id, None)
- } else {
- let mut element = (self.render)(self, window, cx);
- let layout_id = element.request_layout(window, cx);
- (layout_id, Some(element))
+ // Disable caching when inspecting so that mouse_hit_test has all hitboxes.
+ let caching_disabled = window.is_inspector_picking(cx);
+ match self.cached_style.as_ref() {
+ Some(style) if !caching_disabled => {
+ let mut root_style = Style::default();
+ root_style.refine(style);
+ let layout_id = window.request_layout(root_style, None, cx);
+ (layout_id, None)
+ }
+ _ => {
+ let mut element = (self.render)(self, window, cx);
+ let layout_id = element.request_layout(window, cx);
+ (layout_id, Some(element))
+ }
}
})
}
@@ -169,6 +186,7 @@ impl Element for AnyView {
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
bounds: Bounds<Pixels>,
element: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -176,70 +194,69 @@ impl Element for AnyView {
) -> Option<AnyElement> {
window.set_view_id(self.entity_id());
window.with_rendered_view(self.entity_id(), |window| {
- if self.cached_style.is_some() {
- window.with_element_state::<AnyViewState, _>(
- global_id.unwrap(),
- |element_state, window| {
- let content_mask = window.content_mask();
- let text_style = window.text_style();
-
- if let Some(mut element_state) = element_state {
- if element_state.cache_key.bounds == bounds
- && element_state.cache_key.content_mask == content_mask
- && element_state.cache_key.text_style == text_style
- && !window.dirty_views.contains(&self.entity_id())
- && !window.refreshing
- {
- let prepaint_start = window.prepaint_index();
- window.reuse_prepaint(element_state.prepaint_range.clone());
- cx.entities
- .extend_accessed(&element_state.accessed_entities);
- let prepaint_end = window.prepaint_index();
- element_state.prepaint_range = prepaint_start..prepaint_end;
-
- return (None, element_state);
- }
- }
-
- let refreshing = mem::replace(&mut window.refreshing, true);
- let prepaint_start = window.prepaint_index();
- let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
- let mut element = (self.render)(self, window, cx);
- element.layout_as_root(bounds.size.into(), window, cx);
- element.prepaint_at(bounds.origin, window, cx);
- element
- });
-
- let prepaint_end = window.prepaint_index();
- window.refreshing = refreshing;
-
- (
- Some(element),
- AnyViewState {
- accessed_entities,
- prepaint_range: prepaint_start..prepaint_end,
- paint_range: PaintIndex::default()..PaintIndex::default(),
- cache_key: ViewCacheKey {
- bounds,
- content_mask,
- text_style,
- },
- },
- )
- },
- )
- } else {
- let mut element = element.take().unwrap();
+ if let Some(mut element) = element.take() {
element.prepaint(window, cx);
-
- Some(element)
+ return Some(element);
}
+
+ window.with_element_state::<AnyViewState, _>(
+ global_id.unwrap(),
+ |element_state, window| {
+ let content_mask = window.content_mask();
+ let text_style = window.text_style();
+
+ if let Some(mut element_state) = element_state {
+ if element_state.cache_key.bounds == bounds
+ && element_state.cache_key.content_mask == content_mask
+ && element_state.cache_key.text_style == text_style
+ && !window.dirty_views.contains(&self.entity_id())
+ && !window.refreshing
+ {
+ let prepaint_start = window.prepaint_index();
+ window.reuse_prepaint(element_state.prepaint_range.clone());
+ cx.entities
+ .extend_accessed(&element_state.accessed_entities);
+ let prepaint_end = window.prepaint_index();
+ element_state.prepaint_range = prepaint_start..prepaint_end;
+
+ return (None, element_state);
+ }
+ }
+
+ let refreshing = mem::replace(&mut window.refreshing, true);
+ let prepaint_start = window.prepaint_index();
+ let (mut element, accessed_entities) = cx.detect_accessed_entities(|cx| {
+ let mut element = (self.render)(self, window, cx);
+ element.layout_as_root(bounds.size.into(), window, cx);
+ element.prepaint_at(bounds.origin, window, cx);
+ element
+ });
+
+ let prepaint_end = window.prepaint_index();
+ window.refreshing = refreshing;
+
+ (
+ Some(element),
+ AnyViewState {
+ accessed_entities,
+ prepaint_range: prepaint_start..prepaint_end,
+ paint_range: PaintIndex::default()..PaintIndex::default(),
+ cache_key: ViewCacheKey {
+ bounds,
+ content_mask,
+ text_style,
+ },
+ },
+ )
+ },
+ )
})
}
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
element: &mut Self::PrepaintState,
@@ -247,7 +264,8 @@ impl Element for AnyView {
cx: &mut App,
) {
window.with_rendered_view(self.entity_id(), |window| {
- if self.cached_style.is_some() {
+ let caching_disabled = window.is_inspector_picking(cx);
+ if self.cached_style.is_some() && !caching_disabled {
window.with_element_state::<AnyViewState, _>(
global_id.unwrap(),
|element_state, window| {
@@ -1,3 +1,5 @@
+#[cfg(any(feature = "inspector", debug_assertions))]
+use crate::Inspector;
use crate::{
Action, AnyDrag, AnyElement, AnyImageCache, AnyTooltip, AnyView, App, AppContext, Arena, Asset,
AsyncWindowContext, AvailableSpace, Background, BorderStyle, Bounds, BoxShadow, Context,
@@ -13,7 +15,7 @@ use crate::{
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
TransformationMatrix, Underline, UnderlineStyle, WindowAppearance, WindowBackgroundAppearance,
WindowBounds, WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
- point, prelude::*, px, size, transparent_black,
+ point, prelude::*, px, rems, size, transparent_black,
};
use anyhow::{Context as _, Result, anyhow};
use collections::{FxHashMap, FxHashSet};
@@ -412,7 +414,7 @@ pub(crate) struct CursorStyleRequest {
}
/// An identifier for a [Hitbox].
-#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct HitboxId(usize);
impl HitboxId {
@@ -502,6 +504,10 @@ pub(crate) struct Frame {
pub(crate) cursor_styles: Vec<CursorStyleRequest>,
#[cfg(any(test, feature = "test-support"))]
pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>,
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub(crate) next_inspector_instance_ids: FxHashMap<Rc<crate::InspectorElementPath>, usize>,
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub(crate) inspector_hitboxes: FxHashMap<HitboxId, crate::InspectorElementId>,
}
#[derive(Clone, Default)]
@@ -542,6 +548,12 @@ impl Frame {
#[cfg(any(test, feature = "test-support"))]
debug_bounds: FxHashMap::default(),
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ next_inspector_instance_ids: FxHashMap::default(),
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ inspector_hitboxes: FxHashMap::default(),
}
}
@@ -557,6 +569,12 @@ impl Frame {
self.hitboxes.clear();
self.deferred_draws.clear();
self.focus = None;
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ {
+ self.next_inspector_instance_ids.clear();
+ self.inspector_hitboxes.clear();
+ }
}
pub(crate) fn hit_test(&self, position: Point<Pixels>) -> HitTest {
@@ -648,6 +666,8 @@ pub struct Window {
pub(crate) pending_input_observers: SubscriberSet<(), AnyObserver>,
prompt: Option<RenderablePromptHandle>,
pub(crate) client_inset: Option<Pixels>,
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ inspector: Option<Entity<Inspector>>,
}
#[derive(Clone, Debug, Default)]
@@ -935,6 +955,8 @@ impl Window {
prompt: None,
client_inset: None,
image_cache_stack: Vec::new(),
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ inspector: None,
})
}
@@ -1658,9 +1680,30 @@ impl Window {
self.invalidator.set_phase(DrawPhase::Prepaint);
self.tooltip_bounds.take();
+ let _inspector_width: Pixels = rems(30.0).to_pixels(self.rem_size());
+ let root_size = {
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ {
+ if self.inspector.is_some() {
+ let mut size = self.viewport_size;
+ size.width = (size.width - _inspector_width).max(px(0.0));
+ size
+ } else {
+ self.viewport_size
+ }
+ }
+ #[cfg(not(any(feature = "inspector", debug_assertions)))]
+ {
+ self.viewport_size
+ }
+ };
+
// Layout all root elements.
let mut root_element = self.root.as_ref().unwrap().clone().into_any();
- root_element.prepaint_as_root(Point::default(), self.viewport_size.into(), self, cx);
+ root_element.prepaint_as_root(Point::default(), root_size.into(), self, cx);
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ let inspector_element = self.prepaint_inspector(_inspector_width, cx);
let mut sorted_deferred_draws =
(0..self.next_frame.deferred_draws.len()).collect::<SmallVec<[_; 8]>>();
@@ -1672,7 +1715,7 @@ impl Window {
let mut tooltip_element = None;
if let Some(prompt) = self.prompt.take() {
let mut element = prompt.view.any_view().into_any();
- element.prepaint_as_root(Point::default(), self.viewport_size.into(), self, cx);
+ element.prepaint_as_root(Point::default(), root_size.into(), self, cx);
prompt_element = Some(element);
self.prompt = Some(prompt);
} else if let Some(active_drag) = cx.active_drag.take() {
@@ -1691,6 +1734,9 @@ impl Window {
self.invalidator.set_phase(DrawPhase::Paint);
root_element.paint(self, cx);
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ self.paint_inspector(inspector_element, cx);
+
self.paint_deferred_draws(&sorted_deferred_draws, cx);
if let Some(mut prompt_element) = prompt_element {
@@ -1700,6 +1746,9 @@ impl Window {
} else if let Some(mut tooltip_element) = tooltip_element {
tooltip_element.paint(self, cx);
}
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ self.paint_inspector_hitbox(cx);
}
fn prepaint_tooltip(&mut self, cx: &mut App) -> Option<AnyElement> {
@@ -3200,6 +3249,13 @@ impl Window {
self.reset_cursor_style(cx);
}
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ if self.is_inspector_picking(cx) {
+ self.handle_inspector_mouse_event(event, cx);
+ // When inspector is picking, all other mouse handling is skipped.
+ return;
+ }
+
let mut mouse_listeners = mem::take(&mut self.rendered_frame.mouse_listeners);
// Capture phase, events bubble from back to front. Handlers for this phase are used for
@@ -3830,6 +3886,197 @@ impl Window {
pub fn gpu_specs(&self) -> Option<GpuSpecs> {
self.platform_window.gpu_specs()
}
+
+ /// Toggles the inspector mode on this window.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub fn toggle_inspector(&mut self, cx: &mut App) {
+ self.inspector = match self.inspector {
+ None => Some(cx.new(|_| Inspector::new())),
+ Some(_) => None,
+ };
+ self.refresh();
+ }
+
+ /// Returns true if the window is in inspector mode.
+ pub fn is_inspector_picking(&self, _cx: &App) -> bool {
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ {
+ if let Some(inspector) = &self.inspector {
+ return inspector.read(_cx).is_picking();
+ }
+ }
+ false
+ }
+
+ /// Executes the provided function with mutable access to an inspector state.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub fn with_inspector_state<T: 'static, R>(
+ &mut self,
+ _inspector_id: Option<&crate::InspectorElementId>,
+ cx: &mut App,
+ f: impl FnOnce(&mut Option<T>, &mut Self) -> R,
+ ) -> R {
+ if let Some(inspector_id) = _inspector_id {
+ if let Some(inspector) = &self.inspector {
+ let inspector = inspector.clone();
+ let active_element_id = inspector.read(cx).active_element_id();
+ if Some(inspector_id) == active_element_id {
+ return inspector.update(cx, |inspector, _cx| {
+ inspector.with_active_element_state(self, f)
+ });
+ }
+ }
+ }
+ f(&mut None, self)
+ }
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub(crate) fn build_inspector_element_id(
+ &mut self,
+ path: crate::InspectorElementPath,
+ ) -> crate::InspectorElementId {
+ self.invalidator.debug_assert_paint_or_prepaint();
+ let path = Rc::new(path);
+ let next_instance_id = self
+ .next_frame
+ .next_inspector_instance_ids
+ .entry(path.clone())
+ .or_insert(0);
+ let instance_id = *next_instance_id;
+ *next_instance_id += 1;
+ crate::InspectorElementId { path, instance_id }
+ }
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ fn prepaint_inspector(&mut self, inspector_width: Pixels, cx: &mut App) -> Option<AnyElement> {
+ if let Some(inspector) = self.inspector.take() {
+ let mut inspector_element = AnyView::from(inspector.clone()).into_any_element();
+ inspector_element.prepaint_as_root(
+ point(self.viewport_size.width - inspector_width, px(0.0)),
+ size(inspector_width, self.viewport_size.height).into(),
+ self,
+ cx,
+ );
+ self.inspector = Some(inspector);
+ Some(inspector_element)
+ } else {
+ None
+ }
+ }
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ fn paint_inspector(&mut self, mut inspector_element: Option<AnyElement>, cx: &mut App) {
+ if let Some(mut inspector_element) = inspector_element {
+ inspector_element.paint(self, cx);
+ };
+ }
+
+ /// Registers a hitbox that can be used for inspector picking mode, allowing users to select and
+ /// inspect UI elements by clicking on them.
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ pub fn insert_inspector_hitbox(
+ &mut self,
+ hitbox_id: HitboxId,
+ inspector_id: Option<&crate::InspectorElementId>,
+ cx: &App,
+ ) {
+ self.invalidator.debug_assert_paint_or_prepaint();
+ if !self.is_inspector_picking(cx) {
+ return;
+ }
+ if let Some(inspector_id) = inspector_id {
+ self.next_frame
+ .inspector_hitboxes
+ .insert(hitbox_id, inspector_id.clone());
+ }
+ }
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ fn paint_inspector_hitbox(&mut self, cx: &App) {
+ if let Some(inspector) = self.inspector.as_ref() {
+ let inspector = inspector.read(cx);
+ if let Some((hitbox_id, _)) = self.hovered_inspector_hitbox(inspector, &self.next_frame)
+ {
+ if let Some(hitbox) = self
+ .next_frame
+ .hitboxes
+ .iter()
+ .find(|hitbox| hitbox.id == hitbox_id)
+ {
+ self.paint_quad(crate::fill(hitbox.bounds, crate::rgba(0x61afef4d)));
+ }
+ }
+ }
+ }
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ fn handle_inspector_mouse_event(&mut self, event: &dyn Any, cx: &mut App) {
+ let Some(inspector) = self.inspector.clone() else {
+ return;
+ };
+ if event.downcast_ref::<MouseMoveEvent>().is_some() {
+ inspector.update(cx, |inspector, _cx| {
+ if let Some((_, inspector_id)) =
+ self.hovered_inspector_hitbox(inspector, &self.rendered_frame)
+ {
+ inspector.hover(inspector_id, self);
+ }
+ });
+ } else if event.downcast_ref::<crate::MouseDownEvent>().is_some() {
+ inspector.update(cx, |inspector, _cx| {
+ if let Some((_, inspector_id)) =
+ self.hovered_inspector_hitbox(inspector, &self.rendered_frame)
+ {
+ inspector.select(inspector_id, self);
+ }
+ });
+ } else if let Some(event) = event.downcast_ref::<crate::ScrollWheelEvent>() {
+ // This should be kept in sync with SCROLL_LINES in x11 platform.
+ const SCROLL_LINES: f32 = 3.0;
+ const SCROLL_PIXELS_PER_LAYER: f32 = 36.0;
+ let delta_y = event
+ .delta
+ .pixel_delta(px(SCROLL_PIXELS_PER_LAYER / SCROLL_LINES))
+ .y;
+ if let Some(inspector) = self.inspector.clone() {
+ inspector.update(cx, |inspector, _cx| {
+ if let Some(depth) = inspector.pick_depth.as_mut() {
+ *depth += delta_y.0 / SCROLL_PIXELS_PER_LAYER;
+ let max_depth = self.mouse_hit_test.0.len() as f32 - 0.5;
+ if *depth < 0.0 {
+ *depth = 0.0;
+ } else if *depth > max_depth {
+ *depth = max_depth;
+ }
+ if let Some((_, inspector_id)) =
+ self.hovered_inspector_hitbox(inspector, &self.rendered_frame)
+ {
+ inspector.set_active_element_id(inspector_id.clone(), self);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ #[cfg(any(feature = "inspector", debug_assertions))]
+ fn hovered_inspector_hitbox(
+ &self,
+ inspector: &Inspector,
+ frame: &Frame,
+ ) -> Option<(HitboxId, crate::InspectorElementId)> {
+ if let Some(pick_depth) = inspector.pick_depth {
+ let depth = (pick_depth as i64).try_into().unwrap_or(0);
+ let max_skipped = self.mouse_hit_test.0.len().saturating_sub(1);
+ let skip_count = (depth as usize).min(max_skipped);
+ for hitbox_id in self.mouse_hit_test.0.iter().skip(skip_count) {
+ if let Some(inspector_id) = frame.inspector_hitboxes.get(hitbox_id) {
+ return Some((*hitbox_id, inspector_id.clone()));
+ }
+ }
+ }
+ return None;
+ }
}
// #[derive(Clone, Copy, Eq, PartialEq, Hash)]
@@ -4069,7 +4316,7 @@ pub enum ElementId {
FocusHandle(FocusId),
/// A combination of a name and an integer.
NamedInteger(SharedString, u64),
- /// A path
+ /// A path.
Path(Arc<std::path::Path>),
}
@@ -13,6 +13,7 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
{
type Element = gpui::Component<Self>;
+ #[track_caller]
fn into_element(self) -> Self::Element {
gpui::Component::new(self)
}
@@ -393,7 +393,7 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
let output = quote! {
/// Sets the box shadow of the element.
/// [Docs](https://tailwindcss.com/docs/box-shadow)
- #visibility fn shadow(mut self, shadows: smallvec::SmallVec<[gpui::BoxShadow; 2]>) -> Self {
+ #visibility fn shadow(mut self, shadows: std::vec::Vec<gpui::BoxShadow>) -> Self {
self.style().box_shadow = Some(shadows);
self
}
@@ -409,9 +409,9 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
/// [Docs](https://tailwindcss.com/docs/box-shadow)
#visibility fn shadow_sm(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
- use smallvec::smallvec;
+ use std::vec;
- self.style().box_shadow = Some(smallvec
#visibility fn shadow_md(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
- use smallvec::smallvec;
+ use std::vec;
- self.style().box_shadow = Some(smallvec
#visibility fn shadow_lg(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
- use smallvec::smallvec;
+ use std::vec;
- self.style().box_shadow = Some(smallvec
#visibility fn shadow_xl(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
- use smallvec::smallvec;
+ use std::vec;
- self.style().box_shadow = Some(smallvec
#visibility fn shadow_2xl(mut self) -> Self {
use gpui::{BoxShadow, hsla, point, px};
- use smallvec::smallvec;
+ use std::vec;
- self.style().box_shadow = Some(smallvec![BoxShadow {
+ self.style().box_shadow = Some(vec![BoxShadow {
color: hsla(0., 0., 0., 0.25),
offset: point(px(0.), px(25.)),
blur_radius: px(50.),
@@ -0,0 +1,28 @@
+[package]
+name = "inspector_ui"
+version = "0.1.0"
+publish.workspace = true
+edition.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/inspector_ui.rs"
+
+[dependencies]
+anyhow.workspace = true
+command_palette_hooks.workspace = true
+editor.workspace = true
+gpui.workspace = true
+language.workspace = true
+project.workspace = true
+serde_json.workspace = true
+serde_json_lenient.workspace = true
+theme.workspace = true
+ui.workspace = true
+util.workspace = true
+workspace.workspace = true
+workspace-hack.workspace = true
+zed_actions.workspace = true
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,84 @@
+# Inspector
+
+This is a tool for inspecting and manipulating rendered elements in Zed. It is
+only available in debug builds. Use the `dev::ToggleInspector` action to toggle
+inspector mode and click on UI elements to inspect them.
+
+# Current features
+
+* Picking of elements via the mouse, with scroll wheel to inspect occluded elements.
+
+* Temporary manipulation of the selected element.
+
+* Layout info and JSON-based style manipulation for `Div`.
+
+* Navigation to code that constructed the element.
+
+# Known bugs
+
+* The style inspector buffer will leak memory over time due to building up
+history on each change of inspected element. Instead of using `Project` to
+create it, should just directly build the `Buffer` and `File` each time the inspected element changes.
+
+# Future features
+
+* Info and manipulation of element types other than `Div`.
+
+* Ability to highlight current element after it's been picked.
+
+* Indicate when the picked element has disappeared.
+
+* Hierarchy view?
+
+## Better manipulation than JSON
+
+The current approach is not easy to move back to the code. Possibilities:
+
+* Editable list of style attributes to apply.
+
+* Rust buffer of code that does a very lenient parse to get the style attributes. Some options:
+
+ - Take all the identifier-like tokens and use them if they are the name of an attribute. A custom completion provider in a buffer could be used.
+
+ - Use TreeSitter to parse out the fluent style method chain. With this approach the buffer could even be the actual code file. Tricky part of this is LSP - ideally the LSP already being used by the developer's Zed would be used.
+
+## Source locations
+
+* Mode to navigate to source code on every element change while picking.
+
+* Tracking of more source locations - currently the source location is often in a ui compoenent. Ideally this would have a way for the components to indicate that they are probably not the source location the user is looking for.
+
+## Persistent modification
+
+Currently, element modifications disappear when picker mode is started. Handling this well is tricky. Potential features:
+
+* Support modifying multiple elements at once. This requires a way to specify which elements are modified - possibly wildcards in a match of the `InspectorElementId` path. This might default to ignoring all numeric parts and just matching on the names.
+
+* Show a list of active modifications in the UI.
+
+* Support for modifications being partial overrides instead of snapshots. A trickiness here is that multiple modifications may apply to the same element.
+
+* The code should probably distinguish the data that is provided by the element and the modifications from the inspector. Currently these are conflated in element states.
+
+# Code cleanups
+
+## Remove special side pane rendering
+
+Currently the inspector has special rendering in the UI, but maybe it could just be a workspace item.
+
+## Pull more inspector logic out of GPUI
+
+Currently `crates/gpui/inspector.rs` and `crates/inspector_ui/inspector.rs` are quite entangled. It seems cleaner to pull as much logic a possible out of GPUI.
+
+## Cleaner lifecycle for inspector state viewers / editors
+
+Currently element state inspectors are just called on render. Ideally instead they would be implementors of some trait like:
+
+```
+trait StateInspector: Render {
+ fn new(cx: &mut App) -> Task<Self>;
+ fn element_changed(inspector_id: &InspectorElementId, window: &mut Window, cx: &mut App);
+}
+```
+
+See `div_inspector.rs` - it needs to initialize itself, keep track of its own loading state, and keep track of the last inspected ID in its render function.
@@ -0,0 +1,20 @@
+fn main() {
+ let cargo_manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
+ let mut path = std::path::PathBuf::from(&cargo_manifest_dir);
+
+ if path.file_name().as_ref().and_then(|name| name.to_str()) != Some("inspector_ui") {
+ panic!(
+ "expected CARGO_MANIFEST_DIR to end with crates/inspector_ui, but got {cargo_manifest_dir}"
+ );
+ }
+ path.pop();
+
+ if path.file_name().as_ref().and_then(|name| name.to_str()) != Some("crates") {
+ panic!(
+ "expected CARGO_MANIFEST_DIR to end with crates/inspector_ui, but got {cargo_manifest_dir}"
+ );
+ }
+ path.pop();
+
+ println!("cargo:rustc-env=ZED_REPO_DIR={}", path.display());
+}
@@ -0,0 +1,223 @@
+use anyhow::Result;
+use editor::{Editor, EditorEvent, EditorMode, MultiBuffer};
+use gpui::{
+ AsyncWindowContext, DivInspectorState, Entity, InspectorElementId, IntoElement, WeakEntity,
+ Window,
+};
+use language::Buffer;
+use language::language_settings::SoftWrap;
+use project::{Project, ProjectPath};
+use std::path::Path;
+use ui::{Label, LabelSize, Tooltip, prelude::*, v_flex};
+
+/// Path used for unsaved buffer that contains style json. To support the json language server, this
+/// matches the name used in the generated schemas.
+const ZED_INSPECTOR_STYLE_PATH: &str = "/zed-inspector-style.json";
+
+pub(crate) struct DivInspector {
+ project: Entity<Project>,
+ inspector_id: Option<InspectorElementId>,
+ state: Option<DivInspectorState>,
+ style_buffer: Option<Entity<Buffer>>,
+ style_editor: Option<Entity<Editor>>,
+ last_error: Option<SharedString>,
+}
+
+impl DivInspector {
+ pub fn new(
+ project: Entity<Project>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> DivInspector {
+ // Open the buffer once, so it can then be used for each editor.
+ cx.spawn_in(window, {
+ let project = project.clone();
+ async move |this, cx| Self::open_style_buffer(project, this, cx).await
+ })
+ .detach();
+
+ DivInspector {
+ project,
+ inspector_id: None,
+ state: None,
+ style_buffer: None,
+ style_editor: None,
+ last_error: None,
+ }
+ }
+
+ async fn open_style_buffer(
+ project: Entity<Project>,
+ this: WeakEntity<DivInspector>,
+ cx: &mut AsyncWindowContext,
+ ) -> Result<()> {
+ let worktree = project
+ .update(cx, |project, cx| {
+ project.create_worktree(ZED_INSPECTOR_STYLE_PATH, false, cx)
+ })?
+ .await?;
+
+ let project_path = worktree.read_with(cx, |worktree, _cx| ProjectPath {
+ worktree_id: worktree.id(),
+ path: Path::new("").into(),
+ })?;
+
+ let style_buffer = project
+ .update(cx, |project, cx| project.open_path(project_path, cx))?
+ .await?
+ .1;
+
+ project.update(cx, |project, cx| {
+ project.register_buffer_with_language_servers(&style_buffer, cx)
+ })?;
+
+ this.update_in(cx, |this, window, cx| {
+ this.style_buffer = Some(style_buffer);
+ if let Some(id) = this.inspector_id.clone() {
+ let state =
+ window.with_inspector_state(Some(&id), cx, |state, _window| state.clone());
+ if let Some(state) = state {
+ this.update_inspected_element(&id, state, window, cx);
+ cx.notify();
+ }
+ }
+ })?;
+
+ Ok(())
+ }
+
+ pub fn update_inspected_element(
+ &mut self,
+ id: &InspectorElementId,
+ state: DivInspectorState,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let base_style_json = serde_json::to_string_pretty(&state.base_style);
+ self.state = Some(state);
+
+ if self.inspector_id.as_ref() == Some(id) {
+ return;
+ } else {
+ self.inspector_id = Some(id.clone());
+ }
+ let Some(style_buffer) = self.style_buffer.clone() else {
+ return;
+ };
+
+ let base_style_json = match base_style_json {
+ Ok(base_style_json) => base_style_json,
+ Err(err) => {
+ self.style_editor = None;
+ self.last_error =
+ Some(format!("Failed to convert base_style to JSON: {err}").into());
+ return;
+ }
+ };
+ self.last_error = None;
+
+ style_buffer.update(cx, |style_buffer, cx| {
+ style_buffer.set_text(base_style_json, cx)
+ });
+
+ let style_editor = cx.new(|cx| {
+ let multi_buffer = cx.new(|cx| MultiBuffer::singleton(style_buffer, cx));
+ let mut editor = Editor::new(
+ EditorMode::full(),
+ multi_buffer,
+ Some(self.project.clone()),
+ window,
+ cx,
+ );
+ editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
+ editor.set_show_line_numbers(false, cx);
+ editor.set_show_code_actions(false, cx);
+ editor.set_show_breakpoints(false, cx);
+ editor.set_show_git_diff_gutter(false, cx);
+ editor.set_show_runnables(false, cx);
+ editor.set_show_edit_predictions(Some(false), window, cx);
+ editor
+ });
+
+ cx.subscribe_in(&style_editor, window, {
+ let id = id.clone();
+ move |this, editor, event: &EditorEvent, window, cx| match event {
+ EditorEvent::BufferEdited => {
+ let base_style_json = editor.read(cx).text(cx);
+ match serde_json_lenient::from_str(&base_style_json) {
+ Ok(new_base_style) => {
+ window.with_inspector_state::<DivInspectorState, _>(
+ Some(&id),
+ cx,
+ |state, _window| {
+ if let Some(state) = state.as_mut() {
+ *state.base_style = new_base_style;
+ }
+ },
+ );
+ window.refresh();
+ this.last_error = None;
+ }
+ Err(err) => this.last_error = Some(err.to_string().into()),
+ }
+ }
+ _ => {}
+ }
+ })
+ .detach();
+
+ self.style_editor = Some(style_editor);
+ }
+}
+
+impl Render for DivInspector {
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ v_flex()
+ .size_full()
+ .gap_2()
+ .when_some(self.state.as_ref(), |this, state| {
+ this.child(
+ v_flex()
+ .child(Label::new("Layout").size(LabelSize::Large))
+ .child(render_layout_state(state, cx)),
+ )
+ })
+ .when_some(self.style_editor.as_ref(), |this, style_editor| {
+ this.child(
+ v_flex()
+ .gap_2()
+ .child(Label::new("Style").size(LabelSize::Large))
+ .child(div().h_128().child(style_editor.clone()))
+ .when_some(self.last_error.as_ref(), |this, last_error| {
+ this.child(
+ div()
+ .w_full()
+ .border_1()
+ .border_color(Color::Error.color(cx))
+ .child(Label::new(last_error)),
+ )
+ }),
+ )
+ })
+ .when_none(&self.style_editor, |this| {
+ this.child(Label::new("Loading..."))
+ })
+ .into_any_element()
+ }
+}
+
+fn render_layout_state(state: &DivInspectorState, cx: &App) -> Div {
+ v_flex()
+ .child(div().text_ui(cx).child(format!("Bounds: {}", state.bounds)))
+ .child(
+ div()
+ .id("content-size")
+ .text_ui(cx)
+ .tooltip(Tooltip::text("Size of the element's children"))
+ .child(if state.content_size != state.bounds.size {
+ format!("Content size: {}", state.content_size)
+ } else {
+ "".to_string()
+ }),
+ )
+}
@@ -0,0 +1,168 @@
+use anyhow::{Context as _, anyhow};
+use gpui::{App, DivInspectorState, Inspector, InspectorElementId, IntoElement, Window};
+use std::{cell::OnceCell, path::Path, sync::Arc};
+use ui::{Label, Tooltip, prelude::*};
+use util::{ResultExt as _, command::new_smol_command};
+use workspace::AppState;
+
+use crate::div_inspector::DivInspector;
+
+pub fn init(app_state: Arc<AppState>, cx: &mut App) {
+ cx.on_action(|_: &zed_actions::dev::ToggleInspector, cx| {
+ let Some(active_window) = cx
+ .active_window()
+ .context("no active window to toggle inspector")
+ .log_err()
+ else {
+ return;
+ };
+ // This is deferred to avoid double lease due to window already being updated.
+ cx.defer(move |cx| {
+ active_window
+ .update(cx, |_, window, cx| window.toggle_inspector(cx))
+ .log_err();
+ });
+ });
+
+ // Project used for editor buffers + LSP support
+ let project = project::Project::local(
+ app_state.client.clone(),
+ app_state.node_runtime.clone(),
+ app_state.user_store.clone(),
+ app_state.languages.clone(),
+ app_state.fs.clone(),
+ None,
+ cx,
+ );
+
+ let div_inspector = OnceCell::new();
+ cx.register_inspector_element(move |id, state: &DivInspectorState, window, cx| {
+ let div_inspector = div_inspector
+ .get_or_init(|| cx.new(|cx| DivInspector::new(project.clone(), window, cx)));
+ div_inspector.update(cx, |div_inspector, cx| {
+ div_inspector.update_inspected_element(&id, state.clone(), window, cx);
+ div_inspector.render(window, cx).into_any_element()
+ })
+ });
+
+ cx.set_inspector_renderer(Box::new(render_inspector));
+}
+
+fn render_inspector(
+ inspector: &mut Inspector,
+ window: &mut Window,
+ cx: &mut Context<Inspector>,
+) -> AnyElement {
+ let ui_font = theme::setup_ui_font(window, cx);
+ let colors = cx.theme().colors();
+ let inspector_id = inspector.active_element_id();
+ v_flex()
+ .id("gpui-inspector")
+ .size_full()
+ .bg(colors.panel_background)
+ .text_color(colors.text)
+ .font(ui_font)
+ .border_l_1()
+ .border_color(colors.border)
+ .overflow_y_scroll()
+ .child(
+ h_flex()
+ .p_2()
+ .border_b_1()
+ .border_color(colors.border_variant)
+ .child(
+ IconButton::new("pick-mode", IconName::MagnifyingGlass)
+ .tooltip(Tooltip::text("Start inspector pick mode"))
+ .selected_icon_color(Color::Selected)
+ .toggle_state(inspector.is_picking())
+ .on_click(cx.listener(|inspector, _, window, _cx| {
+ inspector.start_picking();
+ window.refresh();
+ })),
+ )
+ .child(
+ h_flex()
+ .w_full()
+ .justify_end()
+ .child(Label::new("GPUI Inspector").size(LabelSize::Large)),
+ ),
+ )
+ .child(
+ v_flex()
+ .p_2()
+ .gap_2()
+ .when_some(inspector_id, |this, inspector_id| {
+ this.child(render_inspector_id(inspector_id, cx))
+ })
+ .children(inspector.render_inspector_states(window, cx)),
+ )
+ .into_any_element()
+}
+
+fn render_inspector_id(inspector_id: &InspectorElementId, cx: &App) -> Div {
+ let source_location = inspector_id.path.source_location;
+ v_flex()
+ .child(Label::new("Element ID").size(LabelSize::Large))
+ .when(inspector_id.instance_id != 0, |this| {
+ this.child(
+ div()
+ .id("instance-id")
+ .text_ui(cx)
+ .tooltip(Tooltip::text(
+ "Disambiguates elements from the same source location",
+ ))
+ .child(format!("Instance {}", inspector_id.instance_id)),
+ )
+ })
+ .child(
+ div()
+ .id("source-location")
+ .text_ui(cx)
+ .bg(cx.theme().colors().editor_foreground.opacity(0.025))
+ .underline()
+ .child(format!("{}", source_location))
+ .tooltip(Tooltip::text("Click to open by running zed cli"))
+ .on_click(move |_, _window, cx| {
+ cx.background_spawn(open_zed_source_location(source_location))
+ .detach_and_log_err(cx);
+ }),
+ )
+ .child(
+ div()
+ .id("global-id")
+ .text_ui(cx)
+ .min_h_12()
+ .tooltip(Tooltip::text(
+ "GlobalElementId of the nearest ancestor with an ID",
+ ))
+ .child(inspector_id.path.global_id.to_string()),
+ )
+}
+
+async fn open_zed_source_location(
+ location: &'static std::panic::Location<'static>,
+) -> anyhow::Result<()> {
+ let mut path = Path::new(env!("ZED_REPO_DIR")).to_path_buf();
+ path.push(Path::new(location.file()));
+ let path_arg = format!(
+ "{}:{}:{}",
+ path.display(),
+ location.line(),
+ location.column()
+ );
+
+ let output = new_smol_command("zed")
+ .arg(&path_arg)
+ .output()
+ .await
+ .with_context(|| format!("running zed to open {path_arg} failed"))?;
+
+ if !output.status.success() {
+ Err(anyhow!(
+ "running zed to open {path_arg} failed with stderr: {}",
+ String::from_utf8_lossy(&output.stderr)
+ ))
+ } else {
+ Ok(())
+ }
+}
@@ -0,0 +1,24 @@
+#[cfg(debug_assertions)]
+mod div_inspector;
+#[cfg(debug_assertions)]
+mod inspector;
+
+#[cfg(debug_assertions)]
+pub use inspector::init;
+
+#[cfg(not(debug_assertions))]
+pub fn init(_app_state: std::sync::Arc<workspace::AppState>, cx: &mut gpui::App) {
+ use std::any::TypeId;
+ use workspace::notifications::NotifyResultExt as _;
+
+ cx.on_action(|_: &zed_actions::dev::ToggleInspector, cx| {
+ Err::<(), anyhow::Error>(anyhow::anyhow!(
+ "dev::ToggleInspector is only available in debug builds"
+ ))
+ .notify_app_err(cx);
+ });
+
+ command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| {
+ filter.hide_action_types(&[TypeId::of::<zed_actions::dev::ToggleInspector>()]);
+ });
+}
@@ -59,8 +59,10 @@ project.workspace = true
regex.workspace = true
rope.workspace = true
rust-embed.workspace = true
+schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
+serde_json_lenient.workspace = true
settings.workspace = true
smol.workspace = true
snippet_provider.workspace = true
@@ -97,6 +97,65 @@ impl JsonLspAdapter {
let tsconfig_schema = serde_json::Value::from_str(TSCONFIG_SCHEMA).unwrap();
let package_json_schema = serde_json::Value::from_str(PACKAGE_JSON_SCHEMA).unwrap();
+ #[allow(unused_mut)]
+ let mut schemas = serde_json::json!([
+ {
+ "fileMatch": ["tsconfig.json"],
+ "schema":tsconfig_schema
+ },
+ {
+ "fileMatch": ["package.json"],
+ "schema":package_json_schema
+ },
+ {
+ "fileMatch": [
+ schema_file_match(paths::settings_file()),
+ paths::local_settings_file_relative_path()
+ ],
+ "schema": settings_schema,
+ },
+ {
+ "fileMatch": [schema_file_match(paths::keymap_file())],
+ "schema": keymap_schema,
+ },
+ {
+ "fileMatch": [
+ schema_file_match(paths::tasks_file()),
+ paths::local_tasks_file_relative_path()
+ ],
+ "schema": tasks_schema,
+ },
+ {
+ "fileMatch": [
+ schema_file_match(
+ paths::snippets_dir()
+ .join("*.json")
+ .as_path()
+ )
+ ],
+ "schema": snippets_schema,
+ },
+ {
+ "fileMatch": [
+ schema_file_match(paths::debug_scenarios_file()),
+ paths::local_debug_file_relative_path()
+ ],
+ "schema": debug_schema,
+ },
+ ]);
+
+ #[cfg(debug_assertions)]
+ {
+ schemas.as_array_mut().unwrap().push(serde_json::json!(
+ {
+ "fileMatch": [
+ "zed-inspector-style.json"
+ ],
+ "schema": generate_inspector_style_schema(),
+ }
+ ))
+ }
+
// This can be viewed via `dev: open language server logs` -> `json-language-server` ->
// `Server Info`
serde_json::json!({
@@ -108,52 +167,7 @@ impl JsonLspAdapter {
{
"enable": true,
},
- "schemas": [
- {
- "fileMatch": ["tsconfig.json"],
- "schema":tsconfig_schema
- },
- {
- "fileMatch": ["package.json"],
- "schema":package_json_schema
- },
- {
- "fileMatch": [
- schema_file_match(paths::settings_file()),
- paths::local_settings_file_relative_path()
- ],
- "schema": settings_schema,
- },
- {
- "fileMatch": [schema_file_match(paths::keymap_file())],
- "schema": keymap_schema,
- },
- {
- "fileMatch": [
- schema_file_match(paths::tasks_file()),
- paths::local_tasks_file_relative_path()
- ],
- "schema": tasks_schema,
- },
- {
- "fileMatch": [
- schema_file_match(
- paths::snippets_dir()
- .join("*.json")
- .as_path()
- )
- ],
- "schema": snippets_schema,
- },
- {
- "fileMatch": [
- schema_file_match(paths::debug_scenarios_file()),
- paths::local_debug_file_relative_path()
- ],
- "schema": debug_schema,
-
- },
- ]
+ "schemas": schemas
}
})
}
@@ -180,6 +194,16 @@ impl JsonLspAdapter {
}
}
+#[cfg(debug_assertions)]
+fn generate_inspector_style_schema() -> serde_json_lenient::Value {
+ let schema = schemars::r#gen::SchemaSettings::draft07()
+ .with(|settings| settings.option_add_null_type = false)
+ .into_generator()
+ .into_root_schema_for::<gpui::StyleRefinement>();
+
+ serde_json_lenient::to_value(schema).unwrap()
+}
+
#[async_trait(?Send)]
impl LspAdapter for JsonLspAdapter {
fn name(&self) -> LanguageServerName {
@@ -715,9 +715,14 @@ impl Element for MarkdownElement {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
@@ -1189,6 +1194,7 @@ impl Element for MarkdownElement {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
rendered_markdown: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -1206,6 +1212,7 @@ impl Element for MarkdownElement {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
rendered_markdown: &mut Self::RequestLayoutState,
hitbox: &mut Self::PrepaintState,
@@ -66,8 +66,8 @@ use image_store::{ImageItemEvent, ImageStoreEvent};
use ::git::{blame::Blame, status::FileStatus};
use gpui::{
- AnyEntity, App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla,
- SharedString, Task, WeakEntity, Window,
+ App, AppContext, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Hsla, SharedString,
+ Task, WeakEntity, Window,
};
use itertools::Itertools;
use language::{
@@ -2322,7 +2322,7 @@ impl Project {
&mut self,
path: ProjectPath,
cx: &mut Context<Self>,
- ) -> Task<Result<(Option<ProjectEntryId>, AnyEntity)>> {
+ ) -> Task<Result<(Option<ProjectEntryId>, Entity<Buffer>)>> {
let task = self.open_buffer(path.clone(), cx);
cx.spawn(async move |_project, cx| {
let buffer = task.await?;
@@ -2330,8 +2330,7 @@ impl Project {
File::from_dyn(buffer.file()).and_then(|file| file.project_entry_id(cx))
})?;
- let buffer: &AnyEntity = &buffer;
- Ok((project_entry_id, buffer.clone()))
+ Ok((project_entry_id, buffer))
})
}
@@ -19,6 +19,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
let refineable_attr = attrs.iter().find(|attr| attr.path().is_ident("refineable"));
let mut impl_debug_on_refinement = false;
+ let mut derives_serialize = false;
let mut refinement_traits_to_derive = vec![];
if let Some(refineable_attr) = refineable_attr {
@@ -26,6 +27,9 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
if meta.path.is_ident("Debug") {
impl_debug_on_refinement = true;
} else {
+ if meta.path.is_ident("Serialize") {
+ derives_serialize = true;
+ }
refinement_traits_to_derive.push(meta.path);
}
Ok(())
@@ -47,6 +51,21 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
let field_visibilities: Vec<_> = fields.iter().map(|f| &f.vis).collect();
let wrapped_types: Vec<_> = fields.iter().map(|f| get_wrapper_type(f, &f.ty)).collect();
+ let field_attributes: Vec<TokenStream2> = fields
+ .iter()
+ .map(|f| {
+ if derives_serialize {
+ if is_refineable_field(f) {
+ quote! { #[serde(default, skip_serializing_if = "::refineable::IsEmpty::is_empty")] }
+ } else {
+ quote! { #[serde(skip_serializing_if = "::std::option::Option::is_none")] }
+ }
+ } else {
+ quote! {}
+ }
+ })
+ .collect();
+
// Create trait bound that each wrapped type must implement Clone // & Default
let type_param_bounds: Vec<_> = wrapped_types
.iter()
@@ -234,6 +253,26 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
quote! {}
};
+ let refinement_is_empty_conditions: Vec<TokenStream2> = fields
+ .iter()
+ .enumerate()
+ .map(|(i, field)| {
+ let name = &field.ident;
+
+ let condition = if is_refineable_field(field) {
+ quote! { self.#name.is_empty() }
+ } else {
+ quote! { self.#name.is_none() }
+ };
+
+ if i < fields.len() - 1 {
+ quote! { #condition && }
+ } else {
+ condition
+ }
+ })
+ .collect();
+
let mut derive_stream = quote! {};
for trait_to_derive in refinement_traits_to_derive {
derive_stream.extend(quote! { #[derive(#trait_to_derive)] })
@@ -246,6 +285,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
pub struct #refinement_ident #impl_generics {
#(
#[allow(missing_docs)]
+ #field_attributes
#field_visibilities #field_names: #wrapped_types
),*
}
@@ -280,6 +320,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
}
}
+ impl #impl_generics ::refineable::IsEmpty for #refinement_ident #ty_generics
+ #where_clause
+ {
+ fn is_empty(&self) -> bool {
+ #( #refinement_is_empty_conditions )*
+ }
+ }
+
impl #impl_generics From<#refinement_ident #ty_generics> for #ident #ty_generics
#where_clause
{
@@ -1,7 +1,7 @@
pub use derive_refineable::Refineable;
pub trait Refineable: Clone {
- type Refinement: Refineable<Refinement = Self::Refinement> + Default;
+ type Refinement: Refineable<Refinement = Self::Refinement> + IsEmpty + Default;
fn refine(&mut self, refinement: &Self::Refinement);
fn refined(self, refinement: Self::Refinement) -> Self;
@@ -13,6 +13,11 @@ pub trait Refineable: Clone {
}
}
+pub trait IsEmpty {
+ /// When `true`, indicates that use applying this refinement does nothing.
+ fn is_empty(&self) -> bool;
+}
+
pub struct Cascade<S: Refineable>(Vec<Option<S::Refinement>>);
impl<S: Refineable + Default> Default for Cascade<S> {
@@ -581,9 +581,14 @@ impl Element for TerminalElement {
self.interactivity.element_id.clone()
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -597,21 +602,26 @@ impl Element for TerminalElement {
}
}
- let layout_id =
- self.interactivity
- .request_layout(global_id, window, cx, |mut style, window, cx| {
- style.size.width = relative(1.).into();
- style.size.height = relative(1.).into();
- // style.overflow = point(Overflow::Hidden, Overflow::Hidden);
+ let layout_id = self.interactivity.request_layout(
+ global_id,
+ inspector_id,
+ window,
+ cx,
+ |mut style, window, cx| {
+ style.size.width = relative(1.).into();
+ style.size.height = relative(1.).into();
+ // style.overflow = point(Overflow::Hidden, Overflow::Hidden);
- window.request_layout(style, None, cx)
- });
+ window.request_layout(style, None, cx)
+ },
+ );
(layout_id, ())
}
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -620,6 +630,7 @@ impl Element for TerminalElement {
let rem_size = self.rem_size(cx);
self.interactivity.prepaint(
global_id,
+ inspector_id,
bounds,
bounds.size,
window,
@@ -904,6 +915,7 @@ impl Element for TerminalElement {
fn paint(
&mut self,
global_id: Option<&GlobalElementId>,
+ inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
_: &mut Self::RequestLayoutState,
layout: &mut Self::PrepaintState,
@@ -947,6 +959,7 @@ impl Element for TerminalElement {
let block_below_cursor_element = layout.block_below_cursor_element.take();
self.interactivity.paint(
global_id,
+ inspector_id,
bounds,
Some(&layout.hitbox),
window,
@@ -41,7 +41,7 @@ impl RenderOnce for SplitButton {
)
.child(self.right)
.bg(ElevationIndex::Surface.on_elevation_bg(cx))
- .shadow(smallvec::smallvec![BoxShadow {
+ .shadow(vec![BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.16),
offset: point(px(0.), px(1.)),
blur_radius: px(0.),
@@ -227,9 +227,14 @@ mod uniform_list {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&gpui::GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
@@ -239,6 +244,7 @@ mod uniform_list {
fn prepaint(
&mut self,
_id: Option<&gpui::GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -264,6 +270,7 @@ mod uniform_list {
fn paint(
&mut self,
_id: Option<&gpui::GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
@@ -1,7 +1,6 @@
use crate::KeyBinding;
use crate::{h_flex, prelude::*};
use gpui::{AnyElement, App, BoxShadow, FontStyle, Hsla, IntoElement, Window, point};
-use smallvec::smallvec;
use theme::Appearance;
/// Represents a hint for a keybinding, optionally with a prefix and suffix.
@@ -193,7 +192,7 @@ impl RenderOnce for KeybindingHint {
.border_1()
.border_color(border_color)
.bg(bg_color)
- .shadow(smallvec![BoxShadow {
+ .shadow(vec![BoxShadow {
color: shadow_color,
offset: point(px(0.), px(1.)),
blur_radius: px(0.),
@@ -316,9 +316,14 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
Some(self.id.clone())
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
@@ -394,6 +399,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
_bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -422,6 +428,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
_: Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
child_hitbox: &mut Option<HitboxId>,
@@ -72,7 +72,7 @@ impl RenderOnce for ProgressBar {
.py(px(2.0))
.px(px(4.0))
.bg(self.bg_color)
- .shadow(smallvec::smallvec![gpui::BoxShadow {
+ .shadow(vec![gpui::BoxShadow {
color: gpui::black().opacity(0.08),
offset: point(px(0.), px(1.)),
blur_radius: px(0.),
@@ -116,9 +116,14 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
Some(self.id.clone())
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
@@ -174,6 +179,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -200,6 +206,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
fn paint(
&mut self,
id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
_bounds: Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint_state: &mut Self::PrepaintState,
@@ -162,16 +162,20 @@ impl Scrollbar {
impl Element for Scrollbar {
type RequestLayoutState = ();
-
type PrepaintState = Hitbox;
fn id(&self) -> Option<ElementId> {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
@@ -193,6 +197,7 @@ impl Element for Scrollbar {
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -206,6 +211,7 @@ impl Element for Scrollbar {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
@@ -1,7 +1,6 @@
use std::fmt::{self, Display, Formatter};
use gpui::{App, BoxShadow, Hsla, hsla, point, px};
-use smallvec::{SmallVec, smallvec};
use theme::{ActiveTheme, Appearance};
/// Today, elevation is primarily used to add shadows to elements, and set the correct background for elements like buttons.
@@ -40,14 +39,14 @@ impl Display for ElevationIndex {
impl ElevationIndex {
/// Returns an appropriate shadow for the given elevation index.
- pub fn shadow(self, cx: &App) -> SmallVec<[BoxShadow; 2]> {
+ pub fn shadow(self, cx: &App) -> Vec<BoxShadow> {
let is_light = cx.theme().appearance() == Appearance::Light;
match self {
- ElevationIndex::Surface => smallvec![],
- ElevationIndex::EditorSurface => smallvec![],
+ ElevationIndex::Surface => vec![],
+ ElevationIndex::EditorSurface => vec![],
- ElevationIndex::ElevatedSurface => smallvec![
+ ElevationIndex::ElevatedSurface => vec![
BoxShadow {
color: hsla(0., 0., 0., 0.12),
offset: point(px(0.), px(2.)),
@@ -59,10 +58,10 @@ impl ElevationIndex {
offset: point(px(1.), px(1.)),
blur_radius: px(0.),
spread_radius: px(0.),
- }
+ },
],
- ElevationIndex::ModalSurface => smallvec![
+ ElevationIndex::ModalSurface => vec![
BoxShadow {
color: hsla(0., 0., 0., if is_light { 0.06 } else { 0.12 }),
offset: point(px(0.), px(2.)),
@@ -89,7 +88,7 @@ impl ElevationIndex {
},
],
- _ => smallvec![],
+ _ => vec![],
}
}
@@ -50,33 +50,41 @@ impl Element for WithRemSize {
Element::id(&self.div)
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ Element::source_location(&self.div)
+ }
+
fn request_layout(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
window.with_rem_size(Some(self.rem_size), |window| {
- self.div.request_layout(id, window, cx)
+ self.div.request_layout(id, inspector_id, window, cx)
})
}
fn prepaint(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
cx: &mut App,
) -> Self::PrepaintState {
window.with_rem_size(Some(self.rem_size), |window| {
- self.div.prepaint(id, bounds, request_layout, window, cx)
+ self.div
+ .prepaint(id, inspector_id, bounds, request_layout, window, cx)
})
}
fn paint(
&mut self,
id: Option<&GlobalElementId>,
+ inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
@@ -84,8 +92,15 @@ impl Element for WithRemSize {
cx: &mut App,
) {
window.with_rem_size(Some(self.rem_size), |window| {
- self.div
- .paint(id, bounds, request_layout, prepaint, window, cx)
+ self.div.paint(
+ id,
+ inspector_id,
+ bounds,
+ request_layout,
+ prepaint,
+ window,
+ cx,
+ )
})
}
}
@@ -1113,9 +1113,14 @@ mod element {
Some(self.basis.into())
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
@@ -1132,6 +1137,7 @@ mod element {
fn prepaint(
&mut self,
global_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: Bounds<Pixels>,
_state: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -1224,6 +1230,7 @@ mod element {
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
bounds: gpui::Bounds<ui::prelude::Pixels>,
_: &mut Self::RequestLayoutState,
layout: &mut Self::PrepaintState,
@@ -7277,7 +7277,7 @@ pub fn client_side_decorations(
.when(!tiling.left, |div| div.border_l(BORDER_SIZE))
.when(!tiling.right, |div| div.border_r(BORDER_SIZE))
.when(!tiling.is_tiled(), |div| {
- div.shadow(smallvec::smallvec![gpui::BoxShadow {
+ div.shadow(vec![gpui::BoxShadow {
color: Hsla {
h: 0.,
s: 0.,
@@ -67,6 +67,7 @@ http_client.workspace = true
image_viewer.workspace = true
indoc.workspace = true
inline_completion_button.workspace = true
+inspector_ui.workspace = true
install_cli.workspace = true
jj_ui.workspace = true
journal.workspace = true
@@ -574,6 +574,7 @@ fn main() {
settings_ui::init(cx);
extensions_ui::init(cx);
zeta::init(cx);
+ inspector_ui::init(app_state.clone(), cx);
cx.observe_global::<SettingsStore>({
let fs = fs.clone();
@@ -111,6 +111,12 @@ impl_actions!(
]
);
+pub mod dev {
+ use gpui::actions;
+
+ actions!(dev, [ToggleInspector]);
+}
+
pub mod workspace {
use gpui::action_with_deprecated_aliases;
@@ -105,9 +105,14 @@ impl Element for CompletionDiffElement {
None
}
+ fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
+ None
+ }
+
fn request_layout(
&mut self,
_id: Option<&gpui::GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
@@ -117,6 +122,7 @@ impl Element for CompletionDiffElement {
fn prepaint(
&mut self,
_id: Option<&gpui::GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
_bounds: gpui::Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
window: &mut Window,
@@ -128,6 +134,7 @@ impl Element for CompletionDiffElement {
fn paint(
&mut self,
_id: Option<&gpui::GlobalElementId>,
+ _inspector_id: Option<&gpui::InspectorElementId>,
_bounds: gpui::Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,