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
|
@ -2,13 +2,15 @@
|
|||
//! can be used to describe common units, concepts, and the relationships
|
||||
//! between them.
|
||||
|
||||
use anyhow::{Context as _, anyhow};
|
||||
use core::fmt::Debug;
|
||||
use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
|
||||
use refineable::Refineable;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use schemars::{JsonSchema, SchemaGenerator, schema::Schema};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
|
||||
use std::{
|
||||
cmp::{self, PartialOrd},
|
||||
fmt,
|
||||
fmt::{self, Display},
|
||||
hash::Hash,
|
||||
ops::{Add, Div, Mul, MulAssign, Neg, Sub},
|
||||
};
|
||||
|
@ -71,9 +73,10 @@ pub trait Along {
|
|||
Eq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
Hash,
|
||||
)]
|
||||
#[refineable(Debug)]
|
||||
#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct Point<T: Default + Clone + Debug> {
|
||||
/// The x coordinate of the point.
|
||||
|
@ -375,12 +378,18 @@ impl<T: Clone + Default + Debug> Clone for Point<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Clone + Debug + Display> Display for Point<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
/// A structure representing a two-dimensional size with width and height in a given unit.
|
||||
///
|
||||
/// This struct is generic over the type `T`, which can be any type that implements `Clone`, `Default`, and `Debug`.
|
||||
/// It is commonly used to specify dimensions for elements in a UI, such as a window or element.
|
||||
#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash, Serialize, Deserialize)]
|
||||
#[refineable(Debug)]
|
||||
#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct Size<T: Clone + Default + Debug> {
|
||||
/// The width component of the size.
|
||||
|
@ -649,6 +658,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Clone + Debug + Display> Display for Size<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} × {}", self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + Default + Debug> From<Point<T>> for Size<T> {
|
||||
fn from(point: Point<T>) -> Self {
|
||||
Self {
|
||||
|
@ -1541,6 +1556,18 @@ impl<T: PartialOrd + Default + Debug + Clone> Bounds<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Clone + Debug + Display + Add<T, Output = T>> Display for Bounds<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} - {} (size {})",
|
||||
self.origin,
|
||||
self.bottom_right(),
|
||||
self.size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Size<DevicePixels> {
|
||||
/// Converts the size from physical to logical pixels.
|
||||
pub(crate) fn to_pixels(self, scale_factor: f32) -> Size<Pixels> {
|
||||
|
@ -1647,7 +1674,7 @@ impl<T: Clone + Debug + Copy + Default> Copy for Bounds<T> {}
|
|||
/// assert_eq!(edges.left, 40.0);
|
||||
/// ```
|
||||
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
|
||||
#[refineable(Debug)]
|
||||
#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct Edges<T: Clone + Default + Debug> {
|
||||
/// The size of the top edge.
|
||||
|
@ -2124,7 +2151,7 @@ impl Corner {
|
|||
///
|
||||
/// Each field represents the size of the corner on one side of the box: `top_left`, `top_right`, `bottom_right`, and `bottom_left`.
|
||||
#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
|
||||
#[refineable(Debug)]
|
||||
#[refineable(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[repr(C)]
|
||||
pub struct Corners<T: Clone + Default + Debug> {
|
||||
/// The value associated with the top left corner.
|
||||
|
@ -2508,16 +2535,11 @@ impl From<Percentage> for Radians {
|
|||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct Pixels(pub f32);
|
||||
|
||||
impl std::fmt::Display for Pixels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_fmt(format_args!("{}px", self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Pixels {
|
||||
type Output = f32;
|
||||
|
||||
|
@ -2584,6 +2606,30 @@ impl MulAssign<f32> for Pixels {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for Pixels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}px", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Pixels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ str> for Pixels {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
|
||||
value
|
||||
.strip_suffix("px")
|
||||
.context("expected 'px' suffix")
|
||||
.and_then(|number| Ok(number.parse()?))
|
||||
.map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixels {
|
||||
/// Represents zero pixels.
|
||||
pub const ZERO: Pixels = Pixels(0.0);
|
||||
|
@ -2706,12 +2752,6 @@ impl From<f32> for Pixels {
|
|||
}
|
||||
}
|
||||
|
||||
impl Debug for Pixels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} px", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for f32 {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
pixels.0
|
||||
|
@ -2910,7 +2950,7 @@ impl Ord for ScaledPixels {
|
|||
|
||||
impl Debug for ScaledPixels {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} px (scaled)", self.0)
|
||||
write!(f, "{}px (scaled)", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3032,9 +3072,27 @@ impl Mul<Pixels> for Rems {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for Rems {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}rem", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Rems {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} rem", self.0)
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'_ str> for Rems {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
|
||||
value
|
||||
.strip_suffix("rem")
|
||||
.context("expected 'rem' suffix")
|
||||
.and_then(|number| Ok(number.parse()?))
|
||||
.map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3044,7 +3102,7 @@ impl Debug for Rems {
|
|||
/// affected by the current font size, or a number of rems, which is relative to the font size of
|
||||
/// the root element. It is used for specifying dimensions that are either independent of or
|
||||
/// related to the typographic scale.
|
||||
#[derive(Clone, Copy, Debug, Neg, PartialEq)]
|
||||
#[derive(Clone, Copy, Neg, PartialEq)]
|
||||
pub enum AbsoluteLength {
|
||||
/// A length in pixels.
|
||||
Pixels(Pixels),
|
||||
|
@ -3126,6 +3184,87 @@ impl Default for AbsoluteLength {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for AbsoluteLength {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Pixels(pixels) => write!(f, "{pixels}"),
|
||||
Self::Rems(rems) => write!(f, "{rems}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for AbsoluteLength {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
const EXPECTED_ABSOLUTE_LENGTH: &str = "number with 'px' or 'rem' suffix";
|
||||
|
||||
impl TryFrom<&'_ str> for AbsoluteLength {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
|
||||
if let Ok(pixels) = value.try_into() {
|
||||
Ok(Self::Pixels(pixels))
|
||||
} else if let Ok(rems) = value.try_into() {
|
||||
Ok(Self::Rems(rems))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"invalid AbsoluteLength '{value}', expected {EXPECTED_ABSOLUTE_LENGTH}"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for AbsoluteLength {
|
||||
fn schema_name() -> String {
|
||||
"AbsoluteLength".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
|
||||
use schemars::schema::{InstanceType, SchemaObject, StringValidation};
|
||||
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
string: Some(Box::new(StringValidation {
|
||||
pattern: Some(r"^-?\d+(\.\d+)?(px|rem)$".to_string()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AbsoluteLength {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct StringVisitor;
|
||||
|
||||
impl de::Visitor<'_> for StringVisitor {
|
||||
type Value = AbsoluteLength;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{EXPECTED_ABSOLUTE_LENGTH}")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
AbsoluteLength::try_from(value).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(StringVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for AbsoluteLength {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("{self}"))
|
||||
}
|
||||
}
|
||||
|
||||
/// A non-auto length that can be defined in pixels, rems, or percent of parent.
|
||||
///
|
||||
/// This enum represents lengths that have a specific value, as opposed to lengths that are automatically
|
||||
|
@ -3180,14 +3319,89 @@ impl DefiniteLength {
|
|||
}
|
||||
|
||||
impl Debug for DefiniteLength {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DefiniteLength {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DefiniteLength::Absolute(length) => Debug::fmt(length, f),
|
||||
DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32),
|
||||
DefiniteLength::Absolute(length) => write!(f, "{length}"),
|
||||
DefiniteLength::Fraction(fraction) => write!(f, "{}%", (fraction * 100.0) as i32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const EXPECTED_DEFINITE_LENGTH: &str = "expected number with 'px', 'rem', or '%' suffix";
|
||||
|
||||
impl TryFrom<&'_ str> for DefiniteLength {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
|
||||
if let Some(percentage) = value.strip_suffix('%') {
|
||||
let fraction: f32 = percentage.parse::<f32>().with_context(|| {
|
||||
format!("invalid DefiniteLength '{value}', expected {EXPECTED_DEFINITE_LENGTH}")
|
||||
})?;
|
||||
Ok(DefiniteLength::Fraction(fraction / 100.0))
|
||||
} else if let Ok(absolute_length) = value.try_into() {
|
||||
Ok(DefiniteLength::Absolute(absolute_length))
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"invalid DefiniteLength '{value}', expected {EXPECTED_DEFINITE_LENGTH}"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for DefiniteLength {
|
||||
fn schema_name() -> String {
|
||||
"DefiniteLength".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
|
||||
use schemars::schema::{InstanceType, SchemaObject, StringValidation};
|
||||
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
string: Some(Box::new(StringValidation {
|
||||
pattern: Some(r"^-?\d+(\.\d+)?(px|rem|%)$".to_string()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for DefiniteLength {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct StringVisitor;
|
||||
|
||||
impl de::Visitor<'_> for StringVisitor {
|
||||
type Value = DefiniteLength;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{EXPECTED_DEFINITE_LENGTH}")
|
||||
}
|
||||
|
||||
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||
DefiniteLength::try_from(value).map_err(E::custom)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(StringVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for DefiniteLength {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("{self}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pixels> for DefiniteLength {
|
||||
fn from(pixels: Pixels) -> Self {
|
||||
Self::Absolute(pixels.into())
|
||||
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue