use std::ops::Deref;
use std::sync::Arc;
use truce_params::Params;
use truce_params::sample::Float;
use crate::events::TransportInfo;
pub struct SendPtr<T>(*const T);
impl<T> SendPtr<T> {
pub unsafe fn new(ptr: *const T) -> Self {
Self(ptr)
}
#[must_use]
pub unsafe fn get(&self) -> &T {
unsafe { &*self.0 }
}
#[must_use]
pub fn as_ptr(&self) -> *const T {
self.0
}
}
impl<T> Clone for SendPtr<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for SendPtr<T> {}
unsafe impl<T> Send for SendPtr<T> {}
unsafe impl<T> Sync for SendPtr<T> {}
#[derive(Clone, Copy, Debug)]
pub enum RawWindowHandle {
AppKit(*mut std::ffi::c_void), UiKit(*mut std::ffi::c_void), Win32(*mut std::ffi::c_void), X11(u64), }
pub trait Editor: Send {
fn size(&self) -> (u32, u32);
fn open(&mut self, parent: RawWindowHandle, context: PluginContext);
fn close(&mut self);
fn idle(&mut self) {}
fn set_size(&mut self, _width: u32, _height: u32) -> bool {
false
}
fn can_resize(&self) -> bool {
false
}
fn can_maximize(&self) -> bool {
false
}
fn min_size(&self) -> (u32, u32) {
(1, 1)
}
fn max_size(&self) -> (u32, u32) {
(u32::MAX, u32::MAX)
}
fn size_increment(&self) -> Option<(u32, u32)> {
None
}
fn aspect_ratio(&self) -> Option<(u32, u32)> {
None
}
fn prefers_pow2(&self) -> bool {
false
}
fn set_scale_factor(&mut self, _factor: f64) {}
fn set_uses_system_scale(&mut self, _yes: bool) {}
fn state_changed(&mut self) {}
fn screenshot(&mut self, params: Arc<dyn truce_params::Params>) -> Option<(Vec<u8>, u32, u32)> {
let _ = params;
None
}
}
pub trait IntoEditor {
fn into_editor(self) -> Box<dyn Editor>;
}
impl<E: Editor + 'static> IntoEditor for E {
fn into_editor(self) -> Box<dyn Editor> {
Box::new(self)
}
}
pub trait EditorBridge: Send + Sync {
fn begin_edit(&self, id: u32);
fn set_param(&self, id: u32, normalized: f64);
fn end_edit(&self, id: u32);
fn request_resize(&self, w: u32, h: u32) -> bool;
fn get_param(&self, id: u32) -> f64;
fn get_param_plain(&self, id: u32) -> f64;
fn format_param(&self, id: u32) -> String;
fn format_param_into(&self, id: u32, out: &mut String) {
out.clear();
out.push_str(&self.format_param(id));
}
fn get_meter(&self, id: u32) -> f32;
fn get_state(&self) -> Vec<u8>;
fn set_state(&self, data: Vec<u8>);
fn transport(&self) -> Option<TransportInfo>;
}
pub struct ClosureBridge {
pub begin_edit: Box<dyn Fn(u32) + Send + Sync>,
pub set_param: Box<dyn Fn(u32, f64) + Send + Sync>,
pub end_edit: Box<dyn Fn(u32) + Send + Sync>,
pub request_resize: Box<dyn Fn(u32, u32) -> bool + Send + Sync>,
pub get_param: Box<dyn Fn(u32) -> f64 + Send + Sync>,
pub get_param_plain: Box<dyn Fn(u32) -> f64 + Send + Sync>,
pub format_param: Box<dyn Fn(u32) -> String + Send + Sync>,
pub get_meter: Box<dyn Fn(u32) -> f32 + Send + Sync>,
pub get_state: Box<dyn Fn() -> Vec<u8> + Send + Sync>,
pub set_state: Box<dyn Fn(Vec<u8>) + Send + Sync>,
pub transport: Box<dyn Fn() -> Option<TransportInfo> + Send + Sync>,
}
impl EditorBridge for ClosureBridge {
fn begin_edit(&self, id: u32) {
(self.begin_edit)(id);
}
fn set_param(&self, id: u32, normalized: f64) {
(self.set_param)(id, normalized);
}
fn end_edit(&self, id: u32) {
(self.end_edit)(id);
}
fn request_resize(&self, w: u32, h: u32) -> bool {
(self.request_resize)(w, h)
}
fn get_param(&self, id: u32) -> f64 {
(self.get_param)(id)
}
fn get_param_plain(&self, id: u32) -> f64 {
(self.get_param_plain)(id)
}
fn format_param(&self, id: u32) -> String {
(self.format_param)(id)
}
fn get_meter(&self, id: u32) -> f32 {
(self.get_meter)(id)
}
fn get_state(&self) -> Vec<u8> {
(self.get_state)()
}
fn set_state(&self, data: Vec<u8>) {
(self.set_state)(data);
}
fn transport(&self) -> Option<TransportInfo> {
(self.transport)()
}
}
pub struct PluginContext<P: ?Sized = dyn Params> {
bridge: Arc<dyn EditorBridge>,
params: Arc<P>,
}
impl<P: ?Sized> Clone for PluginContext<P> {
fn clone(&self) -> Self {
Self {
bridge: Arc::clone(&self.bridge),
params: Arc::clone(&self.params),
}
}
}
impl<P: ?Sized> PluginContext<P> {
pub fn new(bridge: Arc<dyn EditorBridge>, params: Arc<P>) -> Self {
Self { bridge, params }
}
#[must_use]
pub fn bridge(&self) -> &Arc<dyn EditorBridge> {
&self.bridge
}
#[must_use]
pub fn params(&self) -> &Arc<P> {
&self.params
}
pub fn with_params<Q: ?Sized>(&self, params: Arc<Q>) -> PluginContext<Q> {
PluginContext {
bridge: Arc::clone(&self.bridge),
params,
}
}
pub fn begin_edit(&self, id: impl Into<u32>) {
self.bridge.begin_edit(id.into());
}
pub fn set_param(&self, id: impl Into<u32>, normalized: f64) {
self.bridge.set_param(id.into(), normalized);
}
pub fn end_edit(&self, id: impl Into<u32>) {
self.bridge.end_edit(id.into());
}
pub fn automate(&self, id: impl Into<u32>, normalized: f64) {
let id = id.into();
self.bridge.begin_edit(id);
self.bridge.set_param(id, normalized);
self.bridge.end_edit(id);
}
#[must_use]
pub fn request_resize(&self, w: u32, h: u32) -> bool {
self.bridge.request_resize(w, h)
}
pub fn format_param(&self, id: impl Into<u32>) -> String {
self.bridge.format_param(id.into())
}
pub fn format_param_into(&self, id: impl Into<u32>, out: &mut String) {
self.bridge.format_param_into(id.into(), out);
}
pub fn get_meter(&self, id: impl Into<u32>) -> f32 {
self.bridge.get_meter(id.into())
}
#[must_use]
pub fn get_state(&self) -> Vec<u8> {
self.bridge.get_state()
}
pub fn set_state(&self, data: Vec<u8>) {
self.bridge.set_state(data);
}
#[must_use]
pub fn transport(&self) -> Option<TransportInfo> {
self.bridge.transport()
}
}
impl PluginContext<dyn Params> {
pub fn from_closures(bridge: ClosureBridge, params: Arc<dyn Params>) -> Self {
Self {
bridge: Arc::new(bridge),
params,
}
}
}
impl<P: Params + 'static> PluginContext<P> {
#[must_use]
pub fn dyn_erase(self) -> PluginContext<dyn Params> {
PluginContext {
bridge: self.bridge,
params: self.params as Arc<dyn Params>,
}
}
}
impl<P: ?Sized> Deref for PluginContext<P> {
type Target = P;
fn deref(&self) -> &P {
&self.params
}
}
pub fn for_test_params(params: Arc<dyn Params>) -> PluginContext<dyn Params> {
let p_get = Arc::clone(¶ms);
let p_plain = Arc::clone(¶ms);
let p_fmt = Arc::clone(¶ms);
let transport = TransportInfo::for_screenshot();
PluginContext::from_closures(
ClosureBridge {
begin_edit: Box::new(|_| {}),
set_param: Box::new(|_, _| {}),
end_edit: Box::new(|_| {}),
request_resize: Box::new(|_, _| false),
get_param: Box::new(move |id| p_get.get_normalized(id).unwrap_or(0.5)),
get_param_plain: Box::new(move |id| p_plain.get_plain(id).unwrap_or(0.0)),
format_param: Box::new(move |id| {
let plain = p_fmt.get_plain(id).unwrap_or(0.0);
p_fmt
.format_value(id, plain)
.unwrap_or_else(|| format!("{plain:.2}"))
}),
get_meter: Box::new(|_| 0.0),
get_state: Box::new(Vec::new),
set_state: Box::new(|_| {}),
transport: Box::new(move || Some(transport)),
},
params,
)
}
pub trait PluginContextReadF32 {
fn get_param(&self, id: impl Into<u32>) -> f32;
fn get_param_plain(&self, id: impl Into<u32>) -> f32;
}
pub trait PluginContextReadF64 {
fn get_param(&self, id: impl Into<u32>) -> f64;
fn get_param_plain(&self, id: impl Into<u32>) -> f64;
}
impl<P: ?Sized> PluginContextReadF32 for PluginContext<P> {
fn get_param(&self, id: impl Into<u32>) -> f32 {
self.bridge.get_param(id.into()).to_f32()
}
fn get_param_plain(&self, id: impl Into<u32>) -> f32 {
self.bridge.get_param_plain(id.into()).to_f32()
}
}
impl<P: ?Sized> PluginContextReadF64 for PluginContext<P> {
fn get_param(&self, id: impl Into<u32>) -> f64 {
self.bridge.get_param(id.into())
}
fn get_param_plain(&self, id: impl Into<u32>) -> f64 {
self.bridge.get_param_plain(id.into())
}
}
#[must_use]
pub fn fit_logical_size(w: u32, h: u32, editor: &dyn Editor) -> (u32, u32) {
fit_size(
w,
h,
editor.min_size(),
editor.max_size(),
editor.aspect_ratio(),
)
}
#[must_use]
pub fn fit_size(
w: u32,
h: u32,
min: (u32, u32),
max: (u32, u32),
aspect: Option<(u32, u32)>,
) -> (u32, u32) {
let (min_w, min_h) = min;
let (max_w, max_h) = max;
let mut w = w.clamp(min_w.max(1), max_w);
let mut h = h.clamp(min_h.max(1), max_h);
if let Some((num64, denom64)) = ratio64(aspect) {
let h_from_w = u64::from(w) * denom64 / num64;
if h_from_w <= u64::from(h) {
h = derive_height(w, min_h, max_h, num64, denom64).0;
} else {
w = derive_width(h, min_w, max_w, num64, denom64).0;
}
}
(w, h)
}
#[must_use]
pub fn clamp_logical_size(w: u32, h: u32, editor: &dyn Editor) -> (u32, u32) {
let (min_w, min_h) = editor.min_size();
let (max_w, max_h) = editor.max_size();
(w.clamp(min_w.max(1), max_w), h.clamp(min_h.max(1), max_h))
}
#[derive(Default)]
pub struct ResizeCorrector {
pushed_back: bool,
}
impl ResizeCorrector {
pub fn fit(
&mut self,
w: u32,
h: u32,
min: (u32, u32),
max: (u32, u32),
aspect: Option<(u32, u32)>,
) -> ((u32, u32), Option<(u32, u32)>) {
let fitted = fit_size(w, h, min, max, aspect);
if fitted == (w, h) {
self.pushed_back = false;
return (fitted, None);
}
let request = (!self.pushed_back).then_some(fitted);
self.pushed_back = true;
(fitted, request)
}
}
fn ratio64(aspect: Option<(u32, u32)>) -> Option<(u64, u64)> {
match aspect {
Some((num, denom)) if num > 0 && denom > 0 => Some((u64::from(num), u64::from(denom))),
_ => None,
}
}
#[allow(clippy::cast_possible_truncation)]
fn derive_height(w: u32, min_h: u32, max_h: u32, num64: u64, denom64: u64) -> (u32, bool) {
let on_ratio = (u64::from(w) * denom64 / num64).clamp(1, u64::from(u32::MAX)) as u32;
let clamped = on_ratio.clamp(min_h.max(1), max_h);
(clamped, clamped != on_ratio)
}
#[allow(clippy::cast_possible_truncation)]
fn derive_width(h: u32, min_w: u32, max_w: u32, num64: u64, denom64: u64) -> (u32, bool) {
let on_ratio = (u64::from(h) * num64 / denom64).clamp(1, u64::from(u32::MAX)) as u32;
let clamped = on_ratio.clamp(min_w.max(1), max_w);
(clamped, clamped != on_ratio)
}
#[cfg(test)]
mod corrector_tests {
use super::ResizeCorrector;
const MIN: (u32, u32) = (300, 200);
const MAX: (u32, u32) = (900, 600);
#[test]
fn in_bounds_size_passes_through_without_correction() {
let mut c = ResizeCorrector::default();
assert_eq!(c.fit(400, 300, MIN, MAX, None), ((400, 300), None));
}
#[test]
fn out_of_bounds_pushes_once_per_excursion() {
let mut c = ResizeCorrector::default();
let (fitted, req) = c.fit(1200, 800, MIN, MAX, None);
assert_eq!(fitted, (900, 600));
assert_eq!(req, Some((900, 600)));
assert_eq!(c.fit(1200, 800, MIN, MAX, None), ((900, 600), None));
assert_eq!(c.fit(1300, 590, MIN, MAX, None), ((900, 590), None));
assert_eq!(c.fit(1300, 595, MIN, MAX, None), ((900, 595), None));
assert_eq!(c.fit(100, 100, MIN, MAX, None), ((300, 200), None));
assert_eq!(c.fit(800, 500, MIN, MAX, None), ((800, 500), None));
let (_, req) = c.fit(1200, 800, MIN, MAX, None);
assert_eq!(req, Some((900, 600)));
}
#[test]
fn honoured_correction_resets_the_guard() {
let mut c = ResizeCorrector::default();
let _ = c.fit(1200, 800, MIN, MAX, None);
assert_eq!(c.fit(900, 600, MIN, MAX, None), ((900, 600), None));
let (_, req) = c.fit(1200, 800, MIN, MAX, None);
assert_eq!(req, Some((900, 600)));
}
#[test]
fn aspect_violation_corrects_onto_ratio() {
let mut c = ResizeCorrector::default();
let ((w, h), req) = c.fit(800, 600, MIN, MAX, Some((4, 3)));
assert_eq!((w, h), (800, 600), "already on-ratio passes through");
assert_eq!(req, None);
let ((w, h), req) = c.fit(800, 400, MIN, MAX, Some((4, 3)));
assert_eq!((w, h), (533, 400), "height-limited fit onto 4:3");
assert_eq!(req, Some((533, 400)));
}
}
#[cfg(test)]
mod fit_tests {
use super::{Editor, PluginContext, RawWindowHandle, fit_logical_size};
struct StubEditor {
min: (u32, u32),
max: (u32, u32),
aspect: Option<(u32, u32)>,
}
impl Editor for StubEditor {
fn size(&self) -> (u32, u32) {
self.min
}
fn open(&mut self, _parent: RawWindowHandle, _context: PluginContext) {}
fn close(&mut self) {}
fn min_size(&self) -> (u32, u32) {
self.min
}
fn max_size(&self) -> (u32, u32) {
self.max
}
fn aspect_ratio(&self) -> Option<(u32, u32)> {
self.aspect
}
}
fn stub(aspect: Option<(u32, u32)>) -> StubEditor {
StubEditor {
min: (320, 240),
max: (u32::MAX, u32::MAX),
aspect,
}
}
#[test]
fn no_aspect_clamps_each_axis_to_bounds() {
let e = stub(None);
assert_eq!(fit_logical_size(800, 600, &e), (800, 600));
assert_eq!(fit_logical_size(100, 100, &e), (320, 240));
}
#[test]
fn tall_box_is_width_bound() {
let e = stub(Some((4, 3)));
assert_eq!(fit_logical_size(640, 800, &e), (640, 480));
}
#[test]
fn wide_box_is_height_bound() {
let e = stub(Some((4, 3)));
assert_eq!(fit_logical_size(800, 480, &e), (640, 480));
}
#[test]
fn on_ratio_box_is_unchanged() {
let e = stub(Some((4, 3)));
assert_eq!(fit_logical_size(800, 600, &e), (800, 600));
}
#[test]
fn fit_never_exceeds_the_requested_box() {
let e = StubEditor {
min: (320, 180),
max: (u32::MAX, u32::MAX),
aspect: Some((16, 9)),
};
for &(w, h) in &[
(640, 800),
(800, 480),
(1000, 1000),
(321, 900),
(1920, 300),
] {
let (rw, rh) = fit_logical_size(w, h, &e);
assert!(rw <= w && rh <= h, "{rw}x{rh} exceeds box {w}x{h}");
assert!((i64::from(rw) * 9 - i64::from(rh) * 16).abs() <= 16);
assert!(rw >= 320 && rh >= 180);
}
}
}