* init work * implement deferred deserialization * many improvements * rename `DataElement` -> `AnyMapElement` * make `data` in `Memory` as public field of type with public interface * make interface more rich * transform most unwraps to proper error handling * make `AnyMap` store by `TypeId`, so now individual type can be counted and reset * improve storing TypeId between different rust versions * rewrite system widgets to use AnyMap * refactor everything * replace `serde_json` -> `ron` * move `any_map` to module * separate `AnyMap` into `AnyMapId` and `serializable::AnyMapId` in order to not require `serde` traits in methods * add `AnyMap` and `serializable::AnyMap` that stores elements just by type, without `Id` * write documentation * change tooltips and color picker to use `Memory::data_temp` * fix bugs and docs * Apply suggestions from code review Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com> * rename `AnyMap` → `TypeMap` * rename `AnyMapId` → `AnyMap`, add generic <Key> to it * rename files `id_map` → `any_map` * move out usages from `serializable` mod * rename `ToDeserialize` → `Serialized` * fix bug with counting * add tests, and... * rename `reset` → `remove` * add function `remove_by_type` * format code * improve code * make identical interface for serialized and simple maps * make serialized maps serialized fully, without features by moving this into `Memory` struct with `#[cfg(feature = "persistence")]` under fields * move `serialized::TypeId` into `AnyMapElement` struct * fix pipeline and add one more test * update docs Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
4ecf304335
commit
186362a4b8
23 changed files with 1221 additions and 63 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -756,7 +756,9 @@ name = "egui"
|
|||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"epaint",
|
||||
"ron",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -22,6 +22,7 @@ include = [
|
|||
[dependencies]
|
||||
epaint = { version = "0.11.0", path = "../epaint", default-features = false }
|
||||
serde = { version = "1", features = ["derive", "rc"], optional = true }
|
||||
ron = { version = "0.6.4", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["default_fonts", "single_threaded"]
|
||||
|
@ -30,8 +31,11 @@ default = ["default_fonts", "single_threaded"]
|
|||
# If you plan on specifying your own fonts you may disable this feature.
|
||||
default_fonts = ["epaint/default_fonts"]
|
||||
|
||||
persistence = ["serde", "epaint/persistence"]
|
||||
persistence = ["serde", "epaint/persistence", "ron"]
|
||||
|
||||
# Only needed if you plan to use the same egui::Context from multiple threads.
|
||||
single_threaded = ["epaint/single_threaded"]
|
||||
multi_threaded = ["epaint/multi_threaded"]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
205
egui/src/any/any_map.rs
Normal file
205
egui/src/any/any_map.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use crate::any::element::{AnyMapElement, AnyMapTrait};
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Stores any object by `Key`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyMap<Key: Hash + Eq>(HashMap<Key, AnyMapElement>);
|
||||
|
||||
impl<Key: Hash + Eq> Default for AnyMap<Key> {
|
||||
fn default() -> Self {
|
||||
AnyMap(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl<Key: Hash + Eq> AnyMap<Key> {
|
||||
pub fn get<T: AnyMapTrait>(&mut self, key: &Key) -> Option<&T> {
|
||||
self.get_mut(key).map(|x| &*x)
|
||||
}
|
||||
|
||||
pub fn get_mut<T: AnyMapTrait>(&mut self, key: &Key) -> Option<&mut T> {
|
||||
self.0.get_mut(key)?.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key: Hash + Eq> AnyMap<Key> {
|
||||
pub fn get_or_insert_with<T: AnyMapTrait>(
|
||||
&mut self,
|
||||
key: Key,
|
||||
or_insert_with: impl FnOnce() -> T,
|
||||
) -> &T {
|
||||
&*self.get_mut_or_insert_with(key, or_insert_with)
|
||||
}
|
||||
|
||||
pub fn get_or_default<T: AnyMapTrait + Default>(&mut self, key: Key) -> &T {
|
||||
self.get_or_insert_with(key, Default::default)
|
||||
}
|
||||
|
||||
pub fn get_mut_or_insert_with<T: AnyMapTrait>(
|
||||
&mut self,
|
||||
key: Key,
|
||||
or_insert_with: impl FnOnce() -> T,
|
||||
) -> &mut T {
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.0.entry(key) {
|
||||
Entry::Vacant(vacant) => vacant
|
||||
.insert(AnyMapElement::new(or_insert_with()))
|
||||
.get_mut()
|
||||
.unwrap(), // this unwrap will never panic, because we insert correct type right now
|
||||
Entry::Occupied(occupied) => occupied.into_mut().get_mut_or_set_with(or_insert_with),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut_or_default<T: AnyMapTrait + Default>(&mut self, key: Key) -> &mut T {
|
||||
self.get_mut_or_insert_with(key, Default::default)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key: Hash + Eq> AnyMap<Key> {
|
||||
pub fn insert<T: AnyMapTrait>(&mut self, key: Key, element: T) {
|
||||
self.0.insert(key, AnyMapElement::new(element));
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &Key) {
|
||||
self.0.remove(key);
|
||||
}
|
||||
|
||||
pub fn remove_by_type<T: AnyMapTrait>(&mut self) {
|
||||
let key = TypeId::of::<T>();
|
||||
self.0.retain(|_, v| v.type_id() != key);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key: Hash + Eq> AnyMap<Key> {
|
||||
/// You could use this function to find is there some leak or misusage.
|
||||
pub fn count<T: AnyMapTrait>(&mut self) -> usize {
|
||||
let key = TypeId::of::<T>();
|
||||
self.0.iter().filter(|(_, v)| v.type_id() == key).count()
|
||||
}
|
||||
|
||||
pub fn count_all(&mut self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn basic_usage() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
|
||||
assert!(map.get::<State>(&0).is_none());
|
||||
map.insert(0, State { a: 42 });
|
||||
|
||||
assert_eq!(*map.get::<State>(&0).unwrap(), State { a: 42 });
|
||||
assert!(map.get::<State>(&1).is_none());
|
||||
map.get_mut::<State>(&0).unwrap().a = 43;
|
||||
assert_eq!(*map.get::<State>(&0).unwrap(), State { a: 43 });
|
||||
|
||||
map.remove(&0);
|
||||
assert!(map.get::<State>(&0).is_none());
|
||||
|
||||
assert_eq!(
|
||||
*map.get_or_insert_with(0, || State { a: 55 }),
|
||||
State { a: 55 }
|
||||
);
|
||||
map.remove(&0);
|
||||
assert_eq!(
|
||||
*map.get_mut_or_insert_with(0, || State { a: 56 }),
|
||||
State { a: 56 }
|
||||
);
|
||||
map.remove(&0);
|
||||
assert_eq!(*map.get_or_default::<State>(0), State { a: 0 });
|
||||
map.remove(&0);
|
||||
assert_eq!(*map.get_mut_or_default::<State>(0), State { a: 0 });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn different_type_same_id() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
|
||||
map.insert(0, State { a: 42 });
|
||||
|
||||
assert_eq!(*map.get::<State>(&0).unwrap(), State { a: 42 });
|
||||
assert!(map.get::<i32>(&0).is_none());
|
||||
|
||||
map.insert(0, 255i32);
|
||||
|
||||
assert_eq!(*map.get::<i32>(&0).unwrap(), 255);
|
||||
assert!(map.get::<State>(&0).is_none());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn cloning() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
|
||||
map.insert(0, State::default());
|
||||
map.insert(10, 10i32);
|
||||
map.insert(11, 11i32);
|
||||
|
||||
let mut cloned_map = map.clone();
|
||||
|
||||
map.insert(12, 12i32);
|
||||
map.insert(1, State { a: 10 });
|
||||
|
||||
assert_eq!(*cloned_map.get::<State>(&0).unwrap(), State { a: 0 });
|
||||
assert!(cloned_map.get::<State>(&1).is_none());
|
||||
assert_eq!(*cloned_map.get::<i32>(&10).unwrap(), 10i32);
|
||||
assert_eq!(*cloned_map.get::<i32>(&11).unwrap(), 11i32);
|
||||
assert!(cloned_map.get::<i32>(&12).is_none());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn counting() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
|
||||
map.insert(0, State::default());
|
||||
map.insert(1, State { a: 10 });
|
||||
map.insert(10, 10i32);
|
||||
map.insert(11, 11i32);
|
||||
map.insert(12, 12i32);
|
||||
|
||||
assert_eq!(map.count::<State>(), 2);
|
||||
assert_eq!(map.count::<i32>(), 3);
|
||||
|
||||
map.remove_by_type::<State>();
|
||||
|
||||
assert_eq!(map.count::<State>(), 0);
|
||||
assert_eq!(map.count::<i32>(), 3);
|
||||
|
||||
map.clear();
|
||||
|
||||
assert_eq!(map.count::<State>(), 0);
|
||||
assert_eq!(map.count::<i32>(), 0);
|
||||
}
|
61
egui/src/any/element.rs
Normal file
61
egui/src/any/element.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use std::any::{Any, TypeId};
|
||||
use std::fmt;
|
||||
|
||||
/// Like [`std::any::Any`], but also implements `Clone`.
|
||||
pub(crate) struct AnyMapElement {
|
||||
value: Box<dyn Any + 'static>,
|
||||
clone_fn: fn(&Box<dyn Any + 'static>) -> Box<dyn Any + 'static>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for AnyMapElement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AnyMapElement")
|
||||
.field("value_type_id", &self.type_id())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AnyMapElement {
|
||||
fn clone(&self) -> Self {
|
||||
AnyMapElement {
|
||||
value: (self.clone_fn)(&self.value),
|
||||
clone_fn: self.clone_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnyMapTrait: 'static + Any + Clone {}
|
||||
|
||||
impl<T: 'static + Any + Clone> AnyMapTrait for T {}
|
||||
|
||||
impl AnyMapElement {
|
||||
pub(crate) fn new<T: AnyMapTrait>(t: T) -> Self {
|
||||
AnyMapElement {
|
||||
value: Box::new(t),
|
||||
clone_fn: |x| {
|
||||
let x = x.downcast_ref::<T>().unwrap(); // This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with type `T`, so type cannot change.
|
||||
Box::new(x.clone())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn type_id(&self) -> TypeId {
|
||||
(*self.value).type_id()
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut<T: AnyMapTrait>(&mut self) -> Option<&mut T> {
|
||||
self.value.downcast_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut_or_set_with<T: AnyMapTrait>(
|
||||
&mut self,
|
||||
set_with: impl FnOnce() -> T,
|
||||
) -> &mut T {
|
||||
if !self.value.is::<T>() {
|
||||
*self = Self::new(set_with());
|
||||
// TODO: log this error, because it can occurs when user used same Id or same type for different widgets
|
||||
}
|
||||
|
||||
self.value.downcast_mut().unwrap() // This unwrap will never panic because we already converted object to required type
|
||||
}
|
||||
}
|
61
egui/src/any/mod.rs
Normal file
61
egui/src/any/mod.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
//! Any-type storages for [`Memory`].
|
||||
//!
|
||||
//! This module contains structs to store arbitrary types using [`Any`] trait. Also, they can be cloned, and structs in [`serializable`] can be de/serialized.
|
||||
//!
|
||||
//! All this is just `HashMap<TypeId, Box<dyn Any + static>>` and `HashMap<Key, Box<dyn Any + static>>`, but with helper functions and hacks for cloning and de/serialization.
|
||||
//!
|
||||
//! # Trait requirements
|
||||
//!
|
||||
//! If you want to store your type here, it must implement `Clone` and `Any` and be `'static`, which means it must not contain references. If you want to store your data in serializable storage, it must implement `serde::Serialize` and `serde::Deserialize` under the `persistent` feature.
|
||||
//!
|
||||
//! # [`TypeMap`]
|
||||
//!
|
||||
//! It stores everything by just type. You should use this map for your widget when all instances of your widgets can have only one state. E.g. for popup windows, for color picker.
|
||||
//!
|
||||
//! To not have intersections, you should create newtype for anything you try to store here, like:
|
||||
//! ```rust
|
||||
//! struct MyEditBool(pub bool);
|
||||
//! ```
|
||||
//!
|
||||
//! # [`AnyMap<Key>`]
|
||||
//!
|
||||
//! In [`Memory`] `Key` = [`Id`].
|
||||
//!
|
||||
//! [`TypeMap`] and [`AnyMap<Key>`] has a quite similar interface, except for [`AnyMap`] you should pass `Key` to get and insert things.
|
||||
//!
|
||||
//! It stores everything by `Key`, this should be used when your widget can have different data for different instances of the widget.
|
||||
//!
|
||||
//! # `serializable`
|
||||
//!
|
||||
//! [`TypeMap`] and [`serializable::TypeMap`] has exactly the same interface, but [`serializable::TypeMap`] only requires serde traits for stored object under `persistent` feature. Same thing for [`AnyMap`] and [`serializable::AnyMap`].
|
||||
//!
|
||||
//! # What could break
|
||||
//!
|
||||
//! Things here could break only when you trying to load this from file.
|
||||
//!
|
||||
//! First, serialized `TypeId` in [`serializable::TypeMap`] could broke if you updated the version of the Rust compiler between runs.
|
||||
//!
|
||||
//! Second, count and reset all instances of a type in [`serializable::AnyMap`] could return an incorrect value for the same reason.
|
||||
//!
|
||||
//! Deserialization errors of loaded elements of these storages can be determined only when you call `get_...` functions, they not logged and not provided to a user, on this errors value is just replaced with `or_insert()`/default value.
|
||||
//!
|
||||
//! # When not to use this
|
||||
//!
|
||||
//! This is not for important widget data. Some errors are just ignored and the correct value of type is inserted when you call. This is done to more simple interface.
|
||||
//!
|
||||
//! You shouldn't use any map here when you need very reliable state storage with rich error-handling. For this purpose you should create your own `Memory` struct and pass it everywhere you need it. Then, you should de/serialize it by yourself, handling all serialization or other errors as you wish.
|
||||
//!
|
||||
//! [`Id`]: crate::Id
|
||||
//! [`Memory`]: crate::Memory
|
||||
//! [`Any`]: std::any::Any
|
||||
//! [`AnyMap<Key>`]: crate::any::AnyMap
|
||||
|
||||
mod any_map;
|
||||
mod element;
|
||||
mod type_map;
|
||||
|
||||
/// Same structs and traits, but also can be de/serialized under `persistence` feature.
|
||||
#[cfg(feature = "persistence")]
|
||||
pub mod serializable;
|
||||
|
||||
pub use self::{any_map::AnyMap, element::AnyMapTrait, type_map::TypeMap};
|
263
egui/src/any/serializable/any_map.rs
Normal file
263
egui/src/any/serializable/any_map.rs
Normal file
|
@ -0,0 +1,263 @@
|
|||
use crate::any::serializable::element::{AnyMapElement, AnyMapTrait};
|
||||
use crate::any::serializable::type_id::TypeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Stores any object by `Key`, and can be de/serialized.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct AnyMap<Key: Hash + Eq>(HashMap<Key, AnyMapElement>);
|
||||
|
||||
impl<Key: Hash + Eq> Default for AnyMap<Key> {
|
||||
fn default() -> Self {
|
||||
AnyMap(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl<Key: Hash + Eq> AnyMap<Key> {
|
||||
pub fn get<T: AnyMapTrait>(&mut self, key: &Key) -> Option<&T> {
|
||||
self.get_mut(key).map(|x| &*x)
|
||||
}
|
||||
|
||||
pub fn get_mut<T: AnyMapTrait>(&mut self, key: &Key) -> Option<&mut T> {
|
||||
self.0.get_mut(key)?.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key: Hash + Eq> AnyMap<Key> {
|
||||
pub fn get_or_insert_with<T: AnyMapTrait>(
|
||||
&mut self,
|
||||
key: Key,
|
||||
or_insert_with: impl FnOnce() -> T,
|
||||
) -> &T {
|
||||
&*self.get_mut_or_insert_with(key, or_insert_with)
|
||||
}
|
||||
|
||||
pub fn get_or_default<T: AnyMapTrait + Default>(&mut self, key: Key) -> &T {
|
||||
self.get_or_insert_with(key, Default::default)
|
||||
}
|
||||
|
||||
pub fn get_mut_or_insert_with<T: AnyMapTrait>(
|
||||
&mut self,
|
||||
key: Key,
|
||||
or_insert_with: impl FnOnce() -> T,
|
||||
) -> &mut T {
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.0.entry(key) {
|
||||
Entry::Vacant(vacant) => vacant
|
||||
.insert(AnyMapElement::new(or_insert_with()))
|
||||
.get_mut()
|
||||
.unwrap(), // this unwrap will never panic, because we insert correct type right now
|
||||
Entry::Occupied(occupied) => occupied.into_mut().get_mut_or_set_with(or_insert_with),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut_or_default<T: AnyMapTrait + Default>(&mut self, key: Key) -> &mut T {
|
||||
self.get_mut_or_insert_with(key, Default::default)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key: Hash + Eq> AnyMap<Key> {
|
||||
pub fn insert<T: AnyMapTrait>(&mut self, key: Key, element: T) {
|
||||
self.0.insert(key, AnyMapElement::new(element));
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &Key) {
|
||||
self.0.remove(key);
|
||||
}
|
||||
|
||||
/// Note that this function could not remove all needed types between runs because if you upgraded the Rust version or for other reasons.
|
||||
pub fn remove_by_type<T: AnyMapTrait>(&mut self) {
|
||||
let key = TypeId::of::<T>();
|
||||
self.0.retain(|_, v| v.type_id() != key);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<Key: Hash + Eq> AnyMap<Key> {
|
||||
/// You could use this function to find is there some leak or misusage. Note, that result of this function could break between runs, if you upgraded the Rust version or for other reasons.
|
||||
pub fn count<T: AnyMapTrait>(&mut self) -> usize {
|
||||
let key = TypeId::of::<T>();
|
||||
self.0.iter().filter(|(_, v)| v.type_id() == key).count()
|
||||
}
|
||||
|
||||
pub fn count_all(&mut self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn discard_different_struct() {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
struct State1 {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct State2 {
|
||||
b: String,
|
||||
}
|
||||
|
||||
let file_string = {
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
map.insert(1, State1 { a: 42 });
|
||||
serde_json::to_string(&map).unwrap()
|
||||
};
|
||||
|
||||
let mut map: AnyMap<i32> = serde_json::from_str(&file_string).unwrap();
|
||||
assert!(map.get::<State2>(&1).is_none());
|
||||
assert_eq!(map.get::<State1>(&1), Some(&State1 { a: 42 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_field_between_runs() {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
struct StateNew {
|
||||
a: i32,
|
||||
|
||||
#[serde(default)]
|
||||
b: String,
|
||||
}
|
||||
|
||||
let file_string = {
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
map.insert(1, State { a: 42 });
|
||||
serde_json::to_string(&map).unwrap()
|
||||
};
|
||||
|
||||
let mut map: AnyMap<i32> = serde_json::from_str(&file_string).unwrap();
|
||||
assert_eq!(
|
||||
map.get::<StateNew>(&1),
|
||||
Some(&StateNew {
|
||||
a: 42,
|
||||
b: String::default()
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn basic_usage() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize, Serialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
|
||||
assert!(map.get::<State>(&0).is_none());
|
||||
map.insert(0, State { a: 42 });
|
||||
|
||||
assert_eq!(*map.get::<State>(&0).unwrap(), State { a: 42 });
|
||||
assert!(map.get::<State>(&1).is_none());
|
||||
map.get_mut::<State>(&0).unwrap().a = 43;
|
||||
assert_eq!(*map.get::<State>(&0).unwrap(), State { a: 43 });
|
||||
|
||||
map.remove(&0);
|
||||
assert!(map.get::<State>(&0).is_none());
|
||||
|
||||
assert_eq!(
|
||||
*map.get_or_insert_with(0, || State { a: 55 }),
|
||||
State { a: 55 }
|
||||
);
|
||||
map.remove(&0);
|
||||
assert_eq!(
|
||||
*map.get_mut_or_insert_with(0, || State { a: 56 }),
|
||||
State { a: 56 }
|
||||
);
|
||||
map.remove(&0);
|
||||
assert_eq!(*map.get_or_default::<State>(0), State { a: 0 });
|
||||
map.remove(&0);
|
||||
assert_eq!(*map.get_mut_or_default::<State>(0), State { a: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_type_same_id() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize, Serialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
|
||||
map.insert(0, State { a: 42 });
|
||||
|
||||
assert_eq!(*map.get::<State>(&0).unwrap(), State { a: 42 });
|
||||
assert!(map.get::<i32>(&0).is_none());
|
||||
|
||||
map.insert(0, 255i32);
|
||||
|
||||
assert_eq!(*map.get::<i32>(&0).unwrap(), 255);
|
||||
assert!(map.get::<State>(&0).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cloning() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize, Serialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
|
||||
map.insert(0, State::default());
|
||||
map.insert(10, 10i32);
|
||||
map.insert(11, 11i32);
|
||||
|
||||
let mut cloned_map = map.clone();
|
||||
|
||||
map.insert(12, 12i32);
|
||||
map.insert(1, State { a: 10 });
|
||||
|
||||
assert_eq!(*cloned_map.get::<State>(&0).unwrap(), State { a: 0 });
|
||||
assert!(cloned_map.get::<State>(&1).is_none());
|
||||
assert_eq!(*cloned_map.get::<i32>(&10).unwrap(), 10i32);
|
||||
assert_eq!(*cloned_map.get::<i32>(&11).unwrap(), 11i32);
|
||||
assert!(cloned_map.get::<i32>(&12).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn counting() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, Deserialize, Serialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: AnyMap<i32> = Default::default();
|
||||
|
||||
map.insert(0, State::default());
|
||||
map.insert(1, State { a: 10 });
|
||||
map.insert(10, 10i32);
|
||||
map.insert(11, 11i32);
|
||||
map.insert(12, 12i32);
|
||||
|
||||
assert_eq!(map.count::<State>(), 2);
|
||||
assert_eq!(map.count::<i32>(), 3);
|
||||
|
||||
map.remove_by_type::<State>();
|
||||
|
||||
assert_eq!(map.count::<State>(), 0);
|
||||
assert_eq!(map.count::<i32>(), 3);
|
||||
|
||||
map.clear();
|
||||
|
||||
assert_eq!(map.count::<State>(), 0);
|
||||
assert_eq!(map.count::<i32>(), 0);
|
||||
}
|
149
egui/src/any/serializable/element.rs
Normal file
149
egui/src/any/serializable/element.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
use crate::any::serializable::type_id::TypeId;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use AnyMapElementInner::{Deserialized, Serialized};
|
||||
|
||||
pub(crate) struct AnyMapElement(AnyMapElementInner);
|
||||
|
||||
enum AnyMapElementInner {
|
||||
Deserialized {
|
||||
value: Box<dyn Any + 'static>,
|
||||
clone_fn: fn(&Box<dyn Any + 'static>) -> Box<dyn Any + 'static>,
|
||||
|
||||
serialize_fn: fn(&Box<dyn Any + 'static>) -> Result<String, ron::Error>,
|
||||
},
|
||||
Serialized(String, TypeId),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct AnyMapElementInnerSer(String, TypeId);
|
||||
|
||||
impl Serialize for AnyMapElement {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let inner = match &self.0 {
|
||||
Deserialized {
|
||||
value,
|
||||
serialize_fn,
|
||||
..
|
||||
} => {
|
||||
let s = serialize_fn(value).map_err(serde::ser::Error::custom)?;
|
||||
AnyMapElementInnerSer(s, self.type_id())
|
||||
}
|
||||
Serialized(s, id) => AnyMapElementInnerSer(s.clone(), *id),
|
||||
};
|
||||
|
||||
inner.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AnyMapElement {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let AnyMapElementInnerSer(s, id) = AnyMapElementInnerSer::deserialize(deserializer)?;
|
||||
|
||||
Ok(AnyMapElement(Serialized(s, id)))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for AnyMapElement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
Deserialized { value, .. } => f
|
||||
.debug_struct("AnyMapElement_Deserialized")
|
||||
.field("value_type_id", &value.type_id())
|
||||
.finish(),
|
||||
Serialized(s, id) => f
|
||||
.debug_tuple("AnyMapElement_Serialized")
|
||||
.field(&s)
|
||||
.field(&id)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AnyMapElement {
|
||||
fn clone(&self) -> Self {
|
||||
match &self.0 {
|
||||
Deserialized {
|
||||
value,
|
||||
clone_fn,
|
||||
serialize_fn,
|
||||
} => AnyMapElement(Deserialized {
|
||||
value: clone_fn(value),
|
||||
clone_fn: *clone_fn,
|
||||
serialize_fn: *serialize_fn,
|
||||
}),
|
||||
Serialized(s, id) => AnyMapElement(Serialized(s.clone(), *id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnyMapTrait: 'static + Any + Clone + Serialize + for<'a> Deserialize<'a> {}
|
||||
impl<T: 'static + Any + Clone + Serialize + for<'a> Deserialize<'a>> AnyMapTrait for T {}
|
||||
|
||||
impl AnyMapElement {
|
||||
pub(crate) fn new<T: AnyMapTrait>(t: T) -> Self {
|
||||
AnyMapElement(Deserialized {
|
||||
value: Box::new(t),
|
||||
clone_fn: |x| {
|
||||
let x = x.downcast_ref::<T>().unwrap(); // This unwrap will never panic, because we always construct this type using this `new` function and because we return &mut reference only with this type `T`, so type cannot change.
|
||||
Box::new(x.clone())
|
||||
},
|
||||
|
||||
serialize_fn: |x| {
|
||||
let x = x.downcast_ref::<T>().unwrap(); // This will never panic too, for same reason.
|
||||
ron::to_string(x)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn type_id(&self) -> TypeId {
|
||||
match self {
|
||||
AnyMapElement(Deserialized { value, .. }) => (**value).type_id().into(),
|
||||
AnyMapElement(Serialized(_, id)) => *id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut<T: AnyMapTrait>(&mut self) -> Option<&mut T> {
|
||||
match self {
|
||||
AnyMapElement(Deserialized { value, .. }) => value.downcast_mut(),
|
||||
AnyMapElement(Serialized(s, _)) => {
|
||||
*self = Self::new(ron::from_str::<T>(s).ok()?);
|
||||
|
||||
match self {
|
||||
AnyMapElement(Deserialized { value, .. }) => value.downcast_mut(),
|
||||
AnyMapElement(Serialized(_, _)) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut_or_set_with<T: AnyMapTrait>(
|
||||
&mut self,
|
||||
set_with: impl FnOnce() -> T,
|
||||
) -> &mut T {
|
||||
match &mut self.0 {
|
||||
Deserialized { value, .. } => {
|
||||
if !value.is::<T>() {
|
||||
*self = Self::new(set_with());
|
||||
// TODO: log this error, because it can occurs when user used same Id or same type for different widgets
|
||||
}
|
||||
}
|
||||
Serialized(s, _) => {
|
||||
*self = Self::new(ron::from_str::<T>(s).unwrap_or_else(|_| set_with()));
|
||||
// TODO: log deserialization error
|
||||
}
|
||||
}
|
||||
|
||||
match &mut self.0 {
|
||||
Deserialized { value, .. } => value.downcast_mut().unwrap(), // This unwrap will never panic because we already converted object to required type
|
||||
Serialized(_, _) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
6
egui/src/any/serializable/mod.rs
Normal file
6
egui/src/any/serializable/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod any_map;
|
||||
mod element;
|
||||
mod type_id;
|
||||
mod type_map;
|
||||
|
||||
pub use self::{any_map::AnyMap, element::AnyMapTrait, type_map::TypeMap};
|
23
egui/src/any/serializable/type_id.rs
Normal file
23
egui/src/any/serializable/type_id.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use std::any::Any;
|
||||
|
||||
/// We need this because `TypeId` can't be deserialized or serialized directly, but this can be done using hashing. However, there is a small possibility that different types will have intersection by hashes of their type ids.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct TypeId(u64);
|
||||
|
||||
impl TypeId {
|
||||
pub fn of<T: Any + 'static>() -> Self {
|
||||
std::any::TypeId::of::<T>().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::any::TypeId> for TypeId {
|
||||
fn from(id: std::any::TypeId) -> Self {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
id.hash(&mut hasher);
|
||||
Self(hasher.finish())
|
||||
}
|
||||
}
|
203
egui/src/any/serializable/type_map.rs
Normal file
203
egui/src/any/serializable/type_map.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use crate::any::serializable::element::{AnyMapElement, AnyMapTrait};
|
||||
use crate::any::serializable::type_id::TypeId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Maps types to a single instance of that type.
|
||||
///
|
||||
/// Used to store state per widget type. In effect a sort of singleton storage.
|
||||
/// Similar to [the `typemap` crate](https://docs.rs/typemap/0.3.3/typemap/) but allows serialization.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct TypeMap(HashMap<TypeId, AnyMapElement>);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl TypeMap {
|
||||
pub fn get<T: AnyMapTrait>(&mut self) -> Option<&T> {
|
||||
self.get_mut().map(|x| &*x)
|
||||
}
|
||||
|
||||
pub fn get_mut<T: AnyMapTrait>(&mut self) -> Option<&mut T> {
|
||||
self.0.get_mut(&TypeId::of::<T>())?.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeMap {
|
||||
pub fn get_or_insert_with<T: AnyMapTrait>(&mut self, or_insert_with: impl FnOnce() -> T) -> &T {
|
||||
&*self.get_mut_or_insert_with(or_insert_with)
|
||||
}
|
||||
|
||||
pub fn get_or_default<T: AnyMapTrait + Default>(&mut self) -> &T {
|
||||
self.get_or_insert_with(Default::default)
|
||||
}
|
||||
|
||||
pub fn get_mut_or_insert_with<T: AnyMapTrait>(
|
||||
&mut self,
|
||||
or_insert_with: impl FnOnce() -> T,
|
||||
) -> &mut T {
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.0.entry(TypeId::of::<T>()) {
|
||||
Entry::Vacant(vacant) => vacant
|
||||
.insert(AnyMapElement::new(or_insert_with()))
|
||||
.get_mut()
|
||||
.unwrap(), // this unwrap will never panic, because we insert correct type right now
|
||||
Entry::Occupied(occupied) => occupied.into_mut().get_mut_or_set_with(or_insert_with),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut_or_default<T: AnyMapTrait + Default>(&mut self) -> &mut T {
|
||||
self.get_mut_or_insert_with(Default::default)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeMap {
|
||||
pub fn insert<T: AnyMapTrait>(&mut self, element: T) {
|
||||
self.0
|
||||
.insert(TypeId::of::<T>(), AnyMapElement::new(element));
|
||||
}
|
||||
|
||||
pub fn remove<T: AnyMapTrait>(&mut self) {
|
||||
self.0.remove(&TypeId::of::<T>());
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn discard_different_struct() {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct State1 {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct State2 {
|
||||
a: String,
|
||||
}
|
||||
|
||||
let file_string = {
|
||||
let mut map: TypeMap = Default::default();
|
||||
map.insert(State1 { a: 42 });
|
||||
serde_json::to_string(&map).unwrap()
|
||||
};
|
||||
|
||||
let mut map: TypeMap = serde_json::from_str(&file_string).unwrap();
|
||||
assert!(map.get::<State2>().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_field_between_runs() {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct StateNew {
|
||||
a: i32,
|
||||
|
||||
#[serde(default)]
|
||||
b: i32,
|
||||
}
|
||||
|
||||
let file_string = {
|
||||
let mut map: TypeMap = Default::default();
|
||||
map.insert(State { a: 42 });
|
||||
serde_json::to_string(&map).unwrap()
|
||||
};
|
||||
|
||||
let mut map: TypeMap = serde_json::from_str(&file_string).unwrap();
|
||||
assert!(map.get::<StateNew>().is_none());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn basic_usage() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map = TypeMap::default();
|
||||
|
||||
assert!(map.get::<State>().is_none());
|
||||
map.insert(State { a: 42 });
|
||||
map.insert(5i32);
|
||||
map.insert((6.0f32, -1i16));
|
||||
|
||||
assert_eq!(*map.get::<State>().unwrap(), State { a: 42 });
|
||||
map.get_mut::<State>().unwrap().a = 43;
|
||||
assert_eq!(*map.get::<State>().unwrap(), State { a: 43 });
|
||||
|
||||
map.remove::<State>();
|
||||
assert!(map.get::<State>().is_none());
|
||||
|
||||
assert_eq!(*map.get_or_insert_with(|| State { a: 55 }), State { a: 55 });
|
||||
map.remove::<State>();
|
||||
assert_eq!(
|
||||
*map.get_mut_or_insert_with(|| State { a: 56 }),
|
||||
State { a: 56 }
|
||||
);
|
||||
map.remove::<State>();
|
||||
assert_eq!(*map.get_or_default::<State>(), State { a: 0 });
|
||||
map.remove::<State>();
|
||||
assert_eq!(*map.get_mut_or_default::<State>(), State { a: 0 });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn cloning() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: TypeMap = Default::default();
|
||||
|
||||
map.insert(State::default());
|
||||
map.insert(10i32);
|
||||
|
||||
let mut cloned_map = map.clone();
|
||||
|
||||
map.insert(11.5f32);
|
||||
map.insert("aoeu".to_string());
|
||||
|
||||
assert_eq!(*cloned_map.get::<State>().unwrap(), State { a: 0 });
|
||||
assert_eq!(*cloned_map.get::<i32>().unwrap(), 10i32);
|
||||
assert!(cloned_map.get::<f32>().is_none());
|
||||
assert!(cloned_map.get::<String>().is_none());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn removing() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: TypeMap = Default::default();
|
||||
|
||||
map.insert(State::default());
|
||||
map.insert(10i32);
|
||||
map.insert(11.5f32);
|
||||
map.insert("aoeu".to_string());
|
||||
|
||||
map.remove::<State>();
|
||||
assert!(map.get::<State>().is_none());
|
||||
assert!(map.get::<i32>().is_some());
|
||||
assert!(map.get::<f32>().is_some());
|
||||
assert!(map.get::<String>().is_some());
|
||||
|
||||
map.clear();
|
||||
assert!(map.get::<State>().is_none());
|
||||
assert!(map.get::<i32>().is_none());
|
||||
assert!(map.get::<f32>().is_none());
|
||||
assert!(map.get::<String>().is_none());
|
||||
}
|
153
egui/src/any/type_map.rs
Normal file
153
egui/src/any/type_map.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
use crate::any::element::{AnyMapElement, AnyMapTrait};
|
||||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Maps types to a single instance of that type.
|
||||
///
|
||||
/// Used to store state per widget type. In effect a sort of singleton storage.
|
||||
/// Similar to [the `typemap` crate](https://docs.rs/typemap/0.3.3/typemap/).
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TypeMap(HashMap<TypeId, AnyMapElement>);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl TypeMap {
|
||||
pub fn get<T: AnyMapTrait>(&mut self) -> Option<&T> {
|
||||
self.get_mut().map(|x| &*x)
|
||||
}
|
||||
|
||||
pub fn get_mut<T: AnyMapTrait>(&mut self) -> Option<&mut T> {
|
||||
self.0.get_mut(&TypeId::of::<T>())?.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeMap {
|
||||
pub fn get_or_insert_with<T: AnyMapTrait>(&mut self, or_insert_with: impl FnOnce() -> T) -> &T {
|
||||
&*self.get_mut_or_insert_with(or_insert_with)
|
||||
}
|
||||
|
||||
pub fn get_or_default<T: AnyMapTrait + Default>(&mut self) -> &T {
|
||||
self.get_or_insert_with(Default::default)
|
||||
}
|
||||
|
||||
pub fn get_mut_or_insert_with<T: AnyMapTrait>(
|
||||
&mut self,
|
||||
or_insert_with: impl FnOnce() -> T,
|
||||
) -> &mut T {
|
||||
use std::collections::hash_map::Entry;
|
||||
match self.0.entry(TypeId::of::<T>()) {
|
||||
Entry::Vacant(vacant) => vacant
|
||||
.insert(AnyMapElement::new(or_insert_with()))
|
||||
.get_mut()
|
||||
.unwrap(), // this unwrap will never panic, because we insert correct type right now
|
||||
Entry::Occupied(occupied) => occupied.into_mut().get_mut_or_set_with(or_insert_with),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut_or_default<T: AnyMapTrait + Default>(&mut self) -> &mut T {
|
||||
self.get_mut_or_insert_with(Default::default)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeMap {
|
||||
pub fn insert<T: AnyMapTrait>(&mut self, element: T) {
|
||||
self.0
|
||||
.insert(TypeId::of::<T>(), AnyMapElement::new(element));
|
||||
}
|
||||
|
||||
pub fn remove<T: AnyMapTrait>(&mut self) {
|
||||
self.0.remove(&TypeId::of::<T>());
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn basic_usage() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map = TypeMap::default();
|
||||
|
||||
assert!(map.get::<State>().is_none());
|
||||
map.insert(State { a: 42 });
|
||||
map.insert(5i32);
|
||||
map.insert((6.0f32, -1i16));
|
||||
|
||||
assert_eq!(*map.get::<State>().unwrap(), State { a: 42 });
|
||||
map.get_mut::<State>().unwrap().a = 43;
|
||||
assert_eq!(*map.get::<State>().unwrap(), State { a: 43 });
|
||||
|
||||
map.remove::<State>();
|
||||
assert!(map.get::<State>().is_none());
|
||||
|
||||
assert_eq!(*map.get_or_insert_with(|| State { a: 55 }), State { a: 55 });
|
||||
map.remove::<State>();
|
||||
assert_eq!(
|
||||
*map.get_mut_or_insert_with(|| State { a: 56 }),
|
||||
State { a: 56 }
|
||||
);
|
||||
map.remove::<State>();
|
||||
assert_eq!(*map.get_or_default::<State>(), State { a: 0 });
|
||||
map.remove::<State>();
|
||||
assert_eq!(*map.get_mut_or_default::<State>(), State { a: 0 });
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn cloning() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: TypeMap = Default::default();
|
||||
|
||||
map.insert(State::default());
|
||||
map.insert(10i32);
|
||||
|
||||
let mut cloned_map = map.clone();
|
||||
|
||||
map.insert(11.5f32);
|
||||
map.insert("aoeu");
|
||||
|
||||
assert_eq!(*cloned_map.get::<State>().unwrap(), State { a: 0 });
|
||||
assert_eq!(*cloned_map.get::<i32>().unwrap(), 10i32);
|
||||
assert!(cloned_map.get::<f32>().is_none());
|
||||
assert!(cloned_map.get::<&'static str>().is_none());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn removing() {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
struct State {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut map: TypeMap = Default::default();
|
||||
|
||||
map.insert(State::default());
|
||||
map.insert(10i32);
|
||||
map.insert(11.5f32);
|
||||
map.insert("aoeu");
|
||||
|
||||
map.remove::<State>();
|
||||
assert!(map.get::<State>().is_none());
|
||||
assert!(map.get::<i32>().is_some());
|
||||
assert!(map.get::<f32>().is_some());
|
||||
assert!(map.get::<&'static str>().is_some());
|
||||
|
||||
map.clear();
|
||||
assert!(map.get::<State>().is_none());
|
||||
assert!(map.get::<i32>().is_none());
|
||||
assert!(map.get::<f32>().is_none());
|
||||
assert!(map.get::<&'static str>().is_none());
|
||||
}
|
|
@ -24,7 +24,7 @@ impl Default for State {
|
|||
|
||||
impl State {
|
||||
pub fn from_memory_with_default_open(ctx: &Context, id: Id, default_open: bool) -> Self {
|
||||
*ctx.memory().collapsing_headers.entry(id).or_insert(State {
|
||||
*ctx.memory().id_data.get_or_insert_with(id, || State {
|
||||
open: default_open,
|
||||
..Default::default()
|
||||
})
|
||||
|
@ -36,8 +36,8 @@ impl State {
|
|||
Some(true)
|
||||
} else {
|
||||
ctx.memory()
|
||||
.collapsing_headers
|
||||
.get(&id)
|
||||
.id_data
|
||||
.get::<State>(&id)
|
||||
.map(|state| state.open)
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +295,7 @@ impl CollapsingHeader {
|
|||
})
|
||||
.inner
|
||||
});
|
||||
ui.memory().collapsing_headers.insert(id, state);
|
||||
ui.memory().id_data.insert(id, state);
|
||||
|
||||
if let Some(ret_response) = ret_response {
|
||||
CollapsingResponse {
|
||||
|
|
|
@ -92,14 +92,19 @@ pub fn show_tooltip_at(
|
|||
return; // No good place for a tooltip :(
|
||||
};
|
||||
|
||||
let expected_size = ctx.memory().tooltip.tooltip_size(id);
|
||||
let expected_size = ctx
|
||||
.memory()
|
||||
.data_temp
|
||||
.get_or_default::<crate::containers::popup::MonoState>()
|
||||
.tooltip_size(id);
|
||||
let expected_size = expected_size.unwrap_or_else(|| vec2(64.0, 32.0));
|
||||
let position = position.min(ctx.input().screen_rect().right_bottom() - expected_size);
|
||||
let position = position.max(ctx.input().screen_rect().left_top());
|
||||
|
||||
let response = show_tooltip_area(ctx, id, position, add_contents);
|
||||
ctx.memory()
|
||||
.tooltip
|
||||
.data_temp
|
||||
.get_mut_or_default::<crate::containers::popup::MonoState>()
|
||||
.set_tooltip_size(id, response.rect.size());
|
||||
|
||||
ctx.frame_state().tooltip_rect = Some((id, tooltip_rect.union(response.rect)));
|
||||
|
|
|
@ -159,7 +159,7 @@ impl Resize {
|
|||
ui.make_persistent_id(id_source)
|
||||
});
|
||||
|
||||
let mut state = ui.memory().resize.get(&id).cloned().unwrap_or_else(|| {
|
||||
let mut state = *ui.memory().id_data.get_or_insert_with(id, || {
|
||||
ui.ctx().request_repaint(); // counter frame delay
|
||||
|
||||
let default_size = self
|
||||
|
@ -297,7 +297,7 @@ impl Resize {
|
|||
}
|
||||
}
|
||||
|
||||
ui.memory().resize.insert(id, state);
|
||||
ui.memory().id_data.insert(id, state);
|
||||
|
||||
if ui.ctx().style().debug.show_resize {
|
||||
ui.ctx().debug_painter().debug_rect(
|
||||
|
|
|
@ -98,12 +98,7 @@ impl ScrollArea {
|
|||
|
||||
let id_source = id_source.unwrap_or_else(|| Id::new("scroll_area"));
|
||||
let id = ui.make_persistent_id(id_source);
|
||||
let mut state = ctx
|
||||
.memory()
|
||||
.scroll_areas
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let mut state = *ctx.memory().id_data.get_or_default::<State>(id);
|
||||
|
||||
if let Some(offset) = offset {
|
||||
state.offset = offset;
|
||||
|
@ -357,7 +352,7 @@ impl Prepared {
|
|||
state.offset.y = state.offset.y.max(0.0);
|
||||
state.show_scroll = show_scroll_this_frame;
|
||||
|
||||
ui.memory().scroll_areas.insert(id, state);
|
||||
ui.memory().id_data.insert(id, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -364,7 +364,7 @@ impl<'open> Window<'open> {
|
|||
|
||||
area_content_ui
|
||||
.memory()
|
||||
.collapsing_headers
|
||||
.id_data
|
||||
.insert(collapsing_id, collapsing);
|
||||
|
||||
if let Some(interaction) = interaction {
|
||||
|
@ -458,9 +458,11 @@ fn interact(
|
|||
area_state.pos = new_rect.min;
|
||||
|
||||
if window_interaction.is_resize() {
|
||||
let mut resize_state = ctx.memory().resize.get(&resize_id).cloned().unwrap();
|
||||
resize_state.requested_size = Some(new_rect.size() - margins);
|
||||
ctx.memory().resize.insert(resize_id, resize_state);
|
||||
ctx.memory()
|
||||
.id_data
|
||||
.get_mut::<resize::State>(&resize_id)
|
||||
.unwrap()
|
||||
.requested_size = Some(new_rect.size() - margins);
|
||||
}
|
||||
|
||||
ctx.memory().areas.move_to_top(area_layer_id);
|
||||
|
|
|
@ -856,31 +856,46 @@ impl Context {
|
|||
ui.horizontal(|ui| {
|
||||
ui.label(format!(
|
||||
"{} collapsing headers",
|
||||
self.memory().collapsing_headers.len()
|
||||
self.memory()
|
||||
.id_data
|
||||
.count::<containers::collapsing_header::State>()
|
||||
));
|
||||
if ui.button("Reset").clicked() {
|
||||
self.memory().collapsing_headers = Default::default();
|
||||
self.memory()
|
||||
.id_data
|
||||
.remove_by_type::<containers::collapsing_header::State>();
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!("{} menu bars", self.memory().menu_bar.len()));
|
||||
ui.label(format!(
|
||||
"{} menu bars",
|
||||
self.memory().id_data_temp.count::<menu::BarState>()
|
||||
));
|
||||
if ui.button("Reset").clicked() {
|
||||
self.memory().menu_bar = Default::default();
|
||||
self.memory()
|
||||
.id_data_temp
|
||||
.remove_by_type::<menu::BarState>();
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!("{} scroll areas", self.memory().scroll_areas.len()));
|
||||
ui.label(format!(
|
||||
"{} scroll areas",
|
||||
self.memory().id_data.count::<scroll_area::State>()
|
||||
));
|
||||
if ui.button("Reset").clicked() {
|
||||
self.memory().scroll_areas = Default::default();
|
||||
self.memory().id_data.remove_by_type::<scroll_area::State>();
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(format!("{} resize areas", self.memory().resize.len()));
|
||||
ui.label(format!(
|
||||
"{} resize areas",
|
||||
self.memory().id_data.count::<resize::State>()
|
||||
));
|
||||
if ui.button("Reset").clicked() {
|
||||
self.memory().resize = Default::default();
|
||||
self.memory().id_data.remove_by_type::<resize::State>();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ pub(crate) struct GridLayout {
|
|||
|
||||
impl GridLayout {
|
||||
pub(crate) fn new(ui: &Ui, id: Id) -> Self {
|
||||
let prev_state = ui.memory().grid.get(&id).cloned().unwrap_or_default();
|
||||
let prev_state = ui.memory().id_data.get_or_default::<State>(id).clone();
|
||||
|
||||
// TODO: respect current layout
|
||||
|
||||
|
@ -212,7 +212,7 @@ impl GridLayout {
|
|||
if self.curr_state != self.prev_state {
|
||||
self.ctx
|
||||
.memory()
|
||||
.grid
|
||||
.id_data
|
||||
.insert(self.id, self.curr_state.clone());
|
||||
self.ctx.request_repaint();
|
||||
}
|
||||
|
|
|
@ -289,6 +289,7 @@
|
|||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
mod animation_manager;
|
||||
pub mod any;
|
||||
pub mod containers;
|
||||
mod context;
|
||||
mod data;
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::{
|
||||
area, collapsing_header, menu, resize, scroll_area, util::Cache, widgets::text_edit, window,
|
||||
Id, InputState, LayerId, Pos2, Rect, Style,
|
||||
};
|
||||
use epaint::color::{Color32, Hsva};
|
||||
use crate::{any, area, window, Id, InputState, LayerId, Pos2, Rect, Style};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
@ -14,12 +10,38 @@ use epaint::color::{Color32, Hsva};
|
|||
/// how far the user has scrolled in a `ScrollArea` etc.
|
||||
///
|
||||
/// 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.
|
||||
#[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.
|
||||
#[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.
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
pub data: any::TypeMap,
|
||||
|
||||
/// Same as `data`, but this data will not be saved between runs.
|
||||
#[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.
|
||||
#[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.
|
||||
#[cfg(not(feature = "persistence"))]
|
||||
pub id_data: any::AnyMap<Id>,
|
||||
|
||||
/// Same as `id_data`, but this data will not be saved between runs.
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub id_data_temp: any::AnyMap<Id>,
|
||||
|
||||
/// new scale that will be applied at the start of the next frame
|
||||
pub(crate) new_pixels_per_point: Option<f32>,
|
||||
|
||||
|
@ -29,30 +51,14 @@ pub struct Memory {
|
|||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub(crate) interaction: Interaction,
|
||||
|
||||
// states of various types of widgets
|
||||
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
|
||||
pub(crate) grid: HashMap<Id, crate::grid::State>,
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub(crate) menu_bar: HashMap<Id, menu::BarState>,
|
||||
pub(crate) resize: HashMap<Id, resize::State>,
|
||||
pub(crate) scroll_areas: HashMap<Id, scroll_area::State>,
|
||||
pub(crate) text_edit: HashMap<Id, text_edit::State>,
|
||||
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub(crate) window_interaction: Option<window::WindowInteraction>,
|
||||
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub(crate) drag_value: crate::widgets::drag_value::MonoState,
|
||||
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub(crate) tooltip: crate::containers::popup::MonoState,
|
||||
|
||||
pub(crate) areas: Areas,
|
||||
|
||||
/// Used by color picker
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
pub(crate) color_cache: Cache<Color32, Hsva>,
|
||||
|
||||
/// Which popup-window is open (if any)?
|
||||
/// Could be a combo box, color picker, menu etc.
|
||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||
|
|
|
@ -20,21 +20,19 @@ use epaint::Stroke;
|
|||
|
||||
/// What is saved between frames.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "persistence", serde(default))]
|
||||
pub(crate) struct BarState {
|
||||
open_menu: Option<Id>,
|
||||
}
|
||||
|
||||
impl BarState {
|
||||
fn load(ctx: &Context, bar_id: &Id) -> Self {
|
||||
ctx.memory()
|
||||
.menu_bar
|
||||
.get(bar_id)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
*ctx.memory().id_data_temp.get_or_default(*bar_id)
|
||||
}
|
||||
|
||||
fn save(self, ctx: &Context, bar_id: Id) {
|
||||
ctx.memory().menu_bar.insert(bar_id, self);
|
||||
ctx.memory().id_data_temp.insert(bar_id, self);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Color picker widgets.
|
||||
|
||||
use crate::util::Cache;
|
||||
use crate::*;
|
||||
use epaint::{color::*, *};
|
||||
|
||||
|
@ -369,7 +370,8 @@ pub fn color_edit_button_srgba(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -
|
|||
let mut hsva = ui
|
||||
.ctx()
|
||||
.memory()
|
||||
.color_cache
|
||||
.data_temp
|
||||
.get_or_default::<Cache<Color32, Hsva>>()
|
||||
.get(srgba)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Hsva::from(*srgba));
|
||||
|
@ -378,7 +380,11 @@ pub fn color_edit_button_srgba(ui: &mut Ui, srgba: &mut Color32, alpha: Alpha) -
|
|||
|
||||
*srgba = Color32::from(hsva);
|
||||
|
||||
ui.ctx().memory().color_cache.set(*srgba, hsva);
|
||||
ui.ctx()
|
||||
.memory()
|
||||
.data_temp
|
||||
.get_mut_or_default::<Cache<Color32, Hsva>>()
|
||||
.set(*srgba, hsva);
|
||||
|
||||
response
|
||||
}
|
||||
|
|
|
@ -144,8 +144,8 @@ pub struct TextEdit<'t> {
|
|||
impl<'t> TextEdit<'t> {
|
||||
pub fn cursor(ui: &Ui, id: Id) -> Option<CursorPair> {
|
||||
ui.memory()
|
||||
.text_edit
|
||||
.get(&id)
|
||||
.id_data
|
||||
.get::<State>(&id)
|
||||
.and_then(|state| state.cursorp)
|
||||
}
|
||||
}
|
||||
|
@ -356,7 +356,7 @@ impl<'t> TextEdit<'t> {
|
|||
auto_id // Since we are only storing the cursor a persistent Id is not super important
|
||||
}
|
||||
});
|
||||
let mut state = ui.memory().text_edit.get(&id).cloned().unwrap_or_default();
|
||||
let mut state = ui.memory().id_data.get_or_default::<State>(id).clone();
|
||||
|
||||
let sense = if enabled {
|
||||
Sense::click_and_drag()
|
||||
|
@ -600,7 +600,7 @@ impl<'t> TextEdit<'t> {
|
|||
.galley(response.rect.min, galley, hint_text_color);
|
||||
}
|
||||
|
||||
ui.memory().text_edit.insert(id, state);
|
||||
ui.memory().id_data.insert(id, state);
|
||||
|
||||
response.widget_info(|| WidgetInfo::text_edit(&*text));
|
||||
response
|
||||
|
|
Loading…
Reference in a new issue