Skip to content

Automatic dark/light theming #30

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

Merged
merged 4 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ JsonTree::new("simple-tree", &value).show(ui);
// Customised:
let response = JsonTree::new("customised-tree", &value)
.style(JsonTreeStyle {
bool_color: Color32::YELLOW,
visuals: Some(JsonTreeVisuals {
bool_color: Color32::YELLOW,
..Default::default()
}),
..Default::default()
})
.default_expand(DefaultExpand::All)
Expand Down
17 changes: 11 additions & 6 deletions examples/demo/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use apps::{
editor::JsonEditorExample, search::SearchExample,
toggle_buttons::ToggleButtonsCustomisationDemo, Example, Show,
};
use egui::global_theme_preference_buttons;
use serde_json::json;

mod apps;
Expand All @@ -12,10 +13,8 @@ struct DemoApp {
open_example_idx: Option<usize>,
}

impl DemoApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
cc.egui_ctx.set_theme(egui::Theme::Dark);

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});

Self {
Expand Down Expand Up @@ -52,6 +51,12 @@ impl eframe::App for DemoApp {
egui::SidePanel::left("left-panel")
.resizable(false)
.show(ctx, |ui| {
egui::TopBottomPanel::top("theme-preference-top-panel")
.frame(egui::Frame::side_top_panel(&ctx.style()).inner_margin(10.0))
.show_inside(ui, |ui| {
global_theme_preference_buttons(ui);
});
ui.add_space(10.0);
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| {
for (idx, example) in self.examples.iter().enumerate() {
let is_open = self
Expand Down Expand Up @@ -104,7 +109,7 @@ fn main() {
let _ = eframe::run_native(
"egui JSON Tree Demo",
eframe::NativeOptions::default(),
Box::new(|cc| Ok(Box::new(DemoApp::new(cc)))),
Box::new(|_cc| Ok(Box::<DemoApp>::default())),
);
}

Expand Down Expand Up @@ -133,7 +138,7 @@ fn main() {
.start(
canvas,
web_options,
Box::new(|cc| Ok(Box::new(DemoApp::new(cc)))),
Box::new(|_cc| Ok(Box::<DemoApp>::default())),
)
.await;

Expand Down
11 changes: 7 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! # DefaultRender, RenderBaseValueContext, RenderContext, RenderExpandableDelimiterContext,
//! # RenderPropertyContext,
//! # },
//! # DefaultExpand, JsonTree, JsonTreeStyle, ToggleButtonsState
//! # DefaultExpand, JsonTree, JsonTreeStyle, JsonTreeVisuals, ToggleButtonsState
//! # };
//! # egui::__run_test_ui(|ui| {
//! let value = serde_json::json!({ "foo": "bar", "fizz": [1, 2, 3]});
Expand All @@ -24,8 +24,11 @@
//! // Customised:
//! let response = JsonTree::new("customised-tree", &value)
//! .style(JsonTreeStyle {
//! bool_color: Color32::YELLOW,
//! ..Default::default()
//! visuals: Some(JsonTreeVisuals {
//! bool_color: Color32::YELLOW,
//! ..Default::default()
//! }),
//! ..Default::default()
//! })
//! .default_expand(DefaultExpand::All)
//! .abbreviate_root(true) // Show {...} when the root object is collapsed.
Expand Down Expand Up @@ -86,6 +89,6 @@ pub mod value;

pub use default_expand::DefaultExpand;
pub use response::JsonTreeResponse;
pub use style::JsonTreeStyle;
pub use style::{JsonTreeStyle, JsonTreeVisuals};
pub use toggle_buttons_state::ToggleButtonsState;
pub use tree::JsonTree;
38 changes: 19 additions & 19 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
pointer::{JsonPointer, JsonPointerSegment},
search::SearchTerm,
value::{BaseValueType, ToJsonTreeValue},
JsonTreeStyle,
JsonTreeStyle, JsonTreeVisuals,
};

/// A closure for a user-defined custom rendering implementation.
Expand Down Expand Up @@ -215,13 +215,13 @@ struct ValueLayoutJobCreator;
impl ValueLayoutJobCreator {
fn create(
&self,
style: &JsonTreeStyle,
visuals: &JsonTreeVisuals,
value_str: &str,
value_type: &BaseValueType,
search_term: Option<&SearchTerm>,
font_id: &FontId,
) -> LayoutJob {
let color = style.get_color(value_type);
let color = visuals.get_color(value_type);
let add_quote_if_string = |job: &mut LayoutJob| {
if *value_type == BaseValueType::String {
append(job, "\"", color, None, font_id)
Expand All @@ -234,7 +234,7 @@ impl ValueLayoutJobCreator {
value_str,
color,
search_term,
style.highlight_color,
visuals.highlight_color,
font_id,
);
add_quote_if_string(&mut job);
Expand All @@ -245,7 +245,7 @@ impl ValueLayoutJobCreator {
impl
ComputerMut<
(
&JsonTreeStyle,
&JsonTreeVisuals,
&str,
&BaseValueType,
Option<&SearchTerm>,
Expand All @@ -256,15 +256,15 @@ impl
{
fn compute(
&mut self,
(style, value_str, value_type, search_term, font_id): (
&JsonTreeStyle,
(visuals, value_str, value_type, search_term, font_id): (
&JsonTreeVisuals,
&str,
&BaseValueType,
Option<&SearchTerm>,
&FontId,
),
) -> LayoutJob {
self.create(style, value_str, value_type, search_term, font_id)
self.create(visuals, value_str, value_type, search_term, font_id)
}
}

Expand All @@ -279,7 +279,7 @@ fn render_value(
) -> Response {
let job = ui.ctx().memory_mut(|mem| {
mem.caches.cache::<ValueLayoutJobCreatorCache>().get((
style,
style.visuals(ui),
value_str,
value_type,
search_term,
Expand All @@ -296,7 +296,7 @@ struct PropertyLayoutJobCreator;
impl PropertyLayoutJobCreator {
fn create(
&self,
style: &JsonTreeStyle,
visuals: &JsonTreeVisuals,
property: &JsonPointerSegment,
search_term: Option<&SearchTerm>,
font_id: &FontId,
Expand All @@ -306,15 +306,15 @@ impl PropertyLayoutJobCreator {
JsonPointerSegment::Index(_) => add_array_idx(
&mut job,
&property.to_string(),
style.array_idx_color,
visuals.array_idx_color,
font_id,
),
JsonPointerSegment::Key(_) => add_object_key(
&mut job,
&property.to_string(),
style.object_key_color,
visuals.object_key_color,
search_term,
style.highlight_color,
visuals.highlight_color,
font_id,
),
};
Expand All @@ -325,7 +325,7 @@ impl PropertyLayoutJobCreator {
impl<'a>
ComputerMut<
(
&JsonTreeStyle,
&JsonTreeVisuals,
&JsonPointerSegment<'a>,
Option<&SearchTerm>,
&FontId,
Expand All @@ -335,14 +335,14 @@ impl<'a>
{
fn compute(
&mut self,
(style, parent, search_term, font_id): (
&JsonTreeStyle,
(visuals, parent, search_term, font_id): (
&JsonTreeVisuals,
&JsonPointerSegment,
Option<&SearchTerm>,
&FontId,
),
) -> LayoutJob {
self.create(style, parent, search_term, font_id)
self.create(visuals, parent, search_term, font_id)
}
}

Expand All @@ -356,7 +356,7 @@ fn render_property(
) -> Response {
let job = ui.ctx().memory_mut(|mem| {
mem.caches.cache::<PropertyLayoutJobCreatorCache>().get((
style,
style.visuals(ui),
property,
search_term,
&style.font_id(ui),
Expand Down Expand Up @@ -442,7 +442,7 @@ fn render_delimiter(ui: &mut Ui, style: &JsonTreeStyle, delimiter_str: &str) ->
append(
&mut job,
delimiter_str,
style.punctuation_color,
style.visuals(ui).punctuation_color,
None,
&style.font_id(ui),
);
Expand Down
84 changes: 58 additions & 26 deletions src/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,79 @@ use egui::{Color32, FontId, TextStyle, Ui};

use crate::value::BaseValueType;

/// Contains coloring parameters for JSON syntax highlighting, and search match highlighting.
#[derive(Debug, Clone, Hash)]
/// Styling configuration to control the appearance of the [`JsonTree`](crate::JsonTree).
#[derive(Debug, Clone, Hash, Default)]
pub struct JsonTreeStyle {
/// The colors to use. If not set, defaults to either a dark or light color scheme, depending on [`egui::Visuals::dark_mode`].
pub visuals: Option<JsonTreeVisuals>,
/// The font to use. If not set, defaults to `TextStyle::Monospace.resolve(ui.style())`.
pub font_id: Option<FontId>,
}

impl JsonTreeStyle {
/// Resolves the [`JsonTreeVisuals`] color scheme to use.
pub fn visuals(&self, ui: &Ui) -> &JsonTreeVisuals {
if let Some(visuals) = &self.visuals {
visuals
} else if ui.visuals().dark_mode {
&JsonTreeVisuals::DARK
} else {
&JsonTreeVisuals::LIGHT
}
}

pub(crate) fn font_id(&self, ui: &Ui) -> FontId {
if let Some(font_id) = &self.font_id {
font_id.clone()
} else {
TextStyle::Monospace.resolve(ui.style())
}
}
}

/// Colors for JSON syntax highlighting, and search match highlighting.
#[derive(Debug, Clone, Hash)]
pub struct JsonTreeVisuals {
pub object_key_color: Color32,
pub array_idx_color: Color32,
pub null_color: Color32,
pub bool_color: Color32,
pub number_color: Color32,
pub string_color: Color32,
pub highlight_color: Color32,
/// The text color for array brackets, object braces, colons and commas.
/// The color for array brackets, object braces, colons and commas.
pub punctuation_color: Color32,
/// The font to use. Defaults to `TextStyle::Monospace.resolve(ui.style())`.
pub font_id: Option<FontId>,
}

impl Default for JsonTreeStyle {
impl Default for JsonTreeVisuals {
fn default() -> Self {
Self {
object_key_color: Color32::from_rgb(161, 206, 235),
array_idx_color: Color32::from_rgb(96, 103, 168),
null_color: Color32::from_rgb(103, 154, 209),
bool_color: Color32::from_rgb(103, 154, 209),
number_color: Color32::from_rgb(181, 199, 166),
string_color: Color32::from_rgb(194, 146, 122),
highlight_color: Color32::from_rgba_premultiplied(72, 72, 72, 50),
punctuation_color: Color32::from_gray(140),
font_id: None,
}
Self::DARK
}
}

impl JsonTreeStyle {
impl JsonTreeVisuals {
pub const DARK: Self = Self {
object_key_color: Color32::from_rgb(161, 206, 235),
array_idx_color: Color32::from_rgb(96, 103, 168),
null_color: Color32::from_rgb(103, 154, 209),
bool_color: Color32::from_rgb(103, 154, 209),
number_color: Color32::from_rgb(181, 199, 166),
string_color: Color32::from_rgb(194, 146, 122),
highlight_color: Color32::from_rgba_premultiplied(72, 72, 72, 50),
punctuation_color: Color32::from_gray(140),
};

pub const LIGHT: Self = Self {
object_key_color: Color32::from_rgb(23, 74, 151),
array_idx_color: Color32::from_rgb(158, 46, 103),
null_color: Color32::from_rgb(40, 34, 245),
bool_color: Color32::from_rgb(40, 34, 245),
number_color: Color32::from_rgb(1, 97, 63),
string_color: Color32::from_rgb(149, 38, 31),
highlight_color: Color32::from_rgba_premultiplied(181, 213, 251, 255),
punctuation_color: Color32::from_gray(70),
};

pub fn get_color(&self, base_value_type: &BaseValueType) -> Color32 {
match base_value_type {
BaseValueType::Null => self.null_color,
Expand All @@ -43,12 +83,4 @@ impl JsonTreeStyle {
BaseValueType::String => self.string_color,
}
}

pub(crate) fn font_id(&self, ui: &Ui) -> FontId {
if let Some(font_id) = &self.font_id {
font_id.clone()
} else {
TextStyle::Monospace.resolve(ui.style())
}
}
}