use std::collections::HashMap;
use std::sync::{Arc, mpsc};
use std::time::Instant;
use winit::event::WindowEvent;
use crate::builder::{StyleMap, build_logic};
use crate::draw::{Pane, Scene, ShaderId};
use crate::input::Input;
use crate::loader::{MenuDef, UiMsg, load_menu, load_menu_soft, spawn_watcher};
use crate::logic::Root;
use crate::styles::StyleRegistry;
use crate::textures::{GifMode, TextureId, TextureRegistry};
#[derive(Debug, Clone, PartialEq)]
pub enum PaneAction {
SwitchRoot(String),
Quit,
Custom(String),
Slider(String, f32),
TextChanged(String, String),
TextSubmitted(String, String),
Toggle(String, bool),
Dropdown(String, usize, String),
Radio(String, usize, String),
}
#[derive(Debug, Clone)]
pub enum WriteValue {
Slider(f32),
Toggle(bool),
Text(String),
Selected(usize),
}
fn register_shaders(
renderer: &mut Pane,
device: &wgpu::Device,
shader_dirs: &[String],
tex_reg: &TextureRegistry,
) -> (HashMap<String, ShaderId>, ShaderId) {
let mut map = HashMap::new();
for name in crate::shader_reg::BUILTIN_SHADER_NAMES {
let id = renderer.register_shader(device, &crate::shader_reg::load_shader(name), tex_reg);
map.insert(name.to_string(), id);
}
for dir in shader_dirs {
for (name, src) in crate::shader_reg::scan_shader_dir(dir) {
map.insert(name, renderer.register_shader(device, &src, tex_reg));
}
}
let textured_id = *map
.get("textured")
.expect("[pane_ui] built-in 'textured' shader missing");
(map, textured_id)
}
fn load_texture_cached(
tex_reg: &mut TextureRegistry,
device: &wgpu::Device,
queue: &wgpu::Queue,
tex_cache: &mut HashMap<String, TextureId>,
path: &str,
gif_mode: Option<GifMode>,
) -> Option<TextureId> {
if let Some(&id) = tex_cache.get(path) {
return Some(id);
}
let result = match gif_mode {
Some(mode) => tex_reg.load_gif(device, queue, path, mode),
None => tex_reg.load(device, queue, path),
};
match result {
Ok(id) => {
tex_cache.insert(path.to_string(), id);
Some(id)
}
Err(e) => {
eprintln!("[pane_ui] texture load error \\'{path}\\': {e}");
None
}
}
}
pub(crate) fn init_and_build(
renderer: &mut Pane,
device: &wgpu::Device,
queue: &wgpu::Queue,
menu: &MenuDef,
_tx: &mpsc::Sender<UiMsg>,
tex_reg: &mut TextureRegistry,
) -> (
StyleRegistry,
HashMap<String, Root>,
Option<String>,
ShaderId,
Option<crate::styles::StyleId>,
StyleMap,
) {
let (shader_map, textured_id) = register_shaders(renderer, device, &menu.shader_dirs, tex_reg);
let mut tex_cache: HashMap<String, TextureId> = HashMap::new();
let mut style_registry = StyleRegistry::new();
let style_map = crate::styles_reg::register_all(
&mut style_registry,
&shader_map,
&menu.style_dirs,
textured_id,
&mut |path, gif_mode| {
load_texture_cached(tex_reg, device, queue, &mut tex_cache, path, gif_mode)
},
);
let default_style = menu.default_style.as_deref().and_then(|name| {
if let Some(&(id, _)) = style_map.get(name) {
Some(id)
} else {
eprintln!("[pane_ui] default_style '{name}' not found in style map, ignoring");
None
}
});
let (roots, active_root) = build_logic(menu, &style_map, textured_id, &mut |path, gif_mode| {
load_texture_cached(tex_reg, device, queue, &mut tex_cache, path, gif_mode)
});
(
style_registry,
roots,
active_root,
textured_id,
default_style,
style_map,
)
}
pub(crate) fn msg_to_action(msg: UiMsg) -> Option<PaneAction> {
match msg {
UiMsg::Quit => Some(PaneAction::Quit),
UiMsg::Custom(s) => Some(PaneAction::Custom(s)),
UiMsg::Slider(id, val) => Some(PaneAction::Slider(id, val)),
UiMsg::TextChanged(id, s) => Some(PaneAction::TextChanged(id, s)),
UiMsg::TextSubmitted(id, s) => Some(PaneAction::TextSubmitted(id, s)),
UiMsg::Toggle(tag, checked) => Some(PaneAction::Toggle(tag, checked)),
UiMsg::Dropdown(tag, idx, val) => Some(PaneAction::Dropdown(tag, idx, val)),
UiMsg::Radio(tag, idx, val) => Some(PaneAction::Radio(tag, idx, val)),
_ => None,
}
}
fn init_persistent(
pane: &mut Pane,
menu: &MenuDef,
tx: &mpsc::Sender<UiMsg>,
path_owned: &str,
bg_path: Option<&String>,
clear_color: Option<crate::draw::Color>,
) -> crate::threader::Persistent {
let device = pane
.device()
.expect("standalone: device not yet initialized")
.clone();
let queue = pane
.queue()
.expect("standalone: queue not yet initialized")
.clone();
let mut tex_reg = TextureRegistry::new(&device, &queue);
let (new_styles, new_roots, new_active, textured_id, new_default_style, new_style_map) =
init_and_build(pane, &device, &queue, menu, tx, &mut tex_reg);
let background = bg_path.and_then(|p| {
let result = if p.to_lowercase().ends_with(".gif") {
tex_reg.load_gif(&device, &queue, p, GifMode::Loop)
} else {
tex_reg.load(&device, &queue, p)
};
result.ok().map(|id| {
let (width, height) = tex_reg.dimensions(id);
crate::threader::BackgroundDef {
shader: textured_id,
texture: id,
width,
height,
}
})
});
crate::threader::Persistent {
roots: new_roots,
active_root: new_active,
tab_pages: std::collections::HashMap::new(),
nav: crate::logic::NavState::default(),
styles: new_styles,
style_map: new_style_map,
default_style: new_default_style,
tex_reg: Some(tex_reg),
device: Some(device),
queue: Some(queue),
renderer: None,
background,
clear_color,
pending_actions: Vec::new(),
tx: tx.clone(),
rx: mpsc::channel().1,
ron_path: path_owned.to_string(),
debug: std::env::var("PANE_DEBUG").is_ok(),
headless_accessible: false,
osk: crate::keyboard::OskState::default(),
}
}
fn drain_messages_standalone(
rx: &mpsc::Receiver<UiMsg>,
p: &mut crate::threader::Persistent,
pane: &mut Pane,
path_owned: &str,
tx: &mpsc::Sender<UiMsg>,
) {
while let Ok(msg) = rx.try_recv() {
match msg {
UiMsg::SwitchRoot(name) => {
p.active_root = Some(name.clone());
p.nav = crate::logic::NavState::default();
p.pending_actions.push(PaneAction::SwitchRoot(name));
}
UiMsg::SwitchTabPage { tab_id, page } => {
p.tab_pages.insert(tab_id, page);
}
UiMsg::Quit => std::process::exit(0),
UiMsg::Toast {
message,
duration,
x,
y,
width,
height,
} => {
push_toast_into(p, message, duration, x, y, width, height);
}
UiMsg::Reload => {
if let Ok(new_menu) = load_menu_soft(path_owned) {
let device = p.device.as_ref().unwrap().clone();
let queue = p.queue.as_ref().unwrap().clone();
let prev_active = p.active_root.clone();
let tex_reg = p.tex_reg.as_mut().unwrap();
let (
new_styles,
new_roots,
new_active,
_textured_id,
new_default_style,
new_style_map,
) = init_and_build(pane, &device, &queue, &new_menu, tx, tex_reg);
p.styles = new_styles;
p.style_map = new_style_map;
p.active_root = prev_active
.filter(|name| new_roots.iter().any(|(k, _)| k == name))
.or(new_active);
p.roots = new_roots;
p.default_style = new_default_style;
p.nav = crate::logic::NavState::default();
p.clear_color = new_menu.clear_color;
}
}
other => {
if let Some(a) = msg_to_action(other) {
p.pending_actions.push(a);
}
}
}
}
}
pub fn run(path: &str) {
run_with(path, |_, _| {});
}
pub struct StandaloneHandle<'a> {
persistent: &'a mut crate::threader::Persistent,
}
impl StandaloneHandle<'_> {
#[must_use]
pub fn read(&self, id: &str) -> Option<(&crate::items::UiItem, crate::widgets::WidgetState)> {
read_into(self.persistent, id)
}
pub fn write(&mut self, id: &str, value: &WriteValue) {
write_into(self.persistent, id, value);
}
pub fn create(&mut self, root: &str, builder: impl crate::build::WidgetBuilder) -> bool {
create_into(self.persistent, root, builder)
}
pub fn destroy(&mut self, id: &str) -> bool {
destroy_into(self.persistent, id)
}
pub fn create_root(&mut self, name: impl Into<String>) {
create_root_into(self.persistent, name);
}
#[must_use]
pub fn style_id(&self, name: &str) -> Option<crate::styles::StyleId> {
style_id_in(self.persistent, name)
}
#[must_use]
pub fn default_style(&self) -> Option<&str> {
default_style_in(self.persistent)
}
pub fn set_default_style(&mut self, name: &str) -> bool {
set_default_style_in(self.persistent, name)
}
pub fn push_toast(
&mut self,
message: impl Into<String>,
duration: f32,
x: f32,
y: f32,
width: f32,
height: f32,
) {
push_toast_into(
self.persistent,
message.into(),
duration,
x,
y,
width,
height,
);
}
}
pub fn run_with<F>(path: &str, mut on_action: F)
where
F: FnMut(&mut StandaloneHandle, PaneAction) + 'static,
{
let path_owned = path.to_string();
let menu = load_menu(&path_owned);
let (tx, rx) = mpsc::channel::<UiMsg>();
if menu.hot_reload {
spawn_watcher(
path_owned.clone(),
menu.shader_dirs.clone(),
menu.style_dirs.clone(),
tx.clone(),
);
}
let bg_path = menu.background.clone();
let clear_color = menu.clear_color;
let mut persistent: Option<crate::threader::Persistent> = None;
let mut last_time = Instant::now();
crate::draw::run(
move |pane: &mut Pane, scene: &mut Scene, input: &mut Input, pw: f32, ph: f32, _dt: f32| {
if persistent.is_none() {
persistent = Some(init_persistent(
pane,
&menu,
&tx,
&path_owned,
bg_path.as_ref(),
clear_color,
));
}
let p = persistent.as_mut().unwrap();
drain_messages_standalone(&rx, p, pane, &path_owned, &tx);
let mut frame = crate::threader::Frame {
dt: 0.0,
last_tick: last_time,
pw,
ph,
input: std::mem::take(input),
scene: std::mem::take(scene),
};
{
let mut ctx = crate::threader::FrameCtx {
persistent: p,
frame: &mut frame,
standalone_pane: Some(pane),
};
crate::order::run_frame(&mut ctx, None, None);
}
last_time = frame.last_tick;
*input = frame.input;
*scene = frame.scene;
let actions = std::mem::take(&mut p.pending_actions);
let cc = p.clear_color;
let cursor = [
input.mouse_x,
input.mouse_y,
f32::from(u8::from(input.left_pressed)),
];
let mut handle = StandaloneHandle { persistent: p };
for action in actions {
on_action(&mut handle, action);
}
pane.present_standalone(
scene,
pw,
ph,
frame.dt,
cursor,
cc,
p.tex_reg.as_mut().unwrap(),
);
scene.clear();
},
);
}
pub struct PaneOverlay {
persistent: crate::threader::Persistent,
input: Input,
scene: Scene,
last_time: Instant,
gilrs: Option<gilrs::Gilrs>,
}
#[must_use]
pub fn overlay(
path: &str,
device: &wgpu::Device,
queue: &wgpu::Queue,
format: wgpu::TextureFormat,
gilrs: Option<gilrs::Gilrs>,
) -> PaneOverlay {
let menu = load_menu(path);
let (tx, rx) = mpsc::channel::<UiMsg>();
let mut renderer = Pane::new(device, queue, format);
let mut tex_reg = TextureRegistry::new(device, queue);
let (new_styles, new_roots, new_active, _textured_id, new_default_style, new_style_map) =
init_and_build(&mut renderer, device, queue, &menu, &tx, &mut tex_reg);
if menu.hot_reload {
spawn_watcher(
path.to_string(),
menu.shader_dirs.clone(),
menu.style_dirs.clone(),
tx.clone(),
);
}
let device_arc = Arc::new(device.clone());
let queue_arc = Arc::new(queue.clone());
let persistent = crate::threader::Persistent {
roots: new_roots,
active_root: new_active,
tab_pages: std::collections::HashMap::new(),
nav: crate::logic::NavState::default(),
styles: new_styles,
style_map: new_style_map,
default_style: new_default_style,
tex_reg: Some(tex_reg),
device: Some(device_arc),
queue: Some(queue_arc),
renderer: Some(renderer),
background: None,
clear_color: None,
pending_actions: Vec::new(),
tx,
rx,
ron_path: path.to_string(),
debug: false,
headless_accessible: false,
osk: crate::keyboard::OskState::default(),
};
PaneOverlay {
persistent,
input: Input::new(),
scene: Scene::new(),
last_time: Instant::now(),
gilrs: gilrs.or_else(|| gilrs::Gilrs::new().ok()),
}
}
fn active_root_mut(
persistent: &mut crate::threader::Persistent,
) -> Option<&mut crate::logic::Root> {
persistent
.active_root
.as_deref()
.and_then(|n| persistent.roots.get_mut(n))
}
fn style_id_in(
persistent: &crate::threader::Persistent,
name: &str,
) -> Option<crate::styles::StyleId> {
persistent.style_map.get(name).map(|&(id, _)| id)
}
fn default_style_in(persistent: &crate::threader::Persistent) -> Option<&str> {
let id = persistent.default_style?;
persistent
.style_map
.iter()
.find(|(_, v)| v.0 == id)
.map(|(name, _)| name.as_str())
}
fn set_default_style_in(persistent: &mut crate::threader::Persistent, name: &str) -> bool {
match persistent.style_map.get(name).map(|&(id, _)| id) {
Some(id) => {
persistent.default_style = Some(id);
true
}
None => false,
}
}
fn create_into(
persistent: &mut crate::threader::Persistent,
root: &str,
builder: impl crate::build::WidgetBuilder,
) -> bool {
let (item, state) = builder.build(persistent.default_style);
let id = item.id().to_string();
for r in persistent.roots.values() {
if r.items.iter().any(|(i, _)| i.id() == id) {
eprintln!("[pane_ui] create: id '{id}' already exists");
return false;
}
}
let Some(r) = persistent.roots.get_mut(root) else {
eprintln!("[pane_ui] create: root '{root}' not found");
return false;
};
r.items.push((item, state));
true
}
fn destroy_into(persistent: &mut crate::threader::Persistent, id: &str) -> bool {
for r in persistent.roots.values_mut() {
if let Some(idx) = r.items.iter().position(|(item, _)| item.id() == id) {
r.items.remove(idx);
if persistent.nav.focused_id.as_deref() == Some(id) {
persistent.nav = crate::logic::NavState::default();
}
return true;
}
}
false
}
fn create_root_into(persistent: &mut crate::threader::Persistent, name: impl Into<String>) {
let name = name.into();
persistent
.roots
.entry(name)
.or_insert_with(crate::logic::Root::new);
}
fn read_into<'a>(
persistent: &'a crate::threader::Persistent,
id: &str,
) -> Option<(&'a crate::items::UiItem, crate::widgets::WidgetState)> {
for root in persistent.roots.values() {
for (item, state) in &root.items {
if item.id() == id {
return Some((item, state.visual()));
}
}
}
None
}
fn write_into(persistent: &mut crate::threader::Persistent, id: &str, value: &WriteValue) {
use crate::items::UiItem;
use crate::widgets::ItemState;
for root in persistent.roots.values_mut() {
for (item, state) in &mut root.items {
if item.id() != id {
continue;
}
match (item, state, value) {
(UiItem::Slider(s), ItemState::Slider(st), WriteValue::Slider(v)) => {
st.value = v.clamp(s.min, s.max);
}
(UiItem::Toggle(_), ItemState::Toggle(st), WriteValue::Toggle(v)) => {
st.checked = *v;
}
(UiItem::TextBox(tb), ItemState::TextBox(st), WriteValue::Text(v)) => {
st.text = tb
.max_len
.map_or_else(|| v.clone(), |limit| v.chars().take(limit).collect());
st.cursor_pos = st.text.chars().count();
}
(UiItem::Dropdown(dd), ItemState::Dropdown(st), WriteValue::Selected(i)) => {
st.selected = (*i).min(dd.items.len().saturating_sub(1));
}
(UiItem::RadioGroup(rg), ItemState::RadioGroup(st), WriteValue::Selected(i)) => {
st.selected = (*i).min(rg.items.len().saturating_sub(1));
}
_ => {
eprintln!("[pane_ui] write('{id}'): value type does not match widget type");
}
}
return;
}
}
eprintln!("[pane_ui] write('{id}'): no widget found");
}
pub(crate) fn push_toast_into(
persistent: &mut crate::threader::Persistent,
message: String,
duration: f32,
x: f32,
y: f32,
width: f32,
height: f32,
) {
if let Some(name) = persistent.active_root.as_deref()
&& let Some(root) = persistent.roots.get_mut(name)
{
let id = format!("__toast_{}", root.items.len());
root.items.push((
crate::items::UiItem::Toast(crate::items::Toast {
id,
x,
y,
width,
height,
message,
duration,
shape: persistent.default_style,
}),
crate::widgets::ItemState::Toast(crate::widgets::ToastState::new(duration)),
));
}
}
fn actor_move_to_into(
persistent: &mut crate::threader::Persistent,
id: &str,
x: f32,
y: f32,
speed: f32,
) {
if let Some(r) = active_root_mut(persistent) {
crate::query::actor_move_to_in(&mut r.items, id, x, y, speed);
}
}
fn actor_follow_cursor_into(
persistent: &mut crate::threader::Persistent,
id: &str,
speed: f32,
trail: f32,
) {
if let Some(r) = active_root_mut(persistent) {
crate::query::actor_follow_cursor_in(&mut r.items, id, speed, trail);
}
}
fn actor_reset_into(persistent: &mut crate::threader::Persistent, id: &str) {
if let Some(r) = active_root_mut(persistent) {
crate::query::actor_reset_in(&mut r.items, id);
}
}
fn actor_set_pos_into(persistent: &mut crate::threader::Persistent, id: &str, x: f32, y: f32) {
if let Some(r) = active_root_mut(persistent) {
crate::query::actor_set_pos_in(&mut r.items, id, x, y);
}
}
impl PaneOverlay {
pub fn handle_event(&mut self, event: &WindowEvent, pw: f32, ph: f32) {
self.input.handle_event(event, pw, ph);
}
pub fn handle_gamepad_event(&mut self, event: gilrs::Event) {
self.input.handle_gamepad_event(event);
}
pub fn disable_auto_gamepad(&mut self) {
self.gilrs = None;
}
pub fn draw(
&mut self,
encoder: &mut wgpu::CommandEncoder,
view: &wgpu::TextureView,
pw: f32,
ph: f32,
) -> Vec<PaneAction> {
if let Some(gilrs) = self.gilrs.as_mut() {
while let Some(ev) = gilrs.next_event() {
self.input.handle_gamepad_event(ev);
}
}
self.persistent.pending_actions.clear();
let mut frame = crate::threader::Frame {
dt: 0.0,
last_tick: self.last_time,
pw,
ph,
input: std::mem::take(&mut self.input),
scene: std::mem::take(&mut self.scene),
};
{
let mut ctx = crate::threader::FrameCtx {
persistent: &mut self.persistent,
frame: &mut frame,
standalone_pane: None,
};
crate::order::run_frame(&mut ctx, Some(encoder), Some(view));
}
self.last_time = frame.last_tick;
self.input = frame.input;
self.scene = frame.scene;
std::mem::take(&mut self.persistent.pending_actions)
}
pub fn read(&self, id: &str) -> Option<(&crate::items::UiItem, crate::widgets::WidgetState)> {
read_into(&self.persistent, id)
}
pub fn write(&mut self, id: &str, value: &WriteValue) {
write_into(&mut self.persistent, id, value);
}
pub fn create(&mut self, root: &str, builder: impl crate::build::WidgetBuilder) -> bool {
create_into(&mut self.persistent, root, builder)
}
pub fn destroy(&mut self, id: &str) -> bool {
destroy_into(&mut self.persistent, id)
}
pub fn create_root(&mut self, name: impl Into<String>) {
create_root_into(&mut self.persistent, name);
}
#[must_use]
pub fn style_id(&self, name: &str) -> Option<crate::styles::StyleId> {
style_id_in(&self.persistent, name)
}
#[must_use]
pub fn default_style(&self) -> Option<&str> {
default_style_in(&self.persistent)
}
pub fn set_default_style(&mut self, name: &str) -> bool {
set_default_style_in(&mut self.persistent, name)
}
pub const fn set_debug(&mut self, on: bool) {
self.persistent.debug = on;
}
pub fn push_toast(
&mut self,
message: impl Into<String>,
duration: f32,
x: f32,
y: f32,
width: f32,
height: f32,
) {
push_toast_into(
&mut self.persistent,
message.into(),
duration,
x,
y,
width,
height,
);
}
pub fn actor_move_to(&mut self, id: &str, x: f32, y: f32, speed: f32) {
actor_move_to_into(&mut self.persistent, id, x, y, speed);
}
pub fn actor_follow_cursor(&mut self, id: &str, speed: f32, trail: f32) {
actor_follow_cursor_into(&mut self.persistent, id, speed, trail);
}
pub fn actor_reset(&mut self, id: &str) {
actor_reset_into(&mut self.persistent, id);
}
pub fn actor_set_pos(&mut self, id: &str, x: f32, y: f32) {
actor_set_pos_into(&mut self.persistent, id, x, y);
}
}
pub struct PaneHeadless {
persistent: crate::threader::Persistent,
input: Input,
scene: Scene,
}
#[must_use]
pub fn headless(path: &str) -> PaneHeadless {
let menu = load_menu(path);
let (tx, rx) = mpsc::channel::<UiMsg>();
let dummy_shader = ShaderId::new(0);
let style_map: StyleMap = crate::styles_reg::BUILTIN_STYLE_NAMES
.iter()
.enumerate()
.map(|(i, name)| {
(
name.to_string(),
(crate::styles::StyleId::new(i), dummy_shader),
)
})
.collect();
let (roots, active_root) = build_logic(&menu, &style_map, dummy_shader, &mut |_, _| None);
let default_style = menu
.default_style
.as_deref()
.and_then(|name| style_map.get(name).map(|&(id, _)| id));
let persistent = crate::threader::Persistent {
roots,
active_root,
tab_pages: std::collections::HashMap::new(),
nav: crate::logic::NavState::default(),
styles: StyleRegistry::new(),
style_map,
default_style,
tex_reg: None,
device: None,
queue: None,
renderer: None,
background: None,
clear_color: None,
pending_actions: Vec::new(),
tx,
rx,
ron_path: path.to_string(),
debug: false,
headless_accessible: menu.headless_accessible,
osk: crate::keyboard::OskState::default(),
};
PaneHeadless {
persistent,
input: Input::new(),
scene: Scene::new(),
}
}
impl PaneHeadless {
pub fn update(&mut self, dt: f32) -> Vec<PaneAction> {
self.persistent.pending_actions.clear();
let last_tick = std::time::Instant::now()
.checked_sub(std::time::Duration::from_secs_f32(dt))
.unwrap_or_else(std::time::Instant::now);
let mut frame = crate::threader::Frame {
dt: 0.0,
last_tick,
pw: 0.0,
ph: 0.0,
input: std::mem::take(&mut self.input),
scene: std::mem::take(&mut self.scene),
};
{
let mut ctx = crate::threader::FrameCtx {
persistent: &mut self.persistent,
frame: &mut frame,
standalone_pane: None,
};
crate::order::run_frame(&mut ctx, None, None);
}
self.input = frame.input;
self.scene = frame.scene;
std::mem::take(&mut self.persistent.pending_actions)
}
pub fn press(&mut self, id: &str) {
if !self.persistent.headless_accessible {
eprintln!("[pane_ui] press('{id}') ignored — headless_accessible is false");
return;
}
let tx = self.persistent.tx.clone();
let debug = self.persistent.debug;
let Some(name) = self.persistent.active_root.clone() else {
eprintln!("[pane_ui] press('{id}'): no active root");
return;
};
if let Some(root) = self.persistent.roots.get_mut(&name) {
let found = root.items.iter_mut().any(|(item, state)| {
use crate::items::UiItem;
use crate::widgets::ItemState;
if let (UiItem::Toggle(t), ItemState::Toggle(s)) = (&*item, state)
&& t.id == id
{
{
s.checked = !s.checked;
match &t.action {
crate::loader::ToggleAction::Custom(tag) => {
let _ =
tx.send(crate::loader::UiMsg::Toggle(tag.clone(), s.checked));
}
crate::loader::ToggleAction::Print => {
println!("{}: {}", t.id, s.checked);
}
}
return true;
}
}
crate::logic::press_in_item(&tx, item, id, debug)
});
if !found {
eprintln!("[pane_ui] press('{id}'): no button found");
}
}
}
pub fn active_root(&self) -> Option<&str> {
self.persistent.active_root.as_deref()
}
pub fn read(&self, id: &str) -> Option<(&crate::items::UiItem, crate::widgets::WidgetState)> {
read_into(&self.persistent, id)
}
pub fn write(&mut self, id: &str, value: &WriteValue) {
write_into(&mut self.persistent, id, value);
}
pub fn create(&mut self, root: &str, builder: impl crate::build::WidgetBuilder) -> bool {
create_into(&mut self.persistent, root, builder)
}
pub fn destroy(&mut self, id: &str) -> bool {
destroy_into(&mut self.persistent, id)
}
pub fn create_root(&mut self, name: impl Into<String>) {
create_root_into(&mut self.persistent, name);
}
#[must_use]
pub fn style_id(&self, name: &str) -> Option<crate::styles::StyleId> {
style_id_in(&self.persistent, name)
}
#[must_use]
pub fn default_style(&self) -> Option<&str> {
default_style_in(&self.persistent)
}
pub fn set_default_style(&mut self, name: &str) -> bool {
set_default_style_in(&mut self.persistent, name)
}
pub const fn set_debug(&mut self, on: bool) {
self.persistent.debug = on;
}
pub fn push_toast(
&mut self,
message: impl Into<String>,
duration: f32,
x: f32,
y: f32,
width: f32,
height: f32,
) {
push_toast_into(
&mut self.persistent,
message.into(),
duration,
x,
y,
width,
height,
);
}
pub fn actor_move_to(&mut self, id: &str, x: f32, y: f32, speed: f32) {
actor_move_to_into(&mut self.persistent, id, x, y, speed);
}
pub fn actor_follow_cursor(&mut self, id: &str, speed: f32, trail: f32) {
actor_follow_cursor_into(&mut self.persistent, id, speed, trail);
}
pub fn actor_reset(&mut self, id: &str) {
actor_reset_into(&mut self.persistent, id);
}
pub fn actor_set_pos(&mut self, id: &str, x: f32, y: f32) {
actor_set_pos_into(&mut self.persistent, id, x, y);
}
}