Add initial element inspector for Zed development (#31315)
Open inspector with `dev: toggle inspector` from command palette or `cmd-alt-i` on mac or `ctrl-alt-i` on linux. https://github.com/user-attachments/assets/54c43034-d40b-414e-ba9b-190bed2e6d2f * 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. Big thanks to @as-cii and @maxdeviant for sorting out how to implement the core of an inspector. Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra <me@as-cii.com> Co-authored-by: Marshall Bowers <git@maxdeviant.com> Co-authored-by: Federico Dionisi <code@fdionisi.me>
This commit is contained in:
parent
685933b5c8
commit
ab59982bf7
74 changed files with 2631 additions and 406 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -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
|
||||
|
@ -3032,19 +3072,37 @@ impl Mul<Pixels> for Rems {
|
|||
}
|
||||
}
|
||||
|
||||
impl Debug 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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an absolute length in pixels or rems.
|
||||
///
|
||||
/// `AbsoluteLength` can be either a fixed number of pixels, which is an absolute measurement not
|
||||
/// 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
|
||||
|
@ -3181,11 +3320,86 @@ impl DefiniteLength {
|
|||
|
||||
impl Debug 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),
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DefiniteLength {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
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}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for DefiniteLength {
|
||||
|
@ -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::*;
|
||||
|
|
223
crates/gpui/src/inspector.rs
Normal file
223
crates/gpui/src/inspector.rs
Normal file
|
@ -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,29 +153,40 @@ 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() {
|
||||
// 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)
|
||||
} else {
|
||||
}
|
||||
_ => {
|
||||
let mut element = (self.render)(self, window, cx);
|
||||
let layout_id = element.request_layout(window, cx);
|
||||
(layout_id, Some(element))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&GlobalElementId>,
|
||||
_inspector_id: Option<&InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
element: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
|
@ -176,7 +194,11 @@ 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() {
|
||||
if let Some(mut element) = element.take() {
|
||||
element.prepaint(window, cx);
|
||||
return Some(element);
|
||||
}
|
||||
|
||||
window.with_element_state::<AnyViewState, _>(
|
||||
global_id.unwrap(),
|
||||
|element_state, window| {
|
||||
|
@ -228,18 +250,13 @@ impl Element for AnyView {
|
|||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
let mut element = element.take().unwrap();
|
||||
element.prepaint(window, cx);
|
||||
|
||||
Some(element)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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.),
|
||||
|
|
28
crates/inspector_ui/Cargo.toml
Normal file
28
crates/inspector_ui/Cargo.toml
Normal file
|
@ -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
|
1
crates/inspector_ui/LICENSE-GPL
Symbolic link
1
crates/inspector_ui/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-GPL
|
84
crates/inspector_ui/README.md
Normal file
84
crates/inspector_ui/README.md
Normal file
|
@ -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.
|
20
crates/inspector_ui/build.rs
Normal file
20
crates/inspector_ui/build.rs
Normal file
|
@ -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());
|
||||
}
|
223
crates/inspector_ui/src/div_inspector.rs
Normal file
223
crates/inspector_ui/src/div_inspector.rs
Normal file
|
@ -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()
|
||||
}),
|
||||
)
|
||||
}
|
168
crates/inspector_ui/src/inspector.rs
Normal file
168
crates/inspector_ui/src/inspector.rs
Normal file
|
@ -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(())
|
||||
}
|
||||
}
|
24
crates/inspector_ui/src/inspector_ui.rs
Normal file
24
crates/inspector_ui/src/inspector_ui.rs
Normal file
|
@ -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,18 +97,8 @@ 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();
|
||||
|
||||
// This can be viewed via `dev: open language server logs` -> `json-language-server` ->
|
||||
// `Server Info`
|
||||
serde_json::json!({
|
||||
"json": {
|
||||
"format": {
|
||||
"enable": true,
|
||||
},
|
||||
"validate":
|
||||
{
|
||||
"enable": true,
|
||||
},
|
||||
"schemas": [
|
||||
#[allow(unused_mut)]
|
||||
let mut schemas = serde_json::json!([
|
||||
{
|
||||
"fileMatch": ["tsconfig.json"],
|
||||
"schema":tsconfig_schema
|
||||
|
@ -151,9 +141,33 @@ impl JsonLspAdapter {
|
|||
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!({
|
||||
"json": {
|
||||
"format": {
|
||||
"enable": true,
|
||||
},
|
||||
"validate":
|
||||
{
|
||||
"enable": true,
|
||||
},
|
||||
"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| {
|
||||
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)
|
||||
});
|
||||
},
|
||||
);
|
||||
(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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue