raui_core/widget/component/interactive/
options_view.rsuse crate::{
make_widget, pre_hooks, unpack_named_slots,
view_model::ViewModelValue,
widget::{
component::{
containers::{
anchor_box::PivotBoxProps,
context_box::{portals_context_box, ContextBoxProps},
size_box::{size_box, SizeBoxProps},
},
interactive::{
button::{button, ButtonNotifyMessage, ButtonNotifyProps},
navigation::NavItemActive,
},
},
context::WidgetContext,
node::WidgetNode,
WidgetIdMetaParams,
},
PropsData,
};
use intuicio_data::managed::ManagedLazy;
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
pub trait OptionsViewProxy: Send + Sync {
fn get(&self) -> usize;
fn set(&mut self, value: usize);
}
macro_rules! impl_proxy {
($type:ty) => {
impl OptionsViewProxy for $type {
fn get(&self) -> usize {
*self as _
}
fn set(&mut self, value: usize) {
*self = value as _;
}
}
};
}
impl_proxy!(u8);
impl_proxy!(u16);
impl_proxy!(u32);
impl_proxy!(u64);
impl_proxy!(u128);
impl_proxy!(usize);
impl_proxy!(i8);
impl_proxy!(i16);
impl_proxy!(i32);
impl_proxy!(i64);
impl_proxy!(i128);
impl_proxy!(isize);
impl_proxy!(f32);
impl_proxy!(f64);
impl<T> OptionsViewProxy for ViewModelValue<T>
where
T: OptionsViewProxy,
{
fn get(&self) -> usize {
self.deref().get()
}
fn set(&mut self, value: usize) {
self.deref_mut().set(value);
}
}
#[derive(Clone)]
pub struct OptionsInput(ManagedLazy<dyn OptionsViewProxy>);
impl OptionsInput {
pub fn new(data: ManagedLazy<impl OptionsViewProxy + 'static>) -> Self {
let (lifetime, data) = data.into_inner();
let data = data as *mut dyn OptionsViewProxy;
unsafe { Self(ManagedLazy::<dyn OptionsViewProxy>::new_raw(data, lifetime).unwrap()) }
}
pub fn into_inner(self) -> ManagedLazy<dyn OptionsViewProxy> {
self.0
}
pub fn get<T: TryFrom<usize> + Default>(&self) -> T {
self.0
.read()
.map(|data| data.get())
.and_then(|value| T::try_from(value).ok())
.unwrap_or_default()
}
pub fn set<T: TryInto<usize>>(&mut self, value: T) {
if let Some(mut data) = self.0.write() {
if let Ok(value) = value.try_into() {
data.set(value);
}
}
}
}
impl std::fmt::Debug for OptionsInput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("OptionsInput")
.field(&self.0.read().map(|data| data.get()).unwrap_or_default())
.finish()
}
}
impl<T: OptionsViewProxy + 'static> From<ManagedLazy<T>> for OptionsInput {
fn from(value: ManagedLazy<T>) -> Self {
Self::new(value)
}
}
#[derive(PropsData, Debug, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[props_data(crate::props::PropsData)]
#[prefab(crate::Prefab)]
pub enum OptionsViewMode {
Selected,
#[default]
Option,
}
#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
#[props_data(crate::props::PropsData)]
#[prefab(crate::Prefab)]
pub struct OptionsViewProps {
#[serde(default)]
#[serde(skip)]
pub input: Option<OptionsInput>,
}
impl OptionsViewProps {
pub fn get_index(&self) -> usize {
self.input
.as_ref()
.map(|input| input.get::<usize>())
.unwrap_or_default()
}
pub fn set_index(&mut self, value: usize) {
if let Some(input) = self.input.as_mut() {
input.set(value);
}
}
}
fn use_options_view(context: &mut WidgetContext) {
context.life_cycle.change(|context| {
for msg in context.messenger.messages {
if let Some(msg) = msg.as_any().downcast_ref::<ButtonNotifyMessage>() {
if msg.trigger_stop() {
if msg.sender.key() == "button-selected" {
let mut state = context.state.read_cloned_or_default::<ContextBoxProps>();
state.show = !state.show;
let _ = context.state.write_with(state);
} else if msg.sender.key() == "button-item" {
let mut state = context.state.read_cloned_or_default::<ContextBoxProps>();
state.show = !state.show;
let _ = context.state.write_with(state);
let params = WidgetIdMetaParams::new(msg.sender.meta());
if let Some(value) = params.find_value("index") {
if let Ok(value) = value.parse::<usize>() {
if let Ok(mut options) =
context.props.read_cloned::<OptionsViewProps>()
{
options.set_index(value);
}
}
}
}
}
}
}
});
}
#[pre_hooks(use_options_view)]
pub fn options_view(mut context: WidgetContext) -> WidgetNode {
let WidgetContext {
id,
idref,
key,
props,
state,
named_slots,
listed_slots,
..
} = context;
unpack_named_slots!(named_slots => content);
let state = state.read_cloned_or_default::<ContextBoxProps>();
let active = props.read_cloned::<NavItemActive>().ok();
let options = props.read_cloned_or_default::<OptionsViewProps>();
let selected = listed_slots
.get(options.get_index())
.cloned()
.map(|mut node| {
node.remap_props(|props| props.with(OptionsViewMode::Selected));
node
})
.unwrap_or_default();
let content = if state.show {
let content = match content {
WidgetNode::Component(node) => {
WidgetNode::Component(node.listed_slots(listed_slots.into_iter().enumerate().map(
|(index, mut slot)| {
slot.remap_props(|props| props.with(OptionsViewMode::Option));
make_widget!(button)
.key(format!("button-item?index={}", index))
.merge_props(slot.props().cloned().unwrap_or_default())
.with_props(ButtonNotifyProps(id.to_owned().into()))
.named_slot("content", slot)
},
)))
}
node => node,
};
Some(
make_widget!(size_box)
.key("context")
.merge_props(content.props().cloned().unwrap_or_default())
.with_props(props.read_cloned_or_default::<SizeBoxProps>())
.named_slot("content", content),
)
} else {
None
};
make_widget!(portals_context_box)
.key(key)
.maybe_idref(idref.cloned())
.with_props(props.read_cloned_or_default::<PivotBoxProps>())
.with_props(state)
.named_slot(
"content",
make_widget!(button)
.key("button-selected")
.maybe_with_props(active)
.with_props(ButtonNotifyProps(id.to_owned().into()))
.named_slot("content", selected),
)
.maybe_named_slot("context", content)
.into()
}