zng_app/
lib.rs

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//!
4//! App process implementation.
5//!
6//! # Widget Instantiation
7//!
8//! See [`enable_widget_macros!`] if you want to instantiate widgets without depending on the `zng` crate.
9//!
10//! # Crate
11//!
12#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
13#![recursion_limit = "256"]
14// suppress nag about very simple boxed closure signatures.
15#![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/// Enable widget instantiation in crates that can't depend on the `zng` crate.
61///
62/// This must be called at the top of the crate:
63///
64/// ```
65/// // in lib.rs or main.rs
66/// # use zng_app::*;
67/// enable_widget_macros!();
68/// ```
69#[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; // for doc-tests
88
89#[doc(hidden)]
90pub mod __proc_macro_util {
91    // * don't add glob re-exports, the types leak in rust-analyzer even if all is doc(hidden).
92    // * don't use macro_rules! macros that use $crate , they will fail with "unresolved import" when used from the re-exports.
93
94    #[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/// Desired next step of app main loop.
184#[derive(Copy, Clone, Debug, PartialEq, Eq)]
185#[must_use = "methods that return `AppControlFlow` expect to be inside a controlled loop"]
186pub enum AppControlFlow {
187    /// Immediately try to receive more app events.
188    Poll,
189    /// Sleep until an app event is received.
190    ///
191    /// Note that a deadline might be set in case a timer is running.
192    Wait,
193    /// Exit the loop and drop the app.
194    Exit,
195}
196impl AppControlFlow {
197    /// Assert that the value is [`AppControlFlow::Wait`].
198    #[track_caller]
199    pub fn assert_wait(self) {
200        assert_eq!(AppControlFlow::Wait, self)
201    }
202
203    /// Assert that the value is [`AppControlFlow::Exit`].
204    #[track_caller]
205    pub fn assert_exit(self) {
206        assert_eq!(AppControlFlow::Exit, self)
207    }
208}
209
210/// A headless app controller.
211///
212/// Headless apps don't cause external side-effects like visible windows and don't listen to system events.
213/// They can be used for creating apps like a command line app that renders widgets, or for creating integration tests.
214///
215/// You can start a headless app using [`AppBuilder::run_headless`].
216pub struct HeadlessApp {
217    app: RunningApp,
218}
219impl HeadlessApp {
220    /// If headless rendering is enabled wait until view-process is connected.
221    ///
222    /// When enabled windows are still not visible but frames will be rendered and the frame
223    /// image can be requested.
224    ///
225    /// Note that [`UiNode::render`] is still called when a renderer is disabled and you can still
226    /// query the latest frame from `WINDOWS.widget_tree`. The only thing that
227    /// is disabled is the actual renderer that converts display lists to pixels.
228    ///
229    /// [`UiNode::render`]: crate::widget::node::UiNode::render
230    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    /// Does updates.
243    ///
244    /// If `wait_app_event` is `true` the thread sleeps until at least one app event is received or a timer elapses,
245    /// if it is `false` only responds to app events already in the buffer.
246    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    /// Does updates and calls `on_pre_update` on the first update.
263    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    /// Execute the async `task` in the UI thread, updating the app until it finishes or the app shuts-down.
276    ///
277    /// Returns the task result if the app has not shutdown.
278    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    /// Does [`run_task`] with a `deadline`.
311    ///
312    /// Returns the task result if the app has not shutdown and the `deadline` is not reached.
313    ///
314    /// If the `deadline` is reached an error is logged. Note that you can use [`with_deadline`] to create
315    /// a future with timeout and handle the timeout error.
316    ///
317    /// [`run_task`]: Self::run_task
318    /// [`with_deadline`]: zng_task::with_deadline
319    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    /// Does [`run_task`] with a deadline, panics on timeout.
336    ///
337    /// [`run_task`]: Self::run_task
338    #[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    /// Spawn a task that will exit with error after 65 seconds elapses.
368    #[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    /// Requests and wait for app exit.
378    ///
379    /// Forces deinit if exit is cancelled.
380    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    /// If the app has exited.
389    ///
390    /// Exited apps cannot update anymore. The app should be dropped to unload the app scope.
391    pub fn has_exited(&self) -> bool {
392        self.app.has_exited()
393    }
394}
395
396/// Start and manage an app process.
397pub struct APP;
398impl APP {
399    /// If the crate was built with `feature="multi_app"`.
400    ///
401    /// If `true` multiple apps can run in the same process, but only one app per thread at a time.
402    pub fn multi_app_enabled(&self) -> bool {
403        cfg!(feature = "multi_app")
404    }
405
406    /// If an app started building or is running in the current thread.
407    ///
408    /// This is `true` as soon as `APP.minimal()` or `APP.defaults()` is called.
409    ///
410    /// You can use [`app_local!`] to create *static* resources that live for the app lifetime, these statics can be used
411    /// as soon as this is `true`.
412    ///
413    /// [`app_local!`]: zng_app_context::app_local
414    pub fn is_started(&self) -> bool {
415        LocalContext::current_app().is_some()
416    }
417
418    /// If an app is running in the current thread.
419    ///
420    /// Apps are *running* as soon as [`run`], [`run_headless`] or `run_window` are called.
421    /// This will remain `true` until run returns or the [`HeadlessApp`] is dropped.
422    ///
423    /// [`run`]: AppBuilder::run
424    /// [`run_headless`]: AppBuilder::run_headless
425    pub fn is_running(&self) -> bool {
426        self.is_started() && !APP_PROCESS_SV.read().exit
427    }
428
429    /// Gets the unique ID of the current app.
430    ///
431    /// This ID usually does not change as most apps only run once per process, but it can change often during tests.
432    /// Resources that interact with [`app_local!`] values can use this ID to ensure that they are still operating in the same
433    /// app.
434    ///
435    /// [`app_local!`]: zng_app_context::app_local
436    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    /// Returns a [`WindowMode`] value that indicates if the app is headless, headless with renderer or headed.
459    ///
460    /// Note that specific windows can be in headless mode even if the app is headed.
461    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    /// If running with renderer await until a view-process connects.
474    ///
475    /// This method is particularly useful to await for initial service values that are from view-process, such
476    /// as service capabilities. Avoid using this directly in [`run`], windows and other service requests are
477    /// designed await for view-process when needed, blocking the entire run misses on some parallelization.
478    ///
479    /// [`run`]: AppBuilder::run
480    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    /// Defines what raw device events the view-process instance should monitor and notify.
487    ///
488    /// Raw device events are global and can be received even when the app has no visible window.
489    ///
490    /// These events are disabled by default as they can impact performance or may require special security clearance,
491    /// depending on the view-process implementation and operating system.
492    pub fn device_events_filter(&self) -> Var<DeviceEventsFilter> {
493        APP_PROCESS_SV.read().device_events_filter.clone()
494    }
495}
496
497impl APP {
498    /// Starts building an application with only the minimum required config and resources.
499    ///
500    /// This is the recommended builder for tests, it signal init handlers to only load required resources.
501    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    /// Starts building an application with all compiled config and resources.
521    ///
522    /// This is the recommended builder for apps, it signals init handlers to setup all resources upfront, on run, for example, register icon sets,
523    /// default settings views and more. Note that you can still define a lean app by managing the compile time feature flags, and you can also
524    /// override any default resource on run.
525    pub fn defaults(&self) -> AppBuilder {
526        let mut app = self.minimal();
527        app.with_defaults = true;
528        app
529    }
530}
531
532/// Application builder.
533///
534/// You can use `APP` to start building the app.
535pub struct AppBuilder {
536    view_process_exe: Option<PathBuf>,
537    view_process_env: HashMap<Txt, Txt>,
538    with_defaults: bool,
539
540    // cleanup on drop.
541    _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            // disable ping timeout, headless apps manually update so don't ping on a schedule.
562            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    /// Set the path to the executable for the *View Process*.
581    ///
582    /// By the default the current executable is started again as a *View Process*, you can use
583    /// two executables instead, by setting this value.
584    ///
585    /// Note that the `view_process_exe` must start a view server and both
586    /// executables must be build using the same exact [`VERSION`].
587    ///
588    /// [`VERSION`]: zng_view_api::VERSION  
589    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    /// Set an env variable for the view-process.
595    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    /// Starts the app, then starts polling `start` to run.
601    ///
602    /// This method only returns when the app has exited.
603    ///
604    /// The `start` task runs in the app context, note that the future only needs to start the app, usually
605    /// by opening a window, the app will keep running after `start` is finished.
606    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    /// Initializes extensions in headless mode and returns an [`HeadlessApp`].
612    ///
613    /// If `with_renderer` is `true` spawns a renderer process for headless rendering. See [`HeadlessApp::renderer_enabled`]
614    /// for more details.
615    pub fn run_headless(self, with_renderer: bool) -> HeadlessApp {
616        self.run_headless_impl(with_renderer)
617    }
618}
619
620// this module is declared here on purpose so that advanced `impl APP` blocks show later in the docs.
621mod running;
622pub use running::*;
623use zng_view_api::DeviceEventsFilter;
624
625mod private {
626    // https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
627    pub trait Sealed {}
628}
629
630/// Enables [`tracing`] events printing if a subscriber is not already set.
631///
632/// All non-fatal errors in the Zng project are logged using tracing, printing these errors is essential for debugging.
633/// In debug builds this is enabled by default in the app-process on app init with `INFO` level and no span events.
634///
635/// If `span_events` are enabled `tracing::span!` enter and exit are also printed as events.
636///
637/// In `"wasm32"` builds logs to the browser console.
638///
639/// In `"android"` builds logs to logcat.
640///
641/// See also [`test_log`] to enable panicking on error log.
642///
643/// See also [`print_tracing_filter`] for the filter used by this.
644///
645/// # Examples
646///
647/// In the example below this function is called before `init!`, enabling it in all app processes.
648///
649/// ```
650/// # macro_rules! demo { () => {
651/// fn main() {
652///     zng::app::print_tracing(tracing::Level::INFO, false, |_| true);
653///     zng::env::init!();
654/// }
655/// # }}
656/// ```
657///
658/// [`tracing`]: https://docs.rs/tracing
659pub 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}
723/// Filter used by [`print_tracing`], removes some log noise from dependencies.
724///
725/// Use `tracing_subscriber::filter::FilterFn` plug this filter into a tracing setup.
726pub 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        // suppress large info about texture cache
733        if metadata.target() == "zng_webrender::device::gl" {
734            return false;
735        }
736        // suppress config dump
737        if metadata.target() == "zng_webrender::renderer::init" {
738            return false;
739        }
740    } else if metadata.level() == &tracing::Level::WARN && level < &tracing::Level::DEBUG {
741        // suppress webrender warnings:
742        //
743        if metadata.target() == "zng_webrender::device::gl" {
744            // Suppress "Cropping texture upload Box2D((0, 0), (0, 1)) to None"
745            // This happens when an empty frame is rendered.
746            if metadata.line() == Some(4647) {
747                return false;
748            }
749        }
750
751        // suppress font-kit warnings:
752        //
753        if metadata.target() == "font_kit::loaders::freetype" {
754            // Suppress "$fn(): found invalid platform ID $n"
755            // This does not look fully implemented and generates a lot of warns
756            // with the default Ubuntu font set all with valid platform IDs.
757            if metadata.line() == Some(734) {
758                return false;
759            }
760        }
761    }
762
763    filter(metadata)
764}
765
766/// Modifies the [`print_tracing`] subscriber to panic for error logs in the current app.
767#[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}