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.
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        VIEW_PROCESS.is_available()
232    }
233
234    /// Does updates.
235    ///
236    /// If `wait_app_event` is `true` the thread sleeps until at least one app event is received or a timer elapses,
237    /// if it is `false` only responds to app events already in the buffer.
238    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    /// Does updates and calls `on_pre_update` on the first update.
255    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    /// Execute the async `task` in the UI thread, updating the app until it finishes or the app shuts-down.
268    ///
269    /// Returns the task result if the app has not shutdown.
270    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    /// Does [`run_task`] with a `deadline`.
303    ///
304    /// Returns the task result if the app has not shutdown and the `deadline` is not reached.
305    ///
306    /// If the `deadline` is reached an error is logged. Note that you can use [`with_deadline`] to create
307    /// a future with timeout and handle the timeout error.
308    ///
309    /// [`run_task`]: Self::run_task
310    /// [`with_deadline`]: zng_task::with_deadline
311    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    /// Does [`run_task`] with a deadline, panics on timeout.
328    ///
329    /// [`run_task`]: Self::run_task
330    #[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    /// Requests and wait for app exit.
360    ///
361    /// Forces deinit if exit is cancelled.
362    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    /// If the app has exited.
371    ///
372    /// Exited apps cannot update anymore. The app should be dropped to unload the app scope.
373    pub fn has_exited(&self) -> bool {
374        self.app.has_exited()
375    }
376}
377
378/// Start and manage an app process.
379pub struct APP;
380impl APP {
381    /// If the crate was built with `feature="multi_app"`.
382    ///
383    /// If `true` multiple apps can run in the same process, but only one app per thread at a time.
384    pub fn multi_app_enabled(&self) -> bool {
385        cfg!(feature = "multi_app")
386    }
387
388    /// If an app started building or is running in the current thread.
389    ///
390    /// This is `true` as soon as `APP.minimal()` or `APP.defaults()` is called.
391    ///
392    /// You can use [`app_local!`] to create *static* resources that live for the app lifetime, these statics can be used
393    /// as soon as this is `true`.
394    ///
395    /// [`app_local!`]: zng_app_context::app_local
396    pub fn is_started(&self) -> bool {
397        LocalContext::current_app().is_some()
398    }
399
400    /// If an app is running in the current thread.
401    ///
402    /// Apps are *running* as soon as [`run`], [`run_headless`] or `run_window` are called.
403    /// This will remain `true` until run returns or the [`HeadlessApp`] is dropped.
404    ///
405    /// [`run`]: AppBuilder::run
406    /// [`run_headless`]: AppBuilder::run_headless
407    pub fn is_running(&self) -> bool {
408        self.is_started() && !APP_PROCESS_SV.read().exit
409    }
410
411    /// Gets the unique ID of the current app.
412    ///
413    /// This ID usually does not change as most apps only run once per process, but it can change often during tests.
414    /// Resources that interact with [`app_local!`] values can use this ID to ensure that they are still operating in the same
415    /// app.
416    ///
417    /// [`app_local!`]: zng_app_context::app_local
418    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    /// Returns a [`WindowMode`] value that indicates if the app is headless, headless with renderer or headed.
441    ///
442    /// Note that specific windows can be in headless mode even if the app is headed.
443    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    /// Defines what raw device events the view-process instance should monitor and notify.
456    ///
457    /// Raw device events are global and can be received even when the app has no visible window.
458    ///
459    /// These events are disabled by default as they can impact performance or may require special security clearance,
460    /// depending on the view-process implementation and operating system.
461    pub fn device_events_filter(&self) -> Var<DeviceEventsFilter> {
462        APP_PROCESS_SV.read().device_events_filter.clone()
463    }
464}
465
466impl APP {
467    /// Starts building an application with only the minimum required config and resources.
468    ///
469    /// This is the recommended builder for tests, it signal init handlers to only load required resources.
470    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    /// Starts building an application with all compiled config and resources.
490    ///
491    /// This is the recommended builder for apps, it signals init handlers to setup all resources upfront, on run, for example, register icon sets,
492    /// 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
493    /// override any default resource on run.
494    pub fn defaults(&self) -> AppBuilder {
495        let mut app = self.minimal();
496        app.with_defaults = true;
497        app
498    }
499}
500
501/// Application builder.
502///
503/// You can use `APP` to start building the app.
504pub struct AppBuilder {
505    view_process_exe: Option<PathBuf>,
506    view_process_env: HashMap<Txt, Txt>,
507    with_defaults: bool,
508
509    // cleanup on drop.
510    _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            // disable ping timeout, headless apps manually update so don't ping on a schedule.
531            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    /// Set the path to the executable for the *View Process*.
550    ///
551    /// By the default the current executable is started again as a *View Process*, you can use
552    /// two executables instead, by setting this value.
553    ///
554    /// Note that the `view_process_exe` must start a view server and both
555    /// executables must be build using the same exact [`VERSION`].
556    ///
557    /// [`VERSION`]: zng_view_api::VERSION  
558    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    /// Set an env variable for the view-process.
564    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    /// Starts the app, then starts polling `start` to run.
570    ///
571    /// This method only returns when the app has exited.
572    ///
573    /// The `start` task runs in the app context, note that the future only needs to start the app, usually
574    /// by opening a window, the app will keep running after `start` is finished.
575    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    /// Initializes extensions in headless mode and returns an [`HeadlessApp`].
581    ///
582    /// If `with_renderer` is `true` spawns a renderer process for headless rendering. See [`HeadlessApp::renderer_enabled`]
583    /// for more details.
584    pub fn run_headless(self, with_renderer: bool) -> HeadlessApp {
585        self.run_headless_impl(with_renderer)
586    }
587}
588
589// this module is declared here on purpose so that advanced `impl APP` blocks show later in the docs.
590mod running;
591pub use running::*;
592use zng_view_api::DeviceEventsFilter;
593
594mod private {
595    // https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
596    pub trait Sealed {}
597}
598
599/// Enables [`tracing`] events printing if a subscriber is not already set.
600///
601/// All non-fatal errors in the Zng project are logged using tracing, printing these errors is essential for debugging.
602/// In debug builds this is enabled by default in the app-process on app init with `INFO` level and no span events.
603///
604/// If `span_events` are enabled `tracing::span!` enter and exit are also printed as events.
605///
606/// In `"wasm32"` builds logs to the browser console.
607///
608/// In `"android"` builds logs to logcat.
609///
610/// See also [`test_log`] to enable panicking on error log.
611///
612/// See also [`print_tracing_filter`] for the filter used by this.
613///
614/// # Examples
615///
616/// In the example below this function is called before `init!`, enabling it in all app processes.
617///
618/// ```
619/// # macro_rules! demo { () => {
620/// fn main() {
621///     zng::app::print_tracing(tracing::Level::INFO, false, |_| true);
622///     zng::env::init!();
623/// }
624/// # }}
625/// ```
626///
627/// [`tracing`]: https://docs.rs/tracing
628pub 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}
692/// Filter used by [`print_tracing`], removes some log noise from dependencies.
693///
694/// Use `tracing_subscriber::filter::FilterFn` plug this filter into a tracing setup.
695pub 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        // suppress large info about texture cache
702        if metadata.target() == "zng_webrender::device::gl" {
703            return false;
704        }
705        // suppress config dump
706        if metadata.target() == "zng_webrender::renderer::init" {
707            return false;
708        }
709    } else if metadata.level() == &tracing::Level::WARN && level < &tracing::Level::DEBUG {
710        // suppress webrender warnings:
711        //
712        if metadata.target() == "zng_webrender::device::gl" {
713            // Suppress "Cropping texture upload Box2D((0, 0), (0, 1)) to None"
714            // This happens when an empty frame is rendered.
715            if metadata.line() == Some(4647) {
716                return false;
717            }
718        }
719
720        // suppress font-kit warnings:
721        //
722        if metadata.target() == "font_kit::loaders::freetype" {
723            // Suppress "$fn(): found invalid platform ID $n"
724            // This does not look fully implemented and generates a lot of warns
725            // with the default Ubuntu font set all with valid platform IDs.
726            if metadata.line() == Some(734) {
727                return false;
728            }
729        }
730    }
731
732    filter(metadata)
733}
734
735/// Modifies the [`print_tracing`] subscriber to panic for error logs in the current app.
736#[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}