use super::{GraphicalShell, Platform, ProxyAction, Result, SharedState};
use crate::config::Options;
use crate::draw::{DrawImpl, DrawShared, DrawSharedImpl};
use crate::event;
use crate::theme::{self, Theme, ThemeConfig};
use crate::util::warn_about_error;
use crate::{AppData, Window, WindowId};
use std::cell::RefCell;
use std::rc::Rc;
use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopProxy};
pub struct Shell<Data: AppData, G: GraphicalShell, T: Theme<G::Shared>> {
el: EventLoop<ProxyAction>,
windows: Vec<super::Window<Data, G::Surface, T>>,
shared: SharedState<Data, G::Surface, T>,
}
pub trait ShellAssoc {
type DrawShared: DrawSharedImpl;
type Draw: DrawImpl;
}
impl<A: AppData, G: GraphicalShell, T> ShellAssoc for Shell<A, G, T>
where
T: Theme<G::Shared> + 'static,
T::Window: theme::Window,
{
type DrawShared = G::Shared;
type Draw = G::Window;
}
impl<Data: AppData, G, T> Shell<Data, G, T>
where
G: GraphicalShell + Default,
T: Theme<G::Shared> + 'static,
T::Window: theme::Window,
{
#[inline]
pub fn new(data: Data, theme: T) -> Result<Self> {
Self::new_custom(data, G::default(), theme, Options::from_env())
}
}
impl<Data: AppData, G: GraphicalShell, T> Shell<Data, G, T>
where
T: Theme<G::Shared> + 'static,
T::Window: theme::Window,
{
#[inline]
pub fn new_custom(
data: Data,
graphical_shell: impl Into<G>,
mut theme: T,
options: Options,
) -> Result<Self> {
options.init_theme_config(&mut theme)?;
let config = match options.read_config() {
Ok(config) => config,
Err(error) => {
warn_about_error("Shell::new_custom: failed to read config", &error);
Default::default()
}
};
let config = Rc::new(RefCell::new(config));
Self::new_custom_config(data, graphical_shell, theme, options, config)
}
#[inline]
pub fn new_custom_config(
data: Data,
graphical_shell: impl Into<G>,
theme: T,
options: Options,
config: Rc<RefCell<event::Config>>,
) -> Result<Self> {
let el = EventLoopBuilder::with_user_event().build()?;
let windows = vec![];
let mut draw_shared = graphical_shell.into().build()?;
draw_shared.set_raster_config(theme.config().raster());
let pw = PlatformWrapper(&el);
let shared = SharedState::new(data, pw, draw_shared, theme, options, config)?;
Ok(Shell {
el,
windows,
shared,
})
}
#[inline]
pub fn draw_shared(&mut self) -> &mut dyn DrawShared {
&mut self.shared.shell.draw
}
#[inline]
pub fn theme(&self) -> &T {
&self.shared.shell.theme
}
#[inline]
pub fn theme_mut(&mut self) -> &mut T {
&mut self.shared.shell.theme
}
#[inline]
pub fn add(&mut self, window: Window<Data>) -> WindowId {
let id = self.shared.shell.next_window_id();
let win = super::Window::new(&self.shared, id, window);
self.windows.push(win);
id
}
#[inline]
pub fn with(mut self, window: Window<Data>) -> Self {
let _ = self.add(window);
self
}
pub fn create_proxy(&self) -> Proxy {
Proxy(self.el.create_proxy())
}
#[inline]
pub fn run(self) -> Result<()> {
let mut el = super::EventLoop::new(self.windows, self.shared);
self.el
.run(move |event, elwt, control_flow| el.handle(event, elwt, control_flow))?;
Ok(())
}
}
pub(super) struct PlatformWrapper<'a>(&'a EventLoop<ProxyAction>);
impl<'a> PlatformWrapper<'a> {
#[allow(clippy::needless_return)]
pub(super) fn platform(&self) -> Platform {
#[cfg(target_os = "windows")]
return Platform::Windows;
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
cfg_if::cfg_if! {
if #[cfg(all(feature = "wayland", feature = "x11"))] {
use winit::platform::wayland::EventLoopWindowTargetExtWayland;
return if self.0.is_wayland() {
Platform::Wayland
} else {
Platform::X11
};
} else if #[cfg(feature = "wayland")] {
return Platform::Wayland;
} else if #[cfg(feature = "x11")] {
return Platform::X11;
} else {
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
}
}
}
#[cfg(target_os = "macos")]
return Platform::MacOS;
#[cfg(target_os = "android")]
return Platform::Android;
#[cfg(target_os = "ios")]
return Platform::IOS;
#[cfg(target_arch = "wasm32")]
return Platform::Web;
}
pub(super) fn guess_scale_factor(&self) -> f64 {
if let Some(mon) = self.0.primary_monitor() {
return mon.scale_factor();
}
if let Some(mon) = self.0.available_monitors().next() {
return mon.scale_factor();
}
1.0
}
pub(super) fn create_waker(&self) -> std::task::Waker {
use std::sync::{Arc, Mutex};
use std::task::{RawWaker, RawWakerVTable, Waker};
type Data = Mutex<Proxy>;
let proxy = Proxy(self.0.create_proxy());
let a: Arc<Data> = Arc::new(Mutex::new(proxy));
let data = Arc::into_raw(a);
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
unsafe fn clone(data: *const ()) -> RawWaker {
let a = Arc::from_raw(data as *const Data);
let c = Arc::into_raw(a.clone());
let _do_not_drop = Arc::into_raw(a);
RawWaker::new(c as *const (), &VTABLE)
}
unsafe fn wake(data: *const ()) {
let a = Arc::from_raw(data as *const Data);
a.lock().unwrap().wake_async();
}
unsafe fn wake_by_ref(data: *const ()) {
let a = Arc::from_raw(data as *const Data);
a.lock().unwrap().wake_async();
let _do_not_drop = Arc::into_raw(a);
}
unsafe fn drop(data: *const ()) {
let _ = Arc::from_raw(data as *const Data);
}
let raw_waker = RawWaker::new(data as *const (), &VTABLE);
unsafe { Waker::from_raw(raw_waker) }
}
}
pub struct Proxy(EventLoopProxy<ProxyAction>);
pub struct ClosedError;
impl Proxy {
pub fn close(&self, id: WindowId) -> std::result::Result<(), ClosedError> {
self.0
.send_event(ProxyAction::Close(id))
.map_err(|_| ClosedError)
}
pub fn close_all(&self) -> std::result::Result<(), ClosedError> {
self.0
.send_event(ProxyAction::CloseAll)
.map_err(|_| ClosedError)
}
pub fn push<M: std::fmt::Debug + Send + 'static>(
&mut self,
msg: M,
) -> std::result::Result<(), ClosedError> {
self.0
.send_event(ProxyAction::Message(kas::erased::SendErased::new(msg)))
.map_err(|_| ClosedError)
}
fn wake_async(&self) {
let _ = self.0.send_event(ProxyAction::WakeAsync);
}
}