Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support truncation and wrapping options #39

Merged
merged 14 commits into from
Jan 4, 2025
Merged
1 change: 1 addition & 0 deletions examples/demo/src/apps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod custom_input;
pub mod editor;
pub mod search;
pub mod toggle_buttons;
pub mod wrapping;

pub trait Show {
fn title(&self) -> &'static str;
Expand Down
116 changes: 116 additions & 0 deletions examples/demo/src/apps/wrapping.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use egui::{DragValue, Ui};
use egui_json_tree::{
DefaultExpand, JsonTree, JsonTreeMaxWidth, JsonTreeStyle, JsonTreeWrapping,
JsonTreeWrappingConfig,
};
use serde_json::Value;

use super::Show;

pub struct WrappingExample {
value: Value,
wrap: JsonTreeWrapping,
use_custom_max_rows: bool,
}

impl WrappingExample {
pub fn new(value: Value) -> Self {
Self {
value,
wrap: JsonTreeWrapping {
max_rows: 1,
max_width: JsonTreeMaxWidth::UiAvailableWidth,
break_anywhere: true,
},
use_custom_max_rows: true,
}
}
}

impl Show for WrappingExample {
fn title(&self) -> &'static str {
"Wrapping"
}

fn show(&mut self, ui: &mut Ui) {
ui.hyperlink_to("Source", "https://github.com/dmackdev/egui_json_tree/blob/master/examples/demo/src/apps/wrapping.rs");
ui.add_space(10.0);

self.show_max_rows_controls(ui);
ui.add_space(10.0);

self.show_max_width_controls(ui);
ui.add_space(10.0);

ui.checkbox(&mut self.wrap.break_anywhere, "Break anywhere");
ui.separator();

let wrapping_config = JsonTreeWrappingConfig {
value_when_root: self.wrap,
value_with_expanded_parent: self.wrap,
value_in_collapsed_root: self.wrap,
};
JsonTree::new(self.title(), &self.value)
.style(JsonTreeStyle::new().wrapping_config(wrapping_config))
.default_expand(DefaultExpand::All)
.show(ui);
}
}

impl WrappingExample {
fn show_max_rows_controls(&mut self, ui: &mut Ui) {
ui.label(egui::RichText::new("Max Rows:").monospace());
ui.horizontal(|ui| {
if ui
.radio_value(&mut self.use_custom_max_rows, true, "Custom")
.changed()
{
self.wrap.max_rows = 1;
}

if self.use_custom_max_rows {
ui.add(
DragValue::new(&mut self.wrap.max_rows)
.speed(0.1)
.range(1..=10),
);
}
});

if ui
.radio_value(&mut self.use_custom_max_rows, false, "usize::MAX")
.clicked()
{
self.wrap.max_rows = usize::MAX;
}
}

fn show_max_width_controls(&mut self, ui: &mut Ui) {
ui.label(egui::RichText::new("Max Width:").monospace());
ui.horizontal(|ui| {
if ui
.radio(
matches!(self.wrap.max_width, JsonTreeMaxWidth::Points(_)),
"Points",
)
.clicked()
&& !matches!(self.wrap.max_width, JsonTreeMaxWidth::Points(_))
{
self.wrap.max_width = JsonTreeMaxWidth::Points(100.0);
}
if let JsonTreeMaxWidth::Points(ref mut pts) = &mut self.wrap.max_width {
ui.add(DragValue::new(pts).speed(10.0).range(100.0..=10000.0));
}
});

if ui
.radio(
matches!(self.wrap.max_width, JsonTreeMaxWidth::UiAvailableWidth),
"Available Width",
)
.clicked()
{
self.wrap.max_width = JsonTreeMaxWidth::UiAvailableWidth;
}
}
}
53 changes: 36 additions & 17 deletions examples/demo/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use apps::{
copy_to_clipboard::CopyToClipboardExample, custom_input::CustomExample,
editor::JsonEditorExample, search::SearchExample,
toggle_buttons::ToggleButtonsCustomisationDemo, Example, Show,
toggle_buttons::ToggleButtonsCustomisationDemo, wrapping::WrappingExample, Example, Show,
};
use serde_json::json;

Expand All @@ -16,6 +16,20 @@ struct DemoApp {
impl Default for DemoApp {
fn default() -> Self {
let complex_object = json!({"foo": [1, 2, [3]], "bar": { "qux" : false, "thud": { "a/b": [4, 5, { "m~n": "Greetings!" }]}, "grep": 21}, "baz": null});
let long_strings_object = json!({
"baz": "Ullamco ipsum proident occaecat eiusmod ea aute ex non cupidatat laboris duis amet cupidatat. Ullamco sint do enim consectetur Lorem occaecat mollit. Aliquip voluptate ullamco consectetur adipisicing elit fugiat labore laboris. Occaecat non incididunt duis consectetur aliquip dolore cillum eiusmod. Qui sunt est excepteur laborum.",
"bar": [
"Laboris id occaecat sit quis aliqua et. Fugiat nisi nulla nostrud voluptate id enim do esse deserunt non culpa incididunt eiusmod. Minim nulla reprehenderit irure duis amet commodo commodo aliquip ut. Lorem amet ipsum excepteur consectetur qui dolore. In occaecat dolor ullamco voluptate dolore qui incididunt occaecat pariatur est qui aliquip labore non.",
"Velit ex nisi in et enim veniam ullamco reprehenderit consectetur Lorem. Dolor commodo pariatur Lorem proident. Ad minim aliquip excepteur officia consequat nulla mollit adipisicing ut veniam Lorem. Sint mollit occaecat velit do. Nulla aute Lorem non excepteur.",
"Officia culpa in adipisicing sunt qui culpa voluptate ad veniam adipisicing anim ex aute. Laboris ipsum id est cillum minim quis sint ex culpa dolore minim. Lorem excepteur deserunt voluptate minim consequat qui quis enim. Do non irure pariatur exercitation commodo laboris sit. Sunt magna nulla magna Lorem reprehenderit dolore et tempor Lorem esse quis exercitation tempor commodo."
],
"qux": {
"thud": "Et mollit occaecat et aliqua officia adipisicing adipisicing. Fugiat cillum dolor eu laborum cupidatat aliqua et reprehenderit do laboris velit in. Dolor voluptate Lorem pariatur voluptate enim labore in et pariatur consequat esse elit. Do qui aute proident in aliquip. Ea velit quis ex enim proident tempor laboris exercitation aute consectetur minim.",
"fizz": {
"buzz": "Sunt Lorem officia reprehenderit ea esse aliqua in veniam. Do irure amet dolore magna amet tempor anim sit irure tempor proident laborum dolore. Aute et ullamco eiusmod culpa et esse. Minim ut elit laboris est. Est mollit et mollit dolore ea adipisicing nostrud excepteur."
}
}
});

Self {
examples: vec![
Expand All @@ -40,6 +54,7 @@ impl Default for DemoApp {
Box::new(CopyToClipboardExample::new(complex_object.clone())),
Box::new(JsonEditorExample::new(complex_object.clone())),
Box::new(ToggleButtonsCustomisationDemo::new(complex_object)),
Box::new(WrappingExample::new(long_strings_object)),
],
open_example_idx: None,
left_sidebar_expanded: true,
Expand All @@ -62,21 +77,23 @@ impl eframe::App for DemoApp {

ui.label(egui::RichText::new("Examples").monospace());
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
egui::ScrollArea::vertical().show(ui, |ui| {
for (idx, example) in self.examples.iter().enumerate() {
let is_open = self
.open_example_idx
.is_some_and(|open_idx| open_idx == idx);

if ui.selectable_label(is_open, example.title()).clicked() {
if is_open {
self.open_example_idx = None;
} else {
self.open_example_idx = Some(idx);
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.show(ui, |ui| {
for (idx, example) in self.examples.iter().enumerate() {
let is_open = self
.open_example_idx
.is_some_and(|open_idx| open_idx == idx);

if ui.selectable_label(is_open, example.title()).clicked() {
if is_open {
self.open_example_idx = None;
} else {
self.open_example_idx = Some(idx);
}
}
}
}
});
});
});
});

Expand All @@ -100,9 +117,11 @@ impl eframe::App for DemoApp {
egui::CentralPanel::default().show(ctx, |ui| {
match example {
Some(example) => {
egui::ScrollArea::vertical().show(ui, |ui| {
example.show(ui);
});
egui::ScrollArea::vertical()
.auto_shrink([false, false])
.show(ui, |ui| {
example.show(ui);
});
}
None => {
if !self.left_sidebar_expanded {
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub mod value;

pub use default_expand::DefaultExpand;
pub use response::JsonTreeResponse;
pub use style::{JsonTreeStyle, JsonTreeVisuals};
pub use style::{
JsonTreeMaxWidth, JsonTreeStyle, JsonTreeVisuals, JsonTreeWrapping, JsonTreeWrappingConfig,
};
pub use toggle_buttons_state::ToggleButtonsState;
pub use tree::JsonTree;
12 changes: 10 additions & 2 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
delimiters::{SpacingDelimiter, ARRAY_DELIMITERS, OBJECT_DELIMITERS},
pointer::{JsonPointer, JsonPointerSegment},
render::{
JsonTreeRenderer, RenderBaseValueContext, RenderExpandableDelimiterContext,
JsonTreeRenderer, ParentStatus, RenderBaseValueContext, RenderExpandableDelimiterContext,
RenderPropertyContext, RenderSpacingDelimiterContext,
},
response::JsonTreeResponse,
Expand Down Expand Up @@ -94,7 +94,9 @@ impl<'a, 'b, T: ToJsonTreeValue> JsonTreeNode<'a, 'b, T> {
) {
match self.value.to_json_tree_value() {
JsonTreeValue::Base(value, display_value, value_type) => {
ui.horizontal_wrapped(|ui| {
// Use horizontal instead of horizontal_wrapped so that the
// base value always starts inline with the property and not below it.
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;

if let Some(property) = self.parent {
Expand Down Expand Up @@ -127,6 +129,11 @@ impl<'a, 'b, T: ToJsonTreeValue> JsonTreeNode<'a, 'b, T> {
pointer: JsonPointer(path_segments),
style: &self.config.style,
search_term: self.config.search_term.as_ref(),
parent_status: if self.parent.is_some() {
ParentStatus::ExpandedParent
} else {
ParentStatus::NoParent
},
},
);
});
Expand Down Expand Up @@ -259,6 +266,7 @@ impl<'a, 'b, T: ToJsonTreeValue> JsonTreeNode<'a, 'b, T> {
pointer: JsonPointer(path_segments),
style,
search_term: search_term.as_ref(),
parent_status: ParentStatus::CollapsedRoot,
},
);
}
Expand Down
17 changes: 14 additions & 3 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ impl<'a, 'b, T: ToJsonTreeValue> DefaultRender for RenderPropertyContext<'a, 'b,
}
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum ParentStatus {
NoParent,
ExpandedParent,
CollapsedRoot,
}

/// A handle to the information of a render call for a non-recursive JSON value.
pub struct RenderBaseValueContext<'a, 'b, T: ToJsonTreeValue> {
/// The non-recursive JSON value being rendered.
Expand All @@ -100,6 +107,7 @@ pub struct RenderBaseValueContext<'a, 'b, T: ToJsonTreeValue> {
/// The [`JsonTreeStyle`] that the [`JsonTree`](crate::JsonTree) was configured with.
pub style: &'b JsonTreeStyle,
pub(crate) search_term: Option<&'b SearchTerm>,
pub(crate) parent_status: ParentStatus,
}

impl<'a, 'b, T: ToJsonTreeValue> DefaultRender for RenderBaseValueContext<'a, 'b, T> {
Expand All @@ -110,6 +118,7 @@ impl<'a, 'b, T: ToJsonTreeValue> DefaultRender for RenderBaseValueContext<'a, 'b
&self.display_value.to_string(),
&self.value_type,
self.search_term,
self.parent_status,
)
}
}
Expand Down Expand Up @@ -278,8 +287,9 @@ fn render_value(
value_str: &str,
value_type: &BaseValueType,
search_term: Option<&SearchTerm>,
parent_status: ParentStatus,
) -> Response {
let job = ui.ctx().memory_mut(|mem| {
let mut job = ui.ctx().memory_mut(|mem| {
mem.caches.cache::<ValueLayoutJobCreatorCache>().get((
style.resolve_visuals(ui),
value_str,
Expand All @@ -288,7 +298,7 @@ fn render_value(
&style.resolve_font_id(ui),
))
});

job.wrap = style.resolve_value_text_wrapping(parent_status, ui);
render_job(ui, job)
}

Expand Down Expand Up @@ -452,5 +462,6 @@ fn render_delimiter(ui: &mut Ui, style: &JsonTreeStyle, delimiter_str: &str) ->
}

fn render_job(ui: &mut Ui, job: LayoutJob) -> Response {
ui.add(Label::new(job).sense(Sense::click_and_drag()))
let galley = ui.fonts(|f| f.layout_job(job));
ui.add(Label::new(galley).sense(Sense::click_and_drag()))
}
Loading
Loading