use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use iced::{Element, Subscription, Task};
use oxiui_iced::{apply_message, IcedConfig, IcedUiCtx, Message, WidgetState};
use crate::null_ctx::NullUiCtx;
use crate::runner::{LifecycleEvent, LifecycleSnapshot, LifecycleTracker};
use crate::{ContentFn, HookFn, Plugin};
#[derive(Debug, Clone)]
pub enum AppMessage {
Widget(Message),
Lifecycle(LifecycleMsg),
}
#[derive(Debug, Clone)]
pub enum LifecycleMsg {
Resized(f32, f32),
Focus(bool),
Close(iced::window::Id),
}
pub struct OxiIcedState {
pub title: String,
pub content: RefCell<Option<ContentFn>>,
pub pending_clicks: RefCell<HashSet<usize>>,
pub widget_state: RefCell<HashMap<usize, WidgetState>>,
pub on_init: RefCell<Vec<HookFn>>,
pub on_frame: RefCell<Vec<HookFn>>,
pub plugins: RefCell<Vec<Box<dyn Plugin>>>,
pub initialised: Cell<bool>,
pub on_close: RefCell<Vec<HookFn>>,
pub on_resize: RefCell<Vec<HookFn>>,
pub on_focus: RefCell<Vec<HookFn>>,
pub tracker: RefCell<LifecycleTracker>,
}
impl OxiIcedState {
pub fn empty() -> Self {
Self {
title: String::new(),
content: RefCell::new(None),
pending_clicks: RefCell::new(HashSet::new()),
widget_state: RefCell::new(HashMap::new()),
on_init: RefCell::new(Vec::new()),
on_frame: RefCell::new(Vec::new()),
plugins: RefCell::new(Vec::new()),
initialised: Cell::new(false),
on_close: RefCell::new(Vec::new()),
on_resize: RefCell::new(Vec::new()),
on_focus: RefCell::new(Vec::new()),
tracker: RefCell::new(LifecycleTracker::default()),
}
}
fn fire(&self, hooks: &RefCell<Vec<HookFn>>) {
let mut null = NullUiCtx;
if let Ok(mut hooks) = hooks.try_borrow_mut() {
for hook in hooks.iter_mut() {
hook(&mut null);
}
}
}
}
pub fn update(state: &mut OxiIcedState, msg: AppMessage) -> Task<AppMessage> {
match msg {
AppMessage::Widget(m) => {
let mut clicks = state.pending_clicks.borrow_mut();
let mut widget_state = state.widget_state.borrow_mut();
apply_message(&mut widget_state, &mut clicks, &m);
Task::none()
}
AppMessage::Lifecycle(LifecycleMsg::Resized(w, h)) => {
let events = state.tracker.borrow_mut().observe(LifecycleSnapshot {
size: Some((w, h)),
focused: None,
close_requested: false,
});
if events
.iter()
.any(|e| matches!(e, LifecycleEvent::Resized(..)))
{
state.fire(&state.on_resize);
}
Task::none()
}
AppMessage::Lifecycle(LifecycleMsg::Focus(f)) => {
let events = state.tracker.borrow_mut().observe(LifecycleSnapshot {
size: None,
focused: Some(f),
close_requested: false,
});
if events.iter().any(|e| matches!(e, LifecycleEvent::Focus(_))) {
state.fire(&state.on_focus);
}
Task::none()
}
AppMessage::Lifecycle(LifecycleMsg::Close(id)) => {
let events = state.tracker.borrow_mut().observe(LifecycleSnapshot {
size: None,
focused: None,
close_requested: true,
});
if events.iter().any(|e| matches!(e, LifecycleEvent::Close)) {
state.fire(&state.on_close);
}
iced::window::close(id)
}
}
}
pub fn view(state: &OxiIcedState) -> Element<'_, AppMessage> {
let clicks = {
let mut guard = state.pending_clicks.borrow_mut();
std::mem::take(&mut *guard)
};
let widget_state = state.widget_state.borrow().clone();
let config = IcedConfig {
pending_clicks: clicks,
state: widget_state,
spacing: 8.0,
padding: 0.0,
title: state.title.clone(),
spec_capacity_hint: 0,
};
let mut ctx = IcedUiCtx::new(config);
if !state.initialised.get() {
state.initialised.set(true);
if let Ok(mut hooks) = state.on_init.try_borrow_mut() {
for hook in hooks.iter_mut() {
hook(&mut ctx);
}
}
if let Ok(mut plugins) = state.plugins.try_borrow_mut() {
for plugin in plugins.iter_mut() {
plugin.init(&mut ctx);
}
}
}
if let Ok(mut content_guard) = state.content.try_borrow_mut() {
if let Some(ref mut f) = *content_guard {
f(&mut ctx);
}
}
if let Ok(mut hooks) = state.on_frame.try_borrow_mut() {
for hook in hooks.iter_mut() {
hook(&mut ctx);
}
}
if let Ok(mut plugins) = state.plugins.try_borrow_mut() {
for plugin in plugins.iter_mut() {
plugin.update(&mut ctx);
}
}
let elem: Element<'static, Message> = ctx.into_iced_element();
elem.map(AppMessage::Widget)
}
fn lifecycle_filter(
event: iced::Event,
_status: iced::event::Status,
id: iced::window::Id,
) -> Option<AppMessage> {
use iced::window::Event as WindowEvent;
match event {
iced::Event::Window(WindowEvent::Resized(size)) => Some(AppMessage::Lifecycle(
LifecycleMsg::Resized(size.width, size.height),
)),
iced::Event::Window(WindowEvent::Focused) => {
Some(AppMessage::Lifecycle(LifecycleMsg::Focus(true)))
}
iced::Event::Window(WindowEvent::Unfocused) => {
Some(AppMessage::Lifecycle(LifecycleMsg::Focus(false)))
}
iced::Event::Window(WindowEvent::CloseRequested) => {
Some(AppMessage::Lifecycle(LifecycleMsg::Close(id)))
}
_ => None,
}
}
fn subscription(_state: &OxiIcedState) -> Subscription<AppMessage> {
iced::event::listen_with(lifecycle_filter)
}
pub fn run(state: OxiIcedState, iced_theme: iced::Theme, width: f32, height: f32) -> iced::Result {
let boot_state = std::sync::Mutex::new(Some(state));
let boot = move || {
boot_state
.lock()
.ok()
.and_then(|mut g| g.take())
.unwrap_or_else(OxiIcedState::empty)
};
let title_fn = move |s: &OxiIcedState| s.title.clone();
let theme_fn = move |_: &OxiIcedState| iced_theme.clone();
iced::application(boot, update, view)
.title(title_fn)
.theme(theme_fn)
.subscription(subscription)
.exit_on_close_request(false)
.window_size(iced::Size::new(width, height))
.run()
}