-
Notifications
You must be signed in to change notification settings - Fork 251
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
re_ui
crate to encapsulate our own egui theme (#476)
* 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
Showing
37 changed files
with
562 additions
and
456 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
File renamed without changes
File renamed without changes
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
}) | ||
}); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.