use egui::{Context, Modifiers, ScrollArea, Ui}; use std::collections::BTreeSet; use super::About; use super::Demo; use super::View; use crate::is_mobile; // ---------------------------------------------------------------------------- #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "serde", serde(default))] struct Demos { #[cfg_attr(feature = "serde", serde(skip))] demos: Vec>, open: BTreeSet, } impl Default for Demos { fn default() -> Self { Self::from_demos(vec![ Box::new(super::paint_bezier::PaintBezier::default()), Box::new(super::code_editor::CodeEditor::default()), Box::new(super::code_example::CodeExample::default()), Box::new(super::context_menu::ContextMenus::default()), Box::new(super::dancing_strings::DancingStrings::default()), Box::new(super::drag_and_drop::DragAndDropDemo::default()), Box::new(super::font_book::FontBook::default()), Box::new(super::MiscDemoWindow::default()), Box::new(super::multi_touch::MultiTouch::default()), Box::new(super::painting::Painting::default()), Box::new(super::plot_demo::PlotDemo::default()), Box::new(super::scrolling::Scrolling::default()), Box::new(super::sliders::Sliders::default()), Box::new(super::strip_demo::StripDemo::default()), Box::new(super::table_demo::TableDemo::default()), Box::new(super::text_edit::TextEdit::default()), Box::new(super::widget_gallery::WidgetGallery::default()), Box::new(super::window_options::WindowOptions::default()), Box::new(super::tests::WindowResizeTest::default()), Box::new(super::window_with_panels::WindowWithPanels::default()), ]) } } impl Demos { 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); } } } // ---------------------------------------------------------------------------- #[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::new(super::tests::CursorTest::default()), Box::new(super::tests::IdTest::default()), Box::new(super::tests::InputTest::default()), Box::new(super::layout_test::LayoutTest::default()), Box::new(super::tests::ManualLayoutTest::default()), Box::new(super::tests::TableTest::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) { open.insert(key.to_owned()); } } else { open.remove(key); } } // ---------------------------------------------------------------------------- /// 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, } impl Default for DemoWindows { fn default() -> Self { Self { about_is_open: true, about: Default::default(), demos: Default::default(), tests: Default::default(), } } } impl DemoWindows { /// Show the app ui (menu bar and windows). pub fn ui(&mut self, ctx: &Context) { if is_mobile(ctx) { self.mobile_ui(ctx); } else { self.desktop_ui(ctx); } } fn mobile_ui(&mut self, ctx: &Context) { if self.about_is_open { let screen_size = ctx.input().screen_rect.size(); let default_width = (screen_size.x - 20.0).min(400.0); 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; } }); }); self.about_is_open &= !close; } else { self.mobile_top_bar(ctx); self.show_windows(ctx); } } fn mobile_top_bar(&mut self, ctx: &Context) { egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::menu::bar(ui, |ui| { let font_size = 16.5; ui.menu_button(egui::RichText::new("⏷ demos").size(font_size), |ui| { ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`. self.demo_list_ui(ui); if ui.ui_contains_pointer() && ui.input().pointer.any_click() { ui.close_menu(); } }); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { use egui::special_emojis::{GITHUB, TWITTER}; ui.hyperlink_to( egui::RichText::new(TWITTER).size(font_size), "https://twitter.com/ernerfeldt", ); ui.hyperlink_to( egui::RichText::new(GITHUB).size(font_size), "https://github.com/emilk/egui", ); }); }); }); } fn desktop_ui(&mut self, ctx: &Context) { egui::SidePanel::right("egui_demo_panel") .resizable(false) .default_width(150.0) .show(ctx, |ui| { egui::trace!(ui); ui.vertical_centered(|ui| { ui.heading("✒ egui demos"); }); ui.separator(); use egui::special_emojis::{GITHUB, TWITTER}; ui.hyperlink_to( format!("{} egui on GitHub", GITHUB), "https://github.com/emilk/egui", ); ui.hyperlink_to( format!("{} @ernerfeldt", TWITTER), "https://twitter.com/ernerfeldt", ); ui.separator(); self.demo_list_ui(ui); }); egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::menu::bar(ui, |ui| { file_menu_button(ui); }); }); 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); } 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()); ui.separator(); self.demos.checkboxes(ui); ui.separator(); self.tests.checkboxes(ui); ui.separator(); if ui.button("Organize windows").clicked() { ui.ctx().memory().reset_areas(); } }); }); } } // ---------------------------------------------------------------------------- fn file_menu_button(ui: &mut Ui) { let organize_shortcut = egui::KeyboardShortcut::new(Modifiers::ALT | Modifiers::SHIFT, egui::Key::O); let reset_shortcut = egui::KeyboardShortcut::new(Modifiers::ALT | Modifiers::SHIFT, egui::Key::R); // NOTE: we must check the shortcuts OUTSIDE of the actual "File" menu, // or else they would only be checked if the "File" menu was actually open! if ui.input_mut().consume_shortcut(&organize_shortcut) { ui.ctx().memory().reset_areas(); } if ui.input_mut().consume_shortcut(&reset_shortcut) { *ui.ctx().memory() = Default::default(); } ui.menu_button("File", |ui| { ui.set_min_width(220.0); ui.style_mut().wrap = Some(false); // On the web the browser controls the zoom #[cfg(not(target_arch = "wasm32"))] { egui::gui_zoom::zoom_menu_buttons(ui, None); ui.separator(); } if ui .add( egui::Button::new("Organize Windows") .shortcut_text(ui.ctx().format_shortcut(&organize_shortcut)), ) .clicked() { ui.ctx().memory().reset_areas(); ui.close_menu(); } if ui .add( egui::Button::new("Reset egui memory") .shortcut_text(ui.ctx().format_shortcut(&reset_shortcut)), ) .on_hover_text("Forget scroll, positions, sizes etc") .clicked() { *ui.ctx().memory() = Default::default(); ui.close_menu(); } }); }