use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use baseview::{Event, EventStatus, Window, WindowHandler, WindowOpenOptions, WindowScalePolicy};
use truce_core::editor::{Editor, PluginContext, RawWindowHandle};
use truce_gpu::WgpuBackend;
use truce_gui_types::render::RenderBackend;
use truce_params::Params;
use crate::EditorScale;
use crate::editor::BuiltinEditor;
use crate::platform::ParentWindow;
pub struct GpuEditor<P: Params> {
inner: Arc<Mutex<BuiltinEditor<P>>>,
size: (u32, u32),
scale: EditorScale,
window: Option<baseview::WindowHandle>,
}
unsafe impl<P: Params> Send for GpuEditor<P> {}
impl<P: Params + 'static> GpuEditor<P> {
#[must_use]
pub fn new(inner: BuiltinEditor<P>) -> Self {
let size = inner.size();
Self {
inner: Arc::new(Mutex::new(inner)),
size,
scale: EditorScale::new(crate::backing_scale()),
window: None,
}
}
pub fn new_shared(inner: Arc<Mutex<BuiltinEditor<P>>>) -> Self {
let size = inner.lock().unwrap().size();
Self {
inner,
size,
scale: EditorScale::new(crate::backing_scale()),
window: None,
}
}
}
struct GpuWindowHandler<P: Params> {
inner: Arc<Mutex<BuiltinEditor<P>>>,
gpu: Option<WgpuBackend>,
translator: crate::interaction::BaseviewTranslator,
current_size: (u32, u32),
scale: EditorScale,
last_applied_scale: f32,
}
impl<P: Params + 'static> GpuWindowHandler<P> {
fn on_frame_inner(&mut self, window: &mut Window) {
#[cfg(target_os = "macos")]
{
use raw_window_handle::HasRawWindowHandle;
crate::platform::reanchor_to_superview_top(window.raw_window_handle());
}
if let Some(ref mut gpu) = self.gpu {
if let Some(cur_scale) = self.scale.take_change(&mut self.last_applied_scale) {
gpu.set_scale(cur_scale);
gpu.resize(self.current_size.0, self.current_size.1);
}
if let Ok(mut inner) = self.inner.lock() {
if !inner.has_context() {
static WARNED: AtomicBool = AtomicBool::new(false);
if !WARNED.swap(true, Ordering::Relaxed) {
log::warn!("[truce-gpu] on_frame called but inner has no context");
}
}
let new_size = inner.size();
if new_size != self.current_size {
gpu.resize(new_size.0, new_size.1);
window.resize(baseview::Size::new(
f64::from(new_size.0),
f64::from(new_size.1),
));
self.current_size = new_size;
}
inner.render_to(gpu);
}
gpu.present();
}
}
}
impl<P: Params + 'static> WindowHandler for GpuWindowHandler<P> {
fn on_frame(&mut self, window: &mut Window) {
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
self.on_frame_inner(window);
}));
if let Err(e) = result {
let msg = if let Some(s) = e.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = e.downcast_ref::<String>() {
s.clone()
} else {
"unknown panic".to_string()
};
log::error!("GpuWindowHandler::on_frame panic swallowed: {msg}");
}
}
fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus {
match event {
Event::Mouse(_) => {
let Some(input) = self.translator.translate(&event) else {
return EventStatus::Ignored;
};
if let Ok(mut inner) = self.inner.lock() {
inner.dispatch_events(&[input]);
}
EventStatus::Captured
}
Event::Window(win) => {
if let baseview::WindowEvent::Resized(info) = win {
self.scale.set(info.scale());
crate::platform::note_linux_scale_factor(info.scale());
if let Ok(mut inner) = self.inner.lock()
&& inner.can_resize()
{
let phys = info.physical_size();
let scale = info.scale();
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let (lw, lh) = if scale > 0.0 {
(
(f64::from(phys.width) / scale).round() as u32,
(f64::from(phys.height) / scale).round() as u32,
)
} else {
(phys.width, phys.height)
};
if (lw, lh) != inner.size() && lw > 0 && lh > 0 {
inner.set_size(lw, lh);
}
}
}
EventStatus::Ignored
}
Event::Keyboard(_) => EventStatus::Ignored,
}
}
}
impl<P: Params + 'static> Editor for GpuEditor<P> {
fn size(&self) -> (u32, u32) {
self.inner.lock().map_or(self.size, |g| g.size())
}
fn can_resize(&self) -> bool {
self.inner.lock().is_ok_and(|g| g.can_resize())
}
fn can_maximize(&self) -> bool {
self.inner.lock().is_ok_and(|g| g.can_maximize())
}
fn set_size(&mut self, width: u32, height: u32) -> bool {
self.inner
.lock()
.is_ok_and(|mut g| g.set_size(width, height))
}
fn min_size(&self) -> (u32, u32) {
self.inner.lock().map_or((1, 1), |g| g.min_size())
}
fn max_size(&self) -> (u32, u32) {
self.inner
.lock()
.map_or((u32::MAX, u32::MAX), |g| g.max_size())
}
fn size_increment(&self) -> Option<(u32, u32)> {
self.inner.lock().ok().and_then(|g| g.size_increment())
}
fn open(&mut self, parent: RawWindowHandle, context: PluginContext) {
self.scale
.set(crate::platform::query_backing_scale(&parent));
let system_scale = self.scale.get();
let (lw, lh) = self.size;
if let Ok(mut inner) = self.inner.lock() {
inner.set_context(context);
}
let inner = Arc::clone(&self.inner);
let size = self.size;
let scale_handle = self.scale.clone();
let options = WindowOpenOptions {
title: String::from("truce-gpu"),
size: baseview::Size::new(f64::from(lw), f64::from(lh)),
scale: WindowScalePolicy::SystemScaleFactor,
};
let parent_wrapper = ParentWindow(parent);
let window = baseview::Window::open_parented(
&parent_wrapper,
options,
move |window: &mut Window| {
#[allow(clippy::cast_possible_truncation)]
let scale = system_scale as f32;
let gpu = unsafe { WgpuBackend::from_window(window, size.0, size.1, scale) };
GpuWindowHandler {
inner,
gpu,
translator: crate::interaction::BaseviewTranslator::default(),
current_size: size,
scale: scale_handle,
last_applied_scale: scale,
}
},
);
self.window = Some(window);
}
fn set_scale_factor(&mut self, factor: f64) {
self.scale.set(factor);
}
fn close(&mut self) {
if let Some(mut window) = self.window.take() {
window.close();
}
}
fn idle(&mut self) {
}
fn state_changed(&mut self) {
if let Ok(mut inner) = self.inner.lock() {
inner.state_changed();
}
}
fn screenshot(
&mut self,
_params: Arc<dyn truce_params::Params>,
) -> Option<(Vec<u8>, u32, u32)> {
let mut inner = self.inner.lock().ok()?;
let (lw, lh) = inner.size();
let scale = self.scale.get_f32();
let mut backend = WgpuBackend::headless(lw, lh, scale)?;
inner.render_to(&mut backend);
let pixels = backend.read_pixels();
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss
)]
let (phys_w, phys_h) = (
(lw as f32 * scale).round() as u32,
(lh as f32 * scale).round() as u32,
);
Some((pixels, phys_w, phys_h))
}
}
impl<P: Params + 'static> Drop for GpuEditor<P> {
fn drop(&mut self) {
Editor::close(self);
}
}