use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use baseview::{Event, EventStatus, Window, WindowHandler, WindowOpenOptions, WindowScalePolicy};
use truce_core::editor::{Editor, EditorBridge, 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),
bridge: Arc<dyn EditorBridge>,
scale: EditorScale,
last_applied_scale: f32,
}
impl<P: Params + 'static> WindowHandler for GpuWindowHandler<P> {
fn on_frame(&mut self, _window: &mut Window) {
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);
self.bridge.request_resize(new_size.0, new_size.1);
self.current_size = new_size;
}
inner.render_to(gpu);
}
gpu.present();
}
}
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());
}
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 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;
let bridge = Arc::clone(context.bridge());
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,
bridge,
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);
}
}