mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-28 09:58:41 -06:00
Compare commits
2 commits
ec4404b26c
...
92f8975702
Author | SHA1 | Date | |
---|---|---|---|
griffi-gh | 92f8975702 | ||
griffi-gh | aa9e0de3e9 |
|
@ -6,8 +6,16 @@ use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
/// Implements `Signal` trait for the given type
|
/// Implements `Signal` trait for the given type
|
||||||
#[proc_macro_derive(Signal)]
|
#[proc_macro_derive(Signal)]
|
||||||
pub fn signal(input: TokenStream) -> TokenStream {
|
pub fn derive_signal(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
let name = input.ident;
|
let name = input.ident;
|
||||||
quote!(impl ::hui::signal::Signal for #name {}).into()
|
quote!(impl ::hui::signal::Signal for #name {}).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implements `State` trait for the given type
|
||||||
|
#[proc_macro_derive(State)]
|
||||||
|
pub fn derive_state(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let name = input.ident;
|
||||||
|
quote!(impl ::hui::state::State for #name {}).into()
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ derive_setters = "0.1"
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
tinyset = "0.4"
|
tinyset = "0.4"
|
||||||
image = { version = "0.25", default-features = false, optional = true }
|
image = { version = "0.25", default-features = false, optional = true }
|
||||||
|
rustc-hash = "1.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["el_all", "image", "builtin_font", "pixel_perfect_text", "derive"]
|
default = ["el_all", "image", "builtin_font", "pixel_perfect_text", "derive"]
|
||||||
|
|
|
@ -18,9 +18,7 @@ use crate::{
|
||||||
/// In most cases, you should only have one instance of this struct, but multiple instances are allowed\
|
/// In most cases, you should only have one instance of this struct, but multiple instances are allowed\
|
||||||
/// (Please note that it's possible to render multiple UI "roots" using a single instance)
|
/// (Please note that it's possible to render multiple UI "roots" using a single instance)
|
||||||
pub struct UiInstance {
|
pub struct UiInstance {
|
||||||
//mouse_position: Vec2,
|
|
||||||
stateful_state: StateRepo,
|
stateful_state: StateRepo,
|
||||||
//event_queue: VecDeque<UiEvent>,
|
|
||||||
prev_draw_commands: UiDrawCommandList,
|
prev_draw_commands: UiDrawCommandList,
|
||||||
draw_commands: UiDrawCommandList,
|
draw_commands: UiDrawCommandList,
|
||||||
draw_call: UiDrawCall,
|
draw_call: UiDrawCall,
|
||||||
|
@ -30,7 +28,7 @@ pub struct UiInstance {
|
||||||
events: EventQueue,
|
events: EventQueue,
|
||||||
input: UiInputState,
|
input: UiInputState,
|
||||||
signal: SignalStore,
|
signal: SignalStore,
|
||||||
//True if in the middle of a laying out a frame
|
/// True if in the middle of a laying out a frame
|
||||||
state: bool,
|
state: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +39,7 @@ impl UiInstance {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
UiInstance {
|
UiInstance {
|
||||||
//mouse_position: Vec2::ZERO,
|
//mouse_position: Vec2::ZERO,
|
||||||
stateful_state: StateRepo::default(),
|
stateful_state: StateRepo::new(),
|
||||||
//event_queue: VecDeque::new(),
|
//event_queue: VecDeque::new(),
|
||||||
// root_elements: Vec::new(),
|
// root_elements: Vec::new(),
|
||||||
prev_draw_commands: UiDrawCommandList::default(),
|
prev_draw_commands: UiDrawCommandList::default(),
|
||||||
|
|
135
hui/src/state.rs
135
hui/src/state.rs
|
@ -2,12 +2,141 @@
|
||||||
|
|
||||||
use hashbrown::{HashMap, HashSet};
|
use hashbrown::{HashMap, HashSet};
|
||||||
use nohash_hasher::BuildNoHashHasher;
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
use std::any::Any;
|
use std::{any::Any, hash::{Hash, Hasher}};
|
||||||
|
use rustc_hash::FxHasher;
|
||||||
|
|
||||||
//TODO impl StateRepo functions and automatic cleanup of inactive ids
|
//TODO impl StateRepo functions and automatic cleanup of inactive ids
|
||||||
|
|
||||||
|
#[cfg(feature = "derive")]
|
||||||
|
pub use hui_derive::State;
|
||||||
|
|
||||||
|
/// Marker trait for state objects
|
||||||
|
pub trait State: Any {}
|
||||||
|
|
||||||
|
/// Integer type used to identify a state object
|
||||||
|
type StateId = u64;
|
||||||
|
|
||||||
|
fn hash_local(x: impl Hash, g: &[StateId]) -> StateId {
|
||||||
|
let mut hasher = FxHasher::default();
|
||||||
|
0xdeadbeefu64.hash(&mut hasher);
|
||||||
|
for x in g {
|
||||||
|
x.hash(&mut hasher);
|
||||||
|
}
|
||||||
|
x.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash_global(x: impl Hash) -> StateId {
|
||||||
|
let mut hasher = FxHasher::default();
|
||||||
|
0xcafebabeu64.hash(&mut hasher);
|
||||||
|
x.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct StateRepo {
|
pub struct StateRepo {
|
||||||
state: HashMap<u64, Box<dyn Any>, BuildNoHashHasher<u64>>,
|
/// Stack of ids used to identify state objects
|
||||||
active_ids: HashSet<u64, BuildNoHashHasher<u64>>
|
id_stack: Vec<StateId>,
|
||||||
|
|
||||||
|
/// Implementation detail: used to prevent needlessly reallocating the id stack if the `global`` function is used
|
||||||
|
standby: Vec<StateId>,
|
||||||
|
|
||||||
|
/// Actual state objects
|
||||||
|
state: HashMap<StateId, Box<dyn Any>, BuildNoHashHasher<StateId>>,
|
||||||
|
|
||||||
|
/// IDs that were accessed during the current frame, everything else is considered inactive and can be cleaned up
|
||||||
|
active_ids: HashSet<StateId, BuildNoHashHasher<StateId>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateRepo {
|
||||||
|
/// Push an id to the stack
|
||||||
|
pub fn push(&mut self, id: impl Hash) {
|
||||||
|
self.id_stack.push(hash_global(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pop the last id from the stack
|
||||||
|
///
|
||||||
|
/// ## Panics:
|
||||||
|
/// Panics if the stack is empty
|
||||||
|
pub fn pop(&mut self) {
|
||||||
|
self.id_stack.pop().expect("stack is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`StateRepo`]
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to a state object by its id
|
||||||
|
pub fn acquire<T: State>(&mut self, id: impl Hash) -> Option<&T> {
|
||||||
|
let id = hash_local(id, &self.id_stack);
|
||||||
|
self.active_ids.insert(id);
|
||||||
|
self.state.get(&id).unwrap().downcast_ref::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to a state object by its id or insert a new one
|
||||||
|
pub fn acquire_or_insert<T: State>(&mut self, id: impl Hash, state: T) -> &T {
|
||||||
|
let id = hash_local(id, &self.id_stack);
|
||||||
|
self.state.entry(id)
|
||||||
|
.or_insert_with(|| Box::new(state))
|
||||||
|
.downcast_ref::<T>().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to a state object by its id or insert a new default one
|
||||||
|
pub fn acquire_or_default<T: State + Default>(&mut self, id: impl Hash) -> &T {
|
||||||
|
let id = hash_local(id, &self.id_stack);
|
||||||
|
self.state.entry(id)
|
||||||
|
.or_insert_with(|| Box::<T>::default())
|
||||||
|
.downcast_ref::<T>().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to a state object by its id
|
||||||
|
pub fn acquire_mut<T: State>(&mut self, id: impl Hash) -> Option<&mut T> {
|
||||||
|
let id = hash_local(id, &self.id_stack);
|
||||||
|
self.active_ids.insert(id);
|
||||||
|
self.state.get_mut(&id).unwrap().downcast_mut::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to a state object by its id or insert a new one
|
||||||
|
pub fn acquire_mut_or_insert<T: State>(&mut self, id: impl Hash, state: T) -> &mut T {
|
||||||
|
let id = hash_local(id, &self.id_stack);
|
||||||
|
self.state.entry(id)
|
||||||
|
.or_insert_with(|| Box::new(state))
|
||||||
|
.downcast_mut::<T>().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to a state object by its id or insert a new default one
|
||||||
|
pub fn acquire_mut_or_default<T: State + Default>(&mut self, id: impl Hash) -> &mut T {
|
||||||
|
let id = hash_local(id, &self.id_stack);
|
||||||
|
self.state.entry(id)
|
||||||
|
.or_insert_with(|| Box::<T>::default())
|
||||||
|
.downcast_mut::<T>().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Temporarily forget about current id stack, and use an empty one (within the context of the closure)
|
||||||
|
///
|
||||||
|
/// Can be useful for state management of non-hierarchical objects, e.g. popups
|
||||||
|
pub fn global<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
|
self.standby.clear();
|
||||||
|
std::mem::swap(&mut self.id_stack, &mut self.standby);
|
||||||
|
let ret = f(self);
|
||||||
|
std::mem::swap(&mut self.id_stack, &mut self.standby);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scope the state repo
|
||||||
|
///
|
||||||
|
/// Anything pushed or popped will be discarded after the closure,
|
||||||
|
/// and the stack will be restored to its previous state
|
||||||
|
pub fn scope<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
|
||||||
|
self.standby.clear();
|
||||||
|
self.standby.extend(self.id_stack.iter().copied());
|
||||||
|
let ret = f(self);
|
||||||
|
std::mem::swap(&mut self.id_stack, &mut self.standby);
|
||||||
|
ret
|
||||||
|
//XXX: this is super efficient, but works only for pushes, if anything is popped, it will be lost
|
||||||
|
// let len = self.id_stack.len();
|
||||||
|
// let ret = f(self);
|
||||||
|
// self.id_stack.truncate(len);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue