inspector: Add live editor to inspector (#1205)
Inspired by Zed, this PR implements a simple live Rust and JSON style editor for the inspector. The rust style editor only supports `Styled` and `StyledExt` method calls with no arguments. https://github.com/user-attachments/assets/df7f1746-92d7-416c-afd5-e389ac3ea9ae --------- Co-authored-by: Floyd Wang <gassnake999@gmail.com>
This commit is contained in:
parent
16f9380a63
commit
8992852165
5 changed files with 383 additions and 41 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3149,6 +3149,7 @@ dependencies = [
|
||||||
"enum-iterator",
|
"enum-iterator",
|
||||||
"gpui",
|
"gpui",
|
||||||
"gpui-component-macros",
|
"gpui-component-macros",
|
||||||
|
"gpui_macros",
|
||||||
"html5ever 0.27.0",
|
"html5ever 0.27.0",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
gpui = { git = "https://github.com/zed-industries/zed.git" }
|
gpui = { git = "https://github.com/zed-industries/zed.git" }
|
||||||
|
gpui_macros = { git = "https://github.com/zed-industries/zed.git" }
|
||||||
reqwest_client = { git = "https://github.com/zed-industries/zed.git" }
|
reqwest_client = { git = "https://github.com/zed-industries/zed.git" }
|
||||||
sum_tree = { git = "https://github.com/zed-industries/zed.git" }
|
sum_tree = { git = "https://github.com/zed-industries/zed.git" }
|
||||||
gpui-component = { path = "crates/ui" }
|
gpui-component = { path = "crates/ui" }
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
decimal = ["dep:rust_decimal"]
|
decimal = ["dep:rust_decimal"]
|
||||||
inspector = []
|
inspector = ["gpui/inspector"]
|
||||||
webview = ["dep:wry"]
|
webview = ["dep:wry"]
|
||||||
# For syntax highlighting in Markdown and CodeEditor.
|
# For syntax highlighting in Markdown and CodeEditor.
|
||||||
tree-sitter-languages = [
|
tree-sitter-languages = [
|
||||||
|
|
@ -50,6 +50,7 @@ tree-sitter-languages = [
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
sum_tree.workspace = true
|
sum_tree.workspace = true
|
||||||
|
gpui_macros.workspace = true
|
||||||
gpui-component-macros.workspace = true
|
gpui-component-macros.workspace = true
|
||||||
rust-i18n.workspace = true
|
rust-i18n.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
use std::cell::OnceCell;
|
use std::{cell::OnceCell, collections::HashMap, fmt::Write as _, sync::OnceLock};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, prelude::FluentBuilder, px, AnyElement, App, AppContext as _, Context,
|
actions, div, inspector_reflection::FunctionReflection, prelude::FluentBuilder, px, AnyElement,
|
||||||
DivInspectorState, Entity, Inspector, InspectorElementId, InteractiveElement as _, IntoElement,
|
App, AppContext, Context, DivInspectorState, Entity, Inspector, InspectorElementId,
|
||||||
KeyBinding, ParentElement as _, Render, SharedString, Styled, Window,
|
InteractiveElement as _, IntoElement, KeyBinding, ParentElement as _, Refineable as _, Render,
|
||||||
|
SharedString, StyleRefinement, Styled, Subscription, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
alert::Alert,
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
clipboard::Clipboard,
|
clipboard::Clipboard,
|
||||||
description_list::DescriptionList,
|
description_list::DescriptionList,
|
||||||
|
dropdown::{Dropdown, DropdownState, SearchableVec},
|
||||||
h_flex,
|
h_flex,
|
||||||
input::{InputState, TextInput},
|
input::{InputEvent, InputState, TabSize, TextInput},
|
||||||
link::Link,
|
link::Link,
|
||||||
v_flex, ActiveTheme, IconName, Selectable, Sizable, TITLE_BAR_HEIGHT,
|
v_flex, ActiveTheme, IconName, Selectable, Sizable, TITLE_BAR_HEIGHT,
|
||||||
};
|
};
|
||||||
|
|
@ -43,7 +46,7 @@ pub fn init(cx: &mut App) {
|
||||||
cx.register_inspector_element(move |id, state: &DivInspectorState, window, cx| {
|
cx.register_inspector_element(move |id, state: &DivInspectorState, window, cx| {
|
||||||
let el = inspector_el.get_or_init(|| cx.new(|cx| DivInspector::new(window, cx)));
|
let el = inspector_el.get_or_init(|| cx.new(|cx| DivInspector::new(window, cx)));
|
||||||
el.update(cx, |this, cx| {
|
el.update(cx, |this, cx| {
|
||||||
this.set_inspector_state(id, state.clone(), cx);
|
this.update_inspected_element(id, state.clone(), window, cx);
|
||||||
this.render(window, cx).into_any_element()
|
this.render(window, cx).into_any_element()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -51,59 +54,340 @@ pub fn init(cx: &mut App) {
|
||||||
cx.set_inspector_renderer(Box::new(render_inspector));
|
cx.set_inspector_renderer(Box::new(render_inspector));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct EditorState {
|
||||||
|
/// The input state for the editor.
|
||||||
|
state: Entity<InputState>,
|
||||||
|
/// Error to display from parsing the input, or if serialization errors somehow occur.
|
||||||
|
error: Option<SharedString>,
|
||||||
|
/// Whether the editor is currently being edited.
|
||||||
|
editing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DivInspector {
|
pub struct DivInspector {
|
||||||
inspector_id: Option<InspectorElementId>,
|
inspector_id: Option<InspectorElementId>,
|
||||||
inspector_state: Option<DivInspectorState>,
|
inspector_state: Option<DivInspectorState>,
|
||||||
input_state: Entity<InputState>,
|
rust_dropdown: Entity<DropdownState<SearchableVec<SharedString>>>,
|
||||||
|
rust_state: EditorState,
|
||||||
|
json_state: EditorState,
|
||||||
|
/// Initial style before any edits
|
||||||
|
initial_style: StyleRefinement,
|
||||||
|
/// Part of the initial style that could not be converted to Rust code
|
||||||
|
unconvertible_style: StyleRefinement,
|
||||||
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DivInspector {
|
impl DivInspector {
|
||||||
pub fn new(window: &mut Window, cx: &mut App) -> Self {
|
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let input_state = cx.new(|cx| {
|
let json_input_state = cx.new(|cx| {
|
||||||
InputState::new(window, cx)
|
InputState::new(window, cx)
|
||||||
.code_editor("json")
|
.code_editor("json")
|
||||||
.line_number(false)
|
.line_number(false)
|
||||||
.disabled(true)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let rust_input_state = cx.new(|cx| {
|
||||||
|
InputState::new(window, cx)
|
||||||
|
.code_editor("rust")
|
||||||
|
.line_number(false)
|
||||||
|
.tab_size(TabSize {
|
||||||
|
tab_size: 4,
|
||||||
|
hard_tabs: false,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let rust_dropdown = cx.new(|cx| {
|
||||||
|
DropdownState::new(
|
||||||
|
SearchableVec::new({
|
||||||
|
let mut methods: Vec<_> = StyleMethods::get()
|
||||||
|
.table
|
||||||
|
.iter()
|
||||||
|
.map(|(_, method)| method.name.into())
|
||||||
|
.collect();
|
||||||
|
methods.sort();
|
||||||
|
methods
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let _subscriptions = vec![
|
||||||
|
cx.subscribe_in(
|
||||||
|
&json_input_state,
|
||||||
|
window,
|
||||||
|
|this: &mut DivInspector, _, event: &InputEvent, window, cx| match event {
|
||||||
|
InputEvent::Change(new_style) => {
|
||||||
|
this.edit_json(new_style, window, cx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
cx.subscribe_in(
|
||||||
|
&rust_input_state,
|
||||||
|
window,
|
||||||
|
|this: &mut DivInspector, _, event: &InputEvent, window, cx| match event {
|
||||||
|
InputEvent::Change(new_style) => {
|
||||||
|
this.edit_rust(new_style, window, cx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let rust_state = EditorState {
|
||||||
|
state: rust_input_state,
|
||||||
|
error: None,
|
||||||
|
editing: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let json_state = EditorState {
|
||||||
|
state: json_input_state,
|
||||||
|
error: None,
|
||||||
|
editing: false,
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
inspector_id: None,
|
inspector_id: None,
|
||||||
inspector_state: None,
|
inspector_state: None,
|
||||||
input_state,
|
rust_dropdown,
|
||||||
|
rust_state,
|
||||||
|
json_state,
|
||||||
|
initial_style: Default::default(),
|
||||||
|
unconvertible_style: Default::default(),
|
||||||
|
_subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_inspector_state(
|
pub fn update_inspected_element(
|
||||||
&mut self,
|
&mut self,
|
||||||
inspector_id: InspectorElementId,
|
inspector_id: InspectorElementId,
|
||||||
state: DivInspectorState,
|
state: DivInspectorState,
|
||||||
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
// Skip updating if the inspector ID hasn't changed
|
||||||
|
if self.inspector_id.as_ref() == Some(&inspector_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let initial_style = state.base_style.as_ref();
|
||||||
|
self.initial_style = initial_style.clone();
|
||||||
|
self.json_state.editing = false;
|
||||||
|
self.update_json_from_style(initial_style, window, cx);
|
||||||
|
self.rust_state.editing = false;
|
||||||
|
let rust_style = self.update_rust_from_style(initial_style, window, cx);
|
||||||
|
self.unconvertible_style = initial_style.subtract(&rust_style);
|
||||||
self.inspector_id = Some(inspector_id);
|
self.inspector_id = Some(inspector_id);
|
||||||
self.inspector_state = Some(state);
|
self.inspector_state = Some(state);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn edit_json(&mut self, code: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if !self.json_state.editing {
|
||||||
|
self.json_state.editing = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match serde_json::from_str::<StyleRefinement>(code) {
|
||||||
|
Ok(new_style) => {
|
||||||
|
self.json_state.error = None;
|
||||||
|
self.rust_state.error = None;
|
||||||
|
self.rust_state.editing = false;
|
||||||
|
let rust_style = self.update_rust_from_style(&new_style, window, cx);
|
||||||
|
self.unconvertible_style = new_style.subtract(&rust_style);
|
||||||
|
self.update_element_style(new_style, window, cx);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let e = format!("{}", e);
|
||||||
|
self.json_state.error = Some(e.into());
|
||||||
|
window.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_rust(&mut self, code: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if !self.rust_state.editing {
|
||||||
|
self.rust_state.editing = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (new_style, err) = rust_to_style(self.unconvertible_style.clone(), code);
|
||||||
|
self.rust_state.error = err;
|
||||||
|
self.json_state.error = None;
|
||||||
|
self.json_state.editing = false;
|
||||||
|
self.update_json_from_style(&new_style, window, cx);
|
||||||
|
self.update_element_style(new_style, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_element_style(
|
||||||
|
&self,
|
||||||
|
style: StyleRefinement,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
window.with_inspector_state::<DivInspectorState, _>(
|
||||||
|
self.inspector_id.as_ref(),
|
||||||
|
cx,
|
||||||
|
|state, _window| {
|
||||||
|
if let Some(state) = state {
|
||||||
|
*state.base_style = style;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
window.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_style(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.rust_state.editing = false;
|
||||||
|
let rust_style = self.update_rust_from_style(&self.initial_style, window, cx);
|
||||||
|
self.unconvertible_style = self.initial_style.subtract(&rust_style);
|
||||||
|
self.json_state.editing = false;
|
||||||
|
self.update_json_from_style(&self.initial_style, window, cx);
|
||||||
|
if let Some(state) = self.inspector_state.as_mut() {
|
||||||
|
*state.base_style = self.initial_style.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_json_from_style(
|
||||||
|
&self,
|
||||||
|
style: &StyleRefinement,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.json_state.state.update(cx, |state, cx| {
|
||||||
|
state.set_value(style_to_json(style), window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_rust_from_style(
|
||||||
|
&self,
|
||||||
|
style: &StyleRefinement,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> StyleRefinement {
|
||||||
|
self.rust_state.state.update(cx, |state, cx| {
|
||||||
|
let (rust_code, rust_style) = style_to_rust(style);
|
||||||
|
state.set_value(rust_code, window, cx);
|
||||||
|
rust_style
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rust_add_style(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if let Some(method) = self.rust_dropdown.read(cx).selected_value() {
|
||||||
|
let code = self.rust_state.state.read(cx).value();
|
||||||
|
let new_code = format!(" .{method}()\n");
|
||||||
|
let Some(insert_pos) = code.rfind('}') else {
|
||||||
|
self.rust_state.error = Some("Failed to add method: Could not find `}`".into());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let code = format!("{}{}{}", &code[..insert_pos], new_code, &code[insert_pos..]);
|
||||||
|
|
||||||
|
self.rust_state.editing = true;
|
||||||
|
self.rust_state.state.update(cx, |state, cx| {
|
||||||
|
state.set_value(code, window, cx);
|
||||||
|
// an edit event will be triggered, the style will be updated there
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_to_json(style: &StyleRefinement) -> String {
|
||||||
|
serde_json::to_string_pretty(style).unwrap_or_else(|e| format!("{{ \"error\": \"{}\" }}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StyleMethods {
|
||||||
|
table: Vec<(Box<StyleRefinement>, FunctionReflection<StyleRefinement>)>,
|
||||||
|
map: HashMap<&'static str, FunctionReflection<StyleRefinement>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleMethods {
|
||||||
|
fn get() -> &'static Self {
|
||||||
|
static STYLE_METHODS: OnceLock<StyleMethods> = OnceLock::new();
|
||||||
|
STYLE_METHODS.get_or_init(|| {
|
||||||
|
let table: Vec<_> = [
|
||||||
|
crate::styled_ext_reflection::methods::<StyleRefinement>(),
|
||||||
|
gpui::styled_reflection::methods::<StyleRefinement>(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|method| (Box::new(method.invoke(StyleRefinement::default())), method))
|
||||||
|
.collect();
|
||||||
|
let map = table
|
||||||
|
.iter()
|
||||||
|
.map(|(_, method)| (method.name, method.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Self { table, map }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_to_rust(input_style: &StyleRefinement) -> (String, StyleRefinement) {
|
||||||
|
let methods: Vec<_> = StyleMethods::get()
|
||||||
|
.table
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(style, method)| {
|
||||||
|
if input_style.is_superset_of(style) {
|
||||||
|
Some(method)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let mut code = "fn build() -> Div {\n div()\n".to_string();
|
||||||
|
let mut style = StyleRefinement::default();
|
||||||
|
for method in methods {
|
||||||
|
let before_invoke = style.clone();
|
||||||
|
style = method.invoke(style);
|
||||||
|
if style != before_invoke {
|
||||||
|
_ = write!(code, " .{}()\n", method.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.push_str("}");
|
||||||
|
(code, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rust_to_style(
|
||||||
|
mut style: StyleRefinement,
|
||||||
|
rust_code: &str,
|
||||||
|
) -> (StyleRefinement, Option<SharedString>) {
|
||||||
|
// remove line comments
|
||||||
|
let rust_code = rust_code
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.find("//").map_or(line, |i| &line[..i]).trim())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.concat();
|
||||||
|
|
||||||
|
let Some(begin) = rust_code.find("div()").map(|i| i + "div()".len()) else {
|
||||||
|
return (style, Some("Expected `div()`".into()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut err = String::new();
|
||||||
|
let methods = rust_code[begin..]
|
||||||
|
.split(&['.', '(', ')', '{', '}'])
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(str::trim);
|
||||||
|
let style_methods = StyleMethods::get();
|
||||||
|
for method in methods {
|
||||||
|
match style_methods.map.get(method) {
|
||||||
|
Some(method_reflection) => style = method_reflection.invoke(style),
|
||||||
|
None => _ = writeln!(err, "Unknown method: {method}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = if err.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(err.into())
|
||||||
|
};
|
||||||
|
(style, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for DivInspector {
|
impl Render for DivInspector {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let input_state = self.input_state.clone();
|
v_flex().size_full().gap_y_4().text_sm().when_some(
|
||||||
let last_styles = input_state.read(cx).value().clone();
|
self.inspector_state.as_ref(),
|
||||||
|
|
||||||
v_flex().size_full().gap_3().text_sm().when_some(
|
|
||||||
self.inspector_state.clone(),
|
|
||||||
|this, state| {
|
|this, state| {
|
||||||
let styles = serde_json::to_string_pretty(&state.base_style);
|
|
||||||
let styles: SharedString = match styles {
|
|
||||||
Ok(json) => json,
|
|
||||||
Err(e) => format!("{{ \"error\": \"{}\" }}", e),
|
|
||||||
}
|
|
||||||
.into();
|
|
||||||
|
|
||||||
if styles != last_styles {
|
|
||||||
input_state.update(cx, |s, cx| s.set_value(styles.clone(), window, cx));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.child(
|
this.child(
|
||||||
DescriptionList::new()
|
DescriptionList::new()
|
||||||
.columns(1)
|
.columns(1)
|
||||||
|
|
@ -115,21 +399,70 @@ impl Render for DivInspector {
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.w_full()
|
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.gap_1()
|
.gap_y_3()
|
||||||
.text_sm()
|
|
||||||
.text_color(cx.theme().description_list_label_foreground)
|
|
||||||
.child("Styles")
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
v_flex().gap_y_2().child("Rust Styles").child(
|
||||||
|
h_flex()
|
||||||
|
.gap_x_2()
|
||||||
|
.child(
|
||||||
|
Dropdown::new(&self.rust_dropdown)
|
||||||
|
.icon(IconName::Search)
|
||||||
|
.small()
|
||||||
|
.cleanable()
|
||||||
|
.flex_1(),
|
||||||
|
)
|
||||||
|
.child(Button::new("rust-add").label("Add").small().on_click(
|
||||||
|
cx.listener(|this, _, window, cx| {
|
||||||
|
this.rust_add_style(window, cx);
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.child(
|
||||||
|
Button::new("rust-reset").label("Reset").small().on_click(
|
||||||
|
cx.listener(|this, _, window, cx| {
|
||||||
|
this.reset_style(window, cx);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.w_full()
|
.gap_y_1()
|
||||||
.font_family("Monaco")
|
.font_family("Monaco")
|
||||||
.text_size(px(12.))
|
.text_size(px(12.))
|
||||||
.border_1()
|
.child(TextInput::new(&self.rust_state.state).h_full())
|
||||||
.border_color(cx.theme().border)
|
.when_some(self.rust_state.error.clone(), |this, err| {
|
||||||
.child(TextInput::new(&input_state).h_full().appearance(false)),
|
this.child(Alert::error("rust-error", err).text_xs())
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_y_3()
|
||||||
|
.h_3_5()
|
||||||
|
.flex_shrink_0()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_x_2()
|
||||||
|
.child(div().flex_1().child("JSON Styles"))
|
||||||
|
.child(Button::new("json-reset").label("Reset").small().on_click(
|
||||||
|
cx.listener(|this, _, window, cx| {
|
||||||
|
this.reset_style(window, cx);
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.flex_1()
|
||||||
|
.gap_y_1()
|
||||||
|
.font_family("Monaco")
|
||||||
|
.text_size(px(12.))
|
||||||
|
.child(TextInput::new(&self.json_state.state).h_full())
|
||||||
|
.when_some(self.json_state.error.clone(), |this, err| {
|
||||||
|
this.child(Alert::error("json-error", err).text_xs())
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -161,7 +494,7 @@ fn render_inspector(
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.h(TITLE_BAR_HEIGHT)
|
.h(TITLE_BAR_HEIGHT)
|
||||||
.line_height(TITLE_BAR_HEIGHT)
|
.line_height(TITLE_BAR_HEIGHT)
|
||||||
.overflow_hidden()
|
.overflow_x_hidden()
|
||||||
.px_2()
|
.px_2()
|
||||||
.border_b_1()
|
.border_b_1()
|
||||||
.border_color(cx.theme().title_bar_border)
|
.border_color(cx.theme().title_bar_border)
|
||||||
|
|
@ -176,6 +509,7 @@ fn render_inspector(
|
||||||
.selected(inspector.is_picking())
|
.selected(inspector.is_picking())
|
||||||
.small()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
|
.cursor_pointer()
|
||||||
.on_click(cx.listener(|this, _, window, _| {
|
.on_click(cx.listener(|this, _, window, _| {
|
||||||
this.start_picking();
|
this.start_picking();
|
||||||
window.refresh();
|
window.refresh();
|
||||||
|
|
@ -188,6 +522,7 @@ fn render_inspector(
|
||||||
.icon(IconName::Close)
|
.icon(IconName::Close)
|
||||||
.small()
|
.small()
|
||||||
.ghost()
|
.ghost()
|
||||||
|
.cursor_pointer()
|
||||||
.on_click(|_, window, cx| {
|
.on_click(|_, window, cx| {
|
||||||
window.dispatch_action(Box::new(ToggleInspector), cx);
|
window.dispatch_action(Box::new(ToggleInspector), cx);
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,10 @@ macro_rules! font_weight {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extends [`gpui::Styled`] with specific styling methods.
|
/// Extends [`gpui::Styled`] with specific styling methods.
|
||||||
|
#[cfg_attr(
|
||||||
|
any(feature = "inspector", debug_assertions),
|
||||||
|
gpui_macros::derive_inspector_reflection
|
||||||
|
)]
|
||||||
pub trait StyledExt: Styled + Sized {
|
pub trait StyledExt: Styled + Sized {
|
||||||
/// Refine the style of this element, applying the given style refinement.
|
/// Refine the style of this element, applying the given style refinement.
|
||||||
fn refine_style(mut self, style: &StyleRefinement) -> Self {
|
fn refine_style(mut self, style: &StyleRefinement) -> Self {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue