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_layout::unit::FrequencyUnits;
20use zng_task::{ParallelIteratorExt, rayon::prelude::*};
21use zng_txt::{ToTxt as _, Txt, formatx};
22use zng_unique_id::{IdEntry, IdMap, IdSet};
23use zng_var::{ResponderVar, ResponseVar, VARS, Var, const_var, response_done_var, response_var, var, var_default};
24use zng_view_api::{
25 DragDropId, ViewProcessGen,
26 api_extension::{ApiExtensionId, ApiExtensionPayload},
27 drag_drop::{DragDropData, DragDropEffect, DragDropError},
28 window::{RenderMode, WindowCapability},
29};
30use zng_wgt::prelude::{InteractionPath, UiNode, WidgetInfo, WidgetInfoBuilder};
31
32use crate::{
33 CloseWindowResult, NestedWindowNode, ParallelWin, ViewExtensionError, WINDOW_CLOSE_EVENT, WINDOW_CLOSE_REQUESTED_EVENT,
34 WindowCloseArgs, WindowCloseRequestedArgs, WindowInstance, WindowInstanceState, WindowLoadingHandle, WindowNode, WindowRoot,
35 WindowVars,
36};
37
38app_local! {
39 pub(crate) static WINDOWS_SV: WindowsService = WindowsService::new();
40}
41pub(crate) struct WindowsService {
42 exit_on_last_close: Var<bool>,
43 pub(crate) default_render_mode: Var<RenderMode>,
44 pub(crate) default_cache_shaders: Var<bool>,
45 parallel: Var<ParallelWin>,
46 pub(crate) frame_duration_from_monitor: Var<bool>,
47 pub(crate) root_extenders: Mutex<Vec<Box<dyn FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static>>>,
49 pub(crate) open_nested_handlers: Mutex<Vec<Box<dyn FnMut(&mut OpenNestedHandlerArgs) + Send + 'static>>>,
50
51 pub(crate) windows: IdMap<WindowId, WindowInstance>,
52 widget_update_buf: Vec<WidgetUpdateArgs>,
53
54 pub(crate) focused: Var<Option<InteractionPath>>,
55 focused_set: bool,
56}
57impl WindowsService {
58 fn new() -> Self {
59 WINDOWS_APP.init_info_provider(Box::new(WINDOWS));
60 #[cfg(feature = "image")]
61 zng_ext_image::IMAGES_WINDOW.hook_render_windows_service(Box::new(WINDOWS));
62 crate::hooks::hook_events();
63 let sv = Self {
64 exit_on_last_close: var(true),
65 default_render_mode: var_default(),
66 default_cache_shaders: var(true),
67 parallel: var_default(),
68 frame_duration_from_monitor: var(true),
69 root_extenders: Mutex::new(vec![]),
70 open_nested_handlers: Mutex::new(vec![]),
71
72 windows: IdMap::new(),
73 widget_update_buf: vec![],
74
75 focused: const_var(None),
76 focused_set: false,
77 };
78 sv.frame_duration_from_monitor
81 .hook(|a| {
82 if *a.value() {
83 WINDOWS_SV.read().set_frame_duration();
84 }
85 true
86 })
87 .perm();
88 sv
89 }
90
91 fn start_widget_update(&mut self, use_vars: bool, use_view: bool, use_renderer: bool) -> Vec<WidgetUpdateArgs> {
95 let mut buf = mem::take(&mut self.widget_update_buf);
96 buf.extend(self.windows.iter_mut().filter_map(|(k, v)| {
97 let mut args = WidgetUpdateArgs {
98 node: v.root.take()?,
99 id: *k,
100 vars: None,
101 view_window: None,
102 view_headless: None,
103 renderer: None,
104 };
105 if use_vars {
106 args.vars = v.vars.clone();
107 }
108 if use_view {
109 args.view_window = v.view_window.clone();
110 args.view_headless = v.view_headless.clone();
111 }
112 if use_renderer {
113 args.renderer = v.renderer.clone();
114 }
115 Some(args)
116 }));
117 buf
118 }
119
120 fn finish_widget_update(&mut self, mut nodes: Vec<WidgetUpdateArgs>) {
121 for args in nodes.drain(..) {
122 self.windows.get_mut(&args.id).unwrap().root = Some(args.node);
123 }
124 self.widget_update_buf = nodes;
125 }
126
127 pub(crate) fn set_frame_duration(&self) {
128 if self.frame_duration_from_monitor.get() {
129 let max = self
130 .windows
131 .values()
132 .filter_map(|v| v.vars.as_ref())
133 .map(|v| v.0.refresh_rate.get())
134 .max()
135 .unwrap_or(60.hertz());
136 VARS.frame_duration().set(max.period());
137 }
138 }
139}
140pub(crate) struct WidgetUpdateArgs {
141 pub id: WindowId,
142 pub node: WindowNode,
143 pub vars: Option<WindowVars>,
144 pub view_window: Option<zng_app::view_process::ViewWindow>,
145 pub view_headless: Option<zng_app::view_process::ViewHeadless>,
146 pub renderer: Option<zng_app::view_process::ViewRenderer>,
147}
148
149pub struct WINDOWS;
151impl WINDOWS {
152 pub fn exit_on_last_close(&self) -> Var<bool> {
162 WINDOWS_SV.read().exit_on_last_close.clone()
163 }
164
165 pub fn default_render_mode(&self) -> Var<RenderMode> {
170 WINDOWS_SV.read().default_render_mode.clone()
171 }
172
173 pub fn default_cache_shaders(&self) -> Var<bool> {
183 WINDOWS_SV.read().default_cache_shaders.clone()
184 }
185
186 pub fn parallel(&self) -> Var<ParallelWin> {
193 WINDOWS_SV.read().parallel.clone()
194 }
195
196 pub fn system_chrome(&self) -> Var<bool> {
205 VIEW_PROCESS_INITED_EVENT.var_map(
206 |a| Some(a.window.contains(WindowCapability::SYSTEM_CHROME)),
207 || VIEW_PROCESS.info().window.contains(WindowCapability::SYSTEM_CHROME),
208 )
209 }
210
211 pub fn frame_duration_from_monitor(&self) -> Var<bool> {
222 WINDOWS_SV.read().frame_duration_from_monitor.clone()
223 }
224}
225impl WINDOWS {
226 pub fn open<F: Future<Output = WindowRoot> + Send + 'static>(
242 &self,
243 window_id: impl Into<WindowId>,
244 new_window: impl IntoFuture<IntoFuture = F>,
245 ) -> ResponseVar<WindowVars> {
246 self.open_impl(window_id.into(), Box::pin(new_window.into_future()), WindowMode::Headed, false)
247 }
248
249 pub fn focus_or_open<F: Future<Output = WindowRoot> + Send + 'static>(
254 &self,
255 window_id: impl Into<WindowId>,
256 new_window: impl IntoFuture<IntoFuture = F>,
257 ) -> ResponseVar<WindowVars> {
258 self.open_impl(window_id.into(), Box::pin(new_window.into_future()), WindowMode::Headed, true)
259 }
260 pub fn open_headless<F: Future<Output = WindowRoot> + Send + 'static>(
268 &self,
269 window_id: impl Into<WindowId>,
270 new_window: impl IntoFuture<IntoFuture = F>,
271 with_renderer: bool,
272 ) -> ResponseVar<WindowVars> {
273 self.open_impl(
274 window_id.into(),
275 Box::pin(new_window.into_future()),
276 if with_renderer {
277 WindowMode::HeadlessWithRenderer
278 } else {
279 WindowMode::Headless
280 },
281 false,
282 )
283 }
284 fn open_impl(
285 &self,
286 window_id: WindowId,
287 new_window: Pin<Box<dyn Future<Output = WindowRoot> + Send + 'static>>,
288 mode: WindowMode,
289 focus_existing: bool,
290 ) -> ResponseVar<WindowVars> {
291 let mode = match (mode, APP.window_mode()) {
292 (m, WindowMode::Headed) => m,
293 (m, WindowMode::HeadlessWithRenderer) => {
294 if m.is_headless() {
295 m
296 } else {
297 WindowMode::HeadlessWithRenderer
298 }
299 }
300 (_, WindowMode::Headless) => WindowMode::Headless,
301 };
302
303 let mut s = WINDOWS_SV.write();
304 match s.windows.entry(window_id) {
305 IdEntry::Vacant(e) => {
306 let (r, rsp) = response_var();
307 e.insert(WindowInstance::new(window_id, mode, new_window, r));
308 rsp
309 }
310 IdEntry::Occupied(e) => {
311 if focus_existing {
312 match &e.get().vars {
313 Some(v) => {
314 v.0.focused.set(true);
315 response_done_var(v.clone())
316 }
317 None => {
318 let (r, rsp) = response_var();
320 UPDATES.once_update("WINDOWS wait build start", move || match WINDOWS.vars(window_id) {
321 Some(v) => r.respond(v),
322 None => tracing::error!("window {window_id:?} build did not start"),
323 });
324 rsp
325 }
326 }
327 } else {
328 panic!("{window_id:?} is already open or opening");
329 }
330 }
331 }
332 }
333
334 pub fn loading_handle(
345 &self,
346 window_id: impl Into<WindowId>,
347 deadline: impl Into<Deadline>,
348 debug_name: impl Into<Txt>,
349 ) -> Option<WindowLoadingHandle> {
350 self.loading_handle_impl(window_id.into(), deadline.into(), debug_name.into())
351 }
352 fn loading_handle_impl(&self, window_id: WindowId, deadline: Deadline, debug_name: Txt) -> Option<WindowLoadingHandle> {
353 let mut s = WINDOWS_SV.write();
354
355 let window = s.windows.get_mut(&window_id)?;
356 if let Some(vars) = &window.vars
357 && !matches!(
358 vars.0.instance_state.get(),
359 WindowInstanceState::Building | WindowInstanceState::Loading
360 )
361 {
362 tracing::debug!("cannot get loading handle `{debug_name}` for window `{window_id:?}`, already loaded");
363 return None;
364 }
365 let h = if let Some(h) = window.pending_loading.upgrade() {
366 h
367 } else {
368 let h: Arc<dyn Any + Send + Sync> = Arc::new(RunOnDrop::new(move || {
369 if let Some(vars) = WINDOWS.vars(window_id) {
370 vars.0.instance_state.modify(move |a| {
371 if matches!(a.value(), WindowInstanceState::Loading) {
372 UPDATES.layout_window(window_id);
373 }
374 });
375 }
376 }));
377 window.pending_loading = Arc::downgrade(&h);
378 h
379 };
380
381 let handle = TIMERS.deadline(deadline);
382 handle
383 .hook(move |a| {
384 let _hold = &h;
385 if a.value().has_elapsed() {
386 tracing::debug!("loading handle `{debug_name}` timeout");
387 false
388 } else {
389 true
390 }
391 })
392 .perm();
393 Some(WindowLoadingHandle(handle))
394 }
395
396 pub fn close(&self, window_id: impl Into<WindowId>) -> ResponseVar<CloseWindowResult> {
403 self.close_together([window_id.into()])
404 }
405
406 pub fn close_together(&self, windows: impl IntoIterator<Item = WindowId>) -> ResponseVar<CloseWindowResult> {
415 self.close_together_impl(windows.into_iter().collect())
416 }
417 fn close_together_impl(&self, request: Vec<WindowId>) -> ResponseVar<CloseWindowResult> {
418 let (r, rsp) = response_var();
419
420 let mut s = WINDOWS_SV.write();
421
422 let mut building = vec![];
424 for id in request.iter().copied() {
425 if let IdEntry::Occupied(w) = s.windows.entry(id) {
426 match &w.get().vars {
427 Some(v) => {
428 let state = v.instance_state();
429 if let WindowInstanceState::Building = state.get() {
430 building.push(state);
431 }
432 }
433 None => {
434 w.remove();
436 }
437 }
438 }
439 }
440 drop(s);
441
442 if building.is_empty() {
443 close_together_all_built(request, r);
444 } else {
445 UPDATES
446 .run(async move {
447 for b in building {
448 b.wait_match(|s| !matches!(s, WindowInstanceState::Building)).await;
449 }
450 close_together_all_built(request, r);
451 })
452 .perm();
453 }
454
455 rsp
456 }
457
458 pub fn close_all(&self) -> ResponseVar<CloseWindowResult> {
466 let set: Vec<_> = WINDOWS_SV.read().windows.keys().copied().collect();
467 self.close_together_impl(set)
468 }
469}
470
471fn close_together_all_built(request: Vec<WindowId>, r: ResponderVar<CloseWindowResult>) {
472 let s = WINDOWS_SV.read();
473 let mut open = IdSet::new();
474 fn collect(s: &WindowsService, request: &mut dyn Iterator<Item = WindowId>, open: &mut IdSet<WindowId>) {
475 for id in request {
476 if let Some(w) = s.windows.get(&id)
477 && open.insert(id)
478 {
479 collect(s, &mut w.vars.as_ref().unwrap().children().get().into_iter(), open);
480 }
481 }
482 }
483 collect(&s, &mut request.into_iter(), &mut open);
484
485 WINDOW_CLOSE_REQUESTED_EVENT.notify(WindowCloseRequestedArgs::now(open));
486 WINDOW_CLOSE_REQUESTED_EVENT
487 .on_event(
488 true,
489 hn_once!(|args: &WindowCloseRequestedArgs| {
490 if args.propagation.is_stopped() {
491 r.respond(CloseWindowResult::Cancel);
492 return;
493 }
494
495 WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(args.windows.clone()));
497 WINDOW_CLOSE_EVENT
498 .on_event(
499 true,
500 hn_once!(|args: &WindowCloseArgs| {
501 let mut nodes;
503 let parallel;
504 {
505 let mut s = WINDOWS_SV.write();
506 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
508 nodes = s.start_widget_update(false, false, false);
510 };
511
512 let deinit = |a: &mut WidgetUpdateArgs| {
513 if args.windows.contains(&a.id) {
514 a.node.with_root(|n| n.deinit());
515 }
516 };
517 if parallel {
518 nodes.par_iter_mut().with_ctx().for_each(deinit);
519 } else {
520 nodes.iter_mut().for_each(deinit);
521 }
522
523 let mut s = WINDOWS_SV.write();
525 s.finish_widget_update(nodes);
526
527 for id in &args.windows {
528 if let Some(w) = s.windows.remove(id) {
529 let vars = w.vars.unwrap();
530 vars.0.instance_state.set(WindowInstanceState::Closed);
531 if vars.0.focused.get() && APP.window_mode().is_headless() {
532 RAW_WINDOW_FOCUS_EVENT.notify(RawWindowFocusArgs::now(Some(*id), None));
534 }
535 }
536 }
537
538 if s.exit_on_last_close.get()
539 && !s.windows.iter().any(|w| w.1.mode.is_headed())
540 && APP.window_mode().is_headed()
541 {
542 zng_app::APP.exit();
543 }
544
545 r.respond(CloseWindowResult::Closed);
546 }),
547 )
548 .perm();
549
550 WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(args.windows.clone()));
552 }),
553 )
554 .perm();
555}
556
557impl WINDOWS {
558 pub fn mode(&self, window_id: impl Into<WindowId>) -> Option<WindowMode> {
562 self.mode_impl(window_id.into())
563 }
564 fn mode_impl(&self, window_id: WindowId) -> Option<WindowMode> {
565 Some(WINDOWS_SV.read().windows.get(&window_id)?.mode)
566 }
567
568 pub fn vars(&self, window_id: impl Into<WindowId>) -> Option<WindowVars> {
570 self.vars_impl(window_id.into())
571 }
572 fn vars_impl(&self, window_id: WindowId) -> Option<WindowVars> {
573 WINDOWS_SV.read().windows.get(&window_id)?.vars.clone()
574 }
575
576 pub fn widget_tree(&self, id: impl Into<WindowId>) -> Option<WidgetInfoTree> {
578 zng_app::window::WindowsService::widget_tree(self, id.into())
579 }
580
581 pub fn widget_info(&self, id: impl Into<WidgetId>) -> Option<WidgetInfo> {
583 zng_app::window::WindowsService::widget_info(self, id.into())
584 }
585
586 pub fn widget_trees(&self) -> Vec<WidgetInfoTree> {
588 WINDOWS_SV.read().windows.values().filter_map(|v| v.info.clone()).collect()
589 }
590}
591impl zng_app::window::WindowsService for WINDOWS {
592 fn widget_tree(&self, id: WindowId) -> Option<WidgetInfoTree> {
593 WINDOWS_SV.read().windows.get(&id)?.info.clone()
594 }
595
596 fn widget_info(&self, id: WidgetId) -> Option<WidgetInfo> {
597 WINDOWS_SV.read().windows.values().find_map(|w| w.info.as_ref()?.get(id))
598 }
599
600 fn update_info(&self, updates: &mut InfoUpdates) {
601 let mut nodes;
602 let parallel;
603 {
604 let mut s = WINDOWS_SV.write();
605 if updates.delivery_list_mut().has_pending_search() {
607 updates
608 .delivery_list_mut()
609 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
610 }
611 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
613 nodes = s.start_widget_update(true, false, false);
615 };
616
617 let updates = Arc::new(mem::take(updates));
619 let rebuild_info = |a: &mut WidgetUpdateArgs| {
620 if updates.delivery_list().enter_window(a.id) {
621 let vars = a.vars.as_ref().unwrap();
623 let access_enabled = vars.access_enabled().get();
624 let info = a.node.with_root(|n| {
625 let mut builder = WidgetInfoBuilder::new(
626 updates.clone(),
627 a.id,
628 access_enabled,
629 WIDGET.id(),
630 WIDGET.bounds(),
631 WIDGET.border(),
632 vars.scale_factor().get(),
633 );
634 n.info(&mut builder);
635
636 builder.finalize(WINDOW.try_info(), true)
637 });
638 a.node.win_ctx.set_widget_tree(info.clone());
639 WINDOWS_SV.write().windows.get_mut(&a.id).unwrap().info = Some(info.clone());
640
641 if access_enabled.contains(AccessEnabled::VIEW) {
642 UPDATES.render_window(a.node.win_ctx.id());
644 }
645 }
646 };
647 if parallel {
648 nodes.par_iter_mut().with_ctx().for_each(rebuild_info);
649 } else {
650 nodes.iter_mut().for_each(rebuild_info);
651 }
652
653 WINDOWS_SV.write().finish_widget_update(nodes);
655 }
656
657 fn update_widgets(&self, updates: &mut WidgetUpdates) {
658 let mut nodes;
659 let parallel;
660 {
661 let mut s = WINDOWS_SV.write();
662 if updates.delivery_list_mut().has_pending_search() {
664 updates
665 .delivery_list_mut()
666 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
667 }
668 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
669 nodes = s.start_widget_update(false, false, false);
671 };
672
673 let update = |a: &mut WidgetUpdateArgs| {
675 if updates.delivery_list().enter_window(a.id) {
676 if a.node.wgt_ctx.take_reinit() {
677 a.node.with_root(|n| {
678 n.deinit();
679 n.init();
680 })
681 } else {
682 a.node.with_root(|n| n.update(updates));
683 }
684 }
685 };
686 if parallel {
687 nodes.par_iter_mut().with_ctx().for_each(update);
688 } else {
689 nodes.iter_mut().for_each(update);
690 }
691
692 WINDOWS_SV.write().finish_widget_update(nodes);
694 }
695
696 fn update_layout(&self, updates: &mut LayoutUpdates) {
697 let mut nodes;
698 let parallel;
699 {
700 let mut s = WINDOWS_SV.write();
701 if updates.delivery_list_mut().has_pending_search() {
703 updates
704 .delivery_list_mut()
705 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
706 }
707 parallel = s.parallel.get().contains(ParallelWin::LAYOUT);
708 nodes = s.start_widget_update(true, true, false);
710 };
711
712 let updates = Arc::new(mem::take(updates));
714 let layout = |a: &mut WidgetUpdateArgs| {
715 crate::window::layout_open_view(a, &updates);
716 };
717 if parallel {
718 nodes.par_iter_mut().with_ctx().for_each(layout);
719 } else {
720 nodes.iter_mut().for_each(layout);
721 }
722
723 WINDOWS_SV.write().finish_widget_update(nodes);
725 }
726
727 fn update_render(&self, render_widgets: &mut RenderUpdates, render_update_widgets: &mut RenderUpdates) {
728 let mut nodes;
729 let parallel;
730 {
731 let mut s = WINDOWS_SV.write();
732 for d in [&mut *render_widgets, &mut *render_update_widgets] {
734 if d.delivery_list_mut().has_pending_search() {
735 d.delivery_list_mut()
736 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
737 }
738 }
739 parallel = s.parallel.get().contains(ParallelWin::RENDER);
740 nodes = s.start_widget_update(true, false, true);
742 };
743
744 let render_widgets = Arc::new(mem::take(render_widgets));
746 let render_update_widgets = Arc::new(mem::take(render_update_widgets));
747 let render = |a: &mut WidgetUpdateArgs| {
748 crate::window::render(a, &render_widgets, &render_update_widgets);
749 };
750 if parallel {
751 nodes.par_iter_mut().with_ctx().for_each(render);
752 } else {
753 nodes.iter_mut().for_each(render);
754 }
755
756 WINDOWS_SV.write().finish_widget_update(nodes);
758 }
759}
760
761#[cfg(feature = "image")]
762impl WINDOWS {
763 pub fn frame_image(&self, window_id: impl Into<WindowId>, mask: Option<zng_ext_image::ImageMaskMode>) -> zng_ext_image::ImageVar {
771 self.frame_image_task(window_id.into(), Box::new(move |v| v.frame_image(mask)))
772 }
773
774 pub fn frame_image_rect(
782 &self,
783 window_id: impl Into<WindowId>,
784 rect: zng_layout::unit::PxRect,
785 mask: Option<zng_ext_image::ImageMaskMode>,
786 ) -> zng_ext_image::ImageVar {
787 self.frame_image_task(window_id.into(), Box::new(move |v| v.frame_image_rect(rect, mask)))
788 }
789
790 fn frame_image_task(
791 &self,
792 window_id: WindowId,
793 task: Box<
794 dyn FnOnce(
795 &zng_app::view_process::ViewRenderer,
796 ) -> Result<zng_app::view_process::ViewImageHandle, zng_task::channel::ChannelError>
797 + Send
798 + Sync,
799 >,
800 ) -> zng_ext_image::ImageVar {
801 use zng_ext_image::*;
802 use zng_txt::*;
803 use zng_var::*;
804
805 let r = var(ImageEntry::new_loading());
806 let rr = r.read_only();
807
808 UPDATES.once_update("WINDOWS.frame_image", move || {
809 let s = WINDOWS_SV.read();
810 if let Some(w) = &s.windows.get(&window_id) {
811 if !w.mode.has_renderer() {
812 return r.set(ImageEntry::new_error(formatx!("window {window_id} has no renderer")));
813 }
814
815 if let Some(n) = &w.root
816 && let Some(v) = &w.renderer
817 && n.frame_id != zng_view_api::window::FrameId::INVALID
818 {
819 return match task(v) {
821 Ok(handle) => {
822 let img = IMAGES.register(None, (handle, Default::default()));
823 img.set_bind(&r).perm();
824 r.hold(img).perm();
825 }
826 Err(e) => r.set(ImageEntry::new_error(e.to_txt())),
827 };
828 }
829
830 use zng_app::view_process::raw_events::RAW_FRAME_RENDERED_EVENT;
832 let mut task = Some(task);
833 RAW_FRAME_RENDERED_EVENT
834 .hook(move |args| {
835 if args.window_id == window_id {
836 let img = WINDOWS.frame_image_task(window_id, task.take().unwrap());
837 img.set_bind(&r).perm();
838 r.hold(r.clone()).perm();
839 false
840 } else {
841 WINDOWS_SV.read().windows.contains_key(&window_id)
842 }
843 })
844 .perm();
845 } else {
846 r.set(ImageEntry::new_error(formatx!("window {window_id} not found")));
847 }
848 });
849
850 rr
851 }
852}
853impl WINDOWS {
854 pub fn bring_to_top(&self, window_id: impl Into<WindowId>) {
858 self.bring_to_top_impl(window_id.into());
859 }
860 fn bring_to_top_impl(&self, window_id: WindowId) {
861 UPDATES.once_update("WINDOWS.bring_to_top", move || {
862 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
863 && let Some(v) = &w.view_window
864 {
865 let _ = v.bring_to_top();
866 } else {
867 tracing::error!("cannot bring_to_top {window_id}, not open in view-process");
868 }
869 });
870 }
871}
872
873#[expect(non_camel_case_types)]
877pub struct WINDOWS_FOCUS;
878impl WINDOWS_FOCUS {
879 pub fn hook_focus_service(&self, focused: Var<Option<InteractionPath>>) {
883 let mut s = WINDOWS_SV.write();
884 assert!(!s.focused_set, "focus service already hooked");
885 s.focused = focused;
886 let mut handler = crate::hooks::focused_widget_handler();
887 s.focused
888 .hook(move |a| {
889 handler(a.value());
890 true
891 })
892 .perm();
893 }
894
895 pub fn focus(&self, window_id: impl Into<WindowId>) {
899 self.focus_impl(window_id.into());
900 }
901 fn focus_impl(&self, window_id: WindowId) {
902 UPDATES.once_update("WINDOWS.focus", move || {
903 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
904 && let Some(v) = &w.view_window
905 {
906 if !RAW_WINDOW_FOCUS_EVENT.with(|a| matches!(a.latest(), Some(w) if w.new_focus == Some(window_id))) {
907 let _ = v.focus();
908 } else {
909 tracing::debug!("skipping focus window request, already focused");
912 }
913 } else {
914 tracing::error!("cannot focus {window_id}, not open in view-process");
915 }
916 });
917 }
918}
919
920#[expect(non_camel_case_types)]
922pub struct WINDOWS_EXTENSIONS;
923
924#[non_exhaustive]
928pub struct WindowRootExtenderArgs {
929 pub root: UiNode,
932}
933impl WINDOWS_EXTENSIONS {
934 pub fn register_root_extender(&self, extender: impl FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static) {
950 self.register_root_extender_impl(Box::new(extender));
951 }
952 fn register_root_extender_impl(&self, extender: Box<dyn FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static>) {
953 UPDATES.once_update("WINDOWS.register_root_extender", move || {
954 WINDOWS_SV.write().root_extenders.get_mut().push(extender);
955 });
956 }
957
958 pub fn register_open_nested_handler(&self, handler: impl FnMut(&mut OpenNestedHandlerArgs) + Send + 'static) {
973 self.register_open_nested_handler_impl(Box::new(handler));
974 }
975 fn register_open_nested_handler_impl(&self, handler: Box<dyn FnMut(&mut OpenNestedHandlerArgs) + Send + 'static>) {
976 UPDATES.once_update("WINDOWS.register_open_nested_handler", move || {
977 WINDOWS_SV.write().open_nested_handlers.get_mut().push(handler);
978 });
979 }
980
981 pub fn view_extensions_init(
992 &self,
993 window_id: impl Into<WindowId>,
994 extension_id: ApiExtensionId,
995 request: ApiExtensionPayload,
996 ) -> Result<(), ViewExtensionError> {
997 self.view_extensions_init_impl(window_id.into(), extension_id, request)
998 }
999 fn view_extensions_init_impl(
1000 &self,
1001 window_id: WindowId,
1002 extension_id: ApiExtensionId,
1003 request: ApiExtensionPayload,
1004 ) -> Result<(), ViewExtensionError> {
1005 match WINDOWS_SV.write().windows.get_mut(&window_id) {
1006 Some(w) => {
1007 if matches!(w.mode, WindowMode::Headless) {
1008 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1009 } else if w.view_generation == ViewProcessGen::INVALID || w.view_generation != VIEW_PROCESS.generation() {
1010 w.extensions_init.push((extension_id, request));
1011 Ok(())
1012 } else {
1013 Err(ViewExtensionError::AlreadyOpenInViewProcess(window_id))
1014 }
1015 }
1016 None => Err(ViewExtensionError::WindowNotFound(window_id)),
1017 }
1018 }
1019 pub(crate) fn take_view_extensions_init(&self, window_id: WindowId) -> Vec<(ApiExtensionId, ApiExtensionPayload)> {
1020 WINDOWS_SV
1021 .write()
1022 .windows
1023 .get_mut(&window_id)
1024 .map(|w| mem::take(&mut w.extensions_init))
1025 .unwrap_or_default()
1026 }
1027
1028 pub fn view_window_extension_raw(
1032 &self,
1033 window_id: impl Into<WindowId>,
1034 extension_id: ApiExtensionId,
1035 request: ApiExtensionPayload,
1036 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1037 self.view_window_extension_raw_impl(window_id.into(), extension_id, request)
1038 }
1039 fn view_window_extension_raw_impl(
1040 &self,
1041 window_id: WindowId,
1042 extension_id: ApiExtensionId,
1043 request: ApiExtensionPayload,
1044 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1045 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1046 if let Some(v) = &w.view_window {
1047 match v.window_extension_raw(extension_id, request) {
1048 Ok(r) => Ok(r),
1049 Err(_) => Err(ViewExtensionError::Disconnected),
1050 }
1051 } else {
1052 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1053 }
1054 } else {
1055 Err(ViewExtensionError::WindowNotFound(window_id))
1056 }
1057 }
1058
1059 pub fn view_window_extension<I, O>(
1063 &self,
1064 window_id: impl Into<WindowId>,
1065 extension_id: ApiExtensionId,
1066 request: &I,
1067 ) -> Result<O, ViewExtensionError>
1068 where
1069 I: serde::Serialize,
1070 O: serde::de::DeserializeOwned,
1071 {
1072 let window_id = window_id.into();
1073 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1074 if let Some(v) = &w.view_window {
1075 match v.window_extension(extension_id, request) {
1076 Ok(r) => match r {
1077 Ok(r) => Ok(r),
1078 Err(e) => Err(ViewExtensionError::Api(e)),
1079 },
1080 Err(_) => Err(ViewExtensionError::Disconnected),
1081 }
1082 } else {
1083 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1084 }
1085 } else {
1086 Err(ViewExtensionError::WindowNotFound(window_id))
1087 }
1088 }
1089
1090 pub fn view_render_extension_raw(
1094 &self,
1095 window_id: impl Into<WindowId>,
1096 extension_id: ApiExtensionId,
1097 request: ApiExtensionPayload,
1098 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1099 self.view_render_extension_raw_impl(window_id.into(), extension_id, request)
1100 }
1101 fn view_render_extension_raw_impl(
1102 &self,
1103 window_id: WindowId,
1104 extension_id: ApiExtensionId,
1105 request: ApiExtensionPayload,
1106 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1107 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1108 if let Some(v) = &w.renderer {
1109 match v.render_extension_raw(extension_id, request) {
1110 Ok(r) => Ok(r),
1111 Err(_) => Err(ViewExtensionError::Disconnected),
1112 }
1113 } else {
1114 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1115 }
1116 } else {
1117 Err(ViewExtensionError::WindowNotFound(window_id))
1118 }
1119 }
1120
1121 pub fn view_render_extension<I, O>(
1125 &self,
1126 window_id: impl Into<WindowId>,
1127 extension_id: ApiExtensionId,
1128 request: &I,
1129 ) -> Result<O, ViewExtensionError>
1130 where
1131 I: serde::Serialize,
1132 O: serde::de::DeserializeOwned,
1133 {
1134 let window_id = window_id.into();
1135 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1136 if let Some(v) = &w.renderer {
1137 match v.render_extension(extension_id, request) {
1138 Ok(r) => match r {
1139 Ok(r) => Ok(r),
1140 Err(e) => Err(ViewExtensionError::Api(e)),
1141 },
1142 Err(_) => Err(ViewExtensionError::Disconnected),
1143 }
1144 } else {
1145 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1146 }
1147 } else {
1148 Err(ViewExtensionError::WindowNotFound(window_id))
1149 }
1150 }
1151}
1152
1153#[expect(non_camel_case_types)]
1155pub struct WINDOWS_DIALOG;
1156
1157impl WINDOWS_DIALOG {
1158 pub fn native_message_dialog(
1165 &self,
1166 window_id: impl Into<WindowId>,
1167 dialog: zng_view_api::dialog::MsgDialog,
1168 ) -> ResponseVar<zng_view_api::dialog::MsgDialogResponse> {
1169 self.native_message_dialog_impl(window_id.into(), dialog)
1170 }
1171 fn native_message_dialog_impl(
1172 &self,
1173 window_id: WindowId,
1174 dialog: zng_view_api::dialog::MsgDialog,
1175 ) -> ResponseVar<zng_view_api::dialog::MsgDialogResponse> {
1176 let (r, rsp) = response_var();
1177
1178 UPDATES.once_update("WINDOWS.native_message_dialog", move || {
1179 use zng_view_api::dialog::MsgDialogResponse;
1180 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1181 && let Some(v) = &w.view_window
1182 {
1183 if let Err(e) = v.message_dialog(dialog, r.clone()) {
1184 r.respond(MsgDialogResponse::Error(formatx!("cannot show dialog, {e}")));
1185 }
1186 } else {
1187 r.respond(MsgDialogResponse::Error(formatx!(
1188 "cannot show dialog, {window_id} not open in view-process"
1189 )));
1190 }
1191 });
1192
1193 rsp
1194 }
1195
1196 pub fn native_file_dialog(
1203 &self,
1204 window_id: impl Into<WindowId>,
1205 dialog: zng_view_api::dialog::FileDialog,
1206 ) -> ResponseVar<zng_view_api::dialog::FileDialogResponse> {
1207 self.native_file_dialog_impl(window_id.into(), dialog)
1208 }
1209 fn native_file_dialog_impl(
1210 &self,
1211 window_id: WindowId,
1212 dialog: zng_view_api::dialog::FileDialog,
1213 ) -> ResponseVar<zng_view_api::dialog::FileDialogResponse> {
1214 let (r, rsp) = response_var();
1215
1216 UPDATES.once_update("WINDOWS.native_file_dialog", move || {
1217 use zng_view_api::dialog::FileDialogResponse;
1218 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1219 && let Some(v) = &w.view_window
1220 {
1221 if let Err(e) = v.file_dialog(dialog, r.clone()) {
1222 r.respond(FileDialogResponse::Error(formatx!("cannot show dialog, {e}")));
1223 }
1224 } else {
1225 r.respond(FileDialogResponse::Error(formatx!(
1226 "cannot show dialog, {window_id} not open in view-process"
1227 )));
1228 }
1229 });
1230
1231 rsp
1232 }
1233
1234 pub fn available_operations(&self) -> WindowCapability {
1239 VIEW_PROCESS.info().window
1240 }
1241}
1242
1243pub struct OpenNestedHandlerArgs {
1247 pub(crate) has_nested: bool,
1248}
1249impl OpenNestedHandlerArgs {
1250 pub(crate) fn new() -> Self {
1251 Self { has_nested: true }
1252 }
1253
1254 pub fn nest(&mut self) -> NestedWindowNode {
1261 NestedWindowNode::new(WINDOW.id())
1262 }
1263}
1264
1265#[allow(non_camel_case_types)]
1267pub struct WINDOWS_DRAG_DROP;
1268impl WINDOWS_DRAG_DROP {
1269 pub fn start_drag_drop(
1273 &self,
1274 window_id: WindowId,
1275 data: Vec<DragDropData>,
1276 allowed_effects: DragDropEffect,
1277 ) -> Result<DragDropId, DragDropError> {
1278 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1279 && let Some(v) = &w.view_window
1280 {
1281 v.start_drag_drop(data, allowed_effects)
1282 .map_err(|e| DragDropError::CannotStart(e.to_txt()))?
1283 } else {
1284 Err(DragDropError::CannotStart(formatx!("window {window_id} not open in view-process")))
1285 }
1286 }
1287
1288 pub fn drag_dropped(&self, window_id: WindowId, drop_id: DragDropId, applied: DragDropEffect) {
1292 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1293 && let Some(v) = &w.view_window
1294 {
1295 let _ = v.drag_dropped(drop_id, applied);
1296 }
1297 }
1298}
1299
1300#[cfg(feature = "image")]
1301impl zng_ext_image::ImageRenderWindowsService for WINDOWS {
1302 fn clone_boxed(&self) -> Box<dyn zng_ext_image::ImageRenderWindowsService> {
1303 Box::new(WINDOWS)
1304 }
1305
1306 fn new_window_root(&self, node: UiNode, render_mode: RenderMode) -> Box<dyn zng_ext_image::ImageRenderWindowRoot> {
1307 Box::new(WindowRoot::new_container2(
1308 WidgetId::new_unique(),
1309 crate::StartPosition::Default,
1310 false,
1311 true,
1312 Some(render_mode),
1313 None,
1314 crate::HeadlessMonitor::default(),
1315 false,
1316 node,
1317 ))
1318 }
1319
1320 fn set_parent_in_window_context(&self, parent_id: WindowId) {
1321 use crate::WINDOW_Ext as _;
1322
1323 WINDOW.vars().0.parent.set(parent_id);
1324 }
1325
1326 fn enable_frame_capture_in_window_context(&self, mask: Option<zng_ext_image::ImageMaskMode>) {
1327 use crate::WINDOW_Ext as _;
1328
1329 let mode = if let Some(mask) = mask {
1330 crate::FrameCaptureMode::AllMask(mask)
1331 } else {
1332 crate::FrameCaptureMode::All
1333 };
1334 WINDOW.vars().0.frame_capture_mode.set(mode);
1335 }
1336
1337 fn open_headless_window(&self, new_window_root: Box<dyn FnOnce() -> Box<dyn zng_ext_image::ImageRenderWindowRoot> + Send>) {
1338 WINDOWS.open_headless(
1339 WindowId::new_unique(),
1340 async move {
1341 use crate::WINDOW_Ext as _;
1342
1343 let root: Box<dyn std::any::Any> = new_window_root();
1344 let w = *root.downcast::<WindowRoot>().expect("expected `WindowRoot` in image render window");
1345 let vars = WINDOW.vars();
1346 vars.auto_size().set(true);
1347 vars.min_size().set(zng_layout::unit::Length::Px(zng_layout::unit::Px(1)));
1348 w
1349 },
1350 true,
1351 );
1352 }
1353
1354 fn close_window(&self, window_id: WindowId) {
1355 WINDOWS.close(window_id);
1356 }
1357}
1358#[cfg(feature = "image")]
1359impl zng_ext_image::ImageRenderWindowRoot for WindowRoot {}