use crate::{
DetachedRenderWindow, MainWindow, SolidPreviewModel, ViewportModel,
bridge::render_thread::RenderContext, gui::solid_preview::preview_state::SolidPreviewState,
};
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,
live_view_mode: i32,
solid_view_mode: i32,
) -> bool {
detached
|| (active_tab == 0
&& ((render_view_tab == 0 && live_view_mode == 1)
|| (render_view_tab == 1 && (solid_view_mode == 1 || solid_view_mode == 2))))
}
pub(in crate::gui) 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(),
ui.global::<ViewportModel>().get_live_view_mode(),
ui.global::<SolidPreviewModel>().get_view_mode(),
);
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>>,
preview_state: &Arc<SolidPreviewState>,
) -> 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 preview_state_rv = Arc::clone(preview_state);
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_deferred = render_ctx_rv.clone();
let preview_state_deferred = Arc::clone(&preview_state_rv);
let ui_weak_deferred = ui.as_weak();
slint::Timer::single_shot(std::time::Duration::ZERO, move || {
let Some(ui) = ui_weak_deferred.upgrade() else {
return;
};
let ctx = render_ctx_deferred
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
crate::gui::render::camera_lighting::resubmit_at_current_pose(
&ui,
&ctx,
&preview_state_deferred,
);
});
}
});
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_or_either_view_mode() {
for active_tab in [0, 1, 2] {
for render_view_tab in [0, 1] {
for live_view_mode in [0, 1] {
for solid_view_mode in [0, 1, 2, 3] {
assert!(render_is_visible(
active_tab,
render_view_tab,
true,
live_view_mode,
solid_view_mode
));
}
}
}
}
}
#[test]
fn docked_live_render_sub_tab_traces_only_in_path_traced_mode() {
assert!(!render_is_visible(0, 0, false, 0, 0));
assert!(render_is_visible(0, 0, false, 1, 0));
}
#[test]
fn docked_edit_sub_tab_traces_only_in_path_traced_or_both_mode() {
assert!(!render_is_visible(0, 1, false, 1, 0));
assert!(!render_is_visible(0, 1, false, 1, 3));
assert!(render_is_visible(0, 1, false, 1, 1));
assert!(render_is_visible(0, 1, false, 1, 2));
}
#[test]
fn docked_is_never_visible_outside_the_3d_tab() {
for render_view_tab in [0, 1] {
for live_view_mode in [0, 1] {
for solid_view_mode in [0, 1, 2, 3] {
assert!(!render_is_visible(
1,
render_view_tab,
false,
live_view_mode,
solid_view_mode
));
assert!(!render_is_visible(
2,
render_view_tab,
false,
live_view_mode,
solid_view_mode
));
}
}
}
}
#[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, 1, 1),
"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, 1, 1);
assert_eq!(docked, active_tab == 0);
}
}
}
}