use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use inset_embedder::{
Matrix4, Offset, Picture, Rect, Size, TextEditingValue, TextInputConfiguration, TextInputHost,
View, ViewConstraints, ViewId, ViewMetrics, ViewPadding, transform3,
};
use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::window::{ImePurpose, Window};
use crate::surface::WinitSurface;
use crate::text_input::ActiveTextInput;
use crate::windows::WinitWindow;
pub(crate) struct WinitView {
pub(crate) id: ViewId,
pub(crate) metrics: Cell<ViewMetrics>,
pub(crate) surface: Arc<Mutex<WinitSurface>>,
pub(crate) window: Arc<Window>,
pub(crate) editing_state: RefCell<TextEditingValue>,
pub(crate) text_input: Cell<Option<ActiveTextInput>>,
pub(crate) transform: RefCell<Option<Matrix4>>,
pub(crate) clear: valo::Color,
pub(crate) latest: RefCell<Option<Arc<Picture>>>,
pub(crate) on_screen: Cell<bool>,
pub(crate) refused: Cell<Option<valo::Refused>>,
pub(crate) retries: Cell<u8>,
pub(crate) resizing: Cell<bool>,
}
const TIMEOUT_RETRIES: u8 = 8;
impl WinitView {
fn draw(&self, picture: &Picture) {
let drawn = self.surface.lock().expect("surface lock").draw(
picture,
self.clear,
self.resizing.get(),
);
match drawn {
Ok(()) => {
self.on_screen.set(true);
self.refused.set(None);
self.retries.set(0);
}
Err(refused) => {
self.on_screen.set(false);
self.refused.set(Some(refused));
}
}
}
pub(crate) fn represent(&self) {
if self.on_screen.get() {
return;
}
let latest = self.latest.borrow().clone();
if let Some(latest) = latest {
self.draw(&latest);
}
}
fn owes_a_frame(&self) -> bool {
!self.on_screen.get() && self.latest.borrow().is_some()
}
pub(crate) fn retries_at_refresh(&self) -> bool {
self.owes_a_frame()
&& self.refused.get() == Some(valo::Refused::Timeout)
&& self.retries.get() < TIMEOUT_RETRIES
}
pub(crate) fn retry(&self) {
self.retries.set(self.retries.get().saturating_add(1));
self.represent();
}
pub(crate) fn resized(&self, size: [u32; 2]) {
self.surface
.lock()
.expect("surface lock")
.surface
.resize(size);
self.on_screen.set(false);
self.refused.set(None);
self.retries.set(0);
}
}
impl View for WinitView {
fn id(&self) -> ViewId {
self.id
}
fn metrics(&self) -> ViewMetrics {
self.metrics.get()
}
fn present(&self, picture: Arc<Picture>) {
let unchanged = self.on_screen.get()
&& self
.latest
.borrow()
.as_ref()
.is_some_and(|latest| **latest == *picture);
if unchanged {
return;
}
*self.latest.borrow_mut() = Some(Arc::clone(&picture));
self.draw(&picture);
}
fn text_input(&self) -> Option<&dyn TextInputHost> {
Some(self)
}
}
impl TextInputHost for WinitView {
fn start(&self, configuration: &TextInputConfiguration) {
self.text_input
.set(Some(ActiveTextInput::new(configuration)));
self.window.set_ime_allowed(true);
self.window.set_ime_purpose(if configuration.obscure_text {
ImePurpose::Password
} else {
ImePurpose::Normal
});
}
fn stop(&self) {
self.text_input.set(None);
self.window.set_ime_allowed(false);
}
fn set_editing_state(&self, value: &TextEditingValue) {
*self.editing_state.borrow_mut() = value.clone();
}
fn set_composing_rect(&self, rect: Rect) {
self.set_ime_area(rect);
}
fn set_caret_rect(&self, rect: Rect) {
self.set_ime_area(rect);
}
fn set_client_geometry(&self, _size: Size, transform: &Matrix4) {
*self.transform.borrow_mut() = Some(*transform);
}
}
impl WinitView {
fn set_ime_area(&self, rect: Rect) {
let origin = match *self.transform.borrow() {
Some(transform) => transform3(transform, Offset::new(rect.left, rect.top)),
None => Offset::new(rect.left, rect.top),
};
let scale = self.metrics.get().device_pixel_ratio;
self.window.set_ime_cursor_area(
PhysicalPosition::new(origin.dx() * scale, origin.dy() * scale),
PhysicalSize::new(rect.width() * scale, rect.height() * scale),
);
}
}
pub(crate) struct HostedView {
pub(crate) window: Arc<Window>,
pub(crate) view: Rc<WinitView>,
pub(crate) handle: Option<Rc<WinitWindow>>,
}
pub(crate) fn window_metrics(window: &Window) -> ViewMetrics {
let [width, height] = surface_size(window).map(f64::from);
let padding = safe_area_padding(window, width, height);
ViewMetrics {
physical_size: [width, height],
physical_constraints: ViewConstraints::tight(width, height),
device_pixel_ratio: window.scale_factor(),
padding,
view_padding: padding,
view_insets: ViewPadding::ZERO,
}
}
pub(crate) fn surface_size(window: &Window) -> [u32; 2] {
let size = if cfg!(target_os = "ios") {
window.outer_size()
} else {
window.inner_size()
};
[size.width, size.height]
}
fn safe_area_padding(window: &Window, width: f64, height: f64) -> ViewPadding {
if !cfg!(target_os = "ios") {
return ViewPadding::ZERO;
}
let safe = window.inner_size();
let origin = window.inner_position().unwrap_or_default();
let left = f64::from(origin.x);
let top = f64::from(origin.y);
ViewPadding {
left,
top,
right: (width - f64::from(safe.width) - left).max(0.0),
bottom: (height - f64::from(safe.height) - top).max(0.0),
}
}