use crossbeam_utils::atomic::AtomicCell;
use iced_baseview::baseview::{Size, WindowOpenOptions, WindowScalePolicy};
use iced_baseview::{
IcedBaseviewSettings, PollSubNotifier, Program, message, shell::window::WindowHandle,
};
use nice_plug_core::context::gui::{GuiContext, ParamSetter};
use nice_plug_core::{
editor::{Editor, ParentWindowHandle},
params::persist::PersistentField,
};
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use serde::{Deserialize, Serialize};
use std::sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
};
use crate::{EditorSettings, application::EditorState};
pub(crate) struct IcedEditor<P: Program + 'static, EState: Send + 'static>
where
<P as Program>::Message: message::MaybeDebug + message::MaybeClone,
{
pub(crate) window_state: Arc<WindowState>,
pub(crate) editor_state: Arc<Mutex<Option<EState>>>,
pub(crate) build: Arc<dyn Fn(EditorState<EState>, NiceGuiContext) -> P + 'static + Send + Sync>,
pub(crate) notifier: PollSubNotifier,
pub(crate) settings: Arc<EditorSettings>,
pub(crate) scaling_factor: AtomicCell<Option<f32>>,
}
impl<P: Program + 'static, State: Send + 'static> Editor for IcedEditor<P, State> {
fn spawn(
&self,
parent: ParentWindowHandle,
context: Arc<dyn GuiContext>,
) -> Box<dyn std::any::Any + Send> {
let nice_ctx = NiceGuiContext {
context: context.clone(),
window_state: self.window_state.clone(),
};
let build = self.build.clone();
let editor_state = EditorState::from_shared(&self.editor_state);
let (unscaled_width, unscaled_height) = self.window_state.logical_size();
let scaling_factor = self.scaling_factor.load();
#[allow(clippy::needless_update)]
let window = iced_baseview::open_parented(
&ParentWindowHandleAdapter(parent),
IcedBaseviewSettings {
window: WindowOpenOptions {
title: String::from("iced window"),
size: Size::new(unscaled_width as f64, unscaled_height as f64),
scale: scaling_factor
.map(|factor| WindowScalePolicy::ScaleFactor(factor as f64))
.unwrap_or(WindowScalePolicy::SystemScaleFactor),
..Default::default()
},
ignore_non_modifier_keys: self.settings.ignore_non_modifier_keys,
always_redraw: self.settings.always_redraw,
},
self.notifier.clone(),
move || (build)(editor_state, nice_ctx),
);
self.window_state.open.store(true, Ordering::Release);
Box::new(IcedEditorHandle {
iced_state: self.window_state.clone(),
_window: window,
})
}
fn size(&self) -> (u32, u32) {
let new_size = self.window_state.requested_logical_size.load();
if let Some(new_size) = new_size {
new_size
} else {
self.window_state.logical_size()
}
}
fn set_scale_factor(&self, factor: f32) -> bool {
if self.window_state.is_open() {
return false;
}
self.scaling_factor.store(Some(factor));
true
}
fn param_value_changed(&self, _id: &str, _normalized_value: f32) {
self.notifier.notify();
}
fn param_modulation_changed(&self, _id: &str, _modulation_offset: f32) {
self.notifier.notify();
}
fn param_values_changed(&self) {
self.notifier.notify();
}
}
struct IcedEditorHandle<Message: 'static + Send> {
iced_state: Arc<WindowState>,
_window: WindowHandle<Message>,
}
unsafe impl<Message: 'static + Send> Send for IcedEditorHandle<Message> {}
impl<Message: 'static + Send> Drop for IcedEditorHandle<Message> {
fn drop(&mut self) {
self.iced_state.open.store(false, Ordering::Release);
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct WindowState {
#[serde(with = "nice_plug_core::params::persist::serialize_atomic_cell")]
pub(crate) logical_size: AtomicCell<(u32, u32)>,
#[serde(skip)]
pub(crate) requested_logical_size: AtomicCell<Option<(u32, u32)>>,
#[serde(skip)]
pub(crate) open: AtomicBool,
}
impl<'a> PersistentField<'a, WindowState> for Arc<WindowState> {
fn set(&self, new_value: WindowState) {
self.logical_size.store(new_value.logical_size.load());
}
fn map<F, R>(&self, f: F) -> R
where
F: Fn(&WindowState) -> R,
{
f(self)
}
}
impl WindowState {
pub fn from_logical_size(width: u32, height: u32) -> Arc<WindowState> {
Arc::new(WindowState {
logical_size: AtomicCell::new((width, height)),
requested_logical_size: Default::default(),
open: AtomicBool::new(false),
})
}
pub fn logical_size(&self) -> (u32, u32) {
self.logical_size.load()
}
pub fn is_open(&self) -> bool {
self.open.load(Ordering::Acquire)
}
pub fn set_requested_logical_size(&self, new_size: (u32, u32)) {
self.requested_logical_size.store(Some(new_size));
}
}
#[derive(Clone)]
pub struct NiceGuiContext {
pub context: Arc<dyn GuiContext>,
window_state: Arc<WindowState>,
}
impl NiceGuiContext {
pub fn logical_size(&self) -> (u32, u32) {
self.window_state.logical_size()
}
pub fn is_open(&self) -> bool {
self.window_state.is_open()
}
pub fn set_requested_logical_size(&self, new_size: (u32, u32)) {
self.window_state.set_requested_logical_size(new_size);
if self.context.request_resize() {
self.window_state.logical_size.store(new_size);
}
}
pub fn param_setter<'a>(&'a self) -> ParamSetter<'a> {
ParamSetter {
raw_context: &*self.context,
}
}
}
struct ParentWindowHandleAdapter(ParentWindowHandle);
unsafe impl HasRawWindowHandle for ParentWindowHandleAdapter {
fn raw_window_handle(&self) -> RawWindowHandle {
match self.0 {
ParentWindowHandle::X11Window(window) => {
let mut handle = raw_window_handle::XcbWindowHandle::empty();
handle.window = window;
RawWindowHandle::Xcb(handle)
}
ParentWindowHandle::AppKitNsView(ns_view) => {
let mut handle = raw_window_handle::AppKitWindowHandle::empty();
handle.ns_view = ns_view;
RawWindowHandle::AppKit(handle)
}
ParentWindowHandle::Win32Hwnd(hwnd) => {
let mut handle = raw_window_handle::Win32WindowHandle::empty();
handle.hwnd = hwnd;
RawWindowHandle::Win32(handle)
}
}
}
}