mirror of
https://github.com/griffi-gh/hUI.git
synced 2024-11-25 16:38:42 -06:00
signal stuff...
This commit is contained in:
parent
4b3f15e6ce
commit
01b30c5979
|
@ -1,15 +1,21 @@
|
||||||
use std::time::Instant;
|
|
||||||
use hui::{
|
use hui::{
|
||||||
color, size,
|
color, size,
|
||||||
layout::{Alignment, Direction},
|
layout::{Alignment, Direction},
|
||||||
element::{
|
element::{
|
||||||
container::Container,
|
container::Container,
|
||||||
fill_rect::FillRect,
|
text::Text,
|
||||||
interactable::ElementInteractableExt,
|
interactable::ElementInteractableExt,
|
||||||
UiElementExt
|
UiElementExt
|
||||||
},
|
},
|
||||||
|
signal::UiSignal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum CounterSignal {
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
}
|
||||||
|
impl UiSignal for CounterSignal {}
|
||||||
|
|
||||||
#[path = "../boilerplate.rs"]
|
#[path = "../boilerplate.rs"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod boilerplate;
|
mod boilerplate;
|
||||||
|
@ -19,16 +25,39 @@ ui_main!(
|
||||||
init: |_| {
|
init: |_| {
|
||||||
0
|
0
|
||||||
},
|
},
|
||||||
run: |ui, size, n| {
|
run: |ui, size, counter| {
|
||||||
Container::default()
|
Container::default()
|
||||||
.with_size(size!(100%))
|
.with_size(size!(100%))
|
||||||
.with_align(Alignment::Center)
|
.with_align(Alignment::Center)
|
||||||
|
.with_direction(Direction::Horizontal)
|
||||||
|
.with_gap(5.)
|
||||||
.with_background(color::WHITE)
|
.with_background(color::WHITE)
|
||||||
.with_children(|ui| {
|
.with_children(|ui| {
|
||||||
FillRect::default()
|
Container::default()
|
||||||
.with_size(size!(40))
|
.with_padding(10.)
|
||||||
.with_corner_radius(8.)
|
.with_corner_radius(8.)
|
||||||
.with_background(color::DARK_RED)
|
.with_background(color::DARK_RED)
|
||||||
|
.with_children(|ui| {
|
||||||
|
Text::new("-")
|
||||||
|
.add_child(ui);
|
||||||
|
})
|
||||||
|
.into_interactable()
|
||||||
|
.on_click(|| {
|
||||||
|
println!("clicked");
|
||||||
|
})
|
||||||
|
.add_child(ui);
|
||||||
|
Text::new(counter.to_string())
|
||||||
|
.with_color(color::BLACK)
|
||||||
|
.with_text_size(32)
|
||||||
|
.add_child(ui);
|
||||||
|
Container::default()
|
||||||
|
.with_padding(10.)
|
||||||
|
.with_corner_radius(8.)
|
||||||
|
.with_background(color::DARK_RED)
|
||||||
|
.with_children(|ui| {
|
||||||
|
Text::new("+")
|
||||||
|
.add_child(ui);
|
||||||
|
})
|
||||||
.into_interactable()
|
.into_interactable()
|
||||||
.on_click(|| {
|
.on_click(|| {
|
||||||
println!("clicked");
|
println!("clicked");
|
||||||
|
@ -36,5 +65,12 @@ ui_main!(
|
||||||
.add_child(ui);
|
.add_child(ui);
|
||||||
})
|
})
|
||||||
.add_root(ui, size);
|
.add_root(ui, size);
|
||||||
|
|
||||||
|
ui.process_signals(|sig| {
|
||||||
|
match sig {
|
||||||
|
CounterSignal::Increment => *counter += 1,
|
||||||
|
CounterSignal::Decrement => *counter -= 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,6 +27,7 @@ document-features = "0.2"
|
||||||
derive_setters = "0.1"
|
derive_setters = "0.1"
|
||||||
#smallvec = "1.13"
|
#smallvec = "1.13"
|
||||||
tinyset = "0.4"
|
tinyset = "0.4"
|
||||||
|
#mopa = "0.2"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["builtin_elements", "builtin_font", "pixel_perfect_text"]
|
default = ["builtin_elements", "builtin_font", "pixel_perfect_text"]
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
input::InputCtx,
|
input::InputCtx,
|
||||||
layout::LayoutInfo,
|
layout::LayoutInfo,
|
||||||
measure::Response,
|
measure::Response,
|
||||||
|
signal::SignalCtx,
|
||||||
state::StateRepo,
|
state::StateRepo,
|
||||||
text::{FontHandle, TextMeasure},
|
text::{FontHandle, TextMeasure},
|
||||||
UiInstance,
|
UiInstance,
|
||||||
|
@ -35,6 +36,7 @@ pub struct ProcessContext<'a> {
|
||||||
pub current_font: FontHandle,
|
pub current_font: FontHandle,
|
||||||
pub images: ImageCtx<'a>,
|
pub images: ImageCtx<'a>,
|
||||||
pub input: InputCtx<'a>,
|
pub input: InputCtx<'a>,
|
||||||
|
//pub signal: SignalCtx<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UiElement {
|
pub trait UiElement {
|
||||||
|
|
|
@ -31,3 +31,4 @@ pub mod interactable;
|
||||||
//TODO add: Image
|
//TODO add: Image
|
||||||
//TODO add: OverlayContainer (for simply laying multiple elements on top of each other)
|
//TODO add: OverlayContainer (for simply laying multiple elements on top of each other)
|
||||||
//TODO add: Button, Checkbox, Dropdown, Input, Radio, Slider, Textarea, Toggle, etc.
|
//TODO add: Button, Checkbox, Dropdown, Input, Radio, Slider, Textarea, Toggle, etc.
|
||||||
|
//TODO add: some sort of "flexible" container (like a poor man's flexbox)
|
||||||
|
|
|
@ -3,24 +3,29 @@
|
||||||
// not sure if this is a good idea...
|
// not sure if this is a good idea...
|
||||||
// but having the ability to add a click event to any element would be nice, and this is a naive way to do it
|
// but having the ability to add a click event to any element would be nice, and this is a naive way to do it
|
||||||
|
|
||||||
use crate::element::{UiElement, MeasureContext, ProcessContext};
|
use crate::{
|
||||||
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
|
signal::{DummySignal, UiSignal},
|
||||||
|
};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
/// Wrapper that allows adding click and hover events to any element
|
/// Wrapper that allows adding click and hover events to any element
|
||||||
pub struct Interactable {
|
pub struct Interactable<H: UiSignal + 'static = DummySignal, C: UiSignal + 'static = DummySignal> {
|
||||||
/// The wrapped element that will be interactable
|
/// The wrapped element that will be interactable
|
||||||
pub element: Box<dyn UiElement>,
|
pub element: Box<dyn UiElement>,
|
||||||
/// Function that will be called if the element is hovered in the current frame
|
|
||||||
|
/// Signal that will be called if the element is hovered in the current frame
|
||||||
///
|
///
|
||||||
/// Will be consumed after the first time it's called
|
/// Will be consumed after the first time it's called
|
||||||
pub hovered: RefCell<Option<Box<dyn FnOnce()>>>,
|
pub hovered: RefCell<Option<H>>,
|
||||||
/// Function that will be called if the element was clicked in the current frame
|
|
||||||
|
/// Signal that will be called if the element was clicked in the current frame
|
||||||
///
|
///
|
||||||
/// Will be consumed after the first time it's called
|
/// Will be consumed after the first time it's called
|
||||||
pub clicked: RefCell<Option<Box<dyn FnOnce()>>>,
|
pub clicked: RefCell<Option<C>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interactable {
|
impl<H: UiSignal, C: UiSignal> Interactable<H, C> {
|
||||||
pub fn new(element: Box<dyn UiElement>) -> Self {
|
pub fn new(element: Box<dyn UiElement>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
element,
|
element,
|
||||||
|
@ -29,16 +34,16 @@ impl Interactable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_click(self, clicked: impl FnOnce() + 'static) -> Self {
|
pub fn on_hover(self, hover: H) -> Self {
|
||||||
Self {
|
Self {
|
||||||
clicked: RefCell::new(Some(Box::new(clicked))),
|
hovered: RefCell::new(Some(hover)),
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_hover(self, clicked: impl FnOnce() + 'static) -> Self {
|
pub fn on_click(self, clicked: C) -> Self {
|
||||||
Self {
|
Self {
|
||||||
clicked: RefCell::new(Some(Box::new(clicked))),
|
clicked: RefCell::new(Some(clicked)),
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,9 +64,9 @@ impl UiElement for Interactable {
|
||||||
//XXX: should we do this AFTER normal process call of wrapped element?
|
//XXX: should we do this AFTER normal process call of wrapped element?
|
||||||
//TODO other events...
|
//TODO other events...
|
||||||
if ctx.input.check_click(rect) {
|
if ctx.input.check_click(rect) {
|
||||||
//TODO better error message
|
if let Some(sig) = self.clicked.take() {
|
||||||
let clicked = self.clicked.borrow_mut().take().expect("you fucked up");
|
//ctx.signal.push(sig);
|
||||||
clicked();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.element.process(ctx)
|
self.element.process(ctx)
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
use glam::Vec2;
|
use glam::Vec2;
|
||||||
use crate::{
|
use crate::{
|
||||||
draw::{
|
draw::{
|
||||||
atlas::{TextureAtlasManager, TextureAtlasMeta}, TextureFormat, ImageHandle, UiDrawCall, UiDrawCommandList
|
ImageHandle, TextureFormat, UiDrawCall, UiDrawCommandList,
|
||||||
}, element::{MeasureContext, ProcessContext, UiElement}, event::{EventQueue, UiEvent}, input::UiInputState, layout::{LayoutInfo, Direction}, state::StateRepo, text::{FontHandle, TextRenderer}
|
atlas::{TextureAtlasManager, TextureAtlasMeta},
|
||||||
|
},
|
||||||
|
element::{MeasureContext, ProcessContext, UiElement},
|
||||||
|
event::{EventQueue, UiEvent},
|
||||||
|
input::UiInputState,
|
||||||
|
layout::{Direction, LayoutInfo},
|
||||||
|
signal::{SigIntStore, UiSignal},
|
||||||
|
state::StateRepo,
|
||||||
|
text::{FontHandle, TextRenderer}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The main instance of the UI system.
|
/// The main instance of the UI system.
|
||||||
|
@ -21,6 +29,7 @@ pub struct UiInstance {
|
||||||
atlas: TextureAtlasManager,
|
atlas: TextureAtlasManager,
|
||||||
events: EventQueue,
|
events: EventQueue,
|
||||||
input: UiInputState,
|
input: UiInputState,
|
||||||
|
signal: SigIntStore,
|
||||||
//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,
|
||||||
}
|
}
|
||||||
|
@ -48,6 +57,7 @@ impl UiInstance {
|
||||||
},
|
},
|
||||||
events: EventQueue::new(),
|
events: EventQueue::new(),
|
||||||
input: UiInputState::new(),
|
input: UiInputState::new(),
|
||||||
|
signal: SigIntStore::new(),
|
||||||
state: false,
|
state: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,6 +238,18 @@ impl UiInstance {
|
||||||
}
|
}
|
||||||
self.events.push(event);
|
self.events.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a "fake" signal to the UI signal queue
|
||||||
|
pub fn push_signal<T: UiSignal + 'static>(&mut self, signal: T) {
|
||||||
|
self.signal.add(signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process all signals of a given type
|
||||||
|
///
|
||||||
|
/// This clears the signal queue for the given type and iterates over all signals
|
||||||
|
pub fn process_signals<T: UiSignal + 'static>(&mut self, f: impl FnMut(T)) {
|
||||||
|
self.signal.drain::<T>().for_each(f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UiInstance {
|
impl Default for UiInstance {
|
||||||
|
|
|
@ -22,5 +22,6 @@ pub mod measure;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
pub mod signal;
|
||||||
|
|
||||||
pub use instance::UiInstance;
|
pub use instance::UiInstance;
|
||||||
|
|
62
hui/src/signal.rs
Normal file
62
hui/src/signal.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use std::any::{Any, TypeId};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use nohash_hasher::BuildNoHashHasher;
|
||||||
|
|
||||||
|
/// A marker trait for signals
|
||||||
|
pub trait UiSignal: Any {}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
|
||||||
|
pub(crate) struct DummySignal;
|
||||||
|
impl UiSignal for DummySignal {}
|
||||||
|
|
||||||
|
pub(crate) struct SigIntStore {
|
||||||
|
///XXX: is this truly the most efficient structure?
|
||||||
|
sig: HashMap<TypeId, Vec<Box<dyn Any>>, BuildNoHashHasher<u64>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SigIntStore {
|
||||||
|
/// Create a new [`SigIntStore`]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
sig: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that store for given signal type exists and return a mutable reference to it
|
||||||
|
fn internal_store<T: UiSignal + 'static>(&mut self) -> &mut Vec<Box<dyn Any>> {
|
||||||
|
let type_id = TypeId::of::<T>();
|
||||||
|
self.sig.entry(type_id).or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a signal to the store
|
||||||
|
///
|
||||||
|
/// Signals are stored in the order they are added
|
||||||
|
pub fn add<T: UiSignal + 'static>(&mut self, sig: T) {
|
||||||
|
let type_id = TypeId::of::<T>();
|
||||||
|
if let Some(v) = self.sig.get_mut(&type_id) {
|
||||||
|
v.push(Box::new(sig));
|
||||||
|
} else {
|
||||||
|
self.sig.insert(type_id, vec![Box::new(sig)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drain all signals of a given type
|
||||||
|
pub fn drain<T: UiSignal + 'static>(&mut self) -> impl Iterator<Item = T> + '_ {
|
||||||
|
self.internal_store::<T>()
|
||||||
|
.drain(..)
|
||||||
|
.map(|x| *x.downcast::<T>().unwrap()) //unchecked?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ctx(&mut self) -> SignalCtx {
|
||||||
|
SignalCtx(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SignalCtx<'a>(&'a mut SigIntStore);
|
||||||
|
|
||||||
|
impl<'a> SignalCtx<'a> {
|
||||||
|
/// Add a signal to the store
|
||||||
|
pub fn push<T: UiSignal + 'static>(&mut self, sig: T) {
|
||||||
|
self.0.add(sig);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue