pub fn open_popout_window( app: >k::Application, preview: &LauncherPreview, state: &Rc>, child_proc: &Rc>>, popouts: &Rc; 2]>>, widgets: &LauncherWidgets, monitor_id: usize, ) { let already_open = { let popouts = popouts.borrow(); popouts[monitor_id].is_some() }; if already_open { return; } if let Some(binding) = widgets.display_panes[monitor_id] .preview_binding .borrow() .as_ref() { binding.set_enabled(false); } let (breakout_size, breakout_limit) = { let state = state.borrow(); ( state.breakout_size_choice(monitor_id), state.breakout_display_size(), ) }; let title = format!("Lesavka {}", widgets.display_panes[monitor_id].title); let window = gtk::ApplicationWindow::builder() .application(app) .title(&title) .default_width(breakout_size.width) .default_height(breakout_size.height) .build(); super::ui_components::install_css(&window); super::ui_components::install_window_icon(&window); super::ui_components::install_window_chrome(&window, &title); window.set_resizable(false); let picture = gtk::Picture::new(); picture.set_hexpand(true); picture.set_vexpand(true); picture.set_halign(gtk::Align::Fill); picture.set_valign(gtk::Align::Fill); picture.set_can_shrink(true); picture.set_keep_aspect_ratio(true); picture.set_size_request(breakout_size.width, breakout_size.height); let root = gtk::Box::new(gtk::Orientation::Vertical, 0); root.set_hexpand(true); root.set_vexpand(true); root.set_halign(gtk::Align::Fill); root.set_valign(gtk::Align::Fill); root.set_size_request(breakout_size.width, breakout_size.height); let frame = gtk::AspectFrame::new(0.5, 0.5, 16.0 / 9.0, false); frame.set_hexpand(true); frame.set_vexpand(true); frame.set_halign(gtk::Align::Fill); frame.set_valign(gtk::Align::Fill); frame.set_size_request(breakout_size.width, breakout_size.height); frame.set_child(Some(&picture)); root.append(&frame); let stream_status = gtk::Label::new(Some("")); let binding = preview .install_on_picture(monitor_id, PreviewSurface::Window, &picture, &stream_status) .expect("preview binding for popout"); window.set_child(Some(&root)); install_popout_drag(&window, &picture); apply_popout_window_geometry(&window, &root, &picture, breakout_size, breakout_limit); let state_handle = Rc::clone(state); let child_proc_handle = Rc::clone(child_proc); let popouts_handle = Rc::clone(popouts); let widgets_handle = widgets.clone(); let close_binding = binding.clone(); window.connect_close_request(move |_| { let handle = { let mut popouts = popouts_handle.borrow_mut(); popouts[monitor_id].take() }; if let Some(handle) = handle { handle.binding.close(); if let Some(preview_binding) = widgets_handle.display_panes[monitor_id] .preview_binding .borrow() .as_ref() { preview_binding.set_enabled(true); } { let mut state = state_handle.borrow_mut(); state.set_display_surface(monitor_id, DisplaySurface::Preview); } let child_running = child_proc_handle.borrow().is_some(); let state_snapshot = state_handle.borrow().clone(); refresh_launcher_ui(&widgets_handle, &state_snapshot, child_running); } else { close_binding.close(); } glib::Propagation::Proceed }); { let mut state = state.borrow_mut(); state.set_display_surface(monitor_id, DisplaySurface::Window); } { let mut popouts = popouts.borrow_mut(); popouts[monitor_id] = Some(PopoutWindowHandle { window: window.clone(), root: root.clone(), frame: frame.clone(), picture: picture.clone(), status_label: stream_status.clone(), binding, }); } let child_running = child_proc.borrow().is_some(); let state_snapshot = state.borrow().clone(); refresh_launcher_ui(widgets, &state_snapshot, child_running); window.present(); schedule_popout_window_geometry( window.clone(), root.clone(), picture.clone(), breakout_size, breakout_limit, ); } pub fn apply_popout_window_size( handle: &PopoutWindowHandle, size: BreakoutSizeChoice, display_limit: super::state::PreviewSourceSize, ) { handle.frame.set_size_request(size.width, size.height); apply_popout_window_geometry( &handle.window, &handle.root, &handle.picture, size, display_limit, ); handle.window.present(); schedule_popout_window_geometry( handle.window.clone(), handle.root.clone(), handle.picture.clone(), size, display_limit, ); } pub fn dock_display_to_preview( state: &Rc>, child_proc: &Rc>>, popouts: &Rc; 2]>>, widgets: &LauncherWidgets, monitor_id: usize, ) { let handle = { let mut popouts = popouts.borrow_mut(); popouts[monitor_id].take() }; if let Some(handle) = handle { handle.binding.close(); handle.window.close(); } if let Some(binding) = widgets.display_panes[monitor_id] .preview_binding .borrow() .as_ref() { binding.set_enabled(true); } { let mut state = state.borrow_mut(); state.set_display_surface(monitor_id, DisplaySurface::Preview); } let child_running = child_proc.borrow().is_some(); if !child_running { let pane = &widgets.display_panes[monitor_id]; pane.picture.set_paintable(Option::<&gdk::Paintable>::None); pane.preview_placeholder.set_visible(true); pane.stream_status.set_text("Connect relay to preview."); } let state_snapshot = state.borrow().clone(); refresh_launcher_ui(widgets, &state_snapshot, child_running); } pub fn dock_all_displays_to_preview( state: &Rc>, child_proc: &Rc>>, popouts: &Rc; 2]>>, widgets: &LauncherWidgets, ) { let mut handles = Vec::new(); { let mut popouts = popouts.borrow_mut(); for monitor_id in 0..2 { if let Some(handle) = popouts[monitor_id].take() { handles.push(handle); } } } for handle in handles { handle.binding.close(); handle.window.close(); } for monitor_id in 0..2 { if let Some(binding) = widgets.display_panes[monitor_id] .preview_binding .borrow() .as_ref() { binding.set_enabled(true); } } { let mut state = state.borrow_mut(); for monitor_id in 0..2 { state.set_display_surface(monitor_id, DisplaySurface::Preview); } } let child_running = child_proc.borrow().is_some(); if !child_running { for pane in &widgets.display_panes { pane.picture.set_paintable(Option::<&gdk::Paintable>::None); pane.preview_placeholder.set_visible(true); pane.stream_status.set_text("Connect relay to preview."); } } let state_snapshot = state.borrow().clone(); refresh_launcher_ui(widgets, &state_snapshot, child_running); } pub fn refresh_display_pane(pane: &DisplayPaneWidgets, surface: DisplaySurface) { if let Some(binding) = pane.preview_binding.borrow().as_ref() { binding.set_enabled(matches!(surface, DisplaySurface::Preview)); } let preview_ready = pane.preview_binding.borrow().is_some(); pane.action_button.set_sensitive(preview_ready); pane.clip_button.set_sensitive(preview_ready); pane.record_button.set_sensitive(preview_ready); pane.save_button.set_sensitive(true); match surface { DisplaySurface::Preview => { pane.stack.set_visible_child_name("preview"); pane.action_button.set_label("Break Out"); pane.preview_placeholder .set_visible(pane.picture.paintable().is_none()); if pane.preview_binding.borrow().is_none() { pane.stream_status.set_text("Preview unavailable"); } } DisplaySurface::Window => { pane.stack.set_visible_child_name("placeholder"); pane.action_button.set_label("Return"); pane.stream_status.set_text("Streaming in its own window"); pane.preview_placeholder.set_visible(false); } } }