diff --git a/examples/demo/src/apps/editor.rs b/examples/demo/src/apps/editor.rs index ace1220..3702268 100644 --- a/examples/demo/src/apps/editor.rs +++ b/examples/demo/src/apps/editor.rs @@ -11,7 +11,7 @@ use egui_json_tree::{ DefaultRender, RenderBaseValueContext, RenderContext, RenderExpandableDelimiterContext, RenderPropertyContext, }, - DefaultExpand, JsonTree, JsonTreeStyle, + DefaultExpand, JsonTree, JsonTreeStyle, ToggleButtonsState, }; use serde_json::Value; @@ -154,7 +154,7 @@ impl Editor { fn show_property_context_menu( &mut self, ui: &mut Ui, - context: RenderPropertyContext<'_, '_, Value>, + mut context: RenderPropertyContext<'_, '_, Value>, ) { context .render_default(ui) @@ -164,6 +164,9 @@ impl Editor { self.edit_events.push(EditEvent::AddToObject { pointer: context.pointer.to_json_pointer_string(), }); + if let Some(state) = context.collapsing_state.as_mut() { + state.set_open(true); + } ui.close_menu(); } @@ -171,6 +174,9 @@ impl Editor { self.edit_events.push(EditEvent::AddToArray { pointer: context.pointer.to_json_pointer_string(), }); + if let Some(state) = context.collapsing_state.as_mut() { + state.set_open(true); + } ui.close_menu(); } @@ -254,7 +260,10 @@ impl Editor { context: RenderExpandableDelimiterContext<'_, '_, Value>, ) { match context.delimiter { - ExpandableDelimiter::OpeningArray => { + ExpandableDelimiter::OpeningArray + | ExpandableDelimiter::CollapsedArray + | ExpandableDelimiter::CollapsedEmptyArray + | ExpandableDelimiter::ClosingArray => { context .render_default(ui) .on_hover_cursor(CursorIcon::ContextMenu) @@ -263,11 +272,15 @@ impl Editor { self.edit_events.push(EditEvent::AddToArray { pointer: context.pointer.to_json_pointer_string(), }); + context.collapsing_state.set_open(true); ui.close_menu(); } }); } - ExpandableDelimiter::OpeningObject => { + ExpandableDelimiter::OpeningObject + | ExpandableDelimiter::CollapsedObject + | ExpandableDelimiter::CollapsedEmptyObject + | ExpandableDelimiter::ClosingObject => { context .render_default(ui) .on_hover_cursor(CursorIcon::ContextMenu) @@ -276,13 +289,11 @@ impl Editor { self.edit_events.push(EditEvent::AddToObject { pointer: context.pointer.to_json_pointer_string(), }); + context.collapsing_state.set_open(true); ui.close_menu(); } }); } - _ => { - context.render_default(ui); - } }; } @@ -436,9 +447,18 @@ impl Show for JsonEditorExample { ui.label("Right click on elements within the tree to edit values and object keys, and add/remove values."); ui.add_space(10.0); + let toggle_buttons_state = match self.editor.state { + Some(_) => ToggleButtonsState::VisibleDisabled, + None => ToggleButtonsState::VisibleEnabled, + }; + + let style = JsonTreeStyle::new() + .abbreviate_root(true) + .toggle_buttons_state(toggle_buttons_state); + JsonTree::new(self.title(), &self.value) .default_expand(DefaultExpand::All) - .style(JsonTreeStyle::new().abbreviate_root(true)) + .style(style) .on_render(|ui, context| self.editor.show(ui, &self.value, context)) .show(ui); diff --git a/src/node.rs b/src/node.rs index e06a269..e140ca3 100644 --- a/src/node.rs +++ b/src/node.rs @@ -122,6 +122,7 @@ impl<'a, T: ToJsonTreeValue> JsonTreeNode<'a, T> { pointer: JsonPointer(path_segments), style: &config.style, search_term: config.search_term.as_ref(), + collapsing_state: None, }, ); renderer.render_spacing_delimiter( @@ -219,6 +220,7 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( value: expandable.value, pointer: JsonPointer(path_segments), style, + collapsing_state: &mut state, }, ); return; @@ -231,6 +233,7 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( value: expandable.value, pointer: JsonPointer(path_segments), style, + collapsing_state: &mut state, }, ); renderer.render_spacing_delimiter( @@ -256,6 +259,7 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( pointer: JsonPointer(path_segments), style, search_term: search_term.as_ref(), + collapsing_state: Some(&mut state), }, ); renderer.render_spacing_delimiter( @@ -300,6 +304,7 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( value: elem, pointer: JsonPointer(path_segments), style, + collapsing_state: &mut state, }, ); } @@ -329,6 +334,7 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( value: expandable.value, pointer: JsonPointer(path_segments), style, + collapsing_state: &mut state, }, ); } else { @@ -341,6 +347,7 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( pointer: JsonPointer(path_segments), style, search_term: config.search_term.as_ref(), + collapsing_state: Some(&mut state), }, ); renderer.render_spacing_delimiter( @@ -360,6 +367,7 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( value: expandable.value, pointer: JsonPointer(path_segments), style, + collapsing_state: &mut state, }, ); } else { @@ -375,6 +383,7 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( value: expandable.value, pointer: JsonPointer(path_segments), style, + collapsing_state: &mut state, }, ); } @@ -442,9 +451,17 @@ fn show_expandable<'a, 'b, T: ToJsonTreeValue>( value: expandable.value, pointer: JsonPointer(path_segments), style, + collapsing_state: &mut state, }, ); }); + + if renderer.render_hook.is_some() { + // show_body_indented will store the CollapsingState, + // but since the subsequent render call above could also mutate the state in the render hook, + // we must store it again. + state.store(ui.ctx()); + } } } diff --git a/src/render.rs b/src/render.rs index dcb3f34..8d83bef 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use egui::{ + collapsing_header::CollapsingState, text::LayoutJob, util::cache::{ComputerMut, FrameCache}, Color32, FontId, Label, Response, Sense, TextFormat, Ui, @@ -25,7 +26,6 @@ pub trait DefaultRender { } /// A handle to the information of a render call. -#[derive(Clone, Copy)] pub enum RenderContext<'a, 'b, T: ToJsonTreeValue> { /// A render call for an array index or an object key. Property(RenderPropertyContext<'a, 'b, T>), @@ -66,16 +66,18 @@ impl<'a, 'b, T: ToJsonTreeValue> RenderContext<'a, 'b, T> { } /// A handle to the information of a render call for an array index or object key. -#[derive(Clone, Copy)] pub struct RenderPropertyContext<'a, 'b, T: ToJsonTreeValue> { /// The array index or object key being rendered. pub property: JsonPointerSegment<'a>, - /// The JSON array or object under this property. + /// The JSON value under this property. pub value: &'a T, /// The full JSON pointer to the array or object under this property. pub pointer: JsonPointer<'a, 'b>, /// The [`JsonTreeStyle`] that the [`JsonTree`](crate::JsonTree) was configured with. pub style: &'b JsonTreeStyle, + /// If an array/object is under this property, contains the [`egui::collapsing_header::CollapsingState`] for it. + /// This can be used to toggle or check whether the array/object is expanded. Any mutations will be stored after the render hook. + pub collapsing_state: Option<&'b mut CollapsingState>, pub(crate) search_term: Option<&'b SearchTerm>, } @@ -86,7 +88,6 @@ impl<'a, 'b, T: ToJsonTreeValue> DefaultRender for RenderPropertyContext<'a, 'b, } /// A handle to the information of a render call for a non-recursive JSON value. -#[derive(Clone, Copy)] pub struct RenderBaseValueContext<'a, 'b, T: ToJsonTreeValue> { /// The non-recursive JSON value being rendered. pub value: &'a T, @@ -114,7 +115,6 @@ impl<'a, 'b, T: ToJsonTreeValue> DefaultRender for RenderBaseValueContext<'a, 'b } /// A handle to the information of a render call for array brackets or object braces. -#[derive(Clone, Copy)] pub struct RenderExpandableDelimiterContext<'a, 'b, T: ToJsonTreeValue> { /// The specific token of the array bracket or object brace being rendered. pub delimiter: ExpandableDelimiter, @@ -124,6 +124,9 @@ pub struct RenderExpandableDelimiterContext<'a, 'b, T: ToJsonTreeValue> { pub pointer: JsonPointer<'a, 'b>, /// The [`JsonTreeStyle`] that the [`JsonTree`](crate::JsonTree) was configured with. pub style: &'b JsonTreeStyle, + /// The [`egui::collapsing_header::CollapsingState`] for the array or object that this delimiter belongs to. + /// This can be used to toggle or check whether the array/object is expanded. Any mutations will be stored after the render hook. + pub collapsing_state: &'b mut CollapsingState, } impl<'a, 'b, T: ToJsonTreeValue> DefaultRender for RenderExpandableDelimiterContext<'a, 'b, T> { @@ -132,7 +135,6 @@ impl<'a, 'b, T: ToJsonTreeValue> DefaultRender for RenderExpandableDelimiterCont } } -#[derive(Clone, Copy)] pub(crate) struct RenderSpacingDelimiterContext<'b> { pub(crate) delimiter: SpacingDelimiter, pub(crate) style: &'b JsonTreeStyle,