Clean up demos
This commit is contained in:
parent
aa3c40c49f
commit
9dc092b778
7 changed files with 291 additions and 349 deletions
|
@ -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 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)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "persistence", serde(default))]
|
||||
pub struct Memory {
|
||||
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")]
|
||||
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"))]
|
||||
pub data: any::TypeMap,
|
||||
|
||||
|
@ -30,11 +33,13 @@ pub struct Memory {
|
|||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
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")]
|
||||
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"))]
|
||||
pub id_data: any::AnyMap<Id>,
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use super::Demo;
|
||||
use egui::{CtxRef, ScrollArea, Ui, Window};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
|
@ -7,17 +8,18 @@ use std::collections::BTreeSet;
|
|||
#[cfg_attr(feature = "persistence", serde(default))]
|
||||
struct Demos {
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
demos: Vec<Box<dyn super::Demo>>,
|
||||
demos: Vec<Box<dyn Demo>>,
|
||||
|
||||
open: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl Default for Demos {
|
||||
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::drag_and_drop::DragAndDropDemo::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::painting::Painting::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::window_options::WindowOptions::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();
|
||||
open.insert(
|
||||
super::widget_gallery::WidgetGallery::default()
|
||||
|
@ -45,8 +43,7 @@ impl Default for Demos {
|
|||
|
||||
Self { demos, open }
|
||||
}
|
||||
}
|
||||
impl Demos {
|
||||
|
||||
pub fn checkboxes(&mut self, ui: &mut Ui) {
|
||||
let Self { demos, open } = self;
|
||||
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;
|
||||
for demo in demos {
|
||||
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) {
|
||||
if is_open {
|
||||
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", serde(default))]
|
||||
pub struct DemoWindows {
|
||||
open_windows: OpenWindows,
|
||||
demos: Demos,
|
||||
tests: Tests,
|
||||
egui_windows: EguiWindows,
|
||||
}
|
||||
|
||||
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: &CtxRef) {
|
||||
let Self {
|
||||
demos,
|
||||
tests,
|
||||
egui_windows,
|
||||
} = self;
|
||||
|
||||
egui::SidePanel::left("side_panel", 190.0).show(ctx, |ui| {
|
||||
ui.heading("✒ egui demos");
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("✒ egui demos");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
|
@ -100,31 +163,32 @@ impl DemoWindows {
|
|||
use egui::special_emojis::{GITHUB, OS_APPLE, OS_LINUX, OS_WINDOWS};
|
||||
|
||||
ui.label("egui is an immediate mode GUI library written in Rust.");
|
||||
ui.hyperlink_to(
|
||||
format!("{} egui home page", GITHUB),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
|
||||
ui.label(format!(
|
||||
"egui can be run on the web, or natively on {}{}{}",
|
||||
"egui runs on the web, or natively on {}{}{}",
|
||||
OS_APPLE, OS_LINUX, OS_WINDOWS,
|
||||
));
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.heading("Windows:");
|
||||
self.demos.checkboxes(ui);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.hyperlink_to(
|
||||
format!("{} egui home page", GITHUB),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("egui:");
|
||||
self.open_windows.checkboxes(ui);
|
||||
|
||||
demos.checkboxes(ui);
|
||||
ui.separator();
|
||||
tests.checkboxes(ui);
|
||||
ui.separator();
|
||||
egui_windows.checkboxes(ui);
|
||||
ui.separator();
|
||||
|
||||
if ui.button("Organize windows").clicked() {
|
||||
ui.ctx().memory().reset_areas();
|
||||
}
|
||||
ui.vertical_centered(|ui| {
|
||||
if ui.button("Organize windows").clicked() {
|
||||
ui.ctx().memory().reset_areas();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -150,53 +214,34 @@ impl DemoWindows {
|
|||
/// Show the open windows.
|
||||
fn windows(&mut self, ctx: &CtxRef) {
|
||||
let Self {
|
||||
open_windows,
|
||||
demos,
|
||||
..
|
||||
tests,
|
||||
egui_windows,
|
||||
} = self;
|
||||
|
||||
Window::new("🔧 Settings")
|
||||
.open(&mut open_windows.settings)
|
||||
.scroll(true)
|
||||
.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);
|
||||
demos.windows(ctx);
|
||||
tests.windows(ctx);
|
||||
egui_windows.windows(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
struct OpenWindows {
|
||||
struct EguiWindows {
|
||||
// egui stuff:
|
||||
settings: bool,
|
||||
inspection: bool,
|
||||
memory: bool,
|
||||
}
|
||||
|
||||
impl Default for OpenWindows {
|
||||
impl Default for EguiWindows {
|
||||
fn default() -> Self {
|
||||
OpenWindows::none()
|
||||
EguiWindows::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenWindows {
|
||||
impl EguiWindows {
|
||||
fn none() -> Self {
|
||||
Self {
|
||||
settings: false,
|
||||
|
@ -216,6 +261,35 @@ impl OpenWindows {
|
|||
ui.checkbox(inspection, "🔍 Inspection");
|
||||
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) {
|
|
@ -4,7 +4,7 @@ use egui::{color::*, *};
|
|||
/// Showcase some ui code
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "persistence", serde(default))]
|
||||
pub struct DemoWindow {
|
||||
pub struct MiscDemoWindow {
|
||||
num_columns: usize,
|
||||
|
||||
widgets: Widgets,
|
||||
|
@ -13,9 +13,9 @@ pub struct DemoWindow {
|
|||
box_painting: BoxPainting,
|
||||
}
|
||||
|
||||
impl Default for DemoWindow {
|
||||
fn default() -> DemoWindow {
|
||||
DemoWindow {
|
||||
impl Default for MiscDemoWindow {
|
||||
fn default() -> MiscDemoWindow {
|
||||
MiscDemoWindow {
|
||||
num_columns: 2,
|
||||
|
||||
widgets: Default::default(),
|
||||
|
@ -26,7 +26,7 @@ impl Default for DemoWindow {
|
|||
}
|
||||
}
|
||||
|
||||
impl Demo for DemoWindow {
|
||||
impl Demo for MiscDemoWindow {
|
||||
fn name(&self) -> &'static str {
|
||||
"✨ 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) {
|
||||
CollapsingHeader::new("Widgets")
|
||||
.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)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "persistence", serde(default))]
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
mod app;
|
||||
pub mod dancing_strings;
|
||||
pub mod demo_window;
|
||||
mod demo_windows;
|
||||
pub mod demo_app_windows;
|
||||
pub mod drag_and_drop;
|
||||
pub mod font_book;
|
||||
pub mod font_contents_emoji;
|
||||
pub mod font_contents_ubuntu;
|
||||
pub mod layout_test;
|
||||
pub mod misc_demo_window;
|
||||
pub mod multi_touch;
|
||||
pub mod painting;
|
||||
pub mod password;
|
||||
|
@ -22,10 +22,12 @@ pub mod sliders;
|
|||
pub mod tests;
|
||||
pub mod toggle_switch;
|
||||
pub mod widget_gallery;
|
||||
mod widgets;
|
||||
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,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -1,114 +1,72 @@
|
|||
//! Source code example about creating other type of your widget which uses `egui::Memory` and
|
||||
//! created using a combination of existing widgets.
|
||||
//! Source code example about creating a widget which uses `egui::Memory` to store UI state.
|
||||
//!
|
||||
//! 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");
|
||||
/// password_ui(ui, &mut password);
|
||||
/// ```
|
||||
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
|
||||
pub fn password_ui(ui: &mut egui::Ui, text: &mut String) -> egui::Response {
|
||||
// This widget has its own state — show or hide password characters.
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
let id = ui.id().with("show_password");
|
||||
|
||||
// 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::<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
|
||||
// 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| {
|
||||
let result = ui.with_layout(egui::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");
|
||||
.add(egui::SelectableLabel::new(plaintext.0, "👁"))
|
||||
.on_hover_text("Show/hide password");
|
||||
if response.clicked() {
|
||||
state.0 = !state.0;
|
||||
plaintext.0 = !plaintext.0;
|
||||
}
|
||||
|
||||
let text_edit_size = ui.available_size();
|
||||
|
||||
// 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
|
||||
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
|
||||
// (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::<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(...)`
|
||||
/// Password entry field with ability to toggle character hiding.
|
||||
///
|
||||
/// ## Example:
|
||||
/// ``` ignore
|
||||
/// ui.add(password(&mut password, "password_1"));
|
||||
/// ui.add(password(&mut password));
|
||||
/// ```
|
||||
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 password(text: &mut String) -> impl egui::Widget + '_ {
|
||||
move |ui: &mut egui::Ui| password_ui(ui, text)
|
||||
}
|
||||
|
||||
pub fn url_to_file_source_code() -> String {
|
||||
|
|
|
@ -14,7 +14,6 @@ pub struct WidgetGallery {
|
|||
scalar: f32,
|
||||
string: String,
|
||||
color: egui::Color32,
|
||||
memory_example: String,
|
||||
}
|
||||
|
||||
impl Default for WidgetGallery {
|
||||
|
@ -26,7 +25,6 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +86,6 @@ impl WidgetGallery {
|
|||
scalar,
|
||||
string,
|
||||
color,
|
||||
memory_example,
|
||||
} = self;
|
||||
|
||||
ui.set_enabled(*enabled);
|
||||
|
@ -204,19 +201,6 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue