Slight improvements to the demo (#5527)

This commit is contained in:
Emil Ernerfeldt 2024-12-28 22:11:41 +01:00 committed by GitHub
parent 820d42802a
commit fa95351675
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 207 additions and 198 deletions

View File

@ -14,6 +14,7 @@ impl crate::Demo for About {
.default_height(480.0) .default_height(480.0)
.open(open) .open(open)
.resizable([true, false]) .resizable([true, false])
.scroll(false)
.show(ctx, |ui| { .show(ctx, |ui| {
use crate::View as _; use crate::View as _;
self.ui(ui); 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.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"); ui.heading("Immediate mode");
about_immediate_mode(ui); about_immediate_mode(ui);
ui.add_space(12.0); // ui.separator(); ui.add_space(12.0);
ui.heading("Links"); ui.heading("Links");
links(ui); links(ui);
@ -50,7 +53,10 @@ impl crate::View for About {
ui.spacing_mut().item_spacing.x = 0.0; ui.spacing_mut().item_spacing.x = 0.0;
ui.label("egui development is sponsored by "); ui.label("egui development is sponsored by ");
ui.hyperlink_to("Rerun.io", "https://www.rerun.io/"); 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); ui.add_space(12.0);
@ -94,12 +100,12 @@ fn about_immediate_mode(ui: &mut egui::Ui) {
fn links(ui: &mut egui::Ui) { fn links(ui: &mut egui::Ui) {
use egui::special_emojis::{GITHUB, TWITTER}; use egui::special_emojis::{GITHUB, TWITTER};
ui.hyperlink_to( ui.hyperlink_to(
format!("{GITHUB} egui on GitHub"), format!("{GITHUB} github.com/emilk/egui"),
"https://github.com/emilk/egui", "https://github.com/emilk/egui",
); );
ui.hyperlink_to( ui.hyperlink_to(
format!("{TWITTER} @ernerfeldt"), format!("{TWITTER} @ernerfeldt"),
"https://twitter.com/ernerfeldt", "https://twitter.com/ernerfeldt",
); );
ui.hyperlink_to("egui documentation", "https://docs.rs/egui/"); ui.hyperlink_to("📓 egui documentation", "https://docs.rs/egui/");
} }

View File

@ -84,9 +84,8 @@ impl CodeExample {
ui.horizontal(|ui| { ui.horizontal(|ui| {
let font_id = egui::TextStyle::Monospace.resolve(ui.style()); let font_id = egui::TextStyle::Monospace.resolve(ui.style());
let indentation = 8.0 * ui.fonts(|f| f.glyph_width(&font_id, ' ')); let indentation = 2.0 * 4.0 * ui.fonts(|f| f.glyph_width(&font_id, ' '));
let item_spacing = ui.spacing_mut().item_spacing; ui.add_space(indentation);
ui.add_space(indentation - item_spacing.x);
egui::Grid::new("code_samples") egui::Grid::new("code_samples")
.striped(true) .striped(true)
@ -120,7 +119,7 @@ impl crate::Demo for CodeExample {
impl crate::View for CodeExample { impl crate::View for CodeExample {
fn ui(&mut self, ui: &mut egui::Ui) { fn ui(&mut self, ui: &mut egui::Ui) {
ui.scope(|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); self.code(ui);
}); });

View File

@ -1,6 +1,6 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use egui::{Context, Modifiers, NumExt as _, ScrollArea, Ui}; use egui::{Context, Modifiers, ScrollArea, Ui};
use super::About; use super::About;
use crate::is_mobile; use crate::is_mobile;
@ -9,73 +9,17 @@ use crate::View;
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] struct DemoGroup {
#[cfg_attr(feature = "serde", serde(default))]
struct Demos {
#[cfg_attr(feature = "serde", serde(skip))]
demos: Vec<Box<dyn Demo>>, demos: Vec<Box<dyn Demo>>,
open: BTreeSet<String>,
} }
impl Default for Demos { impl DemoGroup {
fn default() -> Self { pub fn new(demos: Vec<Box<dyn Demo>>) -> Self {
Self::from_demos(vec![ Self { demos }
Box::<super::paint_bezier::PaintBezier>::default(),
Box::<super::code_editor::CodeEditor>::default(),
Box::<super::code_example::CodeExample>::default(),
Box::<super::context_menu::ContextMenus>::default(),
Box::<super::dancing_strings::DancingStrings>::default(),
Box::<super::drag_and_drop::DragAndDropDemo>::default(),
Box::<super::extra_viewport::ExtraViewport>::default(),
Box::<super::font_book::FontBook>::default(),
Box::<super::frame_demo::FrameDemo>::default(),
Box::<super::highlighting::Highlighting>::default(),
Box::<super::interactive_container::InteractiveContainerDemo>::default(),
Box::<super::MiscDemoWindow>::default(),
Box::<super::modals::Modals>::default(),
Box::<super::multi_touch::MultiTouch>::default(),
Box::<super::painting::Painting>::default(),
Box::<super::pan_zoom::PanZoom>::default(),
Box::<super::panels::Panels>::default(),
Box::<super::screenshot::Screenshot>::default(),
Box::<super::scrolling::Scrolling>::default(),
Box::<super::sliders::Sliders>::default(),
Box::<super::strip_demo::StripDemo>::default(),
Box::<super::table_demo::TableDemo>::default(),
Box::<super::text_edit::TextEditDemo>::default(),
Box::<super::text_layout::TextLayoutDemo>::default(),
Box::<super::tooltips::Tooltips>::default(),
Box::<super::undo_redo::UndoRedoDemo>::default(),
Box::<super::widget_gallery::WidgetGallery>::default(),
Box::<super::window_options::WindowOptions>::default(),
])
}
}
impl Demos {
pub fn from_demos(demos: Vec<Box<dyn Demo>>) -> 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 }
} }
pub fn checkboxes(&mut self, ui: &mut Ui) { pub fn checkboxes(&mut self, ui: &mut Ui, open: &mut BTreeSet<String>) {
let Self { demos, open } = self; let Self { demos } = self;
for demo in demos { for demo in demos {
if demo.is_enabled(ui.ctx()) { if demo.is_enabled(ui.ctx()) {
let mut is_open = open.contains(demo.name()); let mut is_open = open.contains(demo.name());
@ -85,8 +29,8 @@ impl Demos {
} }
} }
pub fn windows(&mut self, ctx: &Context) { pub fn windows(&mut self, ctx: &Context, open: &mut BTreeSet<String>) {
let Self { demos, open } = self; let Self { demos } = self;
for demo in demos { for demo in demos {
let mut is_open = open.contains(demo.name()); let mut is_open = open.contains(demo.name());
demo.show(ctx, &mut is_open); 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<Box<dyn Demo>>,
open: BTreeSet<String>,
}
impl Default for Tests {
fn default() -> Self {
Self::from_demos(vec![
Box::<super::tests::CursorTest>::default(),
Box::<super::tests::GridTest>::default(),
Box::<super::tests::IdTest>::default(),
Box::<super::tests::InputEventHistory>::default(),
Box::<super::tests::InputTest>::default(),
Box::<super::tests::LayoutTest>::default(),
Box::<super::tests::ManualLayoutTest>::default(),
Box::<super::tests::WindowResizeTest>::default(),
])
}
}
impl Tests {
pub fn from_demos(demos: Vec<Box<dyn Demo>>) -> 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<String>, key: &'static str, is_open: bool) { fn set_open(open: &mut BTreeSet<String>, key: &'static str, is_open: bool) {
if is_open { if is_open {
if !open.contains(key) { if !open.contains(key) {
@ -166,23 +51,131 @@ fn set_open(open: &mut BTreeSet<String>, 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::<super::paint_bezier::PaintBezier>::default(),
Box::<super::code_editor::CodeEditor>::default(),
Box::<super::code_example::CodeExample>::default(),
Box::<super::context_menu::ContextMenus>::default(),
Box::<super::dancing_strings::DancingStrings>::default(),
Box::<super::drag_and_drop::DragAndDropDemo>::default(),
Box::<super::extra_viewport::ExtraViewport>::default(),
Box::<super::font_book::FontBook>::default(),
Box::<super::frame_demo::FrameDemo>::default(),
Box::<super::highlighting::Highlighting>::default(),
Box::<super::interactive_container::InteractiveContainerDemo>::default(),
Box::<super::MiscDemoWindow>::default(),
Box::<super::modals::Modals>::default(),
Box::<super::multi_touch::MultiTouch>::default(),
Box::<super::painting::Painting>::default(),
Box::<super::pan_zoom::PanZoom>::default(),
Box::<super::panels::Panels>::default(),
Box::<super::screenshot::Screenshot>::default(),
Box::<super::scrolling::Scrolling>::default(),
Box::<super::sliders::Sliders>::default(),
Box::<super::strip_demo::StripDemo>::default(),
Box::<super::table_demo::TableDemo>::default(),
Box::<super::text_edit::TextEditDemo>::default(),
Box::<super::text_layout::TextLayoutDemo>::default(),
Box::<super::tooltips::Tooltips>::default(),
Box::<super::undo_redo::UndoRedoDemo>::default(),
Box::<super::widget_gallery::WidgetGallery>::default(),
Box::<super::window_options::WindowOptions>::default(),
]),
tests: DemoGroup::new(vec![
Box::<super::tests::CursorTest>::default(),
Box::<super::tests::GridTest>::default(),
Box::<super::tests::IdTest>::default(),
Box::<super::tests::InputEventHistory>::default(),
Box::<super::tests::InputTest>::default(),
Box::<super::tests::LayoutTest>::default(),
Box::<super::tests::ManualLayoutTest>::default(),
Box::<super::tests::WindowResizeTest>::default(),
]),
}
}
}
impl DemoGroups {
pub fn checkboxes(&mut self, ui: &mut Ui, open: &mut BTreeSet<String>) {
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<String>) {
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. /// 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", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))] #[cfg_attr(feature = "serde", serde(default))]
pub struct DemoWindows { pub struct DemoWindows {
about_is_open: bool, #[cfg_attr(feature = "serde", serde(skip))]
about: About, groups: DemoGroups,
demos: Demos,
tests: Tests, open: BTreeSet<String>,
} }
impl Default for DemoWindows { impl Default for DemoWindows {
fn default() -> Self { 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 { Self {
about_is_open: true, groups: Default::default(),
about: Default::default(), open,
demos: Default::default(),
tests: Default::default(),
} }
} }
} }
@ -197,36 +190,35 @@ impl DemoWindows {
} }
} }
fn mobile_ui(&mut self, ctx: &Context) { fn about_is_open(&self) -> bool {
if self.about_is_open { self.open.contains(About::default().name())
let screen_size = ctx.input(|i| i.screen_rect.size()); }
let default_width = (screen_size.x - 32.0).at_most(400.0);
fn mobile_ui(&mut self, ctx: &Context) {
if self.about_is_open() {
let mut close = false; let mut close = false;
egui::Window::new(self.about.name()) egui::CentralPanel::default().show(ctx, |ui| {
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) egui::ScrollArea::vertical()
.default_width(default_width) .auto_shrink(false)
.default_height(ctx.available_rect().height() - 46.0) .show(ui, |ui| {
.vscroll(true) self.groups.about.ui(ui);
.open(&mut self.about_is_open) ui.add_space(12.0);
.resizable(false) ui.vertical_centered_justified(|ui| {
.collapsible(false) if ui
.show(ctx, |ui| { .button(egui::RichText::new("Continue to the demo!").size(20.0))
self.about.ui(ui); .clicked()
ui.add_space(12.0); {
ui.vertical_centered_justified(|ui| { close = true;
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 { } else {
self.mobile_top_bar(ctx); 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); self.groups.windows(ctx, &mut self.open);
}
/// 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);
} }
fn demo_list_ui(&mut self, ui: &mut egui::Ui) { fn demo_list_ui(&mut self, ui: &mut egui::Ui) {
ScrollArea::vertical().show(ui, |ui| { ScrollArea::vertical().show(ui, |ui| {
ui.with_layout(egui::Layout::top_down_justified(egui::Align::LEFT), |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(); ui.separator();
self.demos.checkboxes(ui);
ui.separator();
self.tests.checkboxes(ui);
ui.separator();
if ui.button("Organize windows").clicked() { if ui.button("Organize windows").clicked() {
ui.ctx().memory_mut(|mem| mem.reset_areas()); ui.ctx().memory_mut(|mem| mem.reset_areas());
} }
@ -382,29 +361,29 @@ fn file_menu_button(ui: &mut Ui) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::demo::demo_app_windows::Demos; use crate::{demo::demo_app_windows::DemoGroups, Demo};
use egui::Vec2; use egui::Vec2;
use egui_kittest::kittest::Queryable; use egui_kittest::kittest::Queryable;
use egui_kittest::{Harness, SnapshotOptions}; use egui_kittest::{Harness, SnapshotOptions};
#[test] #[test]
fn demos_should_match_snapshot() { fn demos_should_match_snapshot() {
let demos = Demos::default(); let demos = DemoGroups::default().demos;
let mut errors = Vec::new(); let mut errors = Vec::new();
for mut demo in demos.demos { 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 // Remove the emoji from the demo name
let name = demo let name = demo
.name() .name()
.split_once(' ') .split_once(' ')
.map_or(demo.name(), |(_, name)| name); .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| { let mut harness = Harness::new(|ctx| {
demo.show(ctx, &mut true); demo.show(ctx, &mut true);
}); });

View File

@ -85,7 +85,7 @@ impl crate::View for FontBook {
ui.horizontal_wrapped(|ui| { ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing = egui::Vec2::splat(2.0); 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() if filter.is_empty()
|| glyph_info.name.contains(filter) || glyph_info.name.contains(filter)
|| *filter == chr.to_string() || *filter == chr.to_string()
@ -96,13 +96,9 @@ impl crate::View for FontBook {
.frame(false); .frame(false);
let tooltip_ui = |ui: &mut egui::Ui| { let tooltip_ui = |ui: &mut egui::Ui| {
ui.label( let font_id = self.font_id.clone();
egui::RichText::new(chr.to_string()).font(self.font_id.clone()),
); char_info_ui(ui, chr, glyph_info, font_id);
ui.label(format!(
"{}\nU+{:X}\n\nFound in: {:?}\n\nClick to copy",
glyph_info.name, chr as u32, glyph_info.fonts
));
}; };
if ui.add(button).on_hover_ui(tooltip_ui).clicked() { 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<char, GlyphInfo> { fn available_characters(ui: &egui::Ui, family: egui::FontFamily) -> BTreeMap<char, GlyphInfo> {
ui.fonts(|f| { ui.fonts(|f| {
f.lock() f.lock()

View File

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e640606207265b4f040f793b0ffb989504b6a98b89e95e77a9a9d3e3abc9327a oid sha256:37ba383a2ba7f00f8064d21203c22f9de2f688ea4fbd45d400c979187686cf33
size 80933 size 80861