1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
use egui::{emath::Float, pos2, Align2, Color32, Mesh, Rect, Shape, Vec2};
use crate::toasts::SUCCESS_COLOR;
use crate::{DesignTokens, TopBarStyle};
/// Extension trait for [`egui::Context`].
///
/// This trait provides Rerun-specific helpers and utilities that require access to the egui
/// context.
pub trait ContextExt {
fn ctx(&self) -> &egui::Context;
// -----------------------------------------------------
// Style-related stuff.
// We could have this on a `StyleExt` trait, but we prefer to have it here on `Context`
// so that it is the same style everywhere (instead of being specific to the parent `ui.style`).
/// Text format used for regular body.
fn text_format_body(&self) -> egui::TextFormat {
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(&self.ctx().style()),
self.ctx().style().visuals.text_color(),
)
}
/// Text format used for labels referring to keys and buttons.
fn text_format_key(&self) -> egui::TextFormat {
let mut style = egui::TextFormat::simple(
egui::TextStyle::Monospace.resolve(&self.ctx().style()),
self.ctx().style().visuals.text_color(),
);
style.background = self.ctx().style().visuals.widgets.noninteractive.bg_fill;
style
}
fn rerun_logo_uri(&self) -> &'static str {
if self.ctx().style().visuals.dark_mode {
"bytes://logo_dark_mode"
} else {
"bytes://logo_light_mode"
}
}
/// Hovered UI and spatial primitives should have this outline.
fn hover_stroke(&self) -> egui::Stroke {
// We want something bright here.
self.ctx().style().visuals.widgets.active.fg_stroke
}
/// Selected UI and spatial primitives should have this outline.
fn selection_stroke(&self) -> egui::Stroke {
self.ctx().style().visuals.selection.stroke
// It is tempting to use the background selection color for outlines,
// but in practice it is way too dark for spatial views (you can't tell what is selected).
// Also: background colors should not be used as stroke colors.
// let color = self.ctx().style().visuals.selection.bg_fill;
// let stroke_width = self.ctx().style().visuals.selection.stroke.width;
// egui::Stroke::new(stroke_width, color)
}
/// Text colored to indicate success.
#[must_use]
fn success_text(&self, text: impl Into<String>) -> egui::RichText {
egui::RichText::new(text).color(SUCCESS_COLOR)
}
/// Text colored to indicate a warning.
///
/// For most cases, you should use [`crate::UiExt::warning_label`] instead,
/// which has a nice fat border around it.
#[must_use]
fn warning_text(&self, text: impl Into<String>) -> egui::RichText {
let style = self.ctx().style();
egui::RichText::new(text).color(style.visuals.warn_fg_color)
}
/// Text colored to indicate an error.
///
/// For most cases, you should use [`crate::UiExt::error_label`] instead,
/// which has a nice fat border around it.
#[must_use]
fn error_text(&self, text: impl Into<String>) -> egui::RichText {
let style = self.ctx().style();
egui::RichText::new(text).color(style.visuals.error_fg_color)
}
fn top_bar_style(&self, style_like_web: bool) -> TopBarStyle {
let egui_zoom_factor = self.ctx().zoom_factor();
let fullscreen = self
.ctx()
.input(|i| i.viewport().fullscreen)
.unwrap_or(false);
// 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 = !style_like_web && {
#[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 {
// On mac we want to match the height of the native red/yellow/green close/minimize/maximize buttons.
// TODO(emilk): move the native window buttons to match our Self::title_bar_height
// 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(native_buttons_size_in_native_scale.y / egui_zoom_factor)
} else {
DesignTokens::top_bar_height() - DesignTokens::top_bar_margin().sum().y
};
let indent = if make_room_for_window_buttons {
// Always use the same width measured in native GUI coordinates:
native_buttons_size_in_native_scale.x / egui_zoom_factor
} else {
0.0
};
TopBarStyle { height, indent }
}
/// Paint a watermark
fn paint_watermark(&self) {
if let Ok(egui::load::TexturePoll::Ready { texture }) = self.ctx().try_load_texture(
self.rerun_logo_uri(),
egui::TextureOptions::default(),
egui::SizeHint::Scale(1.0.ord()),
) {
let rect = Align2::RIGHT_BOTTOM
.align_size_within_rect(texture.size, self.ctx().screen_rect())
.translate(-Vec2::splat(16.0));
let mut mesh = Mesh::with_texture(texture.id);
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.ctx().debug_painter().add(Shape::mesh(mesh));
}
}
}
impl ContextExt for egui::Context {
fn ctx(&self) -> &egui::Context {
self
}
}