Introduce egui window resizability

This commit is contained in:
Jakub Turowski
2024-07-14 22:09:36 +02:00
committed by Robbert van der Helm
parent bba7064441
commit 91a0ff745e
6 changed files with 208 additions and 50 deletions

View File

@@ -20,7 +20,7 @@ nih_plug = { path = "..", default-features = false }
raw-window-handle = "0.5"
baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "45465c5f46abed6c6ce370fffde5edc8e4cd5aa3" }
crossbeam = "0.8"
egui-baseview = { git = "https://github.com/BillyDM/egui-baseview.git", rev = "68c4d0e8e5c1c702a888a245f4ac50eddfdfcaed", default-features = false }
egui-baseview = { git = "https://github.com/BillyDM/egui-baseview.git", rev = "87a6cbead6cf89ca27c2f0448e480e901cc2754d", default-features = false }
parking_lot = "0.12"
# To make the state persistable
serde = { version = "1.0", features = ["derive"] }

View File

@@ -1,6 +1,10 @@
//! An [`Editor`] implementation for egui.
use crate::egui::Vec2;
use crate::egui::ViewportCommand;
use crate::EguiState;
use baseview::gl::GlConfig;
use baseview::PhySize;
use baseview::{Size, WindowHandle, WindowOpenOptions, WindowScalePolicy};
use crossbeam::atomic::AtomicCell;
use egui_baseview::egui::Context;
@@ -11,8 +15,6 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
use std::sync::atomic::Ordering;
use std::sync::Arc;
use crate::EguiState;
/// An [`Editor`] implementation that calls an egui draw loop.
pub(crate) struct EguiEditor<T> {
pub(crate) egui_state: Arc<EguiState>,
@@ -67,6 +69,7 @@ where
let build = self.build.clone();
let update = self.update.clone();
let state = self.user_state.clone();
let egui_state = self.egui_state.clone();
let (unscaled_width, unscaled_height) = self.egui_state.size();
let scaling_factor = self.scaling_factor.load();
@@ -100,9 +103,25 @@ where
},
state,
move |egui_ctx, _queue, state| build(egui_ctx, &mut state.write()),
move |egui_ctx, _queue, state| {
move |egui_ctx, queue, state| {
let setter = ParamSetter::new(context.as_ref());
// If the window was requested to resize
if let Some(new_size) = egui_state.requested_size.swap(None) {
// Ask the plugin host to resize to self.size()
if context.request_resize() {
// Resize the content of egui window
queue.resize(PhySize::new(new_size.0, new_size.1));
egui_ctx.send_viewport_cmd(ViewportCommand::InnerSize(Vec2::new(
new_size.0 as f32,
new_size.1 as f32,
)));
// Update the state
egui_state.size.store(new_size);
}
}
// For now, just always redraw. Most plugin GUIs have meters, and those almost always
// need a redraw. Later we can try to be a bit more sophisticated about this. Without
// this we would also have a blank GUI when it gets first opened because most DAWs open
@@ -119,8 +138,16 @@ where
})
}
/// Size of the editor window
fn size(&self) -> (u32, u32) {
self.egui_state.size()
let new_size = self.egui_state.requested_size.load();
// This method will be used to ask the host for new size.
// If the editor is currently being resized and new size hasn't been consumed and set yet, return new requested size.
if let Some(new_size) = new_size {
new_size
} else {
self.egui_state.size()
}
}
fn set_scale_factor(&self, factor: f32) -> bool {

View File

@@ -21,6 +21,7 @@ compile_error!("There's currently no software rendering support for egui");
pub use egui_baseview::egui;
mod editor;
pub mod resizable_window;
pub mod widgets;
/// Create an [`Editor`] instance using an [`egui`][::egui] GUI. Using the user state parameter is
@@ -66,6 +67,11 @@ pub struct EguiState {
/// The window's size in logical pixels before applying `scale_factor`.
#[serde(with = "nih_plug::params::persist::serialize_atomic_cell")]
size: AtomicCell<(u32, u32)>,
/// The new size of the window, if it was requested to resize by the GUI.
#[serde(skip)]
requested_size: AtomicCell<Option<(u32, u32)>>,
/// Whether the editor's window is currently open.
#[serde(skip)]
open: AtomicBool,
@@ -90,6 +96,7 @@ impl EguiState {
pub fn from_size(width: u32, height: u32) -> Arc<EguiState> {
Arc::new(EguiState {
size: AtomicCell::new((width, height)),
requested_size: Default::default(),
open: AtomicBool::new(false),
})
}
@@ -104,4 +111,9 @@ impl EguiState {
pub fn is_open(&self) -> bool {
self.open.load(Ordering::Acquire)
}
/// Set the new size that will be used to resize the window if the host allows.
fn set_requested_size(&self, new_size: (u32, u32)) {
self.requested_size.store(Some(new_size));
}
}

View File

@@ -0,0 +1,80 @@
//! Resizable window wrapper for Egui editor.
//!
//!
use crate::egui::{pos2, CentralPanel, Context, Id, Rect, Response, Sense, Ui, Vec2};
use crate::EguiState;
use egui_baseview::egui::InnerResponse;
/// Adds a corner to the plugin window that can be dragged in order to resize it.
/// Resizing happens through plugin API, hence a custom implementation is needed.
pub struct ResizableWindow {
id: Id,
min_size: Vec2,
}
impl ResizableWindow {
pub fn new(id_source: impl std::hash::Hash) -> Self {
Self {
id: Id::new(id_source),
min_size: Vec2::splat(16.0),
}
}
/// Won't shrink to smaller than this
#[inline]
pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
self.min_size = min_size.into();
self
}
pub fn show<R>(
self,
context: &Context,
egui_state: &EguiState,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
CentralPanel::default().show(context, move |ui| {
let ui_rect = ui.clip_rect();
let mut content_ui = ui.child_ui(ui_rect, *ui.layout());
let ret = add_contents(&mut content_ui);
let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
let corner_rect = Rect::from_min_size(ui_rect.max - corner_size, corner_size);
let corner_response = ui.interact(corner_rect, self.id.with("corner"), Sense::drag());
if let Some(pointer_pos) = corner_response.interact_pointer_pos() {
let desired_size = (pointer_pos - ui_rect.min + 0.5 * corner_response.rect.size())
.max(self.min_size);
if corner_response.dragged() {
egui_state.set_requested_size((
desired_size.x.round() as u32,
desired_size.y.round() as u32,
));
}
}
paint_resize_corner(&content_ui, &corner_response);
ret
})
}
}
pub fn paint_resize_corner(ui: &Ui, response: &Response) {
let stroke = ui.style().interact(response).fg_stroke;
let painter = ui.painter();
let rect = response.rect.translate(-Vec2::splat(2.0)); // move away from the corner
let cp = painter.round_pos_to_pixels(rect.max);
let mut w = 2.0;
while w <= rect.width() && w <= rect.height() {
painter.line_segment([pos2(cp.x - w, cp.y), pos2(cp.x, cp.y - w)], stroke);
w += 4.0;
}
}