From c69ecfe421344f0f10eb93c8f95c9267855f45c7 Mon Sep 17 00:00:00 2001 From: ilya sheprut Date: Sun, 18 Apr 2021 14:13:08 +0600 Subject: [PATCH] Memory usage example in the widget gallery (#307) * init example * add comments * fix grammar in comments * fix CI * change example from view_edit to password * rename file * fix CI --- egui_demo_lib/src/apps/demo/mod.rs | 1 + egui_demo_lib/src/apps/demo/password.rs | 116 ++++++++++++++++++ egui_demo_lib/src/apps/demo/widget_gallery.rs | 16 +++ 3 files changed, 133 insertions(+) create mode 100644 egui_demo_lib/src/apps/demo/password.rs diff --git a/egui_demo_lib/src/apps/demo/mod.rs b/egui_demo_lib/src/apps/demo/mod.rs index 42461645..544acb64 100644 --- a/egui_demo_lib/src/apps/demo/mod.rs +++ b/egui_demo_lib/src/apps/demo/mod.rs @@ -14,6 +14,7 @@ pub mod font_contents_emoji; pub mod font_contents_ubuntu; pub mod layout_test; pub mod painting; +pub mod password; pub mod plot_demo; pub mod scrolling; pub mod sliders; diff --git a/egui_demo_lib/src/apps/demo/password.rs b/egui_demo_lib/src/apps/demo/password.rs new file mode 100644 index 00000000..d12ee821 --- /dev/null +++ b/egui_demo_lib/src/apps/demo/password.rs @@ -0,0 +1,116 @@ +//! Source code example about creating other type of your widget which uses `egui::Memory` and +//! created using a combination of existing widgets. +//! This is meant to be read as a tutorial, hence the plethora of comments. + +use egui::Layout; +use std::fmt::Debug; +use std::hash::Hash; + +/// Password entry field with ability to toggle character hiding. +/// +/// ## Example: +/// ``` ignore +/// password_ui(ui, &mut password, "password_1"); +/// ``` +pub fn password_ui( + ui: &mut egui::Ui, + text: &mut String, + id_source: impl Hash + Debug, +) -> egui::Response { + // This widget has its own state — enabled or disabled, + // so there is the algorithm for this type of widgets: + // 1. Declare state struct + // 2. Create id + // 3. Get state for this widget + // 4. Process ui, change a local copy of the state + // 5. Insert changed state back + + // 1. Declare state struct + // This struct represents the state of this widget. + // It must implement at least `Clone` and be `'static`. If you use the `persistence` feature, + // it also must implement `serde::{Deserialize, Serialize}`. + // You should prefer creating custom newtype structs or enums like this, to avoid TypeId + // intersection errors, especially when you use `Memory::data` without `Id`. + #[derive(Clone, Copy, Default)] + #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] + struct State(bool); + + // 2. Create id + let id = ui.make_persistent_id(id_source); + + // 3. Get state for this widget + // You can read more about available `Memory` functions in the documentation of `egui::Memory` + // struct and `egui::any` module. + // You should get state by value, not by reference to avoid borrowing of `Memory`. + let mut state = *ui.memory().id_data.get_or_default::(id); + + // 4. Process ui, change a local copy of the state + // We want TextEdit to fill entire space, and have button after that, so in that case we can + // change direction to right_to_left. + let result = ui.with_layout(Layout::right_to_left(), |ui| { + // Here a local copy of the state can be changed by a user. + let response = ui + .add(egui::SelectableLabel::new(state.0, "👁")) + .on_hover_text("Toggle symbols hiding"); + if response.clicked() { + state.0 = !state.0; + } + + // Here we use this local state. + ui.add(egui::TextEdit::singleline(text).password(!state.0)); + }); + + // 5. Insert changed state back + ui.memory().id_data.insert(id, state); + + // All done! Return the interaction response so the user can check what happened + // (hovered, clicked, ...) and maybe show a tooltip: + result.response +} + +/// Here is the same code again, but a bit more compact: +#[allow(dead_code)] +fn password_ui_compact( + ui: &mut egui::Ui, + text: &mut String, + id_source: impl Hash + Debug, +) -> egui::Response { + #[derive(Clone, Copy, Default)] + #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] + struct State(bool); + + let id = ui.make_persistent_id(id_source); + let mut state = *ui.memory().id_data.get_or_default::(id); + + let result = ui.with_layout(Layout::right_to_left(), |ui| { + let response = ui + .add(egui::SelectableLabel::new(state.0, "👁")) + .on_hover_text("Toggle symbols hiding"); + if response.clicked() { + state.0 = !state.0; + } + + ui.add(egui::TextEdit::singleline(text).password(!state.0)); + }); + + ui.memory().id_data.insert(id, state); + result.response +} + +// A wrapper that allows the more idiomatic usage pattern: `ui.add(...)` +/// Password entry field with ability to toggle character hiding. +/// +/// ## Example: +/// ``` ignore +/// ui.add(password(&mut password, "password_1")); +/// ``` +pub fn password<'a>( + text: &'a mut String, + id_source: impl Hash + Debug + 'a, +) -> impl egui::Widget + 'a { + move |ui: &mut egui::Ui| password_ui(ui, text, id_source) +} + +pub fn url_to_file_source_code() -> String { + format!("https://github.com/emilk/egui/blob/master/{}", file!()) +} diff --git a/egui_demo_lib/src/apps/demo/widget_gallery.rs b/egui_demo_lib/src/apps/demo/widget_gallery.rs index f2a38c76..661fa4bd 100644 --- a/egui_demo_lib/src/apps/demo/widget_gallery.rs +++ b/egui_demo_lib/src/apps/demo/widget_gallery.rs @@ -14,6 +14,7 @@ pub struct WidgetGallery { scalar: f32, string: String, color: egui::Color32, + memory_example: String, } impl Default for WidgetGallery { @@ -25,6 +26,7 @@ impl Default for WidgetGallery { scalar: 42.0, string: Default::default(), color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5), + memory_example: "qwerty_is_bad_password".to_owned(), } } } @@ -86,6 +88,7 @@ impl WidgetGallery { scalar, string, color, + memory_example, } = self; ui.set_enabled(*enabled); @@ -201,6 +204,19 @@ impl WidgetGallery { This toggle switch is just 15 lines of code.", ); ui.end_row(); + + ui.hyperlink_to( + "egui::Memory usage:", + super::password::url_to_file_source_code(), + ) + .on_hover_text( + "You can use `egui::Memory` to store your own data.\n\ + And state of this widget can be saved and loaded \n\ + between runs automatically under the `persistence`\n\ + feature.", + ); + ui.add(super::password::password(memory_example, "memory example")); + ui.end_row(); } }