Skip to content

Commit

Permalink
re_ui crate to encapsulate our own egui theme (#476)
Browse files Browse the repository at this point in the history
* Create re_ui crate

* Move design tokens into re_ui

* Move StaticImageCache into re_ui

* Create ReUi wrapper around DesignTokens and StaticImageCache

* Move watermark

* Refactor Top Bar logic

* Move icons and medium_icon_toggle_button

* cargo fmt

* Update to latest egui

* Remove glutin dep

* Add top-level docs to re_ui

* Fix clamping of event log
  • Loading branch information
emilk committed Dec 6, 2022
1 parent 91db0ed commit 91b1b41
Show file tree
Hide file tree
Showing 37 changed files with 562 additions and 456 deletions.
239 changes: 105 additions & 134 deletions Cargo.lock

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ opt-level = 2
debug = true

[patch.crates-io]
# 2022-11-30 - table improvements
eframe = { git = "https://github.com/emilk/egui", rev = "7133818c59269fee8cff8d182e776bda8858a727" }
egui = { git = "https://github.com/emilk/egui", rev = "7133818c59269fee8cff8d182e776bda8858a727" }
egui_extras = { git = "https://github.com/emilk/egui", rev = "7133818c59269fee8cff8d182e776bda8858a727" }
egui-wgpu = { git = "https://github.com/emilk/egui", rev = "7133818c59269fee8cff8d182e776bda8858a727" }
emath = { git = "https://github.com/emilk/egui", rev = "7133818c59269fee8cff8d182e776bda8858a727" }
epaint = { git = "https://github.com/emilk/egui", rev = "7133818c59269fee8cff8d182e776bda8858a727" }
# 2022-12-06 - last fixes before egui 0.20 release
eframe = { git = "https://github.com/emilk/egui", rev = "e30ac7f91a35d1f1ba3d8981154cbbc09f2d00d1" }
egui = { git = "https://github.com/emilk/egui", rev = "e30ac7f91a35d1f1ba3d8981154cbbc09f2d00d1" }
egui_extras = { git = "https://github.com/emilk/egui", rev = "e30ac7f91a35d1f1ba3d8981154cbbc09f2d00d1" }
egui-wgpu = { git = "https://github.com/emilk/egui", rev = "e30ac7f91a35d1f1ba3d8981154cbbc09f2d00d1" }
emath = { git = "https://github.com/emilk/egui", rev = "e30ac7f91a35d1f1ba3d8981154cbbc09f2d00d1" }
epaint = { git = "https://github.com/emilk/egui", rev = "e30ac7f91a35d1f1ba3d8981154cbbc09f2d00d1" }

# eframe = { path = "../../egui/crates/eframe" }
# egui = { path = "../../egui/crates/egui" }
Expand Down
3 changes: 1 addition & 2 deletions crates/re_renderer/examples/2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ impl framework::Example for Render2D {

fn new(re_ctx: &mut re_renderer::RenderContext) -> Self {
let rerun_logo =
image::load_from_memory(include_bytes!("../../re_viewer/data/logo_dark_mode.png"))
.unwrap();
image::load_from_memory(include_bytes!("../../re_ui/data/logo_dark_mode.png")).unwrap();

let mut image_data = rerun_logo.as_rgba8().unwrap().to_vec();

Expand Down
31 changes: 31 additions & 0 deletions crates/re_ui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "re_ui"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license = "(MIT OR Apache-2.0) AND OFL-1.1"
publish = false
include = [
"../../LICENSE-APACHE",
"../../LICENSE-MIT",
"**/*.rs",
"Cargo.toml",
"data/*",
]


[features]
default = []


[dependencies]
egui = { version = "0.19", features = ["extra_debug_asserts", "tracing"] }
egui_extras = { version = "0.19", features = ["tracing"] }
image = { version = "0.24", default-features = false, features = ["png"] }
parking_lot = "0.12"
serde = { version = "1", features = ["derive"] }
serde_json = "1"


[dev-dependencies]
eframe = { version = "0.19", default-features = false, features = ["wgpu"] }
1 change: 1 addition & 0 deletions crates/re_ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rerun GUI theme and helpers, built around [`egui`](https://www.egui.rs/).
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
77 changes: 77 additions & 0 deletions crates/re_ui/examples/re_ui_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
fn main() {
let native_options = eframe::NativeOptions {
initial_window_size: Some([1200.0, 800.0].into()),
follow_system_theme: false,
default_theme: eframe::Theme::Dark,

#[cfg(target_os = "macos")]
fullsize_content: re_ui::FULLSIZE_CONTENT,

..Default::default()
};

eframe::run_native(
"re_ui example app",
native_options,
Box::new(move |cc| {
let re_ui = re_ui::ReUi::load_and_apply(&cc.egui_ctx);
Box::new(ExampleApp { re_ui })
}),
);
}

pub struct ExampleApp {
re_ui: re_ui::ReUi,
}

impl eframe::App for ExampleApp {
fn update(&mut self, egui_ctx: &egui::Context, frame: &mut eframe::Frame) {
self.top_bar(egui_ctx, frame);

egui::CentralPanel::default().show(egui_ctx, |ui| {
egui::warn_if_debug_build(ui);
ui.label("Hello world!");
});
}
}

impl ExampleApp {
fn top_bar(&self, egui_ctx: &egui::Context, frame: &mut eframe::Frame) {
let panel_frame = {
egui::Frame {
inner_margin: egui::style::Margin::symmetric(8.0, 2.0),
fill: self.re_ui.design_tokens.top_bar_color,
..Default::default()
}
};

let native_pixels_per_point = frame.info().native_pixels_per_point;
let fullscreen = {
#[cfg(target_os = "macos")]
{
frame.info().window_info.fullscreen
}
#[cfg(not(target_os = "macos"))]
{
false
}
};
let top_bar_style = self
.re_ui
.top_bar_style(native_pixels_per_point, fullscreen);

egui::TopBottomPanel::top("top_bar")
.frame(panel_frame)
.exact_height(top_bar_style.height)
.show(egui_ctx, |ui| {
egui::menu::bar(ui, |ui| {
ui.set_height(top_bar_style.height);
ui.add_space(top_bar_style.indent);

ui.centered_and_justified(|ui| {
ui.strong("re_ui example app");
})
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,13 @@ pub struct DesignTokens {
}

impl DesignTokens {
#[allow(clippy::unused_self)]
pub fn panel_frame(&self, egui_ctx: &egui::Context) -> egui::Frame {
egui::Frame {
fill: egui_ctx.style().visuals.window_fill(),
inner_margin: egui::style::Margin::same(4.0),
..Default::default()
}
}

#[allow(clippy::unused_self)]
pub fn hovering_frame(&self, style: &egui::Style) -> egui::Frame {
egui::Frame {
inner_margin: egui::style::Margin::same(2.0),
outer_margin: egui::style::Margin::same(4.0),
rounding: 4.0.into(),
fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(),
..Default::default()
}
}

#[allow(clippy::unused_self)]
pub fn warning_text(&self, text: impl Into<String>, style: &egui::Style) -> egui::RichText {
egui::RichText::new(text)
.italics()
.color(style.visuals.warn_fg_color)
}

pub fn loop_selection_color() -> egui::Color32 {
egui::Color32::from_rgb(40, 200, 130)
/// Create [`DesignTokens`] and apply style to the given egui context.
pub fn load_and_apply(ctx: &egui::Context) -> Self {
apply_design_tokens(ctx)
}
}

pub(crate) fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens {
fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens {
let apply_font = true;
let apply_font_size = true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ impl Icon {

pub const RIGHT_PANEL_TOGGLE: Icon = Icon::new(
"right_panel_toggle",
include_bytes!("../../data/icons/right_panel_toggle.png"),
include_bytes!("../data/icons/right_panel_toggle.png"),
);
pub const BOTTOM_PANEL_TOGGLE: Icon = Icon::new(
"bottom_panel_toggle",
include_bytes!("../../data/icons/bottom_panel_toggle.png"),
include_bytes!("../data/icons/bottom_panel_toggle.png"),
);
pub const LEFT_PANEL_TOGGLE: Icon = Icon::new(
"left_panel_toggle",
include_bytes!("../../data/icons/left_panel_toggle.png"),
include_bytes!("../data/icons/left_panel_toggle.png"),
);
186 changes: 186 additions & 0 deletions crates/re_ui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//! Rerun GUI theme and helpers, built around [`egui`](https://www.egui.rs/).

mod design_tokens;
pub mod icons;
mod static_image_cache;

pub use design_tokens::DesignTokens;
pub use icons::Icon;
pub use static_image_cache::StaticImageCache;

// ---------------------------------------------------------------------------

/// If true, we fill the entire window, except for the close/maximize/minimize buttons in the top-left.
/// See <https://github.com/emilk/egui/pull/2049>
pub const FULLSIZE_CONTENT: bool = cfg!(target_os = "macos");

// ----------------------------------------------------------------------------

pub struct TopBarStyle {
/// Height of the top bar
pub height: f32,

/// Extra horizontal space in the top left corner to make room for
/// close/minimize/maximize buttons (on Mac)
pub indent: f32,
}

// ----------------------------------------------------------------------------

use parking_lot::Mutex;
use std::sync::Arc;

#[derive(Clone)]
pub struct ReUi {
pub egui_ctx: egui::Context,

/// Colors, styles etc loaded from a design_tokens.json
pub design_tokens: DesignTokens,

pub static_image_cache: Arc<Mutex<StaticImageCache>>,
}

impl ReUi {
/// Create [`ReUi`] and apply style to the given egui context.
pub fn load_and_apply(egui_ctx: &egui::Context) -> Self {
Self {
egui_ctx: egui_ctx.clone(),
design_tokens: DesignTokens::load_and_apply(egui_ctx),
static_image_cache: Arc::new(Mutex::new(StaticImageCache::default())),
}
}

pub fn rerun_logo(&self) -> Arc<egui_extras::RetainedImage> {
if self.egui_ctx.style().visuals.dark_mode {
self.static_image_cache.lock().get(
"logo_dark_mode",
include_bytes!("../data/logo_dark_mode.png"),
)
} else {
self.static_image_cache.lock().get(
"logo_light_mode",
include_bytes!("../data/logo_light_mode.png"),
)
}
}

#[allow(clippy::unused_self)]
pub fn panel_frame(&self) -> egui::Frame {
let style = self.egui_ctx.style();
egui::Frame {
fill: style.visuals.window_fill(),
inner_margin: egui::style::Margin::same(4.0),
..Default::default()
}
}

#[allow(clippy::unused_self)]
pub fn hovering_frame(&self) -> egui::Frame {
let style = self.egui_ctx.style();
egui::Frame {
inner_margin: egui::style::Margin::same(2.0),
outer_margin: egui::style::Margin::same(4.0),
rounding: 4.0.into(),
fill: style.visuals.window_fill(),
stroke: style.visuals.window_stroke(),
..Default::default()
}
}

#[allow(clippy::unused_self)]
pub fn warning_text(&self, text: impl Into<String>) -> egui::RichText {
let style = self.egui_ctx.style();
egui::RichText::new(text)
.italics()
.color(style.visuals.warn_fg_color)
}

pub fn loop_selection_color() -> egui::Color32 {
egui::Color32::from_rgb(40, 200, 130)
}

/// Paint a watermark
pub fn paint_watermark(&self) {
use egui::*;
let logo = self.rerun_logo();
let screen_rect = self.egui_ctx.input().screen_rect;
let size = logo.size_vec2();
let rect = Align2::RIGHT_BOTTOM
.align_size_within_rect(size, screen_rect)
.translate(-Vec2::splat(16.0));
let mut mesh = Mesh::with_texture(logo.texture_id(&self.egui_ctx));
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
mesh.add_rect_with_uv(rect, uv, Color32::WHITE);
self.egui_ctx.debug_painter().add(Shape::mesh(mesh));
}

pub fn top_bar_style(
&self,
native_pixels_per_point: Option<f32>,
fullscreen: bool,
) -> TopBarStyle {
let gui_zoom = if let Some(native_pixels_per_point) = native_pixels_per_point {
native_pixels_per_point / self.egui_ctx.pixels_per_point()
} else {
1.0
};

// On Mac, we share the same space as the native red/yellow/green close/minimize/maximize buttons.
// This means we need to make room for them.
let make_room_for_window_buttons = {
#[cfg(target_os = "macos")]
{
crate::FULLSIZE_CONTENT && !fullscreen
}
#[cfg(not(target_os = "macos"))]
{
_ = fullscreen;
false
}
};

let native_buttons_size_in_native_scale = egui::vec2(64.0, 24.0); // source: I measured /emilk

let height = if make_room_for_window_buttons {
// Use more vertical space when zoomed in…
let height = native_buttons_size_in_native_scale.y;

// …but never shrink below the native button height when zoomed out.
height.max(gui_zoom * native_buttons_size_in_native_scale.y)
} else {
self.egui_ctx.style().spacing.interact_size.y
};

let indent = if make_room_for_window_buttons {
// Always use the same width measured in native GUI coordinates:
gui_zoom * native_buttons_size_in_native_scale.x
} else {
0.0
};

TopBarStyle { height, indent }
}

pub fn medium_icon_toggle_button(
&self,
ui: &mut egui::Ui,
icon: &Icon,
selected: &mut bool,
) -> egui::Response {
let size_points = egui::Vec2::splat(16.0); // TODO(emilk): get from design tokens

let image = self.static_image_cache.lock().get(icon.id, icon.png_bytes);
let texture_id = image.texture_id(ui.ctx());
let tint = if *selected {
ui.visuals().widgets.inactive.fg_stroke.color
} else {
egui::Color32::from_gray(100) // TODO(emilk): get from design tokens
};
let mut response = ui.add(egui::ImageButton::new(texture_id, size_points).tint(tint));
if response.clicked() {
*selected = !*selected;
response.mark_changed();
}
response
}
}

0 comments on commit 91b1b41

Please sign in to comment.