1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 13:50:04 -04:00

Improve web demo for mobile (#1556)

`egui_demo_app/lib`: add "About egui" window, and improve mobile layout

This makes the app responsive, removing the side bars on mobile and turning them into drop-down menus instead.
This commit is contained in:
Emil Ernerfeldt
2022-05-02 13:13:35 +02:00
committed by GitHub
parent 078be52ff8
commit 32b4781da2
11 changed files with 371 additions and 169 deletions

View File

@@ -0,0 +1,94 @@
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct About {}
impl super::Demo for About {
fn name(&self) -> &'static str {
"About egui"
}
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.default_width(320.0)
.open(open)
.show(ctx, |ui| {
use super::View as _;
self.ui(ui);
});
}
}
impl super::View for About {
fn ui(&mut self, ui: &mut egui::Ui) {
use egui::special_emojis::{OS_APPLE, OS_LINUX, OS_WINDOWS};
ui.heading("egui");
ui.label(format!(
"egui is an immediate mode GUI library written in Rust. egui runs both on the web and natively on {}{}{}. \
On the web it is compiled to WebAssembly and rendered with WebGL.{}",
OS_APPLE, OS_LINUX, OS_WINDOWS,
if cfg!(target_arch = "wasm32") {
" Everything you see is rendered as textured triangles. There is no DOM, HTML, JS or CSS. Just Rust."
} else {""}
));
ui.label("egui is designed to be easy to use, portable, and fast.");
ui.add_space(12.0); // ui.separator();
ui.heading("Immediate mode");
about_immediate_mode(ui);
ui.add_space(12.0); // ui.separator();
ui.heading("Links");
links(ui);
}
}
fn about_immediate_mode(ui: &mut egui::Ui) {
use crate::syntax_highlighting::code_view_ui;
ui.style_mut().spacing.interact_size.y = 0.0; // hack to make `horizontal_wrapped` work better with text.
ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("Immediate mode is a GUI paradigm that lets you create a GUI with less code and simpler control flow. For example, this is how you create a ");
let _ = ui.small_button("button");
ui.label(" in egui:");
});
ui.add_space(8.0);
code_view_ui(
ui,
r#"
if ui.button("Save").clicked() {
my_state.save();
}"#
.trim_start_matches('\n'),
);
ui.add_space(8.0);
ui.label("Note how there are no callbacks or messages, and no button state to store.");
ui.label("Immediate mode has its roots in gaming, where everything on the screen is painted at the display refresh rate, i.e. at 60+ frames per second. \
In immediate mode GUIs, the entire interface is layed out and painted at the same high rate. \
This makes immediate mode GUIs especially well suited for highly interactive applications.");
ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("More about immediate mode ");
ui.hyperlink_to("here", "https://github.com/emilk/egui#why-immediate-mode");
ui.label(".");
});
}
fn links(ui: &mut egui::Ui) {
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.hyperlink_to("egui documentation", "https://docs.rs/egui/");
}

View File

@@ -1,7 +1,11 @@
use super::Demo;
use egui::{Context, 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))]
@@ -56,7 +60,7 @@ impl Demos {
let Self { demos, open } = self;
for demo in demos {
let mut is_open = open.contains(demo.name());
ui.checkbox(&mut is_open, demo.name());
ui.toggle_value(&mut is_open, demo.name());
set_open(open, demo.name(), is_open);
}
}
@@ -111,7 +115,7 @@ impl Tests {
let Self { demos, open } = self;
for demo in demos {
let mut is_open = open.contains(demo.name());
ui.checkbox(&mut is_open, demo.name());
ui.toggle_value(&mut is_open, demo.name());
set_open(open, demo.name(), is_open);
}
}
@@ -141,23 +145,103 @@ fn set_open(open: &mut BTreeSet<String>, key: &'static str, is_open: bool) {
// ----------------------------------------------------------------------------
/// A menu bar in which you can select different demo windows to show.
#[derive(Default)]
#[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).
/// `sidebar_ui` can be used to optionally show some things in the sidebar
pub fn ui(&mut self, ctx: &Context) {
let Self { demos, tests } = self;
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 {
egui::CentralPanel::default().show(ctx, |_ui| {}); // just to paint a background for the windows to be on top of. Needed on web because of https://github.com/emilk/egui/issues/1548
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(24.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 = 20.0;
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(), |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")
.min_width(150.0)
.default_width(180.0)
.resizable(false)
.default_width(145.0)
.show(ctx, |ui| {
egui::trace!(ui);
ui.vertical_centered(|ui| {
@@ -166,78 +250,72 @@ impl DemoWindows {
ui.separator();
ScrollArea::vertical().show(ui, |ui| {
use egui::special_emojis::{GITHUB, OS_APPLE, OS_LINUX, OS_WINDOWS, TWITTER};
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.label("egui is an immediate mode GUI library written in Rust.");
ui.separator();
ui.label(format!(
"egui runs on the web, or natively on {}{}{}",
OS_APPLE, OS_LINUX, OS_WINDOWS,
));
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();
demos.checkboxes(ui);
ui.separator();
tests.checkboxes(ui);
ui.separator();
ui.vertical_centered(|ui| {
if ui.button("Organize windows").clicked() {
ui.ctx().memory().reset_areas();
}
});
});
self.demo_list_ui(ui);
});
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
show_menu_bar(ui);
egui::menu::bar(ui, |ui| {
file_menu_button(ui);
});
});
egui::CentralPanel::default().show(ctx, |_ui| {}); // just to paint a background for the windows to be on top of. Needed on web because of https://github.com/emilk/egui/issues/1548
self.windows(ctx);
self.show_windows(ctx);
}
/// Show the open windows.
fn windows(&mut self, ctx: &Context) {
let Self { demos, tests } = self;
fn show_windows(&mut self, ctx: &Context) {
egui::CentralPanel::default().show(ctx, |_ui| {}); // just to paint a background for the windows to be on top of. Needed on web because of https://github.com/emilk/egui/issues/1548
self.about.show(ctx, &mut self.about_is_open);
self.demos.windows(ctx);
self.tests.windows(ctx);
}
demos.windows(ctx);
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 show_menu_bar(ui: &mut Ui) {
trace!(ui);
use egui::*;
menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Organize windows").clicked() {
ui.ctx().memory().reset_areas();
ui.close_menu();
}
if ui
.button("Reset egui memory")
.on_hover_text("Forget scroll, positions, sizes etc")
.clicked()
{
*ui.ctx().memory() = Default::default();
ui.close_menu();
}
});
fn file_menu_button(ui: &mut Ui) {
ui.menu_button("File", |ui| {
if ui.button("Organize windows").clicked() {
ui.ctx().memory().reset_areas();
ui.close_menu();
}
if ui
.button("Reset egui memory")
.on_hover_text("Forget scroll, positions, sizes etc")
.clicked()
{
*ui.ctx().memory() = Default::default();
ui.close_menu();
}
});
}

View File

@@ -4,6 +4,7 @@
// ----------------------------------------------------------------------------
pub mod about;
pub mod code_editor;
pub mod code_example;
pub mod context_menu;
@@ -30,7 +31,8 @@ pub mod window_options;
pub mod window_with_panels;
pub use {
demo_app_windows::DemoWindows, misc_demo_window::MiscDemoWindow, widget_gallery::WidgetGallery,
about::About, demo_app_windows::DemoWindows, misc_demo_window::MiscDemoWindow,
widget_gallery::WidgetGallery,
};
// ----------------------------------------------------------------------------

View File

@@ -370,6 +370,7 @@ impl CustomAxisDemo {
marks
}
#[allow(clippy::unused_self)]
fn ui(&mut self, ui: &mut Ui) -> Response {
const MINS_PER_DAY: f64 = CustomAxisDemo::MINS_PER_DAY;
const MINS_PER_H: f64 = CustomAxisDemo::MINS_PER_H;
@@ -587,6 +588,7 @@ impl ItemsDemo {
struct InteractionDemo {}
impl InteractionDemo {
#[allow(clippy::unused_self)]
fn ui(&mut self, ui: &mut Ui) -> Response {
let plot = Plot::new("interaction_demo").height(300.0);

View File

@@ -92,3 +92,12 @@ fn test_egui_zero_window_size() {
);
}
}
// ----------------------------------------------------------------------------
/// Detect narrow screens. This is used to show a simpler UI on mobile devices,
/// especially for the web demo at <https://egui.rs>.
pub fn is_mobile(ctx: &egui::Context) -> bool {
let screen_size = ctx.input().screen_rect().size();
screen_size.x < 550.0
}