1use std::{any::Any, mem, pin::Pin, sync::Arc};
2
3use parking_lot::Mutex;
4use zng_app::{
5 APP, Deadline, hn_once,
6 timer::TIMERS,
7 update::{InfoUpdates, LayoutUpdates, RenderUpdates, UPDATES, WidgetUpdates},
8 view_process::{
9 VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT,
10 raw_events::{RAW_WINDOW_FOCUS_EVENT, RawWindowFocusArgs},
11 },
12 widget::{
13 WIDGET, WidgetId,
14 info::{WidgetInfoTree, access::AccessEnabled},
15 },
16 window::{WINDOW, WINDOWS_APP, WindowId, WindowMode},
17};
18use zng_app_context::{RunOnDrop, app_local};
19use zng_task::{ParallelIteratorExt, rayon::prelude::*};
20use zng_txt::{ToTxt as _, Txt, formatx};
21use zng_unique_id::{IdEntry, IdMap, IdSet};
22use zng_var::{ResponderVar, ResponseVar, Var, const_var, response_done_var, response_var, var, var_default};
23use zng_view_api::{
24 DragDropId,
25 api_extension::{ApiExtensionId, ApiExtensionPayload},
26 drag_drop::{DragDropData, DragDropEffect, DragDropError},
27 window::{RenderMode, WindowCapability},
28};
29use zng_wgt::prelude::{InteractionPath, UiNode, WidgetInfo, WidgetInfoBuilder};
30
31use crate::{
32 CloseWindowResult, NestedWindowNode, ParallelWin, ViewExtensionError, WINDOW_CLOSE_EVENT, WINDOW_CLOSE_REQUESTED_EVENT,
33 WindowCloseArgs, WindowCloseRequestedArgs, WindowInstance, WindowInstanceState, WindowLoadingHandle, WindowNode, WindowRoot,
34 WindowVars,
35};
36
37app_local! {
38 pub(crate) static WINDOWS_SV: WindowsService = WindowsService::new();
39}
40pub(crate) struct WindowsService {
41 exit_on_last_close: Var<bool>,
42 pub(crate) default_render_mode: Var<RenderMode>,
43 parallel: Var<ParallelWin>,
44 pub(crate) root_extenders: Mutex<Vec<Box<dyn FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static>>>,
46 pub(crate) open_nested_handlers: Mutex<Vec<Box<dyn FnMut(&mut OpenNestedHandlerArgs) + Send + 'static>>>,
47
48 pub(crate) windows: IdMap<WindowId, WindowInstance>,
49 widget_update_buf: Vec<(WindowId, WindowNode, Option<WindowVars>)>,
50
51 pub(crate) focused: Var<Option<InteractionPath>>,
52 focused_set: bool,
53}
54impl WindowsService {
55 fn new() -> Self {
56 WINDOWS_APP.init_info_provider(Box::new(WINDOWS));
57 #[cfg(feature = "image")]
58 zng_ext_image::IMAGES_WINDOW.hook_render_windows_service(Box::new(WINDOWS));
59 crate::hooks::hook_events();
60 Self {
61 exit_on_last_close: var(true),
62 default_render_mode: var_default(),
63 parallel: var_default(),
64 root_extenders: Mutex::new(vec![]),
65 open_nested_handlers: Mutex::new(vec![]),
66
67 windows: IdMap::new(),
68 widget_update_buf: vec![],
69
70 focused: const_var(None),
71 focused_set: false,
72 }
73 }
74
75 fn start_widget_update(&mut self, clone_vars: bool) -> Vec<(WindowId, WindowNode, Option<WindowVars>)> {
79 let mut buf = mem::take(&mut self.widget_update_buf);
80 if clone_vars {
81 buf.extend(
82 self.windows
83 .iter_mut()
84 .filter_map(|(k, v)| Some((*k, v.root.take()?, v.vars.clone()))),
85 );
86 } else {
87 buf.extend(self.windows.iter_mut().filter_map(|(k, v)| Some((*k, v.root.take()?, None))));
88 }
89 buf
90 }
91
92 fn finish_widget_update(&mut self, mut nodes: Vec<(WindowId, WindowNode, Option<WindowVars>)>) {
93 for (id, node, _) in nodes.drain(..) {
94 self.windows.get_mut(&id).unwrap().root = Some(node);
95 }
96 self.widget_update_buf = nodes;
97 }
98}
99
100pub struct WINDOWS;
102impl WINDOWS {
103 pub fn exit_on_last_close(&self) -> Var<bool> {
113 WINDOWS_SV.read().exit_on_last_close.clone()
114 }
115
116 pub fn default_render_mode(&self) -> Var<RenderMode> {
121 WINDOWS_SV.read().default_render_mode.clone()
122 }
123
124 pub fn parallel(&self) -> Var<ParallelWin> {
131 WINDOWS_SV.read().parallel.clone()
132 }
133
134 pub fn system_chrome(&self) -> Var<bool> {
143 VIEW_PROCESS_INITED_EVENT.var_map(
144 |a| Some(a.window.contains(WindowCapability::SYSTEM_CHROME)),
145 || VIEW_PROCESS.info().window.contains(WindowCapability::SYSTEM_CHROME),
146 )
147 }
148}
149impl WINDOWS {
150 pub fn open<F: Future<Output = WindowRoot> + Send + 'static>(
166 &self,
167 window_id: impl Into<WindowId>,
168 new_window: impl IntoFuture<IntoFuture = F>,
169 ) -> ResponseVar<WindowVars> {
170 self.open_impl(window_id.into(), Box::pin(new_window.into_future()), WindowMode::Headed, false)
171 }
172
173 pub fn focus_or_open<F: Future<Output = WindowRoot> + Send + 'static>(
178 &self,
179 window_id: impl Into<WindowId>,
180 new_window: impl IntoFuture<IntoFuture = F>,
181 ) -> ResponseVar<WindowVars> {
182 self.open_impl(window_id.into(), Box::pin(new_window.into_future()), WindowMode::Headed, true)
183 }
184 pub fn open_headless<F: Future<Output = WindowRoot> + Send + 'static>(
192 &self,
193 window_id: impl Into<WindowId>,
194 new_window: impl IntoFuture<IntoFuture = F>,
195 with_renderer: bool,
196 ) -> ResponseVar<WindowVars> {
197 self.open_impl(
198 window_id.into(),
199 Box::pin(new_window.into_future()),
200 if with_renderer {
201 WindowMode::HeadlessWithRenderer
202 } else {
203 WindowMode::Headless
204 },
205 false,
206 )
207 }
208 fn open_impl(
209 &self,
210 window_id: WindowId,
211 new_window: Pin<Box<dyn Future<Output = WindowRoot> + Send + 'static>>,
212 mode: WindowMode,
213 focus_existing: bool,
214 ) -> ResponseVar<WindowVars> {
215 let mode = match (mode, APP.window_mode()) {
216 (m, WindowMode::Headed) => m,
217 (m, WindowMode::HeadlessWithRenderer) => {
218 if m.is_headless() {
219 m
220 } else {
221 WindowMode::HeadlessWithRenderer
222 }
223 }
224 (_, WindowMode::Headless) => WindowMode::Headless,
225 };
226
227 let mut s = WINDOWS_SV.write();
228 match s.windows.entry(window_id) {
229 IdEntry::Vacant(e) => {
230 let (r, rsp) = response_var();
231 e.insert(WindowInstance::new(window_id, mode, new_window, r));
232 rsp
233 }
234 IdEntry::Occupied(e) => {
235 if focus_existing {
236 match &e.get().vars {
237 Some(v) => {
238 v.0.focused.set(true);
239 response_done_var(v.clone())
240 }
241 None => {
242 let (r, rsp) = response_var();
244 UPDATES.once_update("WINDOWS wait build start", move || match WINDOWS.vars(window_id) {
245 Some(v) => r.respond(v),
246 None => tracing::error!("window {window_id:?} build did not start"),
247 });
248 rsp
249 }
250 }
251 } else {
252 panic!("{window_id:?} is already open or opening");
253 }
254 }
255 }
256 }
257
258 pub fn loading_handle(
269 &self,
270 window_id: impl Into<WindowId>,
271 deadline: impl Into<Deadline>,
272 debug_name: impl Into<Txt>,
273 ) -> Option<WindowLoadingHandle> {
274 self.loading_handle_impl(window_id.into(), deadline.into(), debug_name.into())
275 }
276 fn loading_handle_impl(&self, window_id: WindowId, deadline: Deadline, debug_name: Txt) -> Option<WindowLoadingHandle> {
277 let mut s = WINDOWS_SV.write();
278
279 let window = s.windows.get_mut(&window_id)?;
280 if let Some(vars) = &window.vars
281 && !matches!(
282 vars.0.instance_state.get(),
283 WindowInstanceState::Building | WindowInstanceState::Loading
284 )
285 {
286 tracing::debug!("cannot get loading handle `{debug_name}` for window `{window_id:?}`, already loaded");
287 return None;
288 }
289 let h = if let Some(h) = window.pending_loading.upgrade() {
290 h
291 } else {
292 let h: Arc<dyn Any + Send + Sync> = Arc::new(RunOnDrop::new(move || {
293 if let Some(vars) = WINDOWS.vars(window_id) {
294 vars.0.instance_state.modify(move |a| {
295 if matches!(a.value(), WindowInstanceState::Loading) {
296 UPDATES.layout_window(window_id);
297 }
298 });
299 }
300 }));
301 window.pending_loading = Arc::downgrade(&h);
302 h
303 };
304
305 let handle = TIMERS.deadline(deadline);
306 handle
307 .hook(move |a| {
308 let _hold = &h;
309 if a.value().has_elapsed() {
310 tracing::debug!("loading handle `{debug_name}` timeout");
311 false
312 } else {
313 true
314 }
315 })
316 .perm();
317 Some(WindowLoadingHandle(handle))
318 }
319
320 pub fn close(&self, window_id: impl Into<WindowId>) -> ResponseVar<CloseWindowResult> {
327 self.close_together([window_id.into()])
328 }
329
330 pub fn close_together(&self, windows: impl IntoIterator<Item = WindowId>) -> ResponseVar<CloseWindowResult> {
339 self.close_together_impl(windows.into_iter().collect())
340 }
341 fn close_together_impl(&self, request: Vec<WindowId>) -> ResponseVar<CloseWindowResult> {
342 let (r, rsp) = response_var();
343
344 let mut s = WINDOWS_SV.write();
345
346 let mut building = vec![];
348 for id in request.iter().copied() {
349 if let IdEntry::Occupied(w) = s.windows.entry(id) {
350 match &w.get().vars {
351 Some(v) => {
352 let state = v.instance_state();
353 if let WindowInstanceState::Building = state.get() {
354 building.push(state);
355 }
356 }
357 None => {
358 w.remove();
360 }
361 }
362 }
363 }
364 drop(s);
365
366 if building.is_empty() {
367 close_together_all_built(request, r);
368 } else {
369 UPDATES
370 .run(async move {
371 for b in building {
372 b.wait_match(|s| !matches!(s, WindowInstanceState::Building)).await;
373 }
374 close_together_all_built(request, r);
375 })
376 .perm();
377 }
378
379 rsp
380 }
381
382 pub fn close_all(&self) -> ResponseVar<CloseWindowResult> {
390 let set: Vec<_> = WINDOWS_SV.read().windows.keys().copied().collect();
391 self.close_together_impl(set)
392 }
393}
394
395fn close_together_all_built(request: Vec<WindowId>, r: ResponderVar<CloseWindowResult>) {
396 let s = WINDOWS_SV.read();
397 let mut open = IdSet::new();
398 fn collect(s: &WindowsService, request: &mut dyn Iterator<Item = WindowId>, open: &mut IdSet<WindowId>) {
399 for id in request {
400 if let Some(w) = s.windows.get(&id)
401 && open.insert(id)
402 {
403 collect(s, &mut w.vars.as_ref().unwrap().children().get().into_iter(), open);
404 }
405 }
406 }
407 collect(&s, &mut request.into_iter(), &mut open);
408
409 WINDOW_CLOSE_REQUESTED_EVENT.notify(WindowCloseRequestedArgs::now(open));
410 WINDOW_CLOSE_REQUESTED_EVENT
411 .on_event(
412 true,
413 hn_once!(|args: &WindowCloseRequestedArgs| {
414 if args.propagation.is_stopped() {
415 r.respond(CloseWindowResult::Cancel);
416 return;
417 }
418
419 WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(args.windows.clone()));
421 WINDOW_CLOSE_EVENT
422 .on_event(
423 true,
424 hn_once!(|args: &WindowCloseArgs| {
425 let mut nodes;
427 let parallel;
428 {
429 let mut s = WINDOWS_SV.write();
430 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
432 nodes = s.start_widget_update(true);
434 };
435
436 let deinit = |(id, n, _): &mut (WindowId, WindowNode, Option<WindowVars>)| {
437 if args.windows.contains(id) {
438 n.with_root(|n| n.deinit());
439 }
440 };
441 if parallel {
442 nodes.par_iter_mut().with_ctx().for_each(deinit);
443 } else {
444 nodes.iter_mut().for_each(deinit);
445 }
446
447 let mut s = WINDOWS_SV.write();
449 s.finish_widget_update(nodes);
450
451 for id in &args.windows {
452 if let Some(w) = s.windows.remove(id) {
453 let vars = w.vars.unwrap();
454 vars.0.instance_state.set(WindowInstanceState::Closed);
455 if vars.0.focused.get() && APP.window_mode().is_headless() {
456 RAW_WINDOW_FOCUS_EVENT.notify(RawWindowFocusArgs::now(Some(*id), None));
458 }
459 }
460 }
461
462 if s.exit_on_last_close.get()
463 && !s.windows.iter().any(|w| w.1.mode.is_headed())
464 && APP.window_mode().is_headed()
465 {
466 zng_app::APP.exit();
467 }
468
469 r.respond(CloseWindowResult::Closed);
470 }),
471 )
472 .perm();
473
474 WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(args.windows.clone()));
476 }),
477 )
478 .perm();
479}
480
481impl WINDOWS {
482 pub fn mode(&self, window_id: impl Into<WindowId>) -> Option<WindowMode> {
486 self.mode_impl(window_id.into())
487 }
488 fn mode_impl(&self, window_id: WindowId) -> Option<WindowMode> {
489 Some(WINDOWS_SV.read().windows.get(&window_id)?.mode)
490 }
491
492 pub fn vars(&self, window_id: impl Into<WindowId>) -> Option<WindowVars> {
494 self.vars_impl(window_id.into())
495 }
496 fn vars_impl(&self, window_id: WindowId) -> Option<WindowVars> {
497 WINDOWS_SV.read().windows.get(&window_id)?.vars.clone()
498 }
499
500 pub fn widget_tree(&self, id: impl Into<WindowId>) -> Option<WidgetInfoTree> {
502 zng_app::window::WindowsService::widget_tree(self, id.into())
503 }
504
505 pub fn widget_info(&self, id: impl Into<WidgetId>) -> Option<WidgetInfo> {
507 zng_app::window::WindowsService::widget_info(self, id.into())
508 }
509
510 pub fn widget_trees(&self) -> Vec<WidgetInfoTree> {
512 WINDOWS_SV.read().windows.values().filter_map(|v| v.info.clone()).collect()
513 }
514}
515impl zng_app::window::WindowsService for WINDOWS {
516 fn widget_tree(&self, id: WindowId) -> Option<WidgetInfoTree> {
517 WINDOWS_SV.read().windows.get(&id)?.info.clone()
518 }
519
520 fn widget_info(&self, id: WidgetId) -> Option<WidgetInfo> {
521 WINDOWS_SV.read().windows.values().find_map(|w| w.info.as_ref()?.get(id))
522 }
523
524 fn update_info(&self, updates: &mut InfoUpdates) {
525 let mut nodes;
526 let parallel;
527 {
528 let mut s = WINDOWS_SV.write();
529 if updates.delivery_list_mut().has_pending_search() {
531 updates
532 .delivery_list_mut()
533 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
534 }
535 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
537 nodes = s.start_widget_update(true);
539 };
540
541 let updates = Arc::new(mem::take(updates));
543 let rebuild_info = |(id, n, vars): &mut (WindowId, WindowNode, Option<WindowVars>)| {
544 if updates.delivery_list().enter_window(*id) {
545 let vars = vars.as_ref().unwrap();
547 let access_enabled = vars.access_enabled().get();
548 let info = n.with_root(|n| {
549 let mut builder = WidgetInfoBuilder::new(
550 updates.clone(),
551 *id,
552 access_enabled,
553 WIDGET.id(),
554 WIDGET.bounds(),
555 WIDGET.border(),
556 vars.scale_factor().get(),
557 );
558 n.info(&mut builder);
559
560 builder.finalize(WINDOW.try_info(), true)
561 });
562 n.win_ctx.set_widget_tree(info.clone());
563 WINDOWS_SV.write().windows.get_mut(id).unwrap().info = Some(info.clone());
564
565 if access_enabled.contains(AccessEnabled::VIEW) {
566 UPDATES.render_window(n.win_ctx.id());
568 }
569 }
570 };
571 if parallel {
572 nodes.par_iter_mut().with_ctx().for_each(rebuild_info);
573 } else {
574 nodes.iter_mut().for_each(rebuild_info);
575 }
576
577 WINDOWS_SV.write().finish_widget_update(nodes);
579 }
580
581 fn update_widgets(&self, updates: &mut WidgetUpdates) {
582 let mut nodes;
583 let parallel;
584 {
585 let mut s = WINDOWS_SV.write();
586 if updates.delivery_list_mut().has_pending_search() {
588 updates
589 .delivery_list_mut()
590 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
591 }
592 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
593 nodes = s.start_widget_update(false);
595 };
596
597 let update = |(id, n, _): &mut (WindowId, WindowNode, _)| {
599 if updates.delivery_list().enter_window(*id) {
600 if n.wgt_ctx.take_reinit() {
601 n.with_root(|n| {
602 n.deinit();
603 n.init();
604 })
605 } else {
606 n.with_root(|n| n.update(updates));
607 }
608 }
609 };
610 if parallel {
611 nodes.par_iter_mut().with_ctx().for_each(update);
612 } else {
613 nodes.iter_mut().for_each(update);
614 }
615
616 WINDOWS_SV.write().finish_widget_update(nodes);
618 }
619
620 fn update_layout(&self, updates: &mut LayoutUpdates) {
621 let mut nodes;
622 let parallel;
623 {
624 let mut s = WINDOWS_SV.write();
625 if updates.delivery_list_mut().has_pending_search() {
627 updates
628 .delivery_list_mut()
629 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
630 }
631 parallel = s.parallel.get().contains(ParallelWin::LAYOUT);
632 nodes = s.start_widget_update(true);
634 };
635
636 let updates = Arc::new(mem::take(updates));
638 let layout = |args: &mut (WindowId, WindowNode, Option<WindowVars>)| {
639 crate::window::layout_open_view(args, &updates);
640 };
641 if parallel {
642 nodes.par_iter_mut().with_ctx().for_each(layout);
643 } else {
644 nodes.iter_mut().for_each(layout);
645 }
646
647 WINDOWS_SV.write().finish_widget_update(nodes);
649 }
650
651 fn update_render(&self, render_widgets: &mut RenderUpdates, render_update_widgets: &mut RenderUpdates) {
652 let mut nodes;
653 let parallel;
654 {
655 let mut s = WINDOWS_SV.write();
656 for d in [&mut *render_widgets, &mut *render_update_widgets] {
658 if d.delivery_list_mut().has_pending_search() {
659 d.delivery_list_mut()
660 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
661 }
662 }
663 parallel = s.parallel.get().contains(ParallelWin::RENDER);
664 nodes = s.start_widget_update(true);
666 };
667
668 let render_widgets = Arc::new(mem::take(render_widgets));
670 let render_update_widgets = Arc::new(mem::take(render_update_widgets));
671 let render = |args: &mut (WindowId, WindowNode, Option<WindowVars>)| {
672 crate::window::render(args, &render_widgets, &render_update_widgets);
673 };
674 if parallel {
675 nodes.par_iter_mut().with_ctx().for_each(render);
676 } else {
677 nodes.iter_mut().for_each(render);
678 }
679
680 WINDOWS_SV.write().finish_widget_update(nodes);
682 }
683}
684
685#[cfg(feature = "image")]
686impl WINDOWS {
687 pub fn frame_image(&self, window_id: impl Into<WindowId>, mask: Option<zng_ext_image::ImageMaskMode>) -> zng_ext_image::ImageVar {
695 self.frame_image_task(window_id.into(), Box::new(move |v| v.frame_image(mask)))
696 }
697
698 pub fn frame_image_rect(
706 &self,
707 window_id: impl Into<WindowId>,
708 rect: zng_layout::unit::PxRect,
709 mask: Option<zng_ext_image::ImageMaskMode>,
710 ) -> zng_ext_image::ImageVar {
711 self.frame_image_task(window_id.into(), Box::new(move |v| v.frame_image_rect(rect, mask)))
712 }
713
714 fn frame_image_task(
715 &self,
716 window_id: WindowId,
717 task: Box<
718 dyn FnOnce(
719 &zng_app::view_process::ViewRenderer,
720 ) -> Result<zng_app::view_process::ViewImageHandle, zng_task::channel::ChannelError>
721 + Send
722 + Sync,
723 >,
724 ) -> zng_ext_image::ImageVar {
725 use zng_ext_image::*;
726 use zng_txt::*;
727 use zng_var::*;
728
729 let r = var(ImageEntry::new_loading());
730 let rr = r.read_only();
731
732 UPDATES.once_update("WINDOWS.frame_image", move || {
733 let s = WINDOWS_SV.read();
734 if let Some(w) = &s.windows.get(&window_id) {
735 if !w.mode.has_renderer() {
736 return r.set(ImageEntry::new_error(formatx!("window {window_id} has no renderer")));
737 }
738
739 if let Some(n) = &w.root
740 && let Some(v) = &n.renderer
741 && n.frame_id != zng_view_api::window::FrameId::INVALID
742 {
743 return match task(v) {
745 Ok(handle) => {
746 let img = IMAGES.register(None, (handle, Default::default()));
747 img.set_bind(&r).perm();
748 r.hold(img).perm();
749 }
750 Err(e) => r.set(ImageEntry::new_error(e.to_txt())),
751 };
752 }
753
754 use zng_app::view_process::raw_events::RAW_FRAME_RENDERED_EVENT;
756 let mut task = Some(task);
757 RAW_FRAME_RENDERED_EVENT
758 .hook(move |args| {
759 if args.window_id == window_id {
760 let img = WINDOWS.frame_image_task(window_id, task.take().unwrap());
761 img.set_bind(&r).perm();
762 r.hold(r.clone()).perm();
763 false
764 } else {
765 WINDOWS_SV.read().windows.contains_key(&window_id)
766 }
767 })
768 .perm();
769 } else {
770 r.set(ImageEntry::new_error(formatx!("window {window_id} not found")));
771 }
772 });
773
774 rr
775 }
776}
777impl WINDOWS {
778 pub fn bring_to_top(&self, window_id: impl Into<WindowId>) {
782 self.bring_to_top_impl(window_id.into());
783 }
784 fn bring_to_top_impl(&self, window_id: WindowId) {
785 UPDATES.once_update("WINDOWS.bring_to_top", move || {
786 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
787 && let Some(root) = &w.root
788 && let Some(v) = &root.view_window
789 {
790 let _ = v.bring_to_top();
791 } else {
792 tracing::error!("cannot bring_to_top {window_id}, not open in view-process");
793 }
794 });
795 }
796}
797
798#[expect(non_camel_case_types)]
802pub struct WINDOWS_FOCUS;
803impl WINDOWS_FOCUS {
804 pub fn hook_focus_service(&self, focused: Var<Option<InteractionPath>>) {
808 let mut s = WINDOWS_SV.write();
809 assert!(!s.focused_set, "focus service already hooked");
810 s.focused = focused;
811 let mut handler = crate::hooks::focused_widget_handler();
812 s.focused
813 .hook(move |a| {
814 handler(a.value());
815 true
816 })
817 .perm();
818 }
819
820 pub fn focus(&self, window_id: impl Into<WindowId>) {
824 self.focus_impl(window_id.into());
825 }
826 fn focus_impl(&self, window_id: WindowId) {
827 UPDATES.once_update("WINDOWS.focus", move || {
828 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
829 && let Some(root) = &w.root
830 && let Some(v) = &root.view_window
831 {
832 if !RAW_WINDOW_FOCUS_EVENT.with(|a| matches!(a.latest(), Some(w) if w.new_focus == Some(window_id))) {
833 let _ = v.focus();
834 } else {
835 tracing::debug!("skipping focus window request, already focused");
838 }
839 } else {
840 tracing::error!("cannot focus {window_id}, not open in view-process");
841 }
842 });
843 }
844}
845
846#[expect(non_camel_case_types)]
848pub struct WINDOWS_EXTENSIONS;
849
850#[non_exhaustive]
854pub struct WindowRootExtenderArgs {
855 pub root: UiNode,
858}
859impl WINDOWS_EXTENSIONS {
860 pub fn register_root_extender(&self, extender: impl FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static) {
876 self.register_root_extender_impl(Box::new(extender));
877 }
878 fn register_root_extender_impl(&self, extender: Box<dyn FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static>) {
879 UPDATES.once_update("WINDOWS.register_root_extender", move || {
880 WINDOWS_SV.write().root_extenders.get_mut().push(extender);
881 });
882 }
883
884 pub fn register_open_nested_handler(&self, handler: impl FnMut(&mut OpenNestedHandlerArgs) + Send + 'static) {
899 self.register_open_nested_handler_impl(Box::new(handler));
900 }
901 fn register_open_nested_handler_impl(&self, handler: Box<dyn FnMut(&mut OpenNestedHandlerArgs) + Send + 'static>) {
902 UPDATES.once_update("WINDOWS.register_open_nested_handler", move || {
903 WINDOWS_SV.write().open_nested_handlers.get_mut().push(handler);
904 });
905 }
906
907 pub fn view_extensions_init(
918 &self,
919 window_id: impl Into<WindowId>,
920 extension_id: ApiExtensionId,
921 request: ApiExtensionPayload,
922 ) -> Result<(), ViewExtensionError> {
923 self.view_extensions_init_impl(window_id.into(), extension_id, request)
924 }
925 fn view_extensions_init_impl(
926 &self,
927 window_id: WindowId,
928 extension_id: ApiExtensionId,
929 request: ApiExtensionPayload,
930 ) -> Result<(), ViewExtensionError> {
931 match WINDOWS_SV.write().windows.get_mut(&window_id) {
932 Some(w) => {
933 if matches!(w.mode, WindowMode::HeadlessWithRenderer) {
934 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
935 } else if let Some(exts) = &mut w.extensions_init {
936 exts.push((extension_id, request));
937 Ok(())
938 } else {
939 Err(ViewExtensionError::AlreadyOpenInViewProcess(window_id))
940 }
941 }
942 None => Err(ViewExtensionError::WindowNotFound(window_id)),
943 }
944 }
945 pub(crate) fn take_view_extensions_init(&self, window_id: WindowId) -> Vec<(ApiExtensionId, ApiExtensionPayload)> {
946 WINDOWS_SV
947 .write()
948 .windows
949 .get_mut(&window_id)
950 .and_then(|w| w.extensions_init.take())
951 .unwrap_or_default()
952 }
953
954 pub fn view_window_extension_raw(
958 &self,
959 window_id: impl Into<WindowId>,
960 extension_id: ApiExtensionId,
961 request: ApiExtensionPayload,
962 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
963 self.view_window_extension_raw_impl(window_id.into(), extension_id, request)
964 }
965 fn view_window_extension_raw_impl(
966 &self,
967 window_id: WindowId,
968 extension_id: ApiExtensionId,
969 request: ApiExtensionPayload,
970 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
971 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
972 if let Some(r) = &w.root
973 && let Some(v) = &r.view_window
974 {
975 match v.window_extension_raw(extension_id, request) {
976 Ok(r) => Ok(r),
977 Err(_) => Err(ViewExtensionError::Disconnected),
978 }
979 } else {
980 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
981 }
982 } else {
983 Err(ViewExtensionError::WindowNotFound(window_id))
984 }
985 }
986
987 pub fn view_window_extension<I, O>(
991 &self,
992 window_id: impl Into<WindowId>,
993 extension_id: ApiExtensionId,
994 request: &I,
995 ) -> Result<O, ViewExtensionError>
996 where
997 I: serde::Serialize,
998 O: serde::de::DeserializeOwned,
999 {
1000 let window_id = window_id.into();
1001 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1002 if let Some(r) = &w.root
1003 && let Some(v) = &r.view_window
1004 {
1005 match v.window_extension(extension_id, request) {
1006 Ok(r) => match r {
1007 Ok(r) => Ok(r),
1008 Err(e) => Err(ViewExtensionError::Api(e)),
1009 },
1010 Err(_) => Err(ViewExtensionError::Disconnected),
1011 }
1012 } else {
1013 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1014 }
1015 } else {
1016 Err(ViewExtensionError::WindowNotFound(window_id))
1017 }
1018 }
1019
1020 pub fn view_render_extension_raw(
1024 &self,
1025 window_id: impl Into<WindowId>,
1026 extension_id: ApiExtensionId,
1027 request: ApiExtensionPayload,
1028 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1029 self.view_render_extension_raw_impl(window_id.into(), extension_id, request)
1030 }
1031 fn view_render_extension_raw_impl(
1032 &self,
1033 window_id: WindowId,
1034 extension_id: ApiExtensionId,
1035 request: ApiExtensionPayload,
1036 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1037 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1038 if let Some(r) = &w.root
1039 && let Some(v) = &r.renderer
1040 {
1041 match v.render_extension_raw(extension_id, request) {
1042 Ok(r) => Ok(r),
1043 Err(_) => Err(ViewExtensionError::Disconnected),
1044 }
1045 } else {
1046 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1047 }
1048 } else {
1049 Err(ViewExtensionError::WindowNotFound(window_id))
1050 }
1051 }
1052
1053 pub fn view_render_extension<I, O>(
1057 &self,
1058 window_id: impl Into<WindowId>,
1059 extension_id: ApiExtensionId,
1060 request: &I,
1061 ) -> Result<O, ViewExtensionError>
1062 where
1063 I: serde::Serialize,
1064 O: serde::de::DeserializeOwned,
1065 {
1066 let window_id = window_id.into();
1067 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1068 if let Some(r) = &w.root
1069 && let Some(v) = &r.renderer
1070 {
1071 match v.render_extension(extension_id, request) {
1072 Ok(r) => match r {
1073 Ok(r) => Ok(r),
1074 Err(e) => Err(ViewExtensionError::Api(e)),
1075 },
1076 Err(_) => Err(ViewExtensionError::Disconnected),
1077 }
1078 } else {
1079 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1080 }
1081 } else {
1082 Err(ViewExtensionError::WindowNotFound(window_id))
1083 }
1084 }
1085}
1086
1087#[expect(non_camel_case_types)]
1089pub struct WINDOWS_DIALOG;
1090
1091impl WINDOWS_DIALOG {
1092 pub fn native_message_dialog(
1099 &self,
1100 window_id: impl Into<WindowId>,
1101 dialog: zng_view_api::dialog::MsgDialog,
1102 ) -> ResponseVar<zng_view_api::dialog::MsgDialogResponse> {
1103 self.native_message_dialog_impl(window_id.into(), dialog)
1104 }
1105 fn native_message_dialog_impl(
1106 &self,
1107 window_id: WindowId,
1108 dialog: zng_view_api::dialog::MsgDialog,
1109 ) -> ResponseVar<zng_view_api::dialog::MsgDialogResponse> {
1110 let (r, rsp) = response_var();
1111
1112 UPDATES.once_update("WINDOWS.native_message_dialog", move || {
1113 use zng_view_api::dialog::MsgDialogResponse;
1114 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1115 && let Some(root) = &w.root
1116 && let Some(v) = &root.view_window
1117 {
1118 if let Err(e) = v.message_dialog(dialog, r.clone()) {
1119 r.respond(MsgDialogResponse::Error(formatx!("cannot show dialog, {e}")));
1120 }
1121 } else {
1122 r.respond(MsgDialogResponse::Error(formatx!(
1123 "cannot show dialog, {window_id} not open in view-process"
1124 )));
1125 }
1126 });
1127
1128 rsp
1129 }
1130
1131 pub fn native_file_dialog(
1138 &self,
1139 window_id: impl Into<WindowId>,
1140 dialog: zng_view_api::dialog::FileDialog,
1141 ) -> ResponseVar<zng_view_api::dialog::FileDialogResponse> {
1142 self.native_file_dialog_impl(window_id.into(), dialog)
1143 }
1144 fn native_file_dialog_impl(
1145 &self,
1146 window_id: WindowId,
1147 dialog: zng_view_api::dialog::FileDialog,
1148 ) -> ResponseVar<zng_view_api::dialog::FileDialogResponse> {
1149 let (r, rsp) = response_var();
1150
1151 UPDATES.once_update("WINDOWS.native_file_dialog", move || {
1152 use zng_view_api::dialog::FileDialogResponse;
1153 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1154 && let Some(root) = &w.root
1155 && let Some(v) = &root.view_window
1156 {
1157 if let Err(e) = v.file_dialog(dialog, r.clone()) {
1158 r.respond(FileDialogResponse::Error(formatx!("cannot show dialog, {e}")));
1159 }
1160 } else {
1161 r.respond(FileDialogResponse::Error(formatx!(
1162 "cannot show dialog, {window_id} not open in view-process"
1163 )));
1164 }
1165 });
1166
1167 rsp
1168 }
1169
1170 pub fn available_operations(&self) -> WindowCapability {
1175 VIEW_PROCESS.info().window
1176 }
1177}
1178
1179pub struct OpenNestedHandlerArgs {
1183 pub(crate) has_nested: bool,
1184}
1185impl OpenNestedHandlerArgs {
1186 pub(crate) fn new() -> Self {
1187 Self { has_nested: true }
1188 }
1189
1190 pub fn nest(&mut self) -> NestedWindowNode {
1197 NestedWindowNode::new(WINDOW.id())
1198 }
1199}
1200
1201#[allow(non_camel_case_types)]
1203pub struct WINDOWS_DRAG_DROP;
1204impl WINDOWS_DRAG_DROP {
1205 pub fn start_drag_drop(
1209 &self,
1210 window_id: WindowId,
1211 data: Vec<DragDropData>,
1212 allowed_effects: DragDropEffect,
1213 ) -> Result<DragDropId, DragDropError> {
1214 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1215 && let Some(root) = &w.root
1216 && let Some(v) = &root.view_window
1217 {
1218 v.start_drag_drop(data, allowed_effects)
1219 .map_err(|e| DragDropError::CannotStart(e.to_txt()))?
1220 } else {
1221 Err(DragDropError::CannotStart(formatx!("window {window_id} not open in view-process")))
1222 }
1223 }
1224
1225 pub fn drag_dropped(&self, window_id: WindowId, drop_id: DragDropId, applied: DragDropEffect) {
1229 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1230 && let Some(root) = &w.root
1231 && let Some(v) = &root.view_window
1232 {
1233 let _ = v.drag_dropped(drop_id, applied);
1234 }
1235 }
1236}
1237
1238#[cfg(feature = "image")]
1239impl zng_ext_image::ImageRenderWindowsService for WINDOWS {
1240 fn clone_boxed(&self) -> Box<dyn zng_ext_image::ImageRenderWindowsService> {
1241 Box::new(WINDOWS)
1242 }
1243
1244 fn new_window_root(&self, node: UiNode, render_mode: RenderMode) -> Box<dyn zng_ext_image::ImageRenderWindowRoot> {
1245 Box::new(WindowRoot::new_container(
1246 WidgetId::new_unique(),
1247 crate::StartPosition::Default,
1248 false,
1249 true,
1250 Some(render_mode),
1251 crate::HeadlessMonitor::default(),
1252 false,
1253 node,
1254 ))
1255 }
1256
1257 fn set_parent_in_window_context(&self, parent_id: WindowId) {
1258 use crate::WINDOW_Ext as _;
1259
1260 WINDOW.vars().0.parent.set(parent_id);
1261 }
1262
1263 fn enable_frame_capture_in_window_context(&self, mask: Option<zng_ext_image::ImageMaskMode>) {
1264 use crate::WINDOW_Ext as _;
1265
1266 let mode = if let Some(mask) = mask {
1267 crate::FrameCaptureMode::AllMask(mask)
1268 } else {
1269 crate::FrameCaptureMode::All
1270 };
1271 WINDOW.vars().0.frame_capture_mode.set(mode);
1272 }
1273
1274 fn open_headless_window(&self, new_window_root: Box<dyn FnOnce() -> Box<dyn zng_ext_image::ImageRenderWindowRoot> + Send>) {
1275 WINDOWS.open_headless(
1276 WindowId::new_unique(),
1277 async move {
1278 use crate::WINDOW_Ext as _;
1279
1280 let root: Box<dyn std::any::Any> = new_window_root();
1281 let w = *root.downcast::<WindowRoot>().expect("expected `WindowRoot` in image render window");
1282 let vars = WINDOW.vars();
1283 vars.auto_size().set(true);
1284 vars.min_size().set(zng_layout::unit::Length::Px(zng_layout::unit::Px(1)));
1285 w
1286 },
1287 true,
1288 );
1289 }
1290
1291 fn close_window(&self, window_id: WindowId) {
1292 WINDOWS.close(window_id);
1293 }
1294}
1295#[cfg(feature = "image")]
1296impl zng_ext_image::ImageRenderWindowRoot for WindowRoot {}