1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
13#![recursion_limit = "256"]
14#![expect(clippy::type_complexity)]
16#![warn(unused_extern_crates)]
17#![warn(missing_docs)]
18
19use std::{
20 collections::HashMap,
21 path::PathBuf,
22 sync::{Arc, atomic::AtomicBool},
23};
24
25pub mod access;
26pub mod crash_handler;
27pub mod event;
28pub mod handler;
29pub mod memory_profiler;
30pub mod render;
31pub mod shortcut;
32pub mod third_party;
33pub mod timer;
34pub mod trace_recorder;
35pub mod update;
36pub mod view_process;
37pub mod widget;
38pub mod window;
39
40mod tests;
41
42use parking_lot::Mutex;
43use view_process::VIEW_PROCESS;
44use zng_clone_move::async_clmv;
45#[doc(hidden)]
46pub use zng_layout as layout;
47use zng_txt::Txt;
48#[doc(hidden)]
49pub use zng_var as var;
50use zng_var::Var;
51
52pub use zng_time::{DInstant, Deadline, INSTANT, InstantMode};
53
54use update::UPDATES;
55use window::WindowMode;
56use zng_app_context::{AppId, AppScope, LocalContext};
57
58pub use zng_unique_id::static_id;
59
60#[macro_export]
70macro_rules! enable_widget_macros {
71 () => {
72 #[doc(hidden)]
73 #[allow(unused_extern_crates)]
74 extern crate self as zng;
75
76 #[doc(hidden)]
77 pub use $crate::__proc_macro_util;
78 };
79}
80
81#[doc(hidden)]
82#[allow(unused_extern_crates)]
83extern crate self as zng;
84
85#[doc(hidden)]
86#[allow(unused_extern_crates)]
87extern crate self as zng_app; #[doc(hidden)]
90pub mod __proc_macro_util {
91 #[doc(hidden)]
95 pub use zng_unique_id::static_id;
96
97 #[doc(hidden)]
98 pub mod widget {
99 #[doc(hidden)]
100 pub mod builder {
101 #[doc(hidden)]
102 pub use crate::widget::builder::{
103 AnyArcHandler, HandlerInWhenExprError, Importance, InputKind, PropertyArgs, PropertyId, PropertyInfo, PropertyInput,
104 PropertyInputTypes, PropertyNewArgs, SourceLocation, UiNodeInWhenExprError, WgtInfo, WhenInput, WhenInputMember,
105 WhenInputVar, WidgetBuilding, WidgetType, handler_to_args, iter_input_attributes, nest_group_items, new_dyn_handler,
106 new_dyn_other, new_dyn_ui_node, new_dyn_var, panic_input, ui_node_to_args, value_to_args, var_getter, var_state,
107 var_to_args,
108 };
109 }
110
111 #[doc(hidden)]
112 pub mod base {
113 pub use crate::widget::base::{NonWidgetBase, WidgetBase, WidgetExt, WidgetImpl};
114 }
115
116 #[doc(hidden)]
117 pub mod node {
118 pub use crate::widget::node::{ArcNode, IntoUiNode, UiNode};
119 }
120
121 #[doc(hidden)]
122 pub mod info {
123 pub use crate::widget::info::{WidgetInfoBuilder, WidgetLayout, WidgetMeasure};
124 }
125
126 #[doc(hidden)]
127 pub use crate::widget::{easing_property, widget_new};
128
129 #[doc(hidden)]
130 pub use crate::widget::WIDGET;
131 }
132
133 #[doc(hidden)]
134 pub mod update {
135 pub use crate::update::WidgetUpdates;
136 }
137
138 #[doc(hidden)]
139 pub mod layout {
140 #[doc(hidden)]
141 pub mod unit {
142 #[doc(hidden)]
143 pub use crate::layout::unit::{PxSize, TimeUnits};
144 }
145
146 #[doc(hidden)]
147 pub mod context {
148 #[doc(hidden)]
149 pub use crate::layout::context::LAYOUT;
150 }
151 }
152
153 #[doc(hidden)]
154 pub mod render {
155 pub use crate::render::{FrameBuilder, FrameUpdate};
156 }
157
158 #[doc(hidden)]
159 pub mod handler {
160 #[doc(hidden)]
161 pub use crate::handler::{ArcHandler, hn};
162 }
163
164 #[doc(hidden)]
165 pub mod var {
166 #[doc(hidden)]
167 pub use crate::var::{AnyVar, AnyVarValue, Var, expr_var};
168
169 #[doc(hidden)]
170 pub mod animation {
171 #[doc(hidden)]
172 pub mod easing {
173 #[doc(hidden)]
174 pub use crate::var::animation::easing::{
175 back, bounce, circ, cubic, cubic_bezier, ease_in, ease_in_out, ease_out, ease_out_in, elastic, expo, linear, none,
176 quad, quart, quint, reverse, reverse_out, sine, step_ceil, step_floor,
177 };
178 }
179 }
180 }
181}
182
183#[derive(Copy, Clone, Debug, PartialEq, Eq)]
185#[must_use = "methods that return `AppControlFlow` expect to be inside a controlled loop"]
186pub enum AppControlFlow {
187 Poll,
189 Wait,
193 Exit,
195}
196impl AppControlFlow {
197 #[track_caller]
199 pub fn assert_wait(self) {
200 assert_eq!(AppControlFlow::Wait, self)
201 }
202
203 #[track_caller]
205 pub fn assert_exit(self) {
206 assert_eq!(AppControlFlow::Exit, self)
207 }
208}
209
210pub struct HeadlessApp {
217 app: RunningApp,
218}
219impl HeadlessApp {
220 pub fn renderer_enabled(&mut self) -> bool {
231 if VIEW_PROCESS.is_available() {
232 self.run_task(async {
233 let args = crate::view_process::VIEW_PROCESS_INITED_EVENT.var();
234 args.wait_match(|a| !a.is_empty()).await;
235 });
236 true
237 } else {
238 false
239 }
240 }
241
242 pub fn update(&mut self, mut wait_app_event: bool) -> AppControlFlow {
247 if self.app.has_exited() {
248 return AppControlFlow::Exit;
249 }
250
251 loop {
252 match self.app.poll(wait_app_event) {
253 AppControlFlow::Poll => {
254 wait_app_event = false;
255 continue;
256 }
257 flow => return flow,
258 }
259 }
260 }
261
262 pub fn update_observe(&mut self, on_pre_update: impl FnOnce() + Send + 'static) -> bool {
264 let u = Arc::new(AtomicBool::new(false));
265 UPDATES
266 .on_pre_update(hn_once!(u, |_| {
267 u.store(true, std::sync::atomic::Ordering::Relaxed);
268 on_pre_update();
269 }))
270 .perm();
271 let _ = self.update(false);
272 u.load(std::sync::atomic::Ordering::Relaxed)
273 }
274
275 pub fn run_task<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>) -> Option<R>
279 where
280 R: Send + 'static,
281 T: Future<Output = R> + Send + 'static,
282 {
283 let task = task.into_future();
284
285 if self.app.has_exited() {
286 return None;
287 }
288
289 let r = Arc::new(Mutex::new(None::<R>));
290 UPDATES
291 .run(async_clmv!(r, {
292 let fr = task.await;
293 *r.lock() = Some(fr);
294 }))
295 .perm();
296
297 loop {
298 match self.app.poll(true) {
299 AppControlFlow::Exit => return None,
300 _ => {
301 let mut r = r.lock();
302 if r.is_some() {
303 return r.take();
304 }
305 }
306 }
307 }
308 }
309
310 pub fn run_task_deadline<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>, deadline: impl Into<Deadline>) -> Option<R>
320 where
321 R: Send + 'static,
322 T: Future<Output = R> + Send + 'static,
323 {
324 let task = task.into_future();
325 let task = zng_task::with_deadline(task, deadline.into());
326 match self.run_task(task)? {
327 Ok(r) => Some(r),
328 Err(e) => {
329 tracing::error!("run_task reached deadline, {e}");
330 None
331 }
332 }
333 }
334
335 #[cfg(any(test, feature = "test_util"))]
339 pub fn run_test<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>) -> Option<R>
340 where
341 R: Send + 'static,
342 T: Future<Output = R> + Send + 'static,
343 {
344 use std::time::Duration;
345
346 thread_local! {
347 static TIMEOUT: Duration = {
348 let t = std::env::var("ZNG_APP_RUN_TEST_TIMEOUT").unwrap_or_else(|_| "60".to_string());
349 let t: u64 = match t.parse() {
350 Ok(0) => 60,
351 Ok(t) => t,
352 Err(_) => 60,
353 };
354 std::time::Duration::from_secs(t)
355 }
356 }
357 let task = task.into_future();
358 let task = zng_task::with_deadline(task, TIMEOUT.with(|t| *t));
359 match self.run_task(task)? {
360 Ok(r) => Some(r),
361 Err(e) => {
362 panic!("run_test {e}")
363 }
364 }
365 }
366
367 #[cfg(any(test, feature = "test_util"))]
369 pub fn doc_test_deadline(&self) {
370 zng_task::spawn(async {
371 zng_task::deadline(std::time::Duration::from_secs(65)).await;
372 eprintln!("doc_test_deadline reached 65s deadline");
373 zng_env::exit(-1);
374 });
375 }
376
377 pub fn exit(mut self) {
381 let req = APP.exit();
382 while req.is_waiting() {
383 if let AppControlFlow::Exit = self.update(true) {
384 break;
385 }
386 }
387 }
388 pub fn has_exited(&self) -> bool {
392 self.app.has_exited()
393 }
394}
395
396pub struct APP;
398impl APP {
399 pub fn multi_app_enabled(&self) -> bool {
403 cfg!(feature = "multi_app")
404 }
405
406 pub fn is_started(&self) -> bool {
415 LocalContext::current_app().is_some()
416 }
417
418 pub fn is_running(&self) -> bool {
426 self.is_started() && !APP_PROCESS_SV.read().exit
427 }
428
429 pub fn id(&self) -> Option<AppId> {
437 LocalContext::current_app()
438 }
439
440 #[cfg(not(feature = "multi_app"))]
441 fn assert_can_run_single() {
442 use std::sync::atomic::*;
443 static CAN_RUN: AtomicBool = AtomicBool::new(true);
444
445 if !CAN_RUN.swap(false, Ordering::SeqCst) {
446 panic!("only one app is allowed per process")
447 }
448 }
449
450 fn assert_can_run() {
451 #[cfg(not(feature = "multi_app"))]
452 Self::assert_can_run_single();
453 if APP.is_running() {
454 panic!("only one app is allowed per thread")
455 }
456 }
457
458 pub fn window_mode(&self) -> WindowMode {
462 if VIEW_PROCESS.is_available() {
463 if VIEW_PROCESS.is_headless_with_render() {
464 WindowMode::HeadlessWithRenderer
465 } else {
466 WindowMode::Headed
467 }
468 } else {
469 WindowMode::Headless
470 }
471 }
472
473 pub async fn wait_view_process(&self) {
481 if VIEW_PROCESS.is_available() {
482 view_process::VIEW_PROCESS_INITED_EVENT.wait_match(|_| true).await
483 }
484 }
485
486 pub fn device_events_filter(&self) -> Var<DeviceEventsFilter> {
493 APP_PROCESS_SV.read().device_events_filter.clone()
494 }
495}
496
497impl APP {
498 pub fn minimal(&self) -> AppBuilder {
502 zng_env::init_process_name("app-process");
503
504 #[cfg(debug_assertions)]
505 print_tracing(tracing::Level::INFO, false, |_| true);
506 assert_not_view_process();
507 Self::assert_can_run();
508 spawn_deadlock_detection();
509
510 let _ = INSTANT.now();
511 let scope = LocalContext::start_app(AppId::new_unique());
512 AppBuilder {
513 view_process_exe: None,
514 view_process_env: HashMap::new(),
515 with_defaults: false,
516 _cleanup: scope,
517 }
518 }
519
520 pub fn defaults(&self) -> AppBuilder {
526 let mut app = self.minimal();
527 app.with_defaults = true;
528 app
529 }
530}
531
532pub struct AppBuilder {
536 view_process_exe: Option<PathBuf>,
537 view_process_env: HashMap<Txt, Txt>,
538 with_defaults: bool,
539
540 _cleanup: AppScope,
542}
543impl AppBuilder {
544 fn run_impl(self, start: std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>) {
545 let app = RunningApp::start(
546 self._cleanup,
547 true,
548 true,
549 self.view_process_exe,
550 self.view_process_env,
551 !self.with_defaults,
552 );
553
554 UPDATES.run(start).perm();
555
556 app.run_headed();
557 }
558
559 fn run_headless_impl(self, with_renderer: bool) -> HeadlessApp {
560 if with_renderer {
561 unsafe {
563 std::env::set_var("ZNG_VIEW_TIMEOUT", "false");
564 }
565 }
566
567 let app = RunningApp::start(
568 self._cleanup,
569 false,
570 with_renderer,
571 self.view_process_exe,
572 self.view_process_env,
573 !self.with_defaults,
574 );
575
576 HeadlessApp { app }
577 }
578}
579impl AppBuilder {
580 pub fn view_process_exe(mut self, view_process_exe: impl Into<PathBuf>) -> Self {
590 self.view_process_exe = Some(view_process_exe.into());
591 self
592 }
593
594 pub fn view_process_env(mut self, name: impl Into<Txt>, value: impl Into<Txt>) -> Self {
596 self.view_process_env.insert(name.into(), value.into());
597 self
598 }
599
600 pub fn run<F: Future<Output = ()> + Send + 'static>(self, start: impl IntoFuture<IntoFuture = F>) {
607 let start = start.into_future();
608 self.run_impl(Box::pin(start))
609 }
610
611 pub fn run_headless(self, with_renderer: bool) -> HeadlessApp {
616 self.run_headless_impl(with_renderer)
617 }
618}
619
620mod running;
622pub use running::*;
623use zng_view_api::DeviceEventsFilter;
624
625mod private {
626 pub trait Sealed {}
628}
629
630pub fn print_tracing(max: tracing::Level, span_events: bool, filter: impl Fn(&tracing::Metadata) -> bool + Send + Sync + 'static) -> bool {
660 print_tracing_impl(max, span_events, Box::new(filter))
661}
662fn print_tracing_impl(
663 max: tracing::Level,
664 span_events: bool,
665 filter: Box<dyn Fn(&tracing::Metadata) -> bool + Send + Sync + 'static>,
666) -> bool {
667 use tracing_subscriber::prelude::*;
668
669 let layers = tracing_subscriber::registry().with(FilterLayer(max, filter));
670
671 #[cfg(target_os = "android")]
672 let layers = layers.with(tracing_android::layer(&zng_env::about().pkg_name).unwrap());
673 #[cfg(target_os = "android")]
674 let _ = span_events;
675
676 #[cfg(not(target_os = "android"))]
677 let layers = {
678 let mut fmt_layer = tracing_subscriber::fmt::layer().without_time();
679 if span_events {
680 fmt_layer = fmt_layer.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE);
681 }
682
683 #[cfg(target_arch = "wasm32")]
684 let fmt_layer = fmt_layer.with_ansi(false).with_writer(tracing_web::MakeWebConsoleWriter::new());
685
686 layers.with(fmt_layer)
687 };
688
689 layers.try_init().is_ok()
690}
691struct FilterLayer(tracing::Level, Box<dyn Fn(&tracing::Metadata) -> bool + Send + Sync>);
692impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for FilterLayer {
693 fn enabled(&self, metadata: &tracing::Metadata<'_>, _: tracing_subscriber::layer::Context<'_, S>) -> bool {
694 print_tracing_filter(&self.0, metadata, &self.1)
695 }
696
697 fn max_level_hint(&self) -> Option<tracing::metadata::LevelFilter> {
698 Some(self.0.into())
699 }
700
701 #[cfg(any(test, feature = "test_util"))]
702 fn on_event(&self, event: &tracing::Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
703 if event.metadata().level() == &tracing::Level::ERROR && APP.is_running() && TEST_LOG.get() {
704 struct MsgCollector<'a>(&'a mut String);
705 impl tracing::field::Visit for MsgCollector<'_> {
706 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
707 use std::fmt::Write;
708 write!(self.0, "\n {} = {:?}", field.name(), value).unwrap();
709 }
710 }
711
712 let meta = event.metadata();
713 let file = meta.file().unwrap_or("");
714 let line = meta.line().unwrap_or(0);
715
716 let mut msg = format!("[{file}:{line}]");
717 event.record(&mut MsgCollector(&mut msg));
718
719 panic!("[LOG-ERROR]{msg}")
720 }
721 }
722}
723pub fn print_tracing_filter(level: &tracing::Level, metadata: &tracing::Metadata, filter: &dyn Fn(&tracing::Metadata) -> bool) -> bool {
727 if metadata.level() > level {
728 return false;
729 }
730
731 if metadata.level() == &tracing::Level::INFO && level < &tracing::Level::TRACE {
732 if metadata.target() == "zng_webrender::device::gl" {
734 return false;
735 }
736 if metadata.target() == "zng_webrender::renderer::init" {
738 return false;
739 }
740 } else if metadata.level() == &tracing::Level::WARN && level < &tracing::Level::DEBUG {
741 if metadata.target() == "zng_webrender::device::gl" {
744 if metadata.line() == Some(4647) {
747 return false;
748 }
749 }
750
751 if metadata.target() == "font_kit::loaders::freetype" {
754 if metadata.line() == Some(734) {
758 return false;
759 }
760 }
761 }
762
763 filter(metadata)
764}
765
766#[cfg(any(test, feature = "test_util"))]
768pub fn test_log() {
769 TEST_LOG.set(true);
770}
771
772#[cfg(any(test, feature = "test_util"))]
773zng_app_context::app_local! {
774 static TEST_LOG: bool = false;
775}
776
777#[doc(hidden)]
778pub fn name_from_pkg_name(name: &'static str) -> Txt {
779 let mut n = String::new();
780 let mut sep = "";
781 for part in name.split(&['-', '_']) {
782 n.push_str(sep);
783 let mut chars = part.char_indices();
784 let (_, c) = chars.next().unwrap();
785 c.to_uppercase().for_each(|c| n.push(c));
786 if let Some((i, _)) = chars.next() {
787 n.push_str(&part[i..]);
788 }
789 sep = " ";
790 }
791 n.into()
792}
793
794#[doc(hidden)]
795pub fn txt_from_pkg_meta(value: &'static str) -> Txt {
796 value.into()
797}