use std::time::Instant;
use std::{cell::RefCell, pin::Pin, rc::Rc};
use baseview::dpi::LogicalSize;
use baseview::{EventStatus, WindowContext, WindowHandler};
use iced_core::{mouse, theme};
use iced_program::Program;
use iced_runtime::Task;
pub use iced_runtime::core::window::Id;
use iced_runtime::futures::futures::{
self,
channel::mpsc::{self, SendError},
};
pub use iced_runtime::window::{close_events, close_requests, events, open_events, resize_events};
use iced_widget::core::Size;
#[cfg(all(feature = "log", not(feature = "tracing")))]
use log::error;
#[cfg(feature = "tracing")]
use tracing::error;
use crate::graphics::Compositor;
use crate::shell::RuntimeEvent;
pub(super) mod state;
pub(super) struct InstanceWindow<P, C>
where
P: Program,
C: Compositor<Renderer = P::Renderer>,
P::Theme: theme::Base,
{
pub state: state::State<P>,
pub mouse_interaction: mouse::Interaction,
pub surface: C::Surface,
pub surface_version: u64,
pub compositor: C,
pub renderer: P::Renderer,
pub queue: WindowQueue,
pub window: WindowContext,
pub id: iced_core::window::Id,
#[allow(unused)]
pub always_redraw: bool,
pub ignore_non_modifier_keys: bool,
pub redraw_requested: bool,
pub redraw_at: Option<Instant>,
}
pub(super) struct IcedWindowHandler<Message: 'static + Send> {
inner: RefCell<IcedWindowHandlerInner<Message>>,
window_commands_rx: RefCell<mpsc::UnboundedReceiver<WindowCommand>>,
runtime_state: RefCell<RuntimeState>,
window: WindowContext,
event_status: Rc<RefCell<EventStatus>>,
}
struct IcedWindowHandlerInner<Message: 'static + Send> {
runtime_event_tx: mpsc::UnboundedSender<RuntimeEvent<Message>>,
action_rx: mpsc::UnboundedReceiver<iced_runtime::Action<Message>>,
processed_close_signal: bool,
}
struct RuntimeState {
instance: Pin<Box<dyn futures::Future<Output = ()>>>,
runtime_context: futures::task::Context<'static>,
}
impl<Message: 'static + Send> IcedWindowHandler<Message> {
pub fn new(
window: WindowContext,
instance: Pin<Box<dyn futures::Future<Output = ()>>>,
runtime_context: futures::task::Context<'static>,
event_status: Rc<RefCell<EventStatus>>,
window_commands_rx: mpsc::UnboundedReceiver<WindowCommand>,
runtime_event_tx: mpsc::UnboundedSender<RuntimeEvent<Message>>,
action_rx: mpsc::UnboundedReceiver<iced_runtime::Action<Message>>,
) -> Self {
Self {
inner: RefCell::new(IcedWindowHandlerInner {
runtime_event_tx,
action_rx,
processed_close_signal: false,
}),
window_commands_rx: RefCell::new(window_commands_rx),
runtime_state: RefCell::new(RuntimeState {
instance,
runtime_context,
}),
window,
event_status,
}
}
fn drain_window_commands(&self) {
while let Ok(cmd) = { self.window_commands_rx.borrow_mut().try_recv() } {
match cmd {
WindowCommand::CloseWindow => {
self.window.request_close();
}
WindowCommand::ResizeWindow(size) => {
self.window
.resize(baseview::dpi::Size::Logical(LogicalSize {
width: size.width as f64,
height: size.height as f64,
}));
}
WindowCommand::Focus => {
self.window.focus();
}
WindowCommand::SetCursorIcon(cursor) => {
self.window.set_mouse_cursor(cursor);
}
}
}
}
fn poll_runtime(&self) {
let mut runtime = self.runtime_state.borrow_mut();
let RuntimeState {
instance,
runtime_context,
} = &mut *runtime;
let _ = instance.as_mut().poll(runtime_context);
}
}
impl<Message: 'static + Send> WindowHandler for IcedWindowHandler<Message> {
fn on_frame(&self) {
{
let mut inner = self.inner.borrow_mut();
if inner.processed_close_signal {
return;
}
inner
.runtime_event_tx
.start_send(RuntimeEvent::Poll)
.unwrap();
}
self.poll_runtime();
{
let mut inner = self.inner.borrow_mut();
while let Ok(message) = inner.action_rx.try_recv() {
inner
.runtime_event_tx
.start_send(RuntimeEvent::UserEvent(message))
.unwrap();
}
inner
.runtime_event_tx
.start_send(RuntimeEvent::OnFrame)
.unwrap();
}
self.poll_runtime();
self.drain_window_commands();
}
fn on_event(&self, event: baseview::Event) -> EventStatus {
if matches!(
event,
baseview::Event::Mouse(baseview::MouseEvent::ButtonPressed { .. })
) && !self.window.has_focus()
{
self.window.focus();
}
let ignore_event;
{
let mut inner = self.inner.borrow_mut();
if inner.processed_close_signal {
return EventStatus::Ignored;
}
ignore_event = if requests_exit(&event) {
inner.processed_close_signal = true;
inner
.runtime_event_tx
.start_send(RuntimeEvent::WillClose)
.expect("Send event");
true
} else {
inner
.runtime_event_tx
.start_send(RuntimeEvent::Baseview((event, true)))
.expect("Send event");
false
};
}
self.poll_runtime();
if !self.inner.borrow().processed_close_signal {
self.drain_window_commands();
}
if ignore_event {
EventStatus::Ignored
} else {
*self.event_status.borrow()
}
}
fn resized(&self, new_size: baseview::WindowSize) {
{
let mut inner = self.inner.borrow_mut();
if inner.processed_close_signal {
return;
}
inner
.runtime_event_tx
.start_send(RuntimeEvent::Resized(new_size))
.expect("Send event");
}
self.poll_runtime();
if !self.inner.borrow().processed_close_signal {
self.drain_window_commands();
}
}
}
pub fn close<T>() -> Task<T> {
iced_runtime::window::close(Id::unique())
}
pub fn resize<T>(new_size: Size) -> Task<T> {
iced_runtime::window::resize(Id::unique(), new_size)
}
pub fn gain_focus<T>() -> Task<T> {
iced_runtime::window::gain_focus(Id::unique())
}
pub fn requests_exit(event: &baseview::Event) -> bool {
match event {
baseview::Event::Window(baseview::WindowEvent::WillClose) => true,
#[cfg(target_os = "macos")]
baseview::Event::Keyboard(event) => {
if event.code == keyboard_types::Code::KeyQ
&& event.modifiers == keyboard_types::Modifiers::META
&& event.state == keyboard_types::KeyState::Down
{
return true;
}
false
}
_ => false,
}
}
#[allow(missing_debug_implementations)]
pub struct IcedWindowHandle<Message: 'static + Send> {
bv_handle: baseview::WindowHandle,
tx: mpsc::UnboundedSender<RuntimeEvent<Message>>,
}
impl<Message: 'static + Send> IcedWindowHandle<Message> {
pub(crate) fn new(
bv_handle: baseview::WindowHandle,
tx: mpsc::UnboundedSender<RuntimeEvent<Message>>,
) -> Self {
Self { bv_handle, tx }
}
pub fn send_baseview_event(&mut self, event: baseview::Event) -> Result<(), SendError> {
self.tx.start_send(RuntimeEvent::Baseview((event, false)))
}
pub fn send_message(&mut self, msg: Message) -> Result<(), SendError> {
self.tx
.start_send(RuntimeEvent::UserEvent(iced_runtime::Action::Output(msg)))
}
pub fn is_open(&self) -> bool {
self.bv_handle.is_open()
}
}
impl<Message: 'static + Send> Drop for IcedWindowHandle<Message> {
fn drop(&mut self) {
self.bv_handle.close();
let _ = self.tx.start_send(RuntimeEvent::UserCloseRequested);
}
}
pub enum WindowCommand {
CloseWindow,
ResizeWindow(crate::core::Size),
Focus,
SetCursorIcon(baseview::MouseCursor),
}
pub struct WindowQueue {
tx: mpsc::UnboundedSender<WindowCommand>,
}
impl WindowQueue {
pub fn new() -> (Self, mpsc::UnboundedReceiver<WindowCommand>) {
let (tx, rx) = mpsc::unbounded();
(Self { tx }, rx)
}
pub fn send(&mut self, command: WindowCommand) {
if let Err(e) = self.tx.start_send(command) {
#[cfg(any(feature = "tracing", feature = "log"))]
error!("Failed to send command to window: {}", e);
}
}
}