From fa9535167594f33e1bdd5157876786ebd614060d Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 28 Dec 2024 22:11:41 +0100 Subject: [PATCH] Slight improvements to the demo (#5527) --- crates/egui_demo_lib/src/demo/about.rs | 16 +- crates/egui_demo_lib/src/demo/code_example.rs | 7 +- .../src/demo/demo_app_windows.rs | 337 ++++++++---------- crates/egui_demo_lib/src/demo/font_book.rs | 41 ++- .../tests/snapshots/demos/Code Example.png | 4 +- 5 files changed, 207 insertions(+), 198 deletions(-) diff --git a/crates/egui_demo_lib/src/demo/about.rs b/crates/egui_demo_lib/src/demo/about.rs index 4a3bfd45..22fa7063 100644 --- a/crates/egui_demo_lib/src/demo/about.rs +++ b/crates/egui_demo_lib/src/demo/about.rs @@ -14,6 +14,7 @@ impl crate::Demo for About { .default_height(480.0) .open(open) .resizable([true, false]) + .scroll(false) .show(ctx, |ui| { use crate::View as _; self.ui(ui); @@ -36,11 +37,13 @@ impl crate::View for About { )); ui.label("egui is designed to be easy to use, portable, and fast."); - ui.add_space(12.0); // ui.separator(); + ui.add_space(12.0); + ui.heading("Immediate mode"); about_immediate_mode(ui); - ui.add_space(12.0); // ui.separator(); + ui.add_space(12.0); + ui.heading("Links"); links(ui); @@ -50,7 +53,10 @@ impl crate::View for About { ui.spacing_mut().item_spacing.x = 0.0; ui.label("egui development is sponsored by "); ui.hyperlink_to("Rerun.io", "https://www.rerun.io/"); - ui.label(", a startup building an SDK for visualizing streams of multimodal data."); + ui.label(", a startup building an SDK for visualizing streams of multimodal data. "); + ui.label("For an example of a real-world egui app, see "); + ui.hyperlink_to("rerun.io/viewer", "https://www.rerun.io/viewer"); + ui.label(" (runs in your browser)."); }); ui.add_space(12.0); @@ -94,12 +100,12 @@ fn about_immediate_mode(ui: &mut egui::Ui) { fn links(ui: &mut egui::Ui) { use egui::special_emojis::{GITHUB, TWITTER}; ui.hyperlink_to( - format!("{GITHUB} egui on GitHub"), + format!("{GITHUB} github.com/emilk/egui"), "https://github.com/emilk/egui", ); ui.hyperlink_to( format!("{TWITTER} @ernerfeldt"), "https://twitter.com/ernerfeldt", ); - ui.hyperlink_to("egui documentation", "https://docs.rs/egui/"); + ui.hyperlink_to("📓 egui documentation", "https://docs.rs/egui/"); } diff --git a/crates/egui_demo_lib/src/demo/code_example.rs b/crates/egui_demo_lib/src/demo/code_example.rs index 3db90ad3..d6c8a7df 100644 --- a/crates/egui_demo_lib/src/demo/code_example.rs +++ b/crates/egui_demo_lib/src/demo/code_example.rs @@ -84,9 +84,8 @@ impl CodeExample { ui.horizontal(|ui| { let font_id = egui::TextStyle::Monospace.resolve(ui.style()); - let indentation = 8.0 * ui.fonts(|f| f.glyph_width(&font_id, ' ')); - let item_spacing = ui.spacing_mut().item_spacing; - ui.add_space(indentation - item_spacing.x); + let indentation = 2.0 * 4.0 * ui.fonts(|f| f.glyph_width(&font_id, ' ')); + ui.add_space(indentation); egui::Grid::new("code_samples") .striped(true) @@ -120,7 +119,7 @@ impl crate::Demo for CodeExample { impl crate::View for CodeExample { fn ui(&mut self, ui: &mut egui::Ui) { ui.scope(|ui| { - ui.spacing_mut().item_spacing = egui::vec2(8.0, 8.0); + ui.spacing_mut().item_spacing = egui::vec2(8.0, 6.0); self.code(ui); }); diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 2cfcdfae..cee322c0 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -1,6 +1,6 @@ use std::collections::BTreeSet; -use egui::{Context, Modifiers, NumExt as _, ScrollArea, Ui}; +use egui::{Context, Modifiers, ScrollArea, Ui}; use super::About; use crate::is_mobile; @@ -9,73 +9,17 @@ use crate::View; // ---------------------------------------------------------------------------- -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "serde", serde(default))] -struct Demos { - #[cfg_attr(feature = "serde", serde(skip))] +struct DemoGroup { demos: Vec>, - - open: BTreeSet, } -impl Default for Demos { - fn default() -> Self { - Self::from_demos(vec![ - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - ]) - } -} - -impl Demos { - pub fn from_demos(demos: Vec>) -> Self { - let mut open = BTreeSet::new(); - - // Explains egui very well - open.insert( - super::code_example::CodeExample::default() - .name() - .to_owned(), - ); - - // Shows off the features - open.insert( - super::widget_gallery::WidgetGallery::default() - .name() - .to_owned(), - ); - - Self { demos, open } +impl DemoGroup { + pub fn new(demos: Vec>) -> Self { + Self { demos } } - pub fn checkboxes(&mut self, ui: &mut Ui) { - let Self { demos, open } = self; + pub fn checkboxes(&mut self, ui: &mut Ui, open: &mut BTreeSet) { + let Self { demos } = self; for demo in demos { if demo.is_enabled(ui.ctx()) { let mut is_open = open.contains(demo.name()); @@ -85,8 +29,8 @@ impl Demos { } } - pub fn windows(&mut self, ctx: &Context) { - let Self { demos, open } = self; + pub fn windows(&mut self, ctx: &Context, open: &mut BTreeSet) { + let Self { demos } = self; for demo in demos { let mut is_open = open.contains(demo.name()); demo.show(ctx, &mut is_open); @@ -95,65 +39,6 @@ impl Demos { } } -// ---------------------------------------------------------------------------- - -#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] -#[cfg_attr(feature = "serde", serde(default))] -struct Tests { - #[cfg_attr(feature = "serde", serde(skip))] - demos: Vec>, - - open: BTreeSet, -} - -impl Default for Tests { - fn default() -> Self { - Self::from_demos(vec![ - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - Box::::default(), - ]) - } -} - -impl Tests { - pub fn from_demos(demos: Vec>) -> Self { - let mut open = BTreeSet::new(); - open.insert( - super::widget_gallery::WidgetGallery::default() - .name() - .to_owned(), - ); - - Self { demos, open } - } - - pub fn checkboxes(&mut self, ui: &mut Ui) { - let Self { demos, open } = self; - for demo in demos { - let mut is_open = open.contains(demo.name()); - ui.toggle_value(&mut is_open, demo.name()); - set_open(open, demo.name(), is_open); - } - } - - pub fn windows(&mut self, ctx: &Context) { - let Self { demos, open } = self; - for demo in demos { - let mut is_open = open.contains(demo.name()); - demo.show(ctx, &mut is_open); - set_open(open, demo.name(), is_open); - } - } -} - -// ---------------------------------------------------------------------------- - fn set_open(open: &mut BTreeSet, key: &'static str, is_open: bool) { if is_open { if !open.contains(key) { @@ -166,23 +51,131 @@ fn set_open(open: &mut BTreeSet, key: &'static str, is_open: bool) { // ---------------------------------------------------------------------------- +pub struct DemoGroups { + about: About, + demos: DemoGroup, + tests: DemoGroup, +} + +impl Default for DemoGroups { + fn default() -> Self { + Self { + about: About::default(), + demos: DemoGroup::new(vec![ + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + ]), + tests: DemoGroup::new(vec![ + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + Box::::default(), + ]), + } + } +} + +impl DemoGroups { + pub fn checkboxes(&mut self, ui: &mut Ui, open: &mut BTreeSet) { + let Self { + about, + demos, + tests, + } = self; + + { + let mut is_open = open.contains(about.name()); + ui.toggle_value(&mut is_open, about.name()); + set_open(open, about.name(), is_open); + } + ui.separator(); + demos.checkboxes(ui, open); + ui.separator(); + tests.checkboxes(ui, open); + } + + pub fn windows(&mut self, ctx: &Context, open: &mut BTreeSet) { + let Self { + about, + demos, + tests, + } = self; + { + let mut is_open = open.contains(about.name()); + about.show(ctx, &mut is_open); + set_open(open, about.name(), is_open); + } + demos.windows(ctx, open); + tests.windows(ctx, open); + } +} + +// ---------------------------------------------------------------------------- + /// A menu bar in which you can select different demo windows to show. #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] pub struct DemoWindows { - about_is_open: bool, - about: About, - demos: Demos, - tests: Tests, + #[cfg_attr(feature = "serde", serde(skip))] + groups: DemoGroups, + + open: BTreeSet, } impl Default for DemoWindows { fn default() -> Self { + let mut open = BTreeSet::new(); + + // Explains egui very well + set_open(&mut open, About::default().name(), true); + + // Explains egui very well + set_open( + &mut open, + super::code_example::CodeExample::default().name(), + true, + ); + + // Shows off the features + set_open( + &mut open, + super::widget_gallery::WidgetGallery::default().name(), + true, + ); + Self { - about_is_open: true, - about: Default::default(), - demos: Default::default(), - tests: Default::default(), + groups: Default::default(), + open, } } } @@ -197,36 +190,35 @@ impl DemoWindows { } } - fn mobile_ui(&mut self, ctx: &Context) { - if self.about_is_open { - let screen_size = ctx.input(|i| i.screen_rect.size()); - let default_width = (screen_size.x - 32.0).at_most(400.0); + fn about_is_open(&self) -> bool { + self.open.contains(About::default().name()) + } + fn mobile_ui(&mut self, ctx: &Context) { + if self.about_is_open() { let mut close = false; - egui::Window::new(self.about.name()) - .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) - .default_width(default_width) - .default_height(ctx.available_rect().height() - 46.0) - .vscroll(true) - .open(&mut self.about_is_open) - .resizable(false) - .collapsible(false) - .show(ctx, |ui| { - self.about.ui(ui); - ui.add_space(12.0); - ui.vertical_centered_justified(|ui| { - if ui - .button(egui::RichText::new("Continue to the demo!").size(20.0)) - .clicked() - { - close = true; - } + egui::CentralPanel::default().show(ctx, |ui| { + egui::ScrollArea::vertical() + .auto_shrink(false) + .show(ui, |ui| { + self.groups.about.ui(ui); + ui.add_space(12.0); + ui.vertical_centered_justified(|ui| { + if ui + .button(egui::RichText::new("Continue to the demo!").size(20.0)) + .clicked() + { + close = true; + } + }); }); - }); - self.about_is_open &= !close; + }); + if close { + set_open(&mut self.open, About::default().name(), false); + } } else { self.mobile_top_bar(ctx); - self.show_windows(ctx); + self.groups.windows(ctx, &mut self.open); } } @@ -292,27 +284,14 @@ impl DemoWindows { }); }); - self.show_windows(ctx); - } - - /// Show the open windows. - fn show_windows(&mut self, ctx: &Context) { - self.about.show(ctx, &mut self.about_is_open); - self.demos.windows(ctx); - self.tests.windows(ctx); + self.groups.windows(ctx, &mut self.open); } fn demo_list_ui(&mut self, ui: &mut egui::Ui) { ScrollArea::vertical().show(ui, |ui| { ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |ui| { - ui.toggle_value(&mut self.about_is_open, self.about.name()); - + self.groups.checkboxes(ui, &mut self.open); ui.separator(); - self.demos.checkboxes(ui); - ui.separator(); - self.tests.checkboxes(ui); - ui.separator(); - if ui.button("Organize windows").clicked() { ui.ctx().memory_mut(|mem| mem.reset_areas()); } @@ -382,29 +361,29 @@ fn file_menu_button(ui: &mut Ui) { #[cfg(test)] mod tests { - use crate::demo::demo_app_windows::Demos; + use crate::{demo::demo_app_windows::DemoGroups, Demo}; use egui::Vec2; use egui_kittest::kittest::Queryable; use egui_kittest::{Harness, SnapshotOptions}; #[test] fn demos_should_match_snapshot() { - let demos = Demos::default(); + let demos = DemoGroups::default().demos; let mut errors = Vec::new(); for mut demo in demos.demos { + // Widget Gallery needs to be customized (to set a specific date) and has its own test + if demo.name() == crate::WidgetGallery::default().name() { + continue; + } + // Remove the emoji from the demo name let name = demo .name() .split_once(' ') .map_or(demo.name(), |(_, name)| name); - // Widget Gallery needs to be customized (to set a specific date) and has its own test - if name == "Widget Gallery" { - continue; - } - let mut harness = Harness::new(|ctx| { demo.show(ctx, &mut true); }); diff --git a/crates/egui_demo_lib/src/demo/font_book.rs b/crates/egui_demo_lib/src/demo/font_book.rs index e2310f8a..352bc027 100644 --- a/crates/egui_demo_lib/src/demo/font_book.rs +++ b/crates/egui_demo_lib/src/demo/font_book.rs @@ -85,7 +85,7 @@ impl crate::View for FontBook { ui.horizontal_wrapped(|ui| { ui.spacing_mut().item_spacing = egui::Vec2::splat(2.0); - for (&chr, glyph_info) in available_glyphs { + for (&chr, glyph_info) in available_glyphs.iter() { if filter.is_empty() || glyph_info.name.contains(filter) || *filter == chr.to_string() @@ -96,13 +96,9 @@ impl crate::View for FontBook { .frame(false); let tooltip_ui = |ui: &mut egui::Ui| { - ui.label( - egui::RichText::new(chr.to_string()).font(self.font_id.clone()), - ); - ui.label(format!( - "{}\nU+{:X}\n\nFound in: {:?}\n\nClick to copy", - glyph_info.name, chr as u32, glyph_info.fonts - )); + let font_id = self.font_id.clone(); + + char_info_ui(ui, chr, glyph_info, font_id); }; if ui.add(button).on_hover_ui(tooltip_ui).clicked() { @@ -115,6 +111,35 @@ impl crate::View for FontBook { } } +fn char_info_ui(ui: &mut egui::Ui, chr: char, glyph_info: &GlyphInfo, font_id: egui::FontId) { + let resp = ui.label(egui::RichText::new(chr.to_string()).font(font_id)); + + egui::Grid::new("char_info") + .num_columns(2) + .striped(true) + .show(ui, |ui| { + ui.label("Name"); + ui.label(glyph_info.name.clone()); + ui.end_row(); + + ui.label("Hex"); + ui.label(format!("{:X}", chr as u32)); + ui.end_row(); + + ui.label("Width"); + ui.label(format!("{:.1} pts", resp.rect.width())); + ui.end_row(); + + ui.label("Fonts"); + ui.label( + format!("{:?}", glyph_info.fonts) + .trim_start_matches('[') + .trim_end_matches(']'), + ); + ui.end_row(); + }); +} + fn available_characters(ui: &egui::Ui, family: egui::FontFamily) -> BTreeMap { ui.fonts(|f| { f.lock() diff --git a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png index 04fa9ba3..b8350301 100644 --- a/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png +++ b/crates/egui_demo_lib/tests/snapshots/demos/Code Example.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e640606207265b4f040f793b0ffb989504b6a98b89e95e77a9a9d3e3abc9327a -size 80933 +oid sha256:37ba383a2ba7f00f8064d21203c22f9de2f688ea4fbd45d400c979187686cf33 +size 80861