use super::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum StateKey {
Indexed(usize),
Named(&'static str),
Keyed(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct State<T> {
key: StateKey,
_marker: std::marker::PhantomData<T>,
}
fn downcast_or_panic<'a, T: 'static>(
boxed: &'a dyn std::any::Any,
ctx: std::fmt::Arguments<'_>,
) -> &'a T {
boxed
.downcast_ref::<T>()
.unwrap_or_else(|| panic!("{ctx} — expected {}", std::any::type_name::<T>()))
}
fn downcast_or_panic_mut<'a, T: 'static>(
boxed: &'a mut dyn std::any::Any,
ctx: std::fmt::Arguments<'_>,
) -> &'a mut T {
boxed
.downcast_mut::<T>()
.unwrap_or_else(|| panic!("{ctx} — expected {}", std::any::type_name::<T>()))
}
impl<T: 'static> State<T> {
pub(crate) fn from_idx(idx: usize) -> Self {
Self {
key: StateKey::Indexed(idx),
_marker: std::marker::PhantomData,
}
}
pub(crate) fn from_named(id: &'static str) -> Self {
Self {
key: StateKey::Named(id),
_marker: std::marker::PhantomData,
}
}
pub(crate) fn from_keyed(id: String) -> Self {
Self {
key: StateKey::Keyed(id),
_marker: std::marker::PhantomData,
}
}
pub fn get<'a>(&self, ui: &'a Context) -> &'a T {
match &self.key {
StateKey::Indexed(idx) => downcast_or_panic::<T>(
ui.hook_states[*idx].as_ref(),
format_args!("use_state type mismatch at hook index {idx}"),
),
StateKey::Named(id) => {
let boxed = ui.named_states.get(id).unwrap_or_else(|| {
panic!("use_state_named: no entry for id {id:?} — was use_state_named called?")
});
downcast_or_panic::<T>(
boxed.as_ref(),
format_args!("use_state_named type mismatch for id {id:?}"),
)
}
StateKey::Keyed(id) => {
let boxed = ui.keyed_states.get(id).unwrap_or_else(|| {
panic!("use_state_keyed: no entry for id {id:?} — was use_state_keyed called?")
});
downcast_or_panic::<T>(
boxed.as_ref(),
format_args!("use_state_keyed type mismatch for id {id:?}"),
)
}
}
}
pub fn get_mut<'a>(&self, ui: &'a mut Context) -> &'a mut T {
match &self.key {
StateKey::Indexed(idx) => downcast_or_panic_mut::<T>(
ui.hook_states[*idx].as_mut(),
format_args!("use_state type mismatch at hook index {idx}"),
),
StateKey::Named(id) => {
let boxed = ui.named_states.get_mut(id).unwrap_or_else(|| {
panic!("use_state_named: no entry for id {id:?} — was use_state_named called?")
});
downcast_or_panic_mut::<T>(
boxed.as_mut(),
format_args!("use_state_named type mismatch for id {id:?}"),
)
}
StateKey::Keyed(id) => {
let boxed = ui.keyed_states.get_mut(id).unwrap_or_else(|| {
panic!("use_state_keyed: no entry for id {id:?} — was use_state_keyed called?")
});
downcast_or_panic_mut::<T>(
boxed.as_mut(),
format_args!("use_state_keyed type mismatch for id {id:?}"),
)
}
}
}
}
#[derive(Debug, Clone, Default)]
#[must_use = "Response contains interaction state — check .clicked, .hovered, or .changed"]
pub struct Response {
pub clicked: bool,
pub right_clicked: bool,
pub hovered: bool,
pub changed: bool,
pub focused: bool,
pub gained_focus: bool,
pub lost_focus: bool,
pub rect: Rect,
}
impl Response {
pub fn none() -> Self {
Self::default()
}
#[must_use = "on_hover returns the Response for further chaining"]
pub fn on_hover(self, ctx: &mut Context, text: impl Into<String>) -> Self {
if !self.hovered || self.rect.width == 0 || self.rect.height == 0 {
return self;
}
let tooltip_text = text.into();
if tooltip_text.is_empty() {
return self;
}
let lines = super::widgets_display::wrap_tooltip_text(&tooltip_text, 38);
ctx.pending_tooltips.push(PendingTooltip {
anchor_rect: self.rect,
lines,
});
self
}
#[must_use = "on_hover_ui returns the Response for further chaining"]
pub fn on_hover_ui(self, ctx: &mut Context, f: impl FnOnce(&mut Context)) -> Self {
if self.hovered && self.rect.width > 0 && self.rect.height > 0 {
f(ctx);
}
self
}
}