use std::{future::Future, sync::Arc};
use ribir_core::{
prelude::{font_db::FontDBGlyphProvider, *},
window::{
BoxShellWindow, RedrawDemand, Shell, ShellWindow, UiEvent, WindowAttributes, WindowId,
WindowLevel,
},
};
use winit::dpi::{LogicalPosition, LogicalSize};
#[cfg(target_arch = "wasm32")]
pub const RIBIR_CANVAS: &str = "ribir_canvas";
#[cfg(target_arch = "wasm32")]
pub const RIBIR_CONTAINER: &str = "ribir_container";
#[cfg(feature = "debug")]
use ribir_core::debug_tool::{FRAME_TX, FramePacket};
use crate::{
app::{App, CmdSender},
backends::*,
};
pub enum ShellCmd {
Exit,
RequestDraw {
id: WindowId,
demand: RedrawDemand,
},
Draw {
id: WindowId,
wnd_size: Size,
viewport: Rect,
surface_color: Color,
commands: Vec<PaintCommand>,
glyph_provider: FontDBGlyphProvider,
},
Close {
id: WindowId,
},
RunAsync {
fut: BoxFuture<'static, ()>,
},
NewWindow {
attrs: Box<WindowAttributes>,
sender: tokio::sync::oneshot::Sender<BoxShellWindow>,
},
}
impl ShellCmd {
pub fn wnd_id(&self) -> Option<WindowId> {
match self {
ShellCmd::RequestDraw { id, .. } | ShellCmd::Draw { id, .. } | ShellCmd::Close { id } => {
Some(*id)
}
ShellCmd::RunAsync { .. } | ShellCmd::Exit | ShellCmd::NewWindow { .. } => None,
}
}
}
pub(crate) struct RibirShell {
pub(crate) cmd_sender: CmdSender,
}
impl Shell for RibirShell {
fn new_shell_window(
&self, attrs: ribir_core::window::WindowAttributes,
) -> BoxFuture<'static, BoxShellWindow> {
let (sender, receiver) = tokio::sync::oneshot::channel::<BoxShellWindow>();
self
.cmd_sender
.send(ShellCmd::NewWindow { attrs: Box::new(attrs), sender });
Box::pin(async move { receiver.await.unwrap() })
}
fn exit(&self) { self.cmd_sender.send(ShellCmd::Exit); }
fn run_in_shell(&self, fut: BoxFuture<'static, ()>) {
self.cmd_sender.send(ShellCmd::RunAsync { fut });
}
}
pub(crate) struct ShellWndHandle {
pub(crate) sender: CmdSender,
pub(crate) winit_wnd: Arc<winit::window::Window>,
pub(crate) cursor: CursorIcon,
}
pub trait WinitBackend<'a>: Sized {
fn new(window: &'a winit::window::Window) -> impl Future<Output = Self>;
fn on_resize(&mut self, size: DeviceSize);
fn begin_frame(&mut self, surface_color: Color);
fn draw_commands(
&mut self, viewport: DeviceRect, global_matrix: &Transform, commands: &[PaintCommand],
glyph_provider: &dyn GlyphProvider,
);
fn end_frame(&mut self);
#[cfg(feature = "debug")]
fn capture_screenshot(&mut self) -> Option<BoxFuture<'static, Option<PixelImage>>>;
}
pub(crate) struct WinitShellWnd {
pub(crate) winit_wnd: Arc<winit::window::Window>,
backend: Backend<'static>,
}
fn window_size(winit_wnd: &winit::window::Window) -> Size {
let size = winit_wnd
.inner_size()
.to_logical(winit_wnd.scale_factor());
Size::new(size.width, size.height)
}
impl WinitShellWnd {
pub(crate) fn id(&self) -> WindowId { new_id(self.winit_wnd.id()) }
pub(crate) fn deal_cmd(&mut self, cmd: ShellCmd) {
match cmd {
ShellCmd::RequestDraw { demand, .. } => {
if demand.requires_forced_draw() {
App::send_event(UiEvent::RedrawRequest { wnd_id: self.id(), demand });
} else {
self.winit_wnd.request_redraw();
}
}
ShellCmd::Draw { viewport, surface_color, commands, glyph_provider, wnd_size, .. } => {
if wnd_size == window_size(&self.winit_wnd) {
self.backend.begin_frame(surface_color);
let scale_factor = self.winit_wnd.scale_factor() as f32;
let viewport: DeviceRect = viewport
.scale(scale_factor, scale_factor)
.round_out()
.to_i32()
.cast_unit();
self.backend.draw_commands(
viewport,
&Transform::scale(scale_factor, scale_factor),
&commands,
&glyph_provider,
);
#[cfg(feature = "debug")]
self.capture_debug_frame();
self.backend.end_frame();
} else {
self.winit_wnd.request_redraw();
}
}
ShellCmd::Close { id } => {
App::remove_shell_window(id);
}
_ => (),
}
}
pub(crate) fn on_resize(&mut self, size: DeviceSize) { self.backend.on_resize(size); }
#[cfg(feature = "debug")]
fn capture_debug_frame(&mut self) {
if FRAME_TX.get().is_some()
&& let Some(fut) = self.backend.capture_screenshot()
{
let wnd_id = self.id();
App::spawn_local(async move {
if let Some(img) = fut.await
&& let Some(frame_tx) = FRAME_TX.get()
{
let ts_unix_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
static SEQ: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let seq = SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let _ = frame_tx.send(FramePacket { wnd_id, ts_unix_ms, seq, image: Arc::new(img) });
}
});
}
}
}
impl ShellWindow for ShellWndHandle {
fn id(&self) -> WindowId { new_id(self.winit_wnd.id()) }
fn inner_size(&self) -> Size { window_size(&self.winit_wnd) }
fn as_any(&self) -> &dyn Any { self }
fn as_any_mut(&mut self) -> &mut dyn Any { self }
fn set_cursor(&mut self, cursor: CursorIcon) {
self.cursor = cursor;
self.winit_wnd.set_cursor(cursor);
}
fn cursor(&self) -> CursorIcon { self.cursor }
fn focus_window(&mut self) {
self.winit_wnd.focus_window();
self.winit_wnd.request_redraw();
}
fn set_minimized(&mut self, minimized: bool) { self.winit_wnd.set_minimized(minimized); }
fn is_minimized(&self) -> bool { self.winit_wnd.is_minimized().unwrap_or_default() }
fn set_min_size(&mut self, size: Size) {
self
.winit_wnd
.set_min_inner_size(Some(LogicalSize::new(size.width, size.height)));
}
fn set_icon(&mut self, icon: &PixelImage) {
self
.winit_wnd
.set_window_icon(Some(img_to_winit_icon(icon)));
}
fn close(&self) {
self
.sender
.send(ShellCmd::Close { id: self.id() });
}
fn set_title(&mut self, str: &str) { self.winit_wnd.set_title(str); }
fn set_resizable(&mut self, resizable: bool) { self.winit_wnd.set_resizable(resizable); }
fn is_resizable(&self) -> bool { self.winit_wnd.is_resizable() }
fn set_visible(&mut self, visible: bool) { self.winit_wnd.set_visible(visible); }
fn is_visible(&self) -> Option<bool> { self.winit_wnd.is_visible() }
fn set_ime_allowed(&mut self, allowed: bool) { self.winit_wnd.set_ime_allowed(allowed); }
fn set_window_level(&mut self, level: WindowLevel) { self.winit_wnd.set_window_level(level); }
fn set_ime_cursor_area(&mut self, rect: &Rect) {
let position: LogicalPosition<f32> = LogicalPosition::new(rect.origin.x, rect.origin.y);
let size: LogicalSize<f32> = LogicalSize::new(rect.size.width, rect.size.height);
self.winit_wnd.set_ime_cursor_area(position, size);
}
fn request_resize(&mut self, size: Size) {
let _ = self
.winit_wnd
.request_inner_size(LogicalSize::new(size.width, size.height));
}
fn draw_commands(
&mut self, wnd_size: Size, viewport: Rect, surface_color: Color, commands: &[PaintCommand],
) {
let font_db = AppCtx::font_db();
let glyph_provider = font_db.borrow_mut().glyph_provider();
self.sender.send(ShellCmd::Draw {
id: self.id(),
wnd_size,
viewport,
surface_color,
commands: commands.to_vec(),
glyph_provider,
});
}
fn request_draw(&self, demand: RedrawDemand) {
self
.sender
.send(ShellCmd::RequestDraw { id: self.id(), demand });
}
fn position(&self) -> Point {
let scale_factor = self.winit_wnd.scale_factor() as f32;
self
.winit_wnd
.outer_position()
.map(|pos| Point::new(pos.x as f32 / scale_factor, pos.y as f32 / scale_factor))
.unwrap_or_default()
}
fn set_position(&mut self, point: Point) {
let pos = self.position();
if pos != point {
self
.winit_wnd
.set_outer_position(LogicalPosition::new(point.x, point.y));
}
}
}
pub(crate) fn new_id(id: winit::window::WindowId) -> WindowId {
let id: u64 = id.into();
id.into()
}
impl WinitShellWnd {
#[cfg(target_arch = "wasm32")]
pub(crate) fn create_winit_window(mut attrs: WindowAttributes) -> Arc<winit::window::Window> {
use web_sys::wasm_bindgen::JsCast;
use winit::platform::web::WindowAttributesExtWebSys;
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document
.create_element("canvas")
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
canvas.set_class_name(RIBIR_CANVAS);
let style = canvas.style();
let _ = style.set_property("width", "100%");
let _ = style.set_property("height", "100%");
let elems = document.get_elements_by_class_name(RIBIR_CONTAINER);
if let Some(elem) = elems.item(0) {
elem.set_class_name(&elem.class_name().replace(RIBIR_CONTAINER, ""));
elem.append_child(&canvas).unwrap();
} else if let Some(body) = document.body() {
body.append_child(&canvas).unwrap();
} else {
document.append_child(&canvas).unwrap();
}
attrs.0 = attrs.0.with_canvas(Some(canvas));
Arc::new(
App::active_event_loop()
.create_window(attrs.0)
.unwrap(),
)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn create_winit_window(attrs: WindowAttributes) -> Arc<winit::window::Window> {
Arc::new(
App::active_event_loop()
.create_window(attrs.0)
.unwrap(),
)
}
pub(crate) async fn from_winit_window(winit_wnd: Arc<winit::window::Window>) -> Self {
let ptr = winit_wnd.as_ref() as *const winit::window::Window;
let backend = Backend::new(unsafe { &*ptr }).await;
WinitShellWnd { backend, winit_wnd }
}
#[cfg(target_arch = "wasm32")]
pub(crate) async fn new(mut attrs: WindowAttributes) -> Self {
use web_sys::wasm_bindgen::JsCast;
use winit::platform::web::WindowAttributesExtWebSys;
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document
.create_element("canvas")
.unwrap()
.dyn_into::<web_sys::HtmlCanvasElement>()
.unwrap();
canvas.set_class_name(RIBIR_CANVAS);
let style = canvas.style();
let _ = style.set_property("width", "100%");
let _ = style.set_property("height", "100%");
let elems = document.get_elements_by_class_name(RIBIR_CONTAINER);
if let Some(elem) = elems.item(0) {
elem.set_class_name(&elem.class_name().replace(RIBIR_CONTAINER, ""));
elem.append_child(&canvas).unwrap();
} else if let Some(body) = document.body() {
body.append_child(&canvas).unwrap();
} else {
document.append_child(&canvas).unwrap();
}
attrs.0 = attrs.0.with_canvas(Some(canvas));
Self::inner_new(attrs).await
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn new(attrs: WindowAttributes) -> Self { Self::inner_new(attrs).await }
async fn inner_new(attrs: WindowAttributes) -> Self {
let winit_wnd = Arc::new(
App::active_event_loop()
.create_window(attrs.0)
.unwrap(),
);
let ptr = winit_wnd.as_ref() as *const winit::window::Window;
let backend = Backend::new(unsafe { &*ptr }).await;
WinitShellWnd { backend, winit_wnd }
}
}
fn img_to_winit_icon(icon: &PixelImage) -> winit::window::Icon {
assert!(icon.color_format() == ColorFormat::Rgba8);
winit::window::Icon::from_rgba(icon.pixel_bytes().to_vec(), icon.width(), icon.height()).unwrap()
}