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,
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 parallel: Var<ParallelWin>,
45 pub(crate) frame_duration_from_monitor: Var<bool>,
46 pub(crate) root_extenders: Mutex<Vec<Box<dyn FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static>>>,
48 pub(crate) open_nested_handlers: Mutex<Vec<Box<dyn FnMut(&mut OpenNestedHandlerArgs) + Send + 'static>>>,
49
50 pub(crate) windows: IdMap<WindowId, WindowInstance>,
51 widget_update_buf: Vec<(WindowId, WindowNode, Option<WindowVars>)>,
52
53 pub(crate) focused: Var<Option<InteractionPath>>,
54 focused_set: bool,
55}
56impl WindowsService {
57 fn new() -> Self {
58 WINDOWS_APP.init_info_provider(Box::new(WINDOWS));
59 #[cfg(feature = "image")]
60 zng_ext_image::IMAGES_WINDOW.hook_render_windows_service(Box::new(WINDOWS));
61 crate::hooks::hook_events();
62 let sv = Self {
63 exit_on_last_close: var(true),
64 default_render_mode: var_default(),
65 parallel: var_default(),
66 frame_duration_from_monitor: var(true),
67 root_extenders: Mutex::new(vec![]),
68 open_nested_handlers: Mutex::new(vec![]),
69
70 windows: IdMap::new(),
71 widget_update_buf: vec![],
72
73 focused: const_var(None),
74 focused_set: false,
75 };
76 sv.frame_duration_from_monitor
79 .hook(|a| {
80 if *a.value() {
81 WINDOWS_SV.read().set_frame_duration();
82 }
83 true
84 })
85 .perm();
86 sv
87 }
88
89 fn start_widget_update(&mut self, clone_vars: bool) -> Vec<(WindowId, WindowNode, Option<WindowVars>)> {
93 let mut buf = mem::take(&mut self.widget_update_buf);
94 if clone_vars {
95 buf.extend(
96 self.windows
97 .iter_mut()
98 .filter_map(|(k, v)| Some((*k, v.root.take()?, v.vars.clone()))),
99 );
100 } else {
101 buf.extend(self.windows.iter_mut().filter_map(|(k, v)| Some((*k, v.root.take()?, None))));
102 }
103 buf
104 }
105
106 fn finish_widget_update(&mut self, mut nodes: Vec<(WindowId, WindowNode, Option<WindowVars>)>) {
107 for (id, node, _) in nodes.drain(..) {
108 self.windows.get_mut(&id).unwrap().root = Some(node);
109 }
110 self.widget_update_buf = nodes;
111 }
112
113 pub(crate) fn set_frame_duration(&self) {
114 if self.frame_duration_from_monitor.get() {
115 let max = self
116 .windows
117 .values()
118 .filter_map(|v| v.vars.as_ref())
119 .map(|v| v.0.refresh_rate.get())
120 .max()
121 .unwrap_or(60.hertz());
122 VARS.frame_duration().set(max.period());
123 }
124 }
125}
126
127pub struct WINDOWS;
129impl WINDOWS {
130 pub fn exit_on_last_close(&self) -> Var<bool> {
140 WINDOWS_SV.read().exit_on_last_close.clone()
141 }
142
143 pub fn default_render_mode(&self) -> Var<RenderMode> {
148 WINDOWS_SV.read().default_render_mode.clone()
149 }
150
151 pub fn parallel(&self) -> Var<ParallelWin> {
158 WINDOWS_SV.read().parallel.clone()
159 }
160
161 pub fn system_chrome(&self) -> Var<bool> {
170 VIEW_PROCESS_INITED_EVENT.var_map(
171 |a| Some(a.window.contains(WindowCapability::SYSTEM_CHROME)),
172 || VIEW_PROCESS.info().window.contains(WindowCapability::SYSTEM_CHROME),
173 )
174 }
175
176 pub fn frame_duration_from_monitor(&self) -> Var<bool> {
187 WINDOWS_SV.read().frame_duration_from_monitor.clone()
188 }
189}
190impl WINDOWS {
191 pub fn open<F: Future<Output = WindowRoot> + Send + 'static>(
207 &self,
208 window_id: impl Into<WindowId>,
209 new_window: impl IntoFuture<IntoFuture = F>,
210 ) -> ResponseVar<WindowVars> {
211 self.open_impl(window_id.into(), Box::pin(new_window.into_future()), WindowMode::Headed, false)
212 }
213
214 pub fn focus_or_open<F: Future<Output = WindowRoot> + Send + 'static>(
219 &self,
220 window_id: impl Into<WindowId>,
221 new_window: impl IntoFuture<IntoFuture = F>,
222 ) -> ResponseVar<WindowVars> {
223 self.open_impl(window_id.into(), Box::pin(new_window.into_future()), WindowMode::Headed, true)
224 }
225 pub fn open_headless<F: Future<Output = WindowRoot> + Send + 'static>(
233 &self,
234 window_id: impl Into<WindowId>,
235 new_window: impl IntoFuture<IntoFuture = F>,
236 with_renderer: bool,
237 ) -> ResponseVar<WindowVars> {
238 self.open_impl(
239 window_id.into(),
240 Box::pin(new_window.into_future()),
241 if with_renderer {
242 WindowMode::HeadlessWithRenderer
243 } else {
244 WindowMode::Headless
245 },
246 false,
247 )
248 }
249 fn open_impl(
250 &self,
251 window_id: WindowId,
252 new_window: Pin<Box<dyn Future<Output = WindowRoot> + Send + 'static>>,
253 mode: WindowMode,
254 focus_existing: bool,
255 ) -> ResponseVar<WindowVars> {
256 let mode = match (mode, APP.window_mode()) {
257 (m, WindowMode::Headed) => m,
258 (m, WindowMode::HeadlessWithRenderer) => {
259 if m.is_headless() {
260 m
261 } else {
262 WindowMode::HeadlessWithRenderer
263 }
264 }
265 (_, WindowMode::Headless) => WindowMode::Headless,
266 };
267
268 let mut s = WINDOWS_SV.write();
269 match s.windows.entry(window_id) {
270 IdEntry::Vacant(e) => {
271 let (r, rsp) = response_var();
272 e.insert(WindowInstance::new(window_id, mode, new_window, r));
273 rsp
274 }
275 IdEntry::Occupied(e) => {
276 if focus_existing {
277 match &e.get().vars {
278 Some(v) => {
279 v.0.focused.set(true);
280 response_done_var(v.clone())
281 }
282 None => {
283 let (r, rsp) = response_var();
285 UPDATES.once_update("WINDOWS wait build start", move || match WINDOWS.vars(window_id) {
286 Some(v) => r.respond(v),
287 None => tracing::error!("window {window_id:?} build did not start"),
288 });
289 rsp
290 }
291 }
292 } else {
293 panic!("{window_id:?} is already open or opening");
294 }
295 }
296 }
297 }
298
299 pub fn loading_handle(
310 &self,
311 window_id: impl Into<WindowId>,
312 deadline: impl Into<Deadline>,
313 debug_name: impl Into<Txt>,
314 ) -> Option<WindowLoadingHandle> {
315 self.loading_handle_impl(window_id.into(), deadline.into(), debug_name.into())
316 }
317 fn loading_handle_impl(&self, window_id: WindowId, deadline: Deadline, debug_name: Txt) -> Option<WindowLoadingHandle> {
318 let mut s = WINDOWS_SV.write();
319
320 let window = s.windows.get_mut(&window_id)?;
321 if let Some(vars) = &window.vars
322 && !matches!(
323 vars.0.instance_state.get(),
324 WindowInstanceState::Building | WindowInstanceState::Loading
325 )
326 {
327 tracing::debug!("cannot get loading handle `{debug_name}` for window `{window_id:?}`, already loaded");
328 return None;
329 }
330 let h = if let Some(h) = window.pending_loading.upgrade() {
331 h
332 } else {
333 let h: Arc<dyn Any + Send + Sync> = Arc::new(RunOnDrop::new(move || {
334 if let Some(vars) = WINDOWS.vars(window_id) {
335 vars.0.instance_state.modify(move |a| {
336 if matches!(a.value(), WindowInstanceState::Loading) {
337 UPDATES.layout_window(window_id);
338 }
339 });
340 }
341 }));
342 window.pending_loading = Arc::downgrade(&h);
343 h
344 };
345
346 let handle = TIMERS.deadline(deadline);
347 handle
348 .hook(move |a| {
349 let _hold = &h;
350 if a.value().has_elapsed() {
351 tracing::debug!("loading handle `{debug_name}` timeout");
352 false
353 } else {
354 true
355 }
356 })
357 .perm();
358 Some(WindowLoadingHandle(handle))
359 }
360
361 pub fn close(&self, window_id: impl Into<WindowId>) -> ResponseVar<CloseWindowResult> {
368 self.close_together([window_id.into()])
369 }
370
371 pub fn close_together(&self, windows: impl IntoIterator<Item = WindowId>) -> ResponseVar<CloseWindowResult> {
380 self.close_together_impl(windows.into_iter().collect())
381 }
382 fn close_together_impl(&self, request: Vec<WindowId>) -> ResponseVar<CloseWindowResult> {
383 let (r, rsp) = response_var();
384
385 let mut s = WINDOWS_SV.write();
386
387 let mut building = vec![];
389 for id in request.iter().copied() {
390 if let IdEntry::Occupied(w) = s.windows.entry(id) {
391 match &w.get().vars {
392 Some(v) => {
393 let state = v.instance_state();
394 if let WindowInstanceState::Building = state.get() {
395 building.push(state);
396 }
397 }
398 None => {
399 w.remove();
401 }
402 }
403 }
404 }
405 drop(s);
406
407 if building.is_empty() {
408 close_together_all_built(request, r);
409 } else {
410 UPDATES
411 .run(async move {
412 for b in building {
413 b.wait_match(|s| !matches!(s, WindowInstanceState::Building)).await;
414 }
415 close_together_all_built(request, r);
416 })
417 .perm();
418 }
419
420 rsp
421 }
422
423 pub fn close_all(&self) -> ResponseVar<CloseWindowResult> {
431 let set: Vec<_> = WINDOWS_SV.read().windows.keys().copied().collect();
432 self.close_together_impl(set)
433 }
434}
435
436fn close_together_all_built(request: Vec<WindowId>, r: ResponderVar<CloseWindowResult>) {
437 let s = WINDOWS_SV.read();
438 let mut open = IdSet::new();
439 fn collect(s: &WindowsService, request: &mut dyn Iterator<Item = WindowId>, open: &mut IdSet<WindowId>) {
440 for id in request {
441 if let Some(w) = s.windows.get(&id)
442 && open.insert(id)
443 {
444 collect(s, &mut w.vars.as_ref().unwrap().children().get().into_iter(), open);
445 }
446 }
447 }
448 collect(&s, &mut request.into_iter(), &mut open);
449
450 WINDOW_CLOSE_REQUESTED_EVENT.notify(WindowCloseRequestedArgs::now(open));
451 WINDOW_CLOSE_REQUESTED_EVENT
452 .on_event(
453 true,
454 hn_once!(|args: &WindowCloseRequestedArgs| {
455 if args.propagation.is_stopped() {
456 r.respond(CloseWindowResult::Cancel);
457 return;
458 }
459
460 WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(args.windows.clone()));
462 WINDOW_CLOSE_EVENT
463 .on_event(
464 true,
465 hn_once!(|args: &WindowCloseArgs| {
466 let mut nodes;
468 let parallel;
469 {
470 let mut s = WINDOWS_SV.write();
471 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
473 nodes = s.start_widget_update(true);
475 };
476
477 let deinit = |(id, n, _): &mut (WindowId, WindowNode, Option<WindowVars>)| {
478 if args.windows.contains(id) {
479 n.with_root(|n| n.deinit());
480 }
481 };
482 if parallel {
483 nodes.par_iter_mut().with_ctx().for_each(deinit);
484 } else {
485 nodes.iter_mut().for_each(deinit);
486 }
487
488 let mut s = WINDOWS_SV.write();
490 s.finish_widget_update(nodes);
491
492 for id in &args.windows {
493 if let Some(w) = s.windows.remove(id) {
494 let vars = w.vars.unwrap();
495 vars.0.instance_state.set(WindowInstanceState::Closed);
496 if vars.0.focused.get() && APP.window_mode().is_headless() {
497 RAW_WINDOW_FOCUS_EVENT.notify(RawWindowFocusArgs::now(Some(*id), None));
499 }
500 }
501 }
502
503 if s.exit_on_last_close.get()
504 && !s.windows.iter().any(|w| w.1.mode.is_headed())
505 && APP.window_mode().is_headed()
506 {
507 zng_app::APP.exit();
508 }
509
510 r.respond(CloseWindowResult::Closed);
511 }),
512 )
513 .perm();
514
515 WINDOW_CLOSE_EVENT.notify(WindowCloseArgs::now(args.windows.clone()));
517 }),
518 )
519 .perm();
520}
521
522impl WINDOWS {
523 pub fn mode(&self, window_id: impl Into<WindowId>) -> Option<WindowMode> {
527 self.mode_impl(window_id.into())
528 }
529 fn mode_impl(&self, window_id: WindowId) -> Option<WindowMode> {
530 Some(WINDOWS_SV.read().windows.get(&window_id)?.mode)
531 }
532
533 pub fn vars(&self, window_id: impl Into<WindowId>) -> Option<WindowVars> {
535 self.vars_impl(window_id.into())
536 }
537 fn vars_impl(&self, window_id: WindowId) -> Option<WindowVars> {
538 WINDOWS_SV.read().windows.get(&window_id)?.vars.clone()
539 }
540
541 pub fn widget_tree(&self, id: impl Into<WindowId>) -> Option<WidgetInfoTree> {
543 zng_app::window::WindowsService::widget_tree(self, id.into())
544 }
545
546 pub fn widget_info(&self, id: impl Into<WidgetId>) -> Option<WidgetInfo> {
548 zng_app::window::WindowsService::widget_info(self, id.into())
549 }
550
551 pub fn widget_trees(&self) -> Vec<WidgetInfoTree> {
553 WINDOWS_SV.read().windows.values().filter_map(|v| v.info.clone()).collect()
554 }
555}
556impl zng_app::window::WindowsService for WINDOWS {
557 fn widget_tree(&self, id: WindowId) -> Option<WidgetInfoTree> {
558 WINDOWS_SV.read().windows.get(&id)?.info.clone()
559 }
560
561 fn widget_info(&self, id: WidgetId) -> Option<WidgetInfo> {
562 WINDOWS_SV.read().windows.values().find_map(|w| w.info.as_ref()?.get(id))
563 }
564
565 fn update_info(&self, updates: &mut InfoUpdates) {
566 let mut nodes;
567 let parallel;
568 {
569 let mut s = WINDOWS_SV.write();
570 if updates.delivery_list_mut().has_pending_search() {
572 updates
573 .delivery_list_mut()
574 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
575 }
576 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
578 nodes = s.start_widget_update(true);
580 };
581
582 let updates = Arc::new(mem::take(updates));
584 let rebuild_info = |(id, n, vars): &mut (WindowId, WindowNode, Option<WindowVars>)| {
585 if updates.delivery_list().enter_window(*id) {
586 let vars = vars.as_ref().unwrap();
588 let access_enabled = vars.access_enabled().get();
589 let info = n.with_root(|n| {
590 let mut builder = WidgetInfoBuilder::new(
591 updates.clone(),
592 *id,
593 access_enabled,
594 WIDGET.id(),
595 WIDGET.bounds(),
596 WIDGET.border(),
597 vars.scale_factor().get(),
598 );
599 n.info(&mut builder);
600
601 builder.finalize(WINDOW.try_info(), true)
602 });
603 n.win_ctx.set_widget_tree(info.clone());
604 WINDOWS_SV.write().windows.get_mut(id).unwrap().info = Some(info.clone());
605
606 if access_enabled.contains(AccessEnabled::VIEW) {
607 UPDATES.render_window(n.win_ctx.id());
609 }
610 }
611 };
612 if parallel {
613 nodes.par_iter_mut().with_ctx().for_each(rebuild_info);
614 } else {
615 nodes.iter_mut().for_each(rebuild_info);
616 }
617
618 WINDOWS_SV.write().finish_widget_update(nodes);
620 }
621
622 fn update_widgets(&self, updates: &mut WidgetUpdates) {
623 let mut nodes;
624 let parallel;
625 {
626 let mut s = WINDOWS_SV.write();
627 if updates.delivery_list_mut().has_pending_search() {
629 updates
630 .delivery_list_mut()
631 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
632 }
633 parallel = s.parallel.get().contains(ParallelWin::UPDATE);
634 nodes = s.start_widget_update(false);
636 };
637
638 let update = |(id, n, _): &mut (WindowId, WindowNode, _)| {
640 if updates.delivery_list().enter_window(*id) {
641 if n.wgt_ctx.take_reinit() {
642 n.with_root(|n| {
643 n.deinit();
644 n.init();
645 })
646 } else {
647 n.with_root(|n| n.update(updates));
648 }
649 }
650 };
651 if parallel {
652 nodes.par_iter_mut().with_ctx().for_each(update);
653 } else {
654 nodes.iter_mut().for_each(update);
655 }
656
657 WINDOWS_SV.write().finish_widget_update(nodes);
659 }
660
661 fn update_layout(&self, updates: &mut LayoutUpdates) {
662 let mut nodes;
663 let parallel;
664 {
665 let mut s = WINDOWS_SV.write();
666 if updates.delivery_list_mut().has_pending_search() {
668 updates
669 .delivery_list_mut()
670 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
671 }
672 parallel = s.parallel.get().contains(ParallelWin::LAYOUT);
673 nodes = s.start_widget_update(true);
675 };
676
677 let updates = Arc::new(mem::take(updates));
679 let layout = |args: &mut (WindowId, WindowNode, Option<WindowVars>)| {
680 crate::window::layout_open_view(args, &updates);
681 };
682 if parallel {
683 nodes.par_iter_mut().with_ctx().for_each(layout);
684 } else {
685 nodes.iter_mut().for_each(layout);
686 }
687
688 WINDOWS_SV.write().finish_widget_update(nodes);
690 }
691
692 fn update_render(&self, render_widgets: &mut RenderUpdates, render_update_widgets: &mut RenderUpdates) {
693 let mut nodes;
694 let parallel;
695 {
696 let mut s = WINDOWS_SV.write();
697 for d in [&mut *render_widgets, &mut *render_update_widgets] {
699 if d.delivery_list_mut().has_pending_search() {
700 d.delivery_list_mut()
701 .fulfill_search(s.windows.values().filter_map(|w| w.info.as_ref()));
702 }
703 }
704 parallel = s.parallel.get().contains(ParallelWin::RENDER);
705 nodes = s.start_widget_update(true);
707 };
708
709 let render_widgets = Arc::new(mem::take(render_widgets));
711 let render_update_widgets = Arc::new(mem::take(render_update_widgets));
712 let render = |args: &mut (WindowId, WindowNode, Option<WindowVars>)| {
713 crate::window::render(args, &render_widgets, &render_update_widgets);
714 };
715 if parallel {
716 nodes.par_iter_mut().with_ctx().for_each(render);
717 } else {
718 nodes.iter_mut().for_each(render);
719 }
720
721 WINDOWS_SV.write().finish_widget_update(nodes);
723 }
724}
725
726#[cfg(feature = "image")]
727impl WINDOWS {
728 pub fn frame_image(&self, window_id: impl Into<WindowId>, mask: Option<zng_ext_image::ImageMaskMode>) -> zng_ext_image::ImageVar {
736 self.frame_image_task(window_id.into(), Box::new(move |v| v.frame_image(mask)))
737 }
738
739 pub fn frame_image_rect(
747 &self,
748 window_id: impl Into<WindowId>,
749 rect: zng_layout::unit::PxRect,
750 mask: Option<zng_ext_image::ImageMaskMode>,
751 ) -> zng_ext_image::ImageVar {
752 self.frame_image_task(window_id.into(), Box::new(move |v| v.frame_image_rect(rect, mask)))
753 }
754
755 fn frame_image_task(
756 &self,
757 window_id: WindowId,
758 task: Box<
759 dyn FnOnce(
760 &zng_app::view_process::ViewRenderer,
761 ) -> Result<zng_app::view_process::ViewImageHandle, zng_task::channel::ChannelError>
762 + Send
763 + Sync,
764 >,
765 ) -> zng_ext_image::ImageVar {
766 use zng_ext_image::*;
767 use zng_txt::*;
768 use zng_var::*;
769
770 let r = var(ImageEntry::new_loading());
771 let rr = r.read_only();
772
773 UPDATES.once_update("WINDOWS.frame_image", move || {
774 let s = WINDOWS_SV.read();
775 if let Some(w) = &s.windows.get(&window_id) {
776 if !w.mode.has_renderer() {
777 return r.set(ImageEntry::new_error(formatx!("window {window_id} has no renderer")));
778 }
779
780 if let Some(n) = &w.root
781 && let Some(v) = &n.renderer
782 && n.frame_id != zng_view_api::window::FrameId::INVALID
783 {
784 return match task(v) {
786 Ok(handle) => {
787 let img = IMAGES.register(None, (handle, Default::default()));
788 img.set_bind(&r).perm();
789 r.hold(img).perm();
790 }
791 Err(e) => r.set(ImageEntry::new_error(e.to_txt())),
792 };
793 }
794
795 use zng_app::view_process::raw_events::RAW_FRAME_RENDERED_EVENT;
797 let mut task = Some(task);
798 RAW_FRAME_RENDERED_EVENT
799 .hook(move |args| {
800 if args.window_id == window_id {
801 let img = WINDOWS.frame_image_task(window_id, task.take().unwrap());
802 img.set_bind(&r).perm();
803 r.hold(r.clone()).perm();
804 false
805 } else {
806 WINDOWS_SV.read().windows.contains_key(&window_id)
807 }
808 })
809 .perm();
810 } else {
811 r.set(ImageEntry::new_error(formatx!("window {window_id} not found")));
812 }
813 });
814
815 rr
816 }
817}
818impl WINDOWS {
819 pub fn bring_to_top(&self, window_id: impl Into<WindowId>) {
823 self.bring_to_top_impl(window_id.into());
824 }
825 fn bring_to_top_impl(&self, window_id: WindowId) {
826 UPDATES.once_update("WINDOWS.bring_to_top", move || {
827 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
828 && let Some(root) = &w.root
829 && let Some(v) = &root.view_window
830 {
831 let _ = v.bring_to_top();
832 } else {
833 tracing::error!("cannot bring_to_top {window_id}, not open in view-process");
834 }
835 });
836 }
837}
838
839#[expect(non_camel_case_types)]
843pub struct WINDOWS_FOCUS;
844impl WINDOWS_FOCUS {
845 pub fn hook_focus_service(&self, focused: Var<Option<InteractionPath>>) {
849 let mut s = WINDOWS_SV.write();
850 assert!(!s.focused_set, "focus service already hooked");
851 s.focused = focused;
852 let mut handler = crate::hooks::focused_widget_handler();
853 s.focused
854 .hook(move |a| {
855 handler(a.value());
856 true
857 })
858 .perm();
859 }
860
861 pub fn focus(&self, window_id: impl Into<WindowId>) {
865 self.focus_impl(window_id.into());
866 }
867 fn focus_impl(&self, window_id: WindowId) {
868 UPDATES.once_update("WINDOWS.focus", move || {
869 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
870 && let Some(root) = &w.root
871 && let Some(v) = &root.view_window
872 {
873 if !RAW_WINDOW_FOCUS_EVENT.with(|a| matches!(a.latest(), Some(w) if w.new_focus == Some(window_id))) {
874 let _ = v.focus();
875 } else {
876 tracing::debug!("skipping focus window request, already focused");
879 }
880 } else {
881 tracing::error!("cannot focus {window_id}, not open in view-process");
882 }
883 });
884 }
885}
886
887#[expect(non_camel_case_types)]
889pub struct WINDOWS_EXTENSIONS;
890
891#[non_exhaustive]
895pub struct WindowRootExtenderArgs {
896 pub root: UiNode,
899}
900impl WINDOWS_EXTENSIONS {
901 pub fn register_root_extender(&self, extender: impl FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static) {
917 self.register_root_extender_impl(Box::new(extender));
918 }
919 fn register_root_extender_impl(&self, extender: Box<dyn FnMut(WindowRootExtenderArgs) -> UiNode + Send + 'static>) {
920 UPDATES.once_update("WINDOWS.register_root_extender", move || {
921 WINDOWS_SV.write().root_extenders.get_mut().push(extender);
922 });
923 }
924
925 pub fn register_open_nested_handler(&self, handler: impl FnMut(&mut OpenNestedHandlerArgs) + Send + 'static) {
940 self.register_open_nested_handler_impl(Box::new(handler));
941 }
942 fn register_open_nested_handler_impl(&self, handler: Box<dyn FnMut(&mut OpenNestedHandlerArgs) + Send + 'static>) {
943 UPDATES.once_update("WINDOWS.register_open_nested_handler", move || {
944 WINDOWS_SV.write().open_nested_handlers.get_mut().push(handler);
945 });
946 }
947
948 pub fn view_extensions_init(
959 &self,
960 window_id: impl Into<WindowId>,
961 extension_id: ApiExtensionId,
962 request: ApiExtensionPayload,
963 ) -> Result<(), ViewExtensionError> {
964 self.view_extensions_init_impl(window_id.into(), extension_id, request)
965 }
966 fn view_extensions_init_impl(
967 &self,
968 window_id: WindowId,
969 extension_id: ApiExtensionId,
970 request: ApiExtensionPayload,
971 ) -> Result<(), ViewExtensionError> {
972 match WINDOWS_SV.write().windows.get_mut(&window_id) {
973 Some(w) => {
974 if matches!(w.mode, WindowMode::HeadlessWithRenderer) {
975 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
976 } else if let Some(exts) = &mut w.extensions_init {
977 exts.push((extension_id, request));
978 Ok(())
979 } else {
980 Err(ViewExtensionError::AlreadyOpenInViewProcess(window_id))
981 }
982 }
983 None => Err(ViewExtensionError::WindowNotFound(window_id)),
984 }
985 }
986 pub(crate) fn take_view_extensions_init(&self, window_id: WindowId) -> Vec<(ApiExtensionId, ApiExtensionPayload)> {
987 WINDOWS_SV
988 .write()
989 .windows
990 .get_mut(&window_id)
991 .and_then(|w| w.extensions_init.take())
992 .unwrap_or_default()
993 }
994
995 pub fn view_window_extension_raw(
999 &self,
1000 window_id: impl Into<WindowId>,
1001 extension_id: ApiExtensionId,
1002 request: ApiExtensionPayload,
1003 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1004 self.view_window_extension_raw_impl(window_id.into(), extension_id, request)
1005 }
1006 fn view_window_extension_raw_impl(
1007 &self,
1008 window_id: WindowId,
1009 extension_id: ApiExtensionId,
1010 request: ApiExtensionPayload,
1011 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1012 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1013 if let Some(r) = &w.root
1014 && let Some(v) = &r.view_window
1015 {
1016 match v.window_extension_raw(extension_id, request) {
1017 Ok(r) => Ok(r),
1018 Err(_) => Err(ViewExtensionError::Disconnected),
1019 }
1020 } else {
1021 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1022 }
1023 } else {
1024 Err(ViewExtensionError::WindowNotFound(window_id))
1025 }
1026 }
1027
1028 pub fn view_window_extension<I, O>(
1032 &self,
1033 window_id: impl Into<WindowId>,
1034 extension_id: ApiExtensionId,
1035 request: &I,
1036 ) -> Result<O, ViewExtensionError>
1037 where
1038 I: serde::Serialize,
1039 O: serde::de::DeserializeOwned,
1040 {
1041 let window_id = window_id.into();
1042 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1043 if let Some(r) = &w.root
1044 && let Some(v) = &r.view_window
1045 {
1046 match v.window_extension(extension_id, request) {
1047 Ok(r) => match r {
1048 Ok(r) => Ok(r),
1049 Err(e) => Err(ViewExtensionError::Api(e)),
1050 },
1051 Err(_) => Err(ViewExtensionError::Disconnected),
1052 }
1053 } else {
1054 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1055 }
1056 } else {
1057 Err(ViewExtensionError::WindowNotFound(window_id))
1058 }
1059 }
1060
1061 pub fn view_render_extension_raw(
1065 &self,
1066 window_id: impl Into<WindowId>,
1067 extension_id: ApiExtensionId,
1068 request: ApiExtensionPayload,
1069 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1070 self.view_render_extension_raw_impl(window_id.into(), extension_id, request)
1071 }
1072 fn view_render_extension_raw_impl(
1073 &self,
1074 window_id: WindowId,
1075 extension_id: ApiExtensionId,
1076 request: ApiExtensionPayload,
1077 ) -> Result<ApiExtensionPayload, ViewExtensionError> {
1078 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1079 if let Some(r) = &w.root
1080 && let Some(v) = &r.renderer
1081 {
1082 match v.render_extension_raw(extension_id, request) {
1083 Ok(r) => Ok(r),
1084 Err(_) => Err(ViewExtensionError::Disconnected),
1085 }
1086 } else {
1087 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1088 }
1089 } else {
1090 Err(ViewExtensionError::WindowNotFound(window_id))
1091 }
1092 }
1093
1094 pub fn view_render_extension<I, O>(
1098 &self,
1099 window_id: impl Into<WindowId>,
1100 extension_id: ApiExtensionId,
1101 request: &I,
1102 ) -> Result<O, ViewExtensionError>
1103 where
1104 I: serde::Serialize,
1105 O: serde::de::DeserializeOwned,
1106 {
1107 let window_id = window_id.into();
1108 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id) {
1109 if let Some(r) = &w.root
1110 && let Some(v) = &r.renderer
1111 {
1112 match v.render_extension(extension_id, request) {
1113 Ok(r) => match r {
1114 Ok(r) => Ok(r),
1115 Err(e) => Err(ViewExtensionError::Api(e)),
1116 },
1117 Err(_) => Err(ViewExtensionError::Disconnected),
1118 }
1119 } else {
1120 Err(ViewExtensionError::NotOpenInViewProcess(window_id))
1121 }
1122 } else {
1123 Err(ViewExtensionError::WindowNotFound(window_id))
1124 }
1125 }
1126}
1127
1128#[expect(non_camel_case_types)]
1130pub struct WINDOWS_DIALOG;
1131
1132impl WINDOWS_DIALOG {
1133 pub fn native_message_dialog(
1140 &self,
1141 window_id: impl Into<WindowId>,
1142 dialog: zng_view_api::dialog::MsgDialog,
1143 ) -> ResponseVar<zng_view_api::dialog::MsgDialogResponse> {
1144 self.native_message_dialog_impl(window_id.into(), dialog)
1145 }
1146 fn native_message_dialog_impl(
1147 &self,
1148 window_id: WindowId,
1149 dialog: zng_view_api::dialog::MsgDialog,
1150 ) -> ResponseVar<zng_view_api::dialog::MsgDialogResponse> {
1151 let (r, rsp) = response_var();
1152
1153 UPDATES.once_update("WINDOWS.native_message_dialog", move || {
1154 use zng_view_api::dialog::MsgDialogResponse;
1155 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1156 && let Some(root) = &w.root
1157 && let Some(v) = &root.view_window
1158 {
1159 if let Err(e) = v.message_dialog(dialog, r.clone()) {
1160 r.respond(MsgDialogResponse::Error(formatx!("cannot show dialog, {e}")));
1161 }
1162 } else {
1163 r.respond(MsgDialogResponse::Error(formatx!(
1164 "cannot show dialog, {window_id} not open in view-process"
1165 )));
1166 }
1167 });
1168
1169 rsp
1170 }
1171
1172 pub fn native_file_dialog(
1179 &self,
1180 window_id: impl Into<WindowId>,
1181 dialog: zng_view_api::dialog::FileDialog,
1182 ) -> ResponseVar<zng_view_api::dialog::FileDialogResponse> {
1183 self.native_file_dialog_impl(window_id.into(), dialog)
1184 }
1185 fn native_file_dialog_impl(
1186 &self,
1187 window_id: WindowId,
1188 dialog: zng_view_api::dialog::FileDialog,
1189 ) -> ResponseVar<zng_view_api::dialog::FileDialogResponse> {
1190 let (r, rsp) = response_var();
1191
1192 UPDATES.once_update("WINDOWS.native_file_dialog", move || {
1193 use zng_view_api::dialog::FileDialogResponse;
1194 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1195 && let Some(root) = &w.root
1196 && let Some(v) = &root.view_window
1197 {
1198 if let Err(e) = v.file_dialog(dialog, r.clone()) {
1199 r.respond(FileDialogResponse::Error(formatx!("cannot show dialog, {e}")));
1200 }
1201 } else {
1202 r.respond(FileDialogResponse::Error(formatx!(
1203 "cannot show dialog, {window_id} not open in view-process"
1204 )));
1205 }
1206 });
1207
1208 rsp
1209 }
1210
1211 pub fn available_operations(&self) -> WindowCapability {
1216 VIEW_PROCESS.info().window
1217 }
1218}
1219
1220pub struct OpenNestedHandlerArgs {
1224 pub(crate) has_nested: bool,
1225}
1226impl OpenNestedHandlerArgs {
1227 pub(crate) fn new() -> Self {
1228 Self { has_nested: true }
1229 }
1230
1231 pub fn nest(&mut self) -> NestedWindowNode {
1238 NestedWindowNode::new(WINDOW.id())
1239 }
1240}
1241
1242#[allow(non_camel_case_types)]
1244pub struct WINDOWS_DRAG_DROP;
1245impl WINDOWS_DRAG_DROP {
1246 pub fn start_drag_drop(
1250 &self,
1251 window_id: WindowId,
1252 data: Vec<DragDropData>,
1253 allowed_effects: DragDropEffect,
1254 ) -> Result<DragDropId, DragDropError> {
1255 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1256 && let Some(root) = &w.root
1257 && let Some(v) = &root.view_window
1258 {
1259 v.start_drag_drop(data, allowed_effects)
1260 .map_err(|e| DragDropError::CannotStart(e.to_txt()))?
1261 } else {
1262 Err(DragDropError::CannotStart(formatx!("window {window_id} not open in view-process")))
1263 }
1264 }
1265
1266 pub fn drag_dropped(&self, window_id: WindowId, drop_id: DragDropId, applied: DragDropEffect) {
1270 if let Some(w) = WINDOWS_SV.read().windows.get(&window_id)
1271 && let Some(root) = &w.root
1272 && let Some(v) = &root.view_window
1273 {
1274 let _ = v.drag_dropped(drop_id, applied);
1275 }
1276 }
1277}
1278
1279#[cfg(feature = "image")]
1280impl zng_ext_image::ImageRenderWindowsService for WINDOWS {
1281 fn clone_boxed(&self) -> Box<dyn zng_ext_image::ImageRenderWindowsService> {
1282 Box::new(WINDOWS)
1283 }
1284
1285 fn new_window_root(&self, node: UiNode, render_mode: RenderMode) -> Box<dyn zng_ext_image::ImageRenderWindowRoot> {
1286 Box::new(WindowRoot::new_container(
1287 WidgetId::new_unique(),
1288 crate::StartPosition::Default,
1289 false,
1290 true,
1291 Some(render_mode),
1292 crate::HeadlessMonitor::default(),
1293 false,
1294 node,
1295 ))
1296 }
1297
1298 fn set_parent_in_window_context(&self, parent_id: WindowId) {
1299 use crate::WINDOW_Ext as _;
1300
1301 WINDOW.vars().0.parent.set(parent_id);
1302 }
1303
1304 fn enable_frame_capture_in_window_context(&self, mask: Option<zng_ext_image::ImageMaskMode>) {
1305 use crate::WINDOW_Ext as _;
1306
1307 let mode = if let Some(mask) = mask {
1308 crate::FrameCaptureMode::AllMask(mask)
1309 } else {
1310 crate::FrameCaptureMode::All
1311 };
1312 WINDOW.vars().0.frame_capture_mode.set(mode);
1313 }
1314
1315 fn open_headless_window(&self, new_window_root: Box<dyn FnOnce() -> Box<dyn zng_ext_image::ImageRenderWindowRoot> + Send>) {
1316 WINDOWS.open_headless(
1317 WindowId::new_unique(),
1318 async move {
1319 use crate::WINDOW_Ext as _;
1320
1321 let root: Box<dyn std::any::Any> = new_window_root();
1322 let w = *root.downcast::<WindowRoot>().expect("expected `WindowRoot` in image render window");
1323 let vars = WINDOW.vars();
1324 vars.auto_size().set(true);
1325 vars.min_size().set(zng_layout::unit::Length::Px(zng_layout::unit::Px(1)));
1326 w
1327 },
1328 true,
1329 );
1330 }
1331
1332 fn close_window(&self, window_id: WindowId) {
1333 WINDOWS.close(window_id);
1334 }
1335}
1336#[cfg(feature = "image")]
1337impl zng_ext_image::ImageRenderWindowRoot for WindowRoot {}