2021-01-02 00:01:01 +00:00
/// All the different demo apps.
2021-01-04 00:44:02 +00:00
#[ derive(Default) ]
#[ cfg_attr(feature = " persistence " , derive(serde::Deserialize, serde::Serialize)) ]
#[ cfg_attr(feature = " persistence " , serde(default)) ]
2021-01-01 20:27:10 +00:00
pub struct Apps {
2021-01-01 16:11:05 +00:00
demo : crate ::apps ::DemoApp ,
2021-01-28 22:50:23 +00:00
easy_mark_editor : crate ::apps ::EasyMarkEditor ,
2021-01-04 00:44:02 +00:00
#[ cfg(feature = " http " ) ]
2021-01-01 16:11:05 +00:00
http : crate ::apps ::HttpApp ,
2021-01-01 20:27:10 +00:00
clock : crate ::apps ::FractalClock ,
2021-01-01 23:13:34 +00:00
color_test : crate ::apps ::ColorTest ,
2021-01-01 20:27:10 +00:00
}
impl Apps {
fn iter_mut ( & mut self ) -> impl Iterator < Item = ( & str , & mut dyn epi ::App ) > {
vec! [
( " demo " , & mut self . demo as & mut dyn epi ::App ) ,
2021-01-28 22:50:23 +00:00
( " easymark " , & mut self . easy_mark_editor as & mut dyn epi ::App ) ,
2021-01-04 00:44:02 +00:00
#[ cfg(feature = " http " ) ]
2021-01-01 20:27:10 +00:00
( " http " , & mut self . http as & mut dyn epi ::App ) ,
( " clock " , & mut self . clock as & mut dyn epi ::App ) ,
2021-01-01 23:13:34 +00:00
( " colors " , & mut self . color_test as & mut dyn epi ::App ) ,
2021-01-01 20:27:10 +00:00
]
. into_iter ( )
}
2021-01-01 16:11:05 +00:00
}
2021-01-02 00:01:01 +00:00
/// Wraps many demo/test apps into one.
2021-01-04 00:44:02 +00:00
#[ derive(Default) ]
#[ cfg_attr(feature = " persistence " , derive(serde::Deserialize, serde::Serialize)) ]
#[ cfg_attr(feature = " persistence " , serde(default)) ]
2021-01-02 00:01:01 +00:00
pub struct WrapApp {
selected_anchor : String ,
apps : Apps ,
backend_panel : BackendPanel ,
}
2021-01-01 16:11:05 +00:00
impl epi ::App for WrapApp {
fn name ( & self ) -> & str {
2021-01-17 13:48:59 +00:00
" egui demo apps "
2021-01-01 16:11:05 +00:00
}
2021-01-04 00:44:02 +00:00
#[ cfg(feature = " persistence " ) ]
2021-01-01 16:11:05 +00:00
fn load ( & mut self , storage : & dyn epi ::Storage ) {
* self = epi ::get_value ( storage , epi ::APP_KEY ) . unwrap_or_default ( )
}
2021-01-04 00:44:02 +00:00
#[ cfg(feature = " persistence " ) ]
2021-01-01 16:11:05 +00:00
fn save ( & mut self , storage : & mut dyn epi ::Storage ) {
epi ::set_value ( storage , epi ::APP_KEY , self ) ;
}
2021-02-12 16:57:53 +00:00
fn max_size_points ( & self ) -> egui ::Vec2 {
self . backend_panel . max_size_points_active
}
2021-03-31 18:53:13 +00:00
// Let's show off that we support transparent windows (on native):
fn transparent ( & self ) -> bool {
true
}
fn clear_color ( & self ) -> egui ::Rgba {
egui ::Rgba ::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs`
}
2021-01-02 13:42:43 +00:00
fn warm_up_enabled ( & self ) -> bool {
2021-02-07 13:46:53 +00:00
// The example windows use a lot of emojis. Pre-cache them by running one frame where everything is open
#[ cfg(debug_assertions) ]
{
false // debug
}
#[ cfg(not(debug_assertions)) ]
{
true // release
}
2021-01-02 13:42:43 +00:00
}
2021-01-01 19:22:18 +00:00
fn update ( & mut self , ctx : & egui ::CtxRef , frame : & mut epi ::Frame < '_ > ) {
2021-01-01 20:27:10 +00:00
if let Some ( web_info ) = frame . info ( ) . web_info . as_ref ( ) {
if let Some ( anchor ) = web_info . web_location_hash . strip_prefix ( " # " ) {
self . selected_anchor = anchor . to_owned ( ) ;
}
}
2021-01-01 16:11:05 +00:00
2021-01-01 20:27:10 +00:00
if self . selected_anchor . is_empty ( ) {
self . selected_anchor = self . apps . iter_mut ( ) . next ( ) . unwrap ( ) . 0. to_owned ( ) ;
2021-01-01 16:11:05 +00:00
}
2021-01-02 00:01:01 +00:00
egui ::TopPanel ::top ( " wrap_app_top_bar " ) . show ( ctx , | ui | {
2021-01-01 20:27:10 +00:00
// A menu-bar is a horizontal layout with some special styles applied.
2021-01-02 00:01:01 +00:00
// egui::menu::bar(ui, |ui| {
ui . horizontal_wrapped ( | ui | {
2021-02-03 00:08:23 +00:00
dark_light_mode_switch ( ui ) ;
2021-01-02 00:01:01 +00:00
ui . checkbox ( & mut self . backend_panel . open , " 💻 Backend " ) ;
ui . separator ( ) ;
2021-01-01 20:27:10 +00:00
for ( anchor , app ) in self . apps . iter_mut ( ) {
if ui
. selectable_label ( self . selected_anchor = = anchor , app . name ( ) )
2021-01-25 17:50:19 +00:00
. clicked ( )
2021-01-01 20:27:10 +00:00
{
self . selected_anchor = anchor . to_owned ( ) ;
if frame . is_web ( ) {
2021-03-08 16:48:23 +00:00
ui . output ( ) . open_url ( format! ( " # {} " , anchor ) ) ;
2021-01-01 20:27:10 +00:00
}
}
}
2021-01-01 16:11:05 +00:00
ui . with_layout ( egui ::Layout ::right_to_left ( ) , | ui | {
2021-01-02 00:01:01 +00:00
if false {
// TODO: fix the overlap on small screens
if let Some ( seconds_since_midnight ) = frame . info ( ) . seconds_since_midnight {
2021-01-25 17:50:19 +00:00
if clock_button ( ui , seconds_since_midnight ) . clicked ( ) {
2021-01-02 00:01:01 +00:00
self . selected_anchor = " clock " . to_owned ( ) ;
if frame . is_web ( ) {
2021-03-08 16:48:23 +00:00
ui . output ( ) . open_url ( " #clock " ) ;
2021-01-02 00:01:01 +00:00
}
2021-01-01 20:27:10 +00:00
}
}
}
2021-01-01 16:11:05 +00:00
egui ::warn_if_debug_build ( ui ) ;
} ) ;
} ) ;
} ) ;
2021-01-02 00:01:01 +00:00
self . backend_panel . update ( ctx , frame ) ;
2021-03-07 18:32:27 +00:00
2021-01-02 13:42:43 +00:00
if self . backend_panel . open | | ctx . memory ( ) . everything_is_visible ( ) {
2021-01-02 11:00:14 +00:00
egui ::SidePanel ::left ( " backend_panel " , 150.0 ) . show ( ctx , | ui | {
2021-01-02 00:01:01 +00:00
self . backend_panel . ui ( ui , frame ) ;
} ) ;
}
2021-01-01 20:27:10 +00:00
for ( anchor , app ) in self . apps . iter_mut ( ) {
2021-01-02 13:42:43 +00:00
if anchor = = self . selected_anchor | | ctx . memory ( ) . everything_is_visible ( ) {
2021-01-01 20:27:10 +00:00
app . update ( ctx , frame ) ;
}
2021-01-01 16:11:05 +00:00
}
2021-03-07 18:32:27 +00:00
self . backend_panel . end_of_frame ( ctx ) ;
2021-01-01 16:11:05 +00:00
}
}
2021-01-01 20:27:10 +00:00
fn clock_button ( ui : & mut egui ::Ui , seconds_since_midnight : f64 ) -> egui ::Response {
let time = seconds_since_midnight ;
let time = format! (
" {:02}:{:02}:{:02}.{:02} " ,
( time % ( 24.0 * 60.0 * 60.0 ) / 3600.0 ) . floor ( ) ,
( time % ( 60.0 * 60.0 ) / 60.0 ) . floor ( ) ,
( time % 60.0 ) . floor ( ) ,
( time % 1.0 * 100.0 ) . floor ( )
) ;
ui . add ( egui ::Button ::new ( time ) . text_style ( egui ::TextStyle ::Monospace ) )
}
2021-01-02 00:01:01 +00:00
2021-02-03 00:08:23 +00:00
/// Show a button to switch to/from dark/light mode (globally).
fn dark_light_mode_switch ( ui : & mut egui ::Ui ) {
let style : egui ::Style = ( * ui . ctx ( ) . style ( ) ) . clone ( ) ;
let new_visuals = style . visuals . light_dark_small_toggle_button ( ui ) ;
if let Some ( visuals ) = new_visuals {
2021-02-03 18:38:50 +00:00
ui . ctx ( ) . set_visuals ( visuals ) ;
2021-02-03 00:08:23 +00:00
}
}
2021-01-02 00:01:01 +00:00
// ----------------------------------------------------------------------------
/// How often we repaint the demo app by default
#[ derive(Clone, Copy, Debug, Eq, PartialEq) ]
enum RunMode {
/// This is the default for the demo.
///
2021-01-17 13:48:59 +00:00
/// If this is selected, egui is only updated if are input events
2021-01-02 00:01:01 +00:00
/// (like mouse movements) or there are some animations in the GUI.
///
/// Reactive mode saves CPU.
///
/// The downside is that the UI can become out-of-date if something it is supposed to monitor changes.
/// For instance, a GUI for a thermostat need to repaint each time the temperature changes.
/// To ensure the UI is up to date you need to call `egui::Context::request_repaint()` each
/// time such an event happens. You can also chose to call `request_repaint()` once every second
/// or after every single frame - this is called `Continuous` mode,
/// and for games and interactive tools that need repainting every frame anyway, this should be the default.
Reactive ,
/// This will call `egui::Context::request_repaint()` at the end of each frame
/// to request the backend to repaint as soon as possible.
///
2021-01-17 13:48:59 +00:00
/// On most platforms this will mean that egui will run at the display refresh rate of e.g. 60 Hz.
2021-01-02 00:01:01 +00:00
///
/// For this demo it is not any reason to do so except to
2021-01-17 13:48:59 +00:00
/// demonstrate how quickly egui runs.
2021-01-02 00:01:01 +00:00
///
/// For games or other interactive apps, this is probably what you want to do.
2021-01-17 13:48:59 +00:00
/// It will guarantee that egui is always up-to-date.
2021-01-02 00:01:01 +00:00
Continuous ,
}
/// Default for demo is Reactive since
/// 1) We want to use minimal CPU
/// 2) There are no external events that could invalidate the UI
/// so there are no events to miss.
impl Default for RunMode {
fn default ( ) -> Self {
RunMode ::Reactive
}
}
// ----------------------------------------------------------------------------
2021-01-04 00:44:02 +00:00
#[ cfg_attr(feature = " persistence " , derive(serde::Deserialize, serde::Serialize)) ]
#[ cfg_attr(feature = " persistence " , serde(default)) ]
2021-01-02 00:01:01 +00:00
struct BackendPanel {
open : bool ,
2021-01-04 00:44:02 +00:00
#[ cfg_attr(feature = " persistence " , serde(skip)) ]
// go back to `Reactive` mode each time we start
2021-01-02 00:01:01 +00:00
run_mode : RunMode ,
/// current slider value for current gui scale
pixels_per_point : Option < f32 > ,
2021-02-12 16:57:53 +00:00
/// maximum size of the web browser canvas
max_size_points_ui : egui ::Vec2 ,
max_size_points_active : egui ::Vec2 ,
2021-01-04 00:44:02 +00:00
#[ cfg_attr(feature = " persistence " , serde(skip)) ]
2021-01-02 00:01:01 +00:00
frame_history : crate ::frame_history ::FrameHistory ,
2021-03-07 18:32:27 +00:00
#[ cfg_attr(feature = " persistence " , serde(skip)) ]
2021-03-08 16:48:23 +00:00
output_event_history : std ::collections ::VecDeque < egui ::output ::OutputEvent > ,
2021-01-02 00:01:01 +00:00
}
2021-02-12 16:57:53 +00:00
impl Default for BackendPanel {
fn default ( ) -> Self {
Self {
open : false ,
run_mode : Default ::default ( ) ,
pixels_per_point : Default ::default ( ) ,
max_size_points_ui : egui ::Vec2 ::new ( 1024.0 , 2048.0 ) ,
max_size_points_active : egui ::Vec2 ::new ( 1024.0 , 2048.0 ) ,
frame_history : Default ::default ( ) ,
2021-03-07 18:32:27 +00:00
output_event_history : Default ::default ( ) ,
2021-02-12 16:57:53 +00:00
}
}
}
2021-01-02 00:01:01 +00:00
impl BackendPanel {
fn update ( & mut self , ctx : & egui ::CtxRef , frame : & mut epi ::Frame < '_ > ) {
self . frame_history
. on_new_frame ( ctx . input ( ) . time , frame . info ( ) . cpu_usage ) ;
if self . run_mode = = RunMode ::Continuous {
// Tell the backend to repaint as soon as possible
ctx . request_repaint ( ) ;
}
}
2021-03-07 18:32:27 +00:00
fn end_of_frame ( & mut self , ctx : & egui ::CtxRef ) {
for event in & ctx . output ( ) . events {
self . output_event_history . push_back ( event . clone ( ) ) ;
}
while self . output_event_history . len ( ) > 10 {
self . output_event_history . pop_front ( ) ;
}
}
2021-01-02 00:01:01 +00:00
fn ui ( & mut self , ui : & mut egui ::Ui , frame : & mut epi ::Frame < '_ > ) {
2021-01-02 11:00:14 +00:00
ui . heading ( " 💻 Backend " ) ;
2021-01-02 00:01:01 +00:00
self . run_mode_ui ( ui ) ;
ui . separator ( ) ;
self . frame_history . ui ( ui ) ;
2021-02-21 10:23:33 +00:00
// For instance: `egui_web` sets `pixels_per_point` every frame to force
// egui to use the same scale as the web zoom factor.
let integration_controls_pixels_per_point = ui . input ( ) . raw . pixels_per_point . is_some ( ) ;
if ! integration_controls_pixels_per_point {
2021-01-02 00:01:01 +00:00
ui . separator ( ) ;
if let Some ( new_pixels_per_point ) = self . pixels_per_point_ui ( ui , frame . info ( ) ) {
2021-02-21 10:23:33 +00:00
ui . ctx ( ) . set_pixels_per_point ( new_pixels_per_point ) ;
2021-01-02 00:01:01 +00:00
}
}
2021-01-02 13:42:43 +00:00
ui . separator ( ) ;
if frame . is_web ( ) {
2021-01-17 13:48:59 +00:00
ui . label ( " egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL. " ) ;
2021-01-02 13:42:43 +00:00
ui . label (
" Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements. \
This is not JavaScript . This is Rust , running at 60 FPS . This is the web page , reinvented with game tech . " );
ui . label ( " This is also work in progress, and not ready for production... yet :) " ) ;
2021-03-21 13:46:32 +00:00
ui . horizontal_wrapped ( | ui | {
2021-01-02 13:42:43 +00:00
ui . label ( " Project home page: " ) ;
ui . hyperlink ( " https://github.com/emilk/egui " ) ;
} ) ;
2021-02-12 16:57:53 +00:00
ui . separator ( ) ;
ui . add (
2021-03-27 15:07:18 +00:00
egui ::Slider ::new ( & mut self . max_size_points_ui . x , 512. 0 ..= f32 ::INFINITY )
2021-02-12 16:57:53 +00:00
. logarithmic ( true )
. largest_finite ( 8192.0 )
. text ( " Max width " ) ,
)
. on_hover_text ( " Maximum width of the egui region of the web page. " ) ;
if ! ui . ctx ( ) . is_using_pointer ( ) {
self . max_size_points_active = self . max_size_points_ui ;
}
2021-01-02 13:42:43 +00:00
} else {
2021-01-02 00:01:01 +00:00
if ui
. button ( " 📱 Phone Size " )
. on_hover_text ( " Resize the window to be small like a phone. " )
2021-01-25 17:50:19 +00:00
. clicked ( )
2021-01-02 00:01:01 +00:00
{
frame . set_window_size ( egui ::Vec2 ::new ( 375.0 , 812.0 ) ) ; // iPhone 12 mini
}
2021-01-25 17:50:19 +00:00
if ui . button ( " Quit " ) . clicked ( ) {
2021-01-02 00:01:01 +00:00
frame . quit ( ) ;
}
}
2021-03-07 18:32:27 +00:00
2021-03-24 20:35:29 +00:00
let mut screen_reader = ui . ctx ( ) . memory ( ) . options . screen_reader ;
ui . checkbox ( & mut screen_reader , " Screen reader " ) . on_hover_text ( " Experimental feature: checking this will turn on the screen reader on supported platforms " ) ;
ui . ctx ( ) . memory ( ) . options . screen_reader = screen_reader ;
2021-03-07 18:32:27 +00:00
ui . collapsing ( " Output events " , | ui | {
2021-03-08 17:36:32 +00:00
ui . set_max_width ( 450.0 ) ;
2021-03-24 20:35:29 +00:00
ui . label (
" Recent output events from egui. \
These are emitted when you switch selected widget with tab , \
and can be hooked up to a screen reader on supported platforms . " ,
) ;
2021-03-08 17:36:32 +00:00
ui . advance_cursor ( 8.0 ) ;
2021-03-07 18:32:27 +00:00
for event in & self . output_event_history {
2021-03-08 17:36:32 +00:00
ui . label ( format! ( " {:?} " , event ) ) ;
2021-03-07 18:32:27 +00:00
}
} ) ;
2021-01-02 00:01:01 +00:00
}
fn pixels_per_point_ui (
& mut self ,
ui : & mut egui ::Ui ,
info : & epi ::IntegrationInfo ,
) -> Option < f32 > {
#![ allow(clippy::float_cmp) ]
self . pixels_per_point = self
. pixels_per_point
. or ( info . native_pixels_per_point )
. or_else ( | | Some ( ui . ctx ( ) . pixels_per_point ( ) ) ) ;
let pixels_per_point = self . pixels_per_point . as_mut ( ) ? ;
ui . horizontal ( | ui | {
2021-02-01 15:55:40 +00:00
ui . spacing_mut ( ) . slider_width = 90.0 ;
2021-01-02 00:01:01 +00:00
ui . add (
2021-03-27 15:07:18 +00:00
egui ::Slider ::new ( pixels_per_point , 0. 5 ..= 5.0 )
2021-01-02 00:01:01 +00:00
. logarithmic ( true )
. text ( " Scale " ) ,
)
. on_hover_text ( " Physical pixels per point. " ) ;
if let Some ( native_pixels_per_point ) = info . native_pixels_per_point {
let button = egui ::Button ::new ( " Reset " )
. enabled ( * pixels_per_point ! = native_pixels_per_point ) ;
if ui
. add ( button )
. on_hover_text ( format! (
" Reset scale to native value ({:.1}) " ,
native_pixels_per_point
) )
2021-01-25 17:50:19 +00:00
. clicked ( )
2021-01-02 00:01:01 +00:00
{
* pixels_per_point = native_pixels_per_point ;
}
}
} ) ;
// We wait until mouse release to activate:
2021-01-25 17:50:19 +00:00
if ui . ctx ( ) . is_using_pointer ( ) {
2021-01-02 00:01:01 +00:00
None
} else {
Some ( * pixels_per_point )
}
}
fn run_mode_ui ( & mut self , ui : & mut egui ::Ui ) {
ui . horizontal ( | ui | {
let run_mode = & mut self . run_mode ;
ui . label ( " Mode: " ) ;
ui . radio_value ( run_mode , RunMode ::Continuous , " Continuous " )
. on_hover_text ( " Repaint everything each frame " ) ;
ui . radio_value ( run_mode , RunMode ::Reactive , " Reactive " )
. on_hover_text ( " Repaint when there are animations or input (e.g. mouse movement) " ) ;
} ) ;
if self . run_mode = = RunMode ::Continuous {
ui . label ( format! (
" Repainting the UI each frame. FPS: {:.1} " ,
self . frame_history . fps ( )
) ) ;
} else {
ui . label ( " Only running UI code when there are animations or input " ) ;
}
}
}