use crate::{
DetachedRenderWindow, MainWindow, ViewportModel, bridge::render_thread::RenderContext,
};
use slint::{
ComponentHandle, Weak,
winit_030::{WinitWindowAccessor, winit::window::WindowLevel},
};
use std::{
cell::RefCell,
rc::Rc,
sync::{Arc, Mutex},
};
use tracing::warn;
pub(super) type FrameTarget = Arc<Mutex<Option<Weak<DetachedRenderWindow>>>>;
#[must_use]
const fn render_is_visible(active_tab: i32, render_view_tab: i32, detached: bool) -> bool {
detached || (active_tab == 0 && render_view_tab == 0)
}
fn recompute_tab_visible(ui: &MainWindow, render_ctx: &Arc<Mutex<RenderContext>>) {
let visible = render_is_visible(
ui.get_active_tab(),
ui.get_render_view_tab(),
ui.get_live_render_detached(),
);
render_ctx.lock().unwrap().tab_visible = visible;
}
fn apply_always_on_top(win: &DetachedRenderWindow) {
let applied = win
.window()
.with_winit_window(|winit_window| {
winit_window.set_window_level(WindowLevel::AlwaysOnTop);
})
.is_some();
if !applied {
warn!(
"Could not set the detached Live Render window always-on-top -- no winit \
window was available (wrong backend, or the event loop isn't running yet); \
the window will show but may not stay above the main window."
);
}
}
fn dock_back(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
window_slot: &Rc<RefCell<Option<DetachedRenderWindow>>>,
frame_target: &FrameTarget,
) {
if let Some(win) = window_slot.borrow().as_ref()
&& let Err(e) = win.hide()
{
warn!("Failed to hide the detached Live Render window: {e}");
}
*frame_target.lock().unwrap() = None;
ui.set_live_render_detached(false);
recompute_tab_visible(ui, render_ctx);
}
fn open_detached_window(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
window_slot: &Rc<RefCell<Option<DetachedRenderWindow>>>,
frame_target: &FrameTarget,
) {
if window_slot.borrow().is_none() {
let win = match DetachedRenderWindow::new() {
Ok(win) => win,
Err(e) => {
warn!("Failed to create the detached Live Render window: {e}");
ui.set_live_render_detached(false);
return;
}
};
let ui_weak = ui.as_weak();
let render_ctx_cr = render_ctx.clone();
let window_slot_cr = window_slot.clone();
let frame_target_cr = frame_target.clone();
win.window().on_close_requested(move || {
if let Some(ui) = ui_weak.upgrade() {
dock_back(&ui, &render_ctx_cr, &window_slot_cr, &frame_target_cr);
}
slint::CloseRequestResponse::HideWindow
});
let ui_weak_rd = ui.as_weak();
let render_ctx_rd = render_ctx.clone();
let window_slot_rd = window_slot.clone();
let frame_target_rd = frame_target.clone();
win.on_redock(move || {
if let Some(ui) = ui_weak_rd.upgrade() {
dock_back(&ui, &render_ctx_rd, &window_slot_rd, &frame_target_rd);
}
});
*window_slot.borrow_mut() = Some(win);
}
let slot = window_slot.borrow();
let win = slot
.as_ref()
.expect("just created or already present above");
win.set_render_image(ui.global::<ViewportModel>().get_render_image());
win.set_has_render(ui.global::<ViewportModel>().get_has_render());
if let Err(e) = win.show() {
warn!("Failed to show the detached Live Render window: {e}");
ui.set_live_render_detached(false);
return;
}
apply_always_on_top(win);
*frame_target.lock().unwrap() = Some(win.as_weak());
ui.set_live_render_detached(true);
recompute_tab_visible(ui, render_ctx);
}
pub(in crate::gui) fn setup_live_render_visibility_callbacks(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
) -> FrameTarget {
let frame_target: FrameTarget = Arc::new(Mutex::new(None));
let window_slot: Rc<RefCell<Option<DetachedRenderWindow>>> = Rc::new(RefCell::new(None));
let render_ctx_at = render_ctx.clone();
let ui_weak_at = ui.as_weak();
ui.on_active_tab_changed(move |_tab| {
if let Some(ui) = ui_weak_at.upgrade() {
recompute_tab_visible(&ui, &render_ctx_at);
}
});
let render_ctx_rv = render_ctx.clone();
let ui_weak_rv = ui.as_weak();
ui.on_render_view_tab_changed(move |_idx| {
if let Some(ui) = ui_weak_rv.upgrade() {
recompute_tab_visible(&ui, &render_ctx_rv);
}
});
let render_ctx_dt = render_ctx.clone();
let ui_weak_dt = ui.as_weak();
let window_slot_dt = window_slot;
let frame_target_dt = frame_target.clone();
ui.on_live_render_detach_toggled(move |want_detached: bool| {
let Some(ui) = ui_weak_dt.upgrade() else {
return;
};
if want_detached {
open_detached_window(&ui, &render_ctx_dt, &window_slot_dt, &frame_target_dt);
} else {
dock_back(&ui, &render_ctx_dt, &window_slot_dt, &frame_target_dt);
}
});
frame_target
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detached_is_visible_regardless_of_either_tab() {
for active_tab in [0, 1, 2] {
for render_view_tab in [0, 1] {
assert!(render_is_visible(active_tab, render_view_tab, true));
}
}
}
#[test]
fn docked_is_visible_only_on_the_3d_tabs_own_live_render_sub_tab() {
assert!(render_is_visible(0, 0, false));
assert!(!render_is_visible(0, 1, false), "Edit sub-tab, docked");
assert!(!render_is_visible(1, 0, false), "Cutting Schedule tab");
assert!(!render_is_visible(2, 0, false), "Files & Downloads tab");
}
#[test]
fn docking_and_undocking_round_trips_visibility_for_every_outer_tab() {
for active_tab in [0, 1, 2] {
for render_view_tab in [0, 1] {
assert!(
render_is_visible(active_tab, render_view_tab, true),
"popped out must always be visible (active_tab={active_tab}, \
render_view_tab={render_view_tab})"
);
let docked = render_is_visible(active_tab, render_view_tab, false);
assert_eq!(docked, active_tab == 0 && render_view_tab == 0);
}
}
}
}