Clean up demos

This commit is contained in:
Emil Ernerfeldt 2021-05-09 10:53:35 +02:00
parent aa3c40c49f
commit 9dc092b778
7 changed files with 291 additions and 349 deletions

View file

@ -11,18 +11,21 @@ use crate::{any, area, window, Id, InputState, LayerId, Pos2, Rect, Style};
/// ///
/// If you want this to persist when closing your app you should serialize `Memory` and store it. /// If you want this to persist when closing your app you should serialize `Memory` and store it.
/// ///
/// If you want to store data for your widgets, you should look at `data`/`data_temp` and `id_data`/`id_data_temp` fields, and read the documentation of [`any`] module. /// If you want to store data for your widgets, you should look at `data`/`data_temp` and
/// `id_data`/`id_data_temp` fields, and read the documentation of [`any`] module.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))] #[cfg_attr(feature = "persistence", serde(default))]
pub struct Memory { pub struct Memory {
pub options: Options, pub options: Options,
/// This map stores current states for widgets that don't require `Id`. This will be saved between different program runs if you use the `persistence` feature. /// This map stores current states for widgets that don't require `Id`.
/// This will be saved between different program runs if you use the `persistence` feature.
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
pub data: any::serializable::TypeMap, pub data: any::serializable::TypeMap,
/// This map stores current states for widgets that don't require `Id`. This will be saved between different program runs if you use the `persistence` feature. /// This map stores current states for widgets that don't require `Id`.
/// This will be saved between different program runs if you use the `persistence` feature.
#[cfg(not(feature = "persistence"))] #[cfg(not(feature = "persistence"))]
pub data: any::TypeMap, pub data: any::TypeMap,
@ -30,11 +33,13 @@ pub struct Memory {
#[cfg_attr(feature = "persistence", serde(skip))] #[cfg_attr(feature = "persistence", serde(skip))]
pub data_temp: any::TypeMap, pub data_temp: any::TypeMap,
/// This map stores current states for all widgets with custom `Id`s. This will be saved between different program runs if you use the `persistence` feature. /// This map stores current states for all widgets with custom `Id`s.
/// This will be saved between different program runs if you use the `persistence` feature.
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
pub id_data: any::serializable::AnyMap<Id>, pub id_data: any::serializable::AnyMap<Id>,
/// This map stores current states for all widgets with custom `Id`s. This will be saved between different program runs if you use the `persistence` feature. /// This map stores current states for all widgets with custom `Id`s.
/// This will be saved between different program runs if you use the `persistence` feature.
#[cfg(not(feature = "persistence"))] #[cfg(not(feature = "persistence"))]
pub id_data: any::AnyMap<Id>, pub id_data: any::AnyMap<Id>,

View file

@ -1,3 +1,4 @@
use super::Demo;
use egui::{CtxRef, ScrollArea, Ui, Window}; use egui::{CtxRef, ScrollArea, Ui, Window};
use std::collections::BTreeSet; use std::collections::BTreeSet;
@ -7,17 +8,18 @@ use std::collections::BTreeSet;
#[cfg_attr(feature = "persistence", serde(default))] #[cfg_attr(feature = "persistence", serde(default))]
struct Demos { struct Demos {
#[cfg_attr(feature = "persistence", serde(skip))] #[cfg_attr(feature = "persistence", serde(skip))]
demos: Vec<Box<dyn super::Demo>>, demos: Vec<Box<dyn Demo>>,
open: BTreeSet<String>, open: BTreeSet<String>,
} }
impl Default for Demos { impl Default for Demos {
fn default() -> Self { fn default() -> Self {
let demos: Vec<Box<dyn super::Demo>> = vec![ Self::from_demos(vec![
Box::new(super::dancing_strings::DancingStrings::default()), Box::new(super::dancing_strings::DancingStrings::default()),
Box::new(super::drag_and_drop::DragAndDropDemo::default()), Box::new(super::drag_and_drop::DragAndDropDemo::default()),
Box::new(super::font_book::FontBook::default()), Box::new(super::font_book::FontBook::default()),
Box::new(super::DemoWindow::default()), Box::new(super::MiscDemoWindow::default()),
Box::new(super::multi_touch::MultiTouch::default()), Box::new(super::multi_touch::MultiTouch::default()),
Box::new(super::painting::Painting::default()), Box::new(super::painting::Painting::default()),
Box::new(super::plot_demo::PlotDemo::default()), Box::new(super::plot_demo::PlotDemo::default()),
@ -26,16 +28,12 @@ impl Default for Demos {
Box::new(super::widget_gallery::WidgetGallery::default()), Box::new(super::widget_gallery::WidgetGallery::default()),
Box::new(super::window_options::WindowOptions::default()), Box::new(super::window_options::WindowOptions::default()),
Box::new(super::tests::WindowResizeTest::default()), Box::new(super::tests::WindowResizeTest::default()),
// Tests: ])
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()),
];
use crate::apps::demo::Demo; impl Demos {
pub fn from_demos(demos: Vec<Box<dyn Demo>>) -> Self {
let mut open = BTreeSet::new(); let mut open = BTreeSet::new();
open.insert( open.insert(
super::widget_gallery::WidgetGallery::default() super::widget_gallery::WidgetGallery::default()
@ -45,8 +43,7 @@ impl Default for Demos {
Self { demos, open } Self { demos, open }
} }
}
impl Demos {
pub fn checkboxes(&mut self, ui: &mut Ui) { pub fn checkboxes(&mut self, ui: &mut Ui) {
let Self { demos, open } = self; let Self { demos, open } = self;
for demo in demos { for demo in demos {
@ -56,7 +53,7 @@ impl Demos {
} }
} }
pub fn show(&mut self, ctx: &CtxRef) { pub fn windows(&mut self, ctx: &CtxRef) {
let Self { demos, open } = self; let Self { demos, open } = 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());
@ -66,6 +63,63 @@ impl Demos {
} }
} }
// ----------------------------------------------------------------------------
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
struct Tests {
#[cfg_attr(feature = "persistence", serde(skip))]
demos: Vec<Box<dyn Demo>>,
open: BTreeSet<String>,
}
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<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.checkbox(&mut is_open, demo.name());
set_open(open, demo.name(), is_open);
}
}
pub fn windows(&mut self, ctx: &CtxRef) {
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) {
@ -83,16 +137,25 @@ fn set_open(open: &mut BTreeSet<String>, key: &'static str, is_open: bool) {
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))] #[cfg_attr(feature = "persistence", serde(default))]
pub struct DemoWindows { pub struct DemoWindows {
open_windows: OpenWindows,
demos: Demos, demos: Demos,
tests: Tests,
egui_windows: EguiWindows,
} }
impl DemoWindows { impl DemoWindows {
/// Show the app ui (menu bar and windows). /// Show the app ui (menu bar and windows).
/// `sidebar_ui` can be used to optionally show some things in the sidebar /// `sidebar_ui` can be used to optionally show some things in the sidebar
pub fn ui(&mut self, ctx: &CtxRef) { pub fn ui(&mut self, ctx: &CtxRef) {
let Self {
demos,
tests,
egui_windows,
} = self;
egui::SidePanel::left("side_panel", 190.0).show(ctx, |ui| { egui::SidePanel::left("side_panel", 190.0).show(ctx, |ui| {
ui.vertical_centered(|ui| {
ui.heading("✒ egui demos"); ui.heading("✒ egui demos");
});
ui.separator(); ui.separator();
@ -100,33 +163,34 @@ impl DemoWindows {
use egui::special_emojis::{GITHUB, OS_APPLE, OS_LINUX, OS_WINDOWS}; use egui::special_emojis::{GITHUB, OS_APPLE, OS_LINUX, OS_WINDOWS};
ui.label("egui is an immediate mode GUI library written in Rust."); ui.label("egui is an immediate mode GUI library written in Rust.");
ui.label(format!(
"egui runs on the web, or natively on {}{}{}",
OS_APPLE, OS_LINUX, OS_WINDOWS,
));
ui.vertical_centered(|ui| {
ui.hyperlink_to( ui.hyperlink_to(
format!("{} egui home page", GITHUB), format!("{} egui home page", GITHUB),
"https://github.com/emilk/egui", "https://github.com/emilk/egui",
); );
});
ui.label(format!(
"egui can be run on the web, or natively on {}{}{}",
OS_APPLE, OS_LINUX, OS_WINDOWS,
));
ui.separator(); ui.separator();
demos.checkboxes(ui);
ui.heading("Windows:"); ui.separator();
self.demos.checkboxes(ui); tests.checkboxes(ui);
ui.separator();
ui.separator(); egui_windows.checkboxes(ui);
ui.label("egui:");
self.open_windows.checkboxes(ui);
ui.separator(); ui.separator();
ui.vertical_centered(|ui| {
if ui.button("Organize windows").clicked() { if ui.button("Organize windows").clicked() {
ui.ctx().memory().reset_areas(); ui.ctx().memory().reset_areas();
} }
}); });
}); });
});
egui::TopPanel::top("menu_bar").show(ctx, |ui| { egui::TopPanel::top("menu_bar").show(ctx, |ui| {
show_menu_bar(ui); show_menu_bar(ui);
@ -150,53 +214,34 @@ impl DemoWindows {
/// Show the open windows. /// Show the open windows.
fn windows(&mut self, ctx: &CtxRef) { fn windows(&mut self, ctx: &CtxRef) {
let Self { let Self {
open_windows,
demos, demos,
.. tests,
egui_windows,
} = self; } = self;
Window::new("🔧 Settings") demos.windows(ctx);
.open(&mut open_windows.settings) tests.windows(ctx);
.scroll(true) egui_windows.windows(ctx);
.show(ctx, |ui| {
ctx.settings_ui(ui);
});
Window::new("🔍 Inspection")
.open(&mut open_windows.inspection)
.scroll(true)
.show(ctx, |ui| {
ctx.inspection_ui(ui);
});
Window::new("📝 Memory")
.open(&mut open_windows.memory)
.resizable(false)
.show(ctx, |ui| {
ctx.memory_ui(ui);
});
demos.show(ctx);
} }
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
struct OpenWindows { struct EguiWindows {
// egui stuff: // egui stuff:
settings: bool, settings: bool,
inspection: bool, inspection: bool,
memory: bool, memory: bool,
} }
impl Default for OpenWindows { impl Default for EguiWindows {
fn default() -> Self { fn default() -> Self {
OpenWindows::none() EguiWindows::none()
} }
} }
impl OpenWindows { impl EguiWindows {
fn none() -> Self { fn none() -> Self {
Self { Self {
settings: false, settings: false,
@ -216,6 +261,35 @@ impl OpenWindows {
ui.checkbox(inspection, "🔍 Inspection"); ui.checkbox(inspection, "🔍 Inspection");
ui.checkbox(memory, "📝 Memory"); ui.checkbox(memory, "📝 Memory");
} }
fn windows(&mut self, ctx: &CtxRef) {
let Self {
settings,
inspection,
memory,
} = self;
Window::new("🔧 Settings")
.open(settings)
.scroll(true)
.show(ctx, |ui| {
ctx.settings_ui(ui);
});
Window::new("🔍 Inspection")
.open(inspection)
.scroll(true)
.show(ctx, |ui| {
ctx.inspection_ui(ui);
});
Window::new("📝 Memory")
.open(memory)
.resizable(false)
.show(ctx, |ui| {
ctx.memory_ui(ui);
});
}
} }
fn show_menu_bar(ui: &mut Ui) { fn show_menu_bar(ui: &mut Ui) {

View file

@ -4,7 +4,7 @@ use egui::{color::*, *};
/// Showcase some ui code /// Showcase some ui code
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))] #[cfg_attr(feature = "persistence", serde(default))]
pub struct DemoWindow { pub struct MiscDemoWindow {
num_columns: usize, num_columns: usize,
widgets: Widgets, widgets: Widgets,
@ -13,9 +13,9 @@ pub struct DemoWindow {
box_painting: BoxPainting, box_painting: BoxPainting,
} }
impl Default for DemoWindow { impl Default for MiscDemoWindow {
fn default() -> DemoWindow { fn default() -> MiscDemoWindow {
DemoWindow { MiscDemoWindow {
num_columns: 2, num_columns: 2,
widgets: Default::default(), widgets: Default::default(),
@ -26,7 +26,7 @@ impl Default for DemoWindow {
} }
} }
impl Demo for DemoWindow { impl Demo for MiscDemoWindow {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"✨ Misc Demos" "✨ Misc Demos"
} }
@ -39,7 +39,7 @@ impl Demo for DemoWindow {
} }
} }
impl View for DemoWindow { impl View for MiscDemoWindow {
fn ui(&mut self, ui: &mut Ui) { fn ui(&mut self, ui: &mut Ui) {
CollapsingHeader::new("Widgets") CollapsingHeader::new("Widgets")
.default_open(true) .default_open(true)
@ -105,6 +105,116 @@ impl View for DemoWindow {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Widgets {
angle: f32,
password: String,
lock_focus: bool,
code_snippet: String,
}
impl Default for Widgets {
fn default() -> Self {
Self {
angle: std::f32::consts::TAU / 3.0,
password: "hunter2".to_owned(),
lock_focus: true,
code_snippet: "\
fn main() {
\tprintln!(\"Hello world!\");
}
"
.to_owned(),
}
}
}
impl Widgets {
pub fn ui(&mut self, ui: &mut Ui) {
let Self {
angle,
password,
lock_focus,
code_snippet,
} = self;
ui.vertical_centered(|ui| {
ui.add(crate::__egui_github_link_file_line!());
});
ui.horizontal_wrapped(|ui| {
// Trick so we don't have to add spaces in the text below:
ui.spacing_mut().item_spacing.x = ui.fonts()[TextStyle::Body].glyph_width(' ');
ui.add(Label::new("Text can have").text_color(Color32::from_rgb(110, 255, 110)));
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version
ui.label("and tooltips.").on_hover_text(
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
);
ui.label("You can mix in other widgets into text, like");
let _ = ui.small_button("this button");
ui.label(".");
ui.label("The default font supports all latin and cyrillic characters (ИÅđ…), common math symbols (∫√∞²⅓…), and many emojis (💓🌟🖩…).")
.on_hover_text("There is currently no support for right-to-left languages.");
ui.label("See the 🔤 Font Book for more!");
ui.monospace("There is also a monospace font.");
});
let tooltip_ui = |ui: &mut Ui| {
ui.heading("The name of the tooltip");
ui.horizontal(|ui| {
ui.label("This tooltip was created with");
ui.monospace(".on_hover_ui(...)");
});
let _ = ui.button("A button you can never press");
};
ui.label("Tooltips can be more than just simple text.")
.on_hover_ui(tooltip_ui);
ui.separator();
ui.horizontal(|ui| {
ui.label("An angle:");
ui.drag_angle(angle);
ui.label(format!("{:.3}τ", *angle / std::f32::consts::TAU))
.on_hover_text("Each τ represents one turn (τ = 2π)");
})
.response
.on_hover_text("The angle is stored in radians, but presented in degrees");
ui.separator();
ui.horizontal(|ui| {
ui.hyperlink_to("Password:", super::password::url_to_file_source_code())
.on_hover_text("See the example code for how to use egui to store UI state");
ui.add(super::password::password(password));
});
ui.separator();
ui.horizontal(|ui| {
ui.label("Code editor:");
ui.separator();
ui.checkbox(lock_focus, "Lock focus").on_hover_text(
"When checked, pressing TAB will insert a tab instead of moving focus",
);
});
ui.add(
TextEdit::multiline(code_snippet)
.code_editor()
.lock_focus(*lock_focus),
);
}
}
// ----------------------------------------------------------------------------
#[derive(PartialEq)] #[derive(PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))] #[cfg_attr(feature = "persistence", serde(default))]

View file

@ -6,13 +6,13 @@
mod app; mod app;
pub mod dancing_strings; pub mod dancing_strings;
pub mod demo_window; pub mod demo_app_windows;
mod demo_windows;
pub mod drag_and_drop; pub mod drag_and_drop;
pub mod font_book; pub mod font_book;
pub mod font_contents_emoji; pub mod font_contents_emoji;
pub mod font_contents_ubuntu; pub mod font_contents_ubuntu;
pub mod layout_test; pub mod layout_test;
pub mod misc_demo_window;
pub mod multi_touch; pub mod multi_touch;
pub mod painting; pub mod painting;
pub mod password; pub mod password;
@ -22,10 +22,12 @@ pub mod sliders;
pub mod tests; pub mod tests;
pub mod toggle_switch; pub mod toggle_switch;
pub mod widget_gallery; pub mod widget_gallery;
mod widgets;
pub mod window_options; pub mod window_options;
pub use {app::*, demo_window::DemoWindow, demo_windows::*, widgets::Widgets}; pub use {
app::DemoApp, demo_app_windows::DemoWindows, misc_demo_window::MiscDemoWindow,
widget_gallery::WidgetGallery,
};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View file

@ -1,114 +1,72 @@
//! Source code example about creating other type of your widget which uses `egui::Memory` and //! Source code example about creating a widget which uses `egui::Memory` to store UI state.
//! created using a combination of existing widgets. //!
//! This is meant to be read as a tutorial, hence the plethora of comments. //! 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. /// Password entry field with ability to toggle character hiding.
/// ///
/// ## Example: /// ## Example:
/// ``` ignore /// ``` ignore
/// password_ui(ui, &mut password, "password_1"); /// password_ui(ui, &mut password);
/// ``` /// ```
pub fn password_ui( pub fn password_ui(ui: &mut egui::Ui, text: &mut String) -> egui::Response {
ui: &mut egui::Ui, // This widget has its own state — show or hide password characters.
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 // 1. Declare state struct
// This struct represents the state of this widget. // This struct represents the state of this widget.
// It must implement at least `Clone` and be `'static`. If you use the `persistence` feature, // It must implement at least `Clone` and be `'static`.
// it also must implement `serde::{Deserialize, Serialize}`. // 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 // You should prefer creating custom newtype structs or enums like this, to avoid `TypeId`
// intersection errors, especially when you use `Memory::data` without `Id`. // intersection errors, especially when you use `Memory::data` without `Id`.
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
struct State(bool); struct State(bool);
// 2. Create id // 2. Create id
let id = ui.make_persistent_id(id_source); let id = ui.id().with("show_password");
// 3. Get state for this widget // 3. Get state for this widget
// You can read more about available `Memory` functions in the documentation of `egui::Memory` // You can read more about available `Memory` functions in the documentation of `egui::Memory`
// struct and `egui::any` module. // struct and `egui::any` module.
// You should get state by value, not by reference to avoid borrowing of `Memory`. // You should get state by value, not by reference to avoid borrowing of `Memory`.
let mut state = *ui.memory().id_data.get_or_default::<State>(id); let mut plaintext = *ui.memory().id_data_temp.get_or_default::<State>(id);
// 4. Process ui, change a local copy of the state // 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 // We want TextEdit to fill entire space, and have button after that, so in that case we can
// change direction to right_to_left. // change direction to right_to_left.
let result = ui.with_layout(Layout::right_to_left(), |ui| { let result = ui.with_layout(egui::Layout::right_to_left(), |ui| {
// Here a local copy of the state can be changed by a user. // Here a local copy of the state can be changed by a user.
let response = ui let response = ui
.add(egui::SelectableLabel::new(state.0, "👁")) .add(egui::SelectableLabel::new(plaintext.0, "👁"))
.on_hover_text("Toggle symbols hiding"); .on_hover_text("Show/hide password");
if response.clicked() { if response.clicked() {
state.0 = !state.0; plaintext.0 = !plaintext.0;
} }
let text_edit_size = ui.available_size();
// Here we use this local state. // Here we use this local state.
ui.add(egui::TextEdit::singleline(text).password(!state.0)); ui.add_sized(
text_edit_size,
egui::TextEdit::singleline(text).password(!plaintext.0),
);
}); });
// 5. Insert changed state back // 5. Insert changed state back
ui.memory().id_data.insert(id, state); ui.memory().id_data_temp.insert(id, plaintext);
// All done! Return the interaction response so the user can check what happened // All done! Return the interaction response so the user can check what happened
// (hovered, clicked, ...) and maybe show a tooltip: // (hovered, clicked, ...) and maybe show a tooltip:
result.response 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::<State>(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(...)` // A wrapper that allows the more idiomatic usage pattern: `ui.add(...)`
/// Password entry field with ability to toggle character hiding. /// Password entry field with ability to toggle character hiding.
/// ///
/// ## Example: /// ## Example:
/// ``` ignore /// ``` ignore
/// ui.add(password(&mut password, "password_1")); /// ui.add(password(&mut password));
/// ``` /// ```
pub fn password<'a>( pub fn password(text: &mut String) -> impl egui::Widget + '_ {
text: &'a mut String, move |ui: &mut egui::Ui| password_ui(ui, text)
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 { pub fn url_to_file_source_code() -> String {

View file

@ -14,7 +14,6 @@ pub struct WidgetGallery {
scalar: f32, scalar: f32,
string: String, string: String,
color: egui::Color32, color: egui::Color32,
memory_example: String,
} }
impl Default for WidgetGallery { impl Default for WidgetGallery {
@ -26,7 +25,6 @@ impl Default for WidgetGallery {
scalar: 42.0, scalar: 42.0,
string: Default::default(), string: Default::default(),
color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5), color: egui::Color32::LIGHT_BLUE.linear_multiply(0.5),
memory_example: "qwerty_is_bad_password".to_owned(),
} }
} }
} }
@ -88,7 +86,6 @@ impl WidgetGallery {
scalar, scalar,
string, string,
color, color,
memory_example,
} = self; } = self;
ui.set_enabled(*enabled); ui.set_enabled(*enabled);
@ -204,19 +201,6 @@ impl WidgetGallery {
This toggle switch is just 15 lines of code.", This toggle switch is just 15 lines of code.",
); );
ui.end_row(); 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();
} }
} }

View file

@ -1,191 +0,0 @@
use egui::{color::*, *};
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
enum Enum {
First,
Second,
Third,
}
impl Default for Enum {
fn default() -> Self {
Enum::First
}
}
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "persistence", serde(default))]
pub struct Widgets {
group_enabled: bool,
count: usize,
radio: Enum,
angle: f32,
color: Color32,
single_line_text_input: String,
multiline_text_input: String,
lock_focus: bool,
code_snippet: String,
}
impl Default for Widgets {
fn default() -> Self {
Self {
group_enabled: true,
radio: Enum::First,
count: 0,
angle: std::f32::consts::TAU / 3.0,
color: (Rgba::from_rgb(0.0, 1.0, 0.5) * 0.75).into(),
single_line_text_input: "Hello World!".to_owned(),
lock_focus: true,
multiline_text_input: "Text can both be so wide that it needs a line break, but you can also add manual line break by pressing enter, creating new paragraphs.\nThis is the start of the next paragraph.\n\nClick me to edit me!".to_owned(),
code_snippet: "\
fn main() {
\tprintln!(\"Hello world!\");
}
".to_owned(),
}
}
}
impl Widgets {
pub fn ui(&mut self, ui: &mut Ui) {
ui.vertical_centered(|ui| {
ui.add(crate::__egui_github_link_file_line!());
});
egui::ComboBox::from_label("Version")
.width(150.0)
.selected_text("foo")
.show_ui(ui, |ui| {
egui::CollapsingHeader::new("Dev")
.default_open(true)
.show(ui, |ui| {
ui.label("contents");
});
});
ui.horizontal_wrapped(|ui| {
// Trick so we don't have to add spaces in the text below:
ui.spacing_mut().item_spacing.x = ui.fonts()[TextStyle::Body].glyph_width(' ');
ui.add(Label::new("Text can have").text_color(Color32::from_rgb(110, 255, 110)));
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version
ui.label("and tooltips.").on_hover_text(
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
);
ui.label("You can mix in other widgets into text, like");
let _ = ui.small_button("this button");
ui.label(".");
ui.label("The default font supports all latin and cyrillic characters (ИÅđ…), common math symbols (∫√∞²⅓…), and many emojis (💓🌟🖩…).")
.on_hover_text("There is currently no support for right-to-left languages.");
ui.label("See the 🔤 Font Book for more!");
ui.monospace("There is also a monospace font.");
});
let tooltip_ui = |ui: &mut Ui| {
ui.heading("The name of the tooltip");
ui.horizontal(|ui| {
ui.label("This tooltip was created with");
ui.monospace(".on_hover_ui(...)");
});
let _ = ui.button("A button you can never press");
};
ui.label("Tooltips can be more than just simple text.")
.on_hover_ui(tooltip_ui);
ui.group(|ui| {
ui.checkbox(&mut self.group_enabled, "Group enabled");
ui.set_enabled(self.group_enabled);
ui.horizontal(|ui| {
ui.radio_value(&mut self.radio, Enum::First, "First");
ui.radio_value(&mut self.radio, Enum::Second, "Second");
ui.radio_value(&mut self.radio, Enum::Third, "Third");
});
egui::ComboBox::from_label("Combo Box")
.selected_text(format!("{:?}", self.radio))
.show_ui(ui, |ui| {
ui.selectable_value(&mut self.radio, Enum::First, "First");
ui.selectable_value(&mut self.radio, Enum::Second, "Second");
ui.selectable_value(&mut self.radio, Enum::Third, "Third");
});
});
ui.horizontal(|ui| {
if ui
.button("Click me")
.on_hover_text("This will just increase a counter.")
.clicked()
{
self.count += 1;
}
ui.label(format!("The button has been clicked {} times.", self.count));
});
ui.separator();
ui.horizontal(|ui| {
ui.label("An angle:");
ui.drag_angle(&mut self.angle);
ui.label(format!("{:.3}τ", self.angle / std::f32::consts::TAU))
.on_hover_text("Each τ represents one turn (τ = 2π)");
})
.response
.on_hover_text("The angle is stored in radians, but presented in degrees");
ui.separator();
ui.horizontal(|ui| {
ui.colored_label(self.color, "Click to select a different text color: ");
ui.color_edit_button_srgba(&mut self.color);
});
ui.separator();
ui.horizontal(|ui| {
ui.label("Password:");
// We let `egui` store the show/hide password toggle:
let show_password_id = Id::new("show_password");
let mut show_password: bool = *ui.memory().id_data.get_or_default(show_password_id);
let response = ui.add_sized(
[140.0, 20.0],
egui::TextEdit::singleline(&mut self.single_line_text_input)
.password(!show_password),
);
if response.lost_focus() && ui.input().key_pressed(egui::Key::Enter) {
// …
}
ui.checkbox(&mut show_password, "Show password");
ui.memory().id_data.insert(show_password_id, show_password);
});
ui.separator();
ui.label("Multiline text input:");
ui.text_edit_multiline(&mut self.multiline_text_input);
ui.separator();
ui.horizontal(|ui| {
ui.label("Code editor:");
ui.separator();
ui.checkbox(&mut self.lock_focus, "Lock focus")
.on_hover_text(
"When checked, pressing TAB will insert a tab instead of moving focus",
);
});
ui.add(
TextEdit::multiline(&mut self.code_snippet)
.code_editor()
.lock_focus(self.lock_focus),
);
}
}