use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use std::time::{Duration, Instant};
use inset_embedder::{
Brightness, Clipboard, Dispatcher, FontSource, ImageCodec, ImageCodecFuture, ImageDecodeError,
ImageFrame, ImageFrameFuture, ImageRepetition, MouseCursor, Platform, PopupMenuEntry,
PopupMenus, SystemFontSource, SystemMouseCursorKind, TargetPlatform, ViewFocusDirection,
ViewFocusEvent, ViewFocusState, ViewId, ViewRef, WindowingOwner,
};
use winit::event_loop::EventLoopProxy;
use crate::os;
use crate::window::HostEvent;
use crate::windows::WinitWindowing;
pub struct WinitPlatform {
pub(crate) wake_due: Cell<Option<Instant>>,
pub(crate) frame_requested: Cell<bool>,
views: RefCell<HashMap<ViewId, ViewRef>>,
implicit_view: Option<ViewId>,
pub(crate) proxy: EventLoopProxy<HostEvent>,
pub(crate) windowing: Rc<WinitWindowing>,
origin: Instant,
pub(crate) brightness: Cell<Brightness>,
pub(crate) cursor_request: Cell<Option<SystemMouseCursorKind>>,
pub(crate) focus_requests: RefCell<Vec<ViewFocusEvent>>,
clipboard: RefCell<Option<arboard::Clipboard>>,
pub(crate) image_loader: RefCell<Option<valo_codec::ImageLoader>>,
pub(crate) images: RefCell<Option<valo::ImageContext>>,
}
impl WinitPlatform {
pub(crate) fn new(
proxy: EventLoopProxy<HostEvent>,
implicit_view: Option<ViewId>,
) -> WinitPlatform {
WinitPlatform {
wake_due: Cell::new(None),
frame_requested: Cell::new(false),
views: RefCell::new(HashMap::new()),
implicit_view,
windowing: Rc::new(WinitWindowing::new(proxy.clone())),
proxy,
origin: Instant::now(),
brightness: Cell::new(Brightness::Light),
cursor_request: Cell::new(None),
focus_requests: RefCell::new(Vec::new()),
image_loader: RefCell::new(None),
images: RefCell::new(None),
clipboard: RefCell::new(None),
}
}
pub fn image_context(&self) -> Option<valo::ImageContext> {
self.images.borrow().clone()
}
fn with_clipboard<T>(&self, f: impl FnOnce(&mut arboard::Clipboard) -> T) -> Option<T> {
let mut slot = self.clipboard.borrow_mut();
if slot.is_none() {
*slot = arboard::Clipboard::new().ok();
}
slot.as_mut().map(f)
}
fn wake_event_loop(&self) {
let _ = self.proxy.send_event(HostEvent::Request);
}
pub(crate) fn elapsed(&self) -> Duration {
self.now().saturating_duration_since(self.origin)
}
pub(crate) fn add_view(&self, view: ViewRef) {
let id = view.id();
assert!(
self.views.borrow_mut().insert(id, view).is_none(),
"duplicate view id {id:?}"
);
}
pub(crate) fn remove_view(&self, id: ViewId) {
assert!(
self.views.borrow_mut().remove(&id).is_some(),
"unknown view id {id:?}"
);
}
}
impl Platform for WinitPlatform {
fn target_platform(&self) -> TargetPlatform {
if cfg!(target_os = "android") {
TargetPlatform::Android
} else if cfg!(target_os = "ios") {
TargetPlatform::IOS
} else if cfg!(target_os = "macos") {
TargetPlatform::MacOS
} else if cfg!(target_os = "windows") {
TargetPlatform::Windows
} else if cfg!(target_os = "fuchsia") {
TargetPlatform::Fuchsia
} else {
TargetPlatform::Linux
}
}
fn platform_brightness(&self) -> Brightness {
self.brightness.get()
}
fn request_frame(&self) {
if !self.frame_requested.replace(true) {
self.wake_event_loop();
}
}
fn now(&self) -> Instant {
Instant::now()
}
fn dispatcher(&self) -> Arc<dyn Dispatcher> {
Arc::new(WinitDispatcher(self.proxy.clone()))
}
fn views(&self) -> Vec<ViewRef> {
self.views.borrow().values().cloned().collect()
}
fn view(&self, id: ViewId) -> Option<ViewRef> {
self.views.borrow().get(&id).cloned()
}
fn open_image_codec(&self, bytes: Arc<[u8]>) -> ImageCodecFuture {
let loader = self.image_loader.borrow().clone();
Box::pin(async move {
if bytes.is_empty() {
return Err(ImageDecodeError::Empty);
}
let loader = loader.ok_or(ImageDecodeError::NoDecoder)?;
let codec = loader
.open(
bytes,
valo_codec::DecodeOptions {
mipmaps: true,
..Default::default()
},
)
.await
.map_err(as_decode_error)?;
Ok(Box::new(WinitImageCodec { codec }) as Box<dyn ImageCodec>)
})
}
fn request_view_focus_change(
&self,
view_id: ViewId,
state: ViewFocusState,
direction: ViewFocusDirection,
) {
self.focus_requests.borrow_mut().push(ViewFocusEvent {
view_id,
state,
direction,
});
self.wake_event_loop();
}
fn implicit_view(&self) -> Option<ViewRef> {
self.implicit_view.and_then(|id| self.view(id))
}
fn font_source(&self) -> Option<Box<dyn FontSource>> {
Some(Box::new(SystemFontSource::platform()))
}
fn windowing_owner(&self) -> Option<Rc<dyn WindowingOwner>> {
Some(Rc::clone(&self.windowing) as Rc<dyn WindowingOwner>)
}
fn import_pixels(&self, pixels: valo::PixelBuffer) -> Option<valo::Image> {
self.image_context()?.upload_pixels(pixels, false).ok()
}
fn clipboard(&self) -> Option<&dyn Clipboard> {
Some(self)
}
fn popup_menus(&self) -> Option<&dyn PopupMenus> {
Some(self)
}
fn mouse_cursor(&self) -> Option<&dyn MouseCursor> {
Some(self)
}
}
impl Clipboard for WinitPlatform {
fn set_text(&self, text: &str) {
let _ = self.with_clipboard(|clipboard| clipboard.set_text(text));
}
fn text(&self) -> Option<String> {
self.with_clipboard(|clipboard| clipboard.get_text().ok())
.flatten()
}
fn has_strings(&self) -> bool {
self.text().is_some()
}
}
impl PopupMenus for WinitPlatform {
fn show(&self, entries: &[PopupMenuEntry]) -> Option<usize> {
let chosen = os::popup_menu(entries);
let _ = self.proxy.send_event(HostEvent::MenuClosed);
chosen
}
}
impl MouseCursor for WinitPlatform {
fn activate_system_cursor(&self, _device: i64, kind: SystemMouseCursorKind) {
self.cursor_request.set(Some(kind));
self.wake_event_loop();
}
}
struct WinitDispatcher(EventLoopProxy<HostEvent>);
impl Dispatcher for WinitDispatcher {
fn wake_at(&self, deadline: Instant) {
let _ = self.0.send_event(HostEvent::WakeAt(deadline));
}
fn dispatch(&self, work: Box<dyn FnOnce() + Send>) {
os::run_off_main(work);
}
}
struct WinitImageCodec {
codec: valo_codec::Codec,
}
impl ImageCodec for WinitImageCodec {
fn frame_count(&self) -> u32 {
self.codec.info().frame_count
}
fn repetition(&self) -> ImageRepetition {
match self.codec.info().repetition {
valo_codec::Repetition::Once => ImageRepetition::Once,
valo_codec::Repetition::Times(times) => ImageRepetition::Times(times),
valo_codec::Repetition::Forever => ImageRepetition::Forever,
}
}
fn next_frame(&mut self) -> ImageFrameFuture<'_> {
let frame = self.codec.next_frame();
Box::pin(async move {
let frame = frame.await.map_err(as_decode_error)?;
Ok(ImageFrame {
image: frame.image,
duration: frame.duration,
})
})
}
}
fn as_decode_error(error: valo_codec::DecodeError) -> ImageDecodeError {
match error {
valo_codec::DecodeError::NoDecoder => ImageDecodeError::NoDecoder,
valo_codec::DecodeError::Unsupported(_) => ImageDecodeError::UnknownFormat,
valo_codec::DecodeError::InvalidData(reason) => ImageDecodeError::Damaged(reason),
other => ImageDecodeError::Failed(other.to_string()),
}
}
pub(crate) fn brightness_of(theme: winit::window::Theme) -> Brightness {
match theme {
winit::window::Theme::Light => Brightness::Light,
winit::window::Theme::Dark => Brightness::Dark,
}
}