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 VIEW_PROCESS.is_available()
232 }
233
234 pub fn update(&mut self, mut wait_app_event: bool) -> AppControlFlow {
239 if self.app.has_exited() {
240 return AppControlFlow::Exit;
241 }
242
243 loop {
244 match self.app.poll(wait_app_event) {
245 AppControlFlow::Poll => {
246 wait_app_event = false;
247 continue;
248 }
249 flow => return flow,
250 }
251 }
252 }
253
254 pub fn update_observe(&mut self, on_pre_update: impl FnOnce() + Send + 'static) -> bool {
256 let u = Arc::new(AtomicBool::new(false));
257 UPDATES
258 .on_pre_update(hn_once!(u, |_| {
259 u.store(true, std::sync::atomic::Ordering::Relaxed);
260 on_pre_update();
261 }))
262 .perm();
263 let _ = self.update(false);
264 u.load(std::sync::atomic::Ordering::Relaxed)
265 }
266
267 pub fn run_task<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>) -> Option<R>
271 where
272 R: Send + 'static,
273 T: Future<Output = R> + Send + 'static,
274 {
275 let task = task.into_future();
276
277 if self.app.has_exited() {
278 return None;
279 }
280
281 let r = Arc::new(Mutex::new(None::<R>));
282 UPDATES
283 .run(async_clmv!(r, {
284 let fr = task.await;
285 *r.lock() = Some(fr);
286 }))
287 .perm();
288
289 loop {
290 match self.app.poll(true) {
291 AppControlFlow::Exit => return None,
292 _ => {
293 let mut r = r.lock();
294 if r.is_some() {
295 return r.take();
296 }
297 }
298 }
299 }
300 }
301
302 pub fn run_task_deadline<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>, deadline: impl Into<Deadline>) -> Option<R>
312 where
313 R: Send + 'static,
314 T: Future<Output = R> + Send + 'static,
315 {
316 let task = task.into_future();
317 let task = zng_task::with_deadline(task, deadline.into());
318 match self.run_task(task)? {
319 Ok(r) => Some(r),
320 Err(e) => {
321 tracing::error!("run_task reached deadline, {e}");
322 None
323 }
324 }
325 }
326
327 #[cfg(any(test, feature = "test_util"))]
331 pub fn run_test<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>) -> Option<R>
332 where
333 R: Send + 'static,
334 T: Future<Output = R> + Send + 'static,
335 {
336 use std::time::Duration;
337
338 thread_local! {
339 static TIMEOUT: Duration = {
340 let t = std::env::var("ZNG_APP_RUN_TEST_TIMEOUT").unwrap_or_else(|_| "60".to_string());
341 let t: u64 = match t.parse() {
342 Ok(0) => 60,
343 Ok(t) => t,
344 Err(_) => 60,
345 };
346 std::time::Duration::from_secs(t)
347 }
348 }
349 let task = task.into_future();
350 let task = zng_task::with_deadline(task, TIMEOUT.with(|t| *t));
351 match self.run_task(task)? {
352 Ok(r) => Some(r),
353 Err(e) => {
354 panic!("run_test {e}")
355 }
356 }
357 }
358
359 pub fn exit(mut self) {
363 let req = APP.exit();
364 while req.is_waiting() {
365 if let AppControlFlow::Exit = self.update(true) {
366 break;
367 }
368 }
369 }
370 pub fn has_exited(&self) -> bool {
374 self.app.has_exited()
375 }
376}
377
378pub struct APP;
380impl APP {
381 pub fn multi_app_enabled(&self) -> bool {
385 cfg!(feature = "multi_app")
386 }
387
388 pub fn is_started(&self) -> bool {
397 LocalContext::current_app().is_some()
398 }
399
400 pub fn is_running(&self) -> bool {
408 self.is_started() && !APP_PROCESS_SV.read().exit
409 }
410
411 pub fn id(&self) -> Option<AppId> {
419 LocalContext::current_app()
420 }
421
422 #[cfg(not(feature = "multi_app"))]
423 fn assert_can_run_single() {
424 use std::sync::atomic::*;
425 static CAN_RUN: AtomicBool = AtomicBool::new(true);
426
427 if !CAN_RUN.swap(false, Ordering::SeqCst) {
428 panic!("only one app is allowed per process")
429 }
430 }
431
432 fn assert_can_run() {
433 #[cfg(not(feature = "multi_app"))]
434 Self::assert_can_run_single();
435 if APP.is_running() {
436 panic!("only one app is allowed per thread")
437 }
438 }
439
440 pub fn window_mode(&self) -> WindowMode {
444 if VIEW_PROCESS.is_available() {
445 if VIEW_PROCESS.is_headless_with_render() {
446 WindowMode::HeadlessWithRenderer
447 } else {
448 WindowMode::Headed
449 }
450 } else {
451 WindowMode::Headless
452 }
453 }
454
455 pub fn device_events_filter(&self) -> Var<DeviceEventsFilter> {
462 APP_PROCESS_SV.read().device_events_filter.clone()
463 }
464}
465
466impl APP {
467 pub fn minimal(&self) -> AppBuilder {
471 zng_env::init_process_name("app-process");
472
473 #[cfg(debug_assertions)]
474 print_tracing(tracing::Level::INFO, false, |_| true);
475 assert_not_view_process();
476 Self::assert_can_run();
477 spawn_deadlock_detection();
478
479 let _ = INSTANT.now();
480 let scope = LocalContext::start_app(AppId::new_unique());
481 AppBuilder {
482 view_process_exe: None,
483 view_process_env: HashMap::new(),
484 with_defaults: false,
485 _cleanup: scope,
486 }
487 }
488
489 pub fn defaults(&self) -> AppBuilder {
495 let mut app = self.minimal();
496 app.with_defaults = true;
497 app
498 }
499}
500
501pub struct AppBuilder {
505 view_process_exe: Option<PathBuf>,
506 view_process_env: HashMap<Txt, Txt>,
507 with_defaults: bool,
508
509 _cleanup: AppScope,
511}
512impl AppBuilder {
513 fn run_impl(self, start: std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>) {
514 let app = RunningApp::start(
515 self._cleanup,
516 true,
517 true,
518 self.view_process_exe,
519 self.view_process_env,
520 !self.with_defaults,
521 );
522
523 UPDATES.run(start).perm();
524
525 app.run_headed();
526 }
527
528 fn run_headless_impl(self, with_renderer: bool) -> HeadlessApp {
529 if with_renderer {
530 unsafe {
532 std::env::set_var("ZNG_VIEW_TIMEOUT", "false");
533 }
534 }
535
536 let app = RunningApp::start(
537 self._cleanup,
538 false,
539 with_renderer,
540 self.view_process_exe,
541 self.view_process_env,
542 !self.with_defaults,
543 );
544
545 HeadlessApp { app }
546 }
547}
548impl AppBuilder {
549 pub fn view_process_exe(mut self, view_process_exe: impl Into<PathBuf>) -> Self {
559 self.view_process_exe = Some(view_process_exe.into());
560 self
561 }
562
563 pub fn view_process_env(mut self, name: impl Into<Txt>, value: impl Into<Txt>) -> Self {
565 self.view_process_env.insert(name.into(), value.into());
566 self
567 }
568
569 pub fn run<F: Future<Output = ()> + Send + 'static>(self, start: impl IntoFuture<IntoFuture = F>) {
576 let start = start.into_future();
577 self.run_impl(Box::pin(start))
578 }
579
580 pub fn run_headless(self, with_renderer: bool) -> HeadlessApp {
585 self.run_headless_impl(with_renderer)
586 }
587}
588
589mod running;
591pub use running::*;
592use zng_view_api::DeviceEventsFilter;
593
594mod private {
595 pub trait Sealed {}
597}
598
599pub fn print_tracing(max: tracing::Level, span_events: bool, filter: impl Fn(&tracing::Metadata) -> bool + Send + Sync + 'static) -> bool {
629 print_tracing_impl(max, span_events, Box::new(filter))
630}
631fn print_tracing_impl(
632 max: tracing::Level,
633 span_events: bool,
634 filter: Box<dyn Fn(&tracing::Metadata) -> bool + Send + Sync + 'static>,
635) -> bool {
636 use tracing_subscriber::prelude::*;
637
638 let layers = tracing_subscriber::registry().with(FilterLayer(max, filter));
639
640 #[cfg(target_os = "android")]
641 let layers = layers.with(tracing_android::layer(&zng_env::about().pkg_name).unwrap());
642 #[cfg(target_os = "android")]
643 let _ = span_events;
644
645 #[cfg(not(target_os = "android"))]
646 let layers = {
647 let mut fmt_layer = tracing_subscriber::fmt::layer().without_time();
648 if span_events {
649 fmt_layer = fmt_layer.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE);
650 }
651
652 #[cfg(target_arch = "wasm32")]
653 let fmt_layer = fmt_layer.with_ansi(false).with_writer(tracing_web::MakeWebConsoleWriter::new());
654
655 layers.with(fmt_layer)
656 };
657
658 layers.try_init().is_ok()
659}
660struct FilterLayer(tracing::Level, Box<dyn Fn(&tracing::Metadata) -> bool + Send + Sync>);
661impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for FilterLayer {
662 fn enabled(&self, metadata: &tracing::Metadata<'_>, _: tracing_subscriber::layer::Context<'_, S>) -> bool {
663 print_tracing_filter(&self.0, metadata, &self.1)
664 }
665
666 fn max_level_hint(&self) -> Option<tracing::metadata::LevelFilter> {
667 Some(self.0.into())
668 }
669
670 #[cfg(any(test, feature = "test_util"))]
671 fn on_event(&self, event: &tracing::Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
672 if event.metadata().level() == &tracing::Level::ERROR && APP.is_running() && TEST_LOG.get() {
673 struct MsgCollector<'a>(&'a mut String);
674 impl tracing::field::Visit for MsgCollector<'_> {
675 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
676 use std::fmt::Write;
677 write!(self.0, "\n {} = {:?}", field.name(), value).unwrap();
678 }
679 }
680
681 let meta = event.metadata();
682 let file = meta.file().unwrap_or("");
683 let line = meta.line().unwrap_or(0);
684
685 let mut msg = format!("[{file}:{line}]");
686 event.record(&mut MsgCollector(&mut msg));
687
688 panic!("[LOG-ERROR]{msg}")
689 }
690 }
691}
692pub fn print_tracing_filter(level: &tracing::Level, metadata: &tracing::Metadata, filter: &dyn Fn(&tracing::Metadata) -> bool) -> bool {
696 if metadata.level() > level {
697 return false;
698 }
699
700 if metadata.level() == &tracing::Level::INFO && level < &tracing::Level::TRACE {
701 if metadata.target() == "zng_webrender::device::gl" {
703 return false;
704 }
705 if metadata.target() == "zng_webrender::renderer::init" {
707 return false;
708 }
709 } else if metadata.level() == &tracing::Level::WARN && level < &tracing::Level::DEBUG {
710 if metadata.target() == "zng_webrender::device::gl" {
713 if metadata.line() == Some(4647) {
716 return false;
717 }
718 }
719
720 if metadata.target() == "font_kit::loaders::freetype" {
723 if metadata.line() == Some(734) {
727 return false;
728 }
729 }
730 }
731
732 filter(metadata)
733}
734
735#[cfg(any(test, feature = "test_util"))]
737pub fn test_log() {
738 TEST_LOG.set(true);
739}
740
741#[cfg(any(test, feature = "test_util"))]
742zng_app_context::app_local! {
743 static TEST_LOG: bool = false;
744}
745
746#[doc(hidden)]
747pub fn name_from_pkg_name(name: &'static str) -> Txt {
748 let mut n = String::new();
749 let mut sep = "";
750 for part in name.split(&['-', '_']) {
751 n.push_str(sep);
752 let mut chars = part.char_indices();
753 let (_, c) = chars.next().unwrap();
754 c.to_uppercase().for_each(|c| n.push(c));
755 if let Some((i, _)) = chars.next() {
756 n.push_str(&part[i..]);
757 }
758 sep = " ";
759 }
760 n.into()
761}
762
763#[doc(hidden)]
764pub fn txt_from_pkg_meta(value: &'static str) -> Txt {
765 value.into()
766}