#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
#![recursion_limit = "256"]
#![expect(clippy::type_complexity)]
#![warn(unused_extern_crates)]
#![warn(missing_docs)]
use std::{
collections::HashMap,
path::PathBuf,
sync::{Arc, atomic::AtomicBool},
};
pub mod access;
pub mod crash_handler;
pub mod event;
pub mod handler;
pub mod memory_profiler;
pub mod render;
pub mod shortcut;
pub mod third_party;
pub mod timer;
pub mod trace_recorder;
pub mod update;
pub mod view_process;
pub mod widget;
pub mod window;
mod tests;
use parking_lot::Mutex;
use view_process::VIEW_PROCESS;
use zng_clone_move::async_clmv;
#[doc(hidden)]
pub use zng_layout as layout;
use zng_txt::Txt;
#[doc(hidden)]
pub use zng_var as var;
use zng_var::Var;
pub use zng_time::{DInstant, Deadline, INSTANT, InstantMode};
use update::UPDATES;
use window::WindowMode;
use zng_app_context::{AppId, AppScope, LocalContext};
pub use zng_unique_id::static_id;
#[macro_export]
macro_rules! enable_widget_macros {
() => {
#[doc(hidden)]
#[allow(unused_extern_crates)]
extern crate self as zng;
#[doc(hidden)]
pub use $crate::__proc_macro_util;
};
}
#[doc(hidden)]
#[allow(unused_extern_crates)]
extern crate self as zng;
#[doc(hidden)]
#[allow(unused_extern_crates)]
extern crate self as zng_app;
#[doc(hidden)]
pub mod __proc_macro_util {
#[doc(hidden)]
pub use zng_unique_id::static_id;
#[doc(hidden)]
pub mod widget {
#[doc(hidden)]
pub mod builder {
#[doc(hidden)]
pub use crate::widget::builder::{
AnyArcHandler, HandlerInWhenExprError, Importance, InputKind, PropertyArgs, PropertyId, PropertyInfo, PropertyInput,
PropertyInputTypes, PropertyNewArgs, SourceLocation, UiNodeInWhenExprError, WgtInfo, WhenInput, WhenInputMember,
WhenInputVar, WidgetBuilding, WidgetType, handler_to_args, iter_input_attributes, nest_group_items, new_dyn_handler,
new_dyn_other, new_dyn_ui_node, new_dyn_var, panic_input, ui_node_to_args, value_to_args, var_getter, var_state,
var_to_args,
};
}
#[doc(hidden)]
pub mod base {
pub use crate::widget::base::{NonWidgetBase, WidgetBase, WidgetExt, WidgetImpl};
}
#[doc(hidden)]
pub mod node {
pub use crate::widget::node::{ArcNode, IntoUiNode, UiNode};
}
#[doc(hidden)]
pub mod info {
pub use crate::widget::info::{WidgetInfoBuilder, WidgetLayout, WidgetMeasure};
}
#[doc(hidden)]
pub use crate::widget::{easing_property, widget_new};
#[doc(hidden)]
pub use crate::widget::WIDGET;
}
#[doc(hidden)]
pub mod update {
pub use crate::update::WidgetUpdates;
}
#[doc(hidden)]
pub mod layout {
#[doc(hidden)]
pub mod unit {
#[doc(hidden)]
pub use crate::layout::unit::{PxSize, TimeUnits};
}
#[doc(hidden)]
pub mod context {
#[doc(hidden)]
pub use crate::layout::context::LAYOUT;
}
}
#[doc(hidden)]
pub mod render {
pub use crate::render::{FrameBuilder, FrameUpdate};
}
#[doc(hidden)]
pub mod handler {
#[doc(hidden)]
pub use crate::handler::{ArcHandler, hn};
}
#[doc(hidden)]
pub mod var {
#[doc(hidden)]
pub use crate::var::{AnyVar, AnyVarValue, Var, expr_var};
#[doc(hidden)]
pub mod animation {
#[doc(hidden)]
pub mod easing {
#[doc(hidden)]
pub use crate::var::animation::easing::{
back, bounce, circ, cubic, cubic_bezier, ease_in, ease_in_out, ease_out, ease_out_in, elastic, expo, linear, none,
quad, quart, quint, reverse, reverse_out, sine, step_ceil, step_floor,
};
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[must_use = "methods that return `AppControlFlow` expect to be inside a controlled loop"]
pub enum AppControlFlow {
Poll,
Wait,
Exit,
}
impl AppControlFlow {
#[track_caller]
pub fn assert_wait(self) {
assert_eq!(AppControlFlow::Wait, self)
}
#[track_caller]
pub fn assert_exit(self) {
assert_eq!(AppControlFlow::Exit, self)
}
}
pub struct HeadlessApp {
app: RunningApp,
}
impl HeadlessApp {
pub fn renderer_enabled(&mut self) -> bool {
if VIEW_PROCESS.is_available() {
self.run_task(async {
let args = crate::view_process::VIEW_PROCESS_INITED_EVENT.var();
args.wait_match(|a| !a.is_empty()).await;
});
true
} else {
false
}
}
pub fn update(&mut self, mut wait_app_event: bool) -> AppControlFlow {
if self.app.has_exited() {
return AppControlFlow::Exit;
}
loop {
match self.app.poll(wait_app_event) {
AppControlFlow::Poll => {
wait_app_event = false;
continue;
}
flow => return flow,
}
}
}
pub fn update_observe(&mut self, on_pre_update: impl FnOnce() + Send + 'static) -> bool {
let u = Arc::new(AtomicBool::new(false));
UPDATES
.on_pre_update(hn_once!(u, |_| {
u.store(true, std::sync::atomic::Ordering::Relaxed);
on_pre_update();
}))
.perm();
let _ = self.update(false);
u.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn run_task<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>) -> Option<R>
where
R: Send + 'static,
T: Future<Output = R> + Send + 'static,
{
let task = task.into_future();
if self.app.has_exited() {
return None;
}
let r = Arc::new(Mutex::new(None::<R>));
UPDATES
.run(async_clmv!(r, {
let fr = task.await;
*r.lock() = Some(fr);
}))
.perm();
loop {
match self.app.poll(true) {
AppControlFlow::Exit => return None,
_ => {
let mut r = r.lock();
if r.is_some() {
return r.take();
}
}
}
}
}
pub fn run_task_deadline<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>, deadline: impl Into<Deadline>) -> Option<R>
where
R: Send + 'static,
T: Future<Output = R> + Send + 'static,
{
let task = task.into_future();
let task = zng_task::with_deadline(task, deadline.into());
match self.run_task(task)? {
Ok(r) => Some(r),
Err(e) => {
tracing::error!("run_task reached deadline, {e}");
None
}
}
}
#[cfg(any(test, feature = "test_util"))]
pub fn run_test<R, T>(&mut self, task: impl IntoFuture<IntoFuture = T>) -> Option<R>
where
R: Send + 'static,
T: Future<Output = R> + Send + 'static,
{
use std::time::Duration;
thread_local! {
static TIMEOUT: Duration = {
let t = std::env::var("ZNG_APP_RUN_TEST_TIMEOUT").unwrap_or_else(|_| "60".to_string());
let t: u64 = match t.parse() {
Ok(0) => 60,
Ok(t) => t,
Err(_) => 60,
};
std::time::Duration::from_secs(t)
}
}
let task = task.into_future();
let task = zng_task::with_deadline(task, TIMEOUT.with(|t| *t));
match self.run_task(task)? {
Ok(r) => Some(r),
Err(e) => {
panic!("run_test {e}")
}
}
}
pub fn exit(mut self) {
let req = APP.exit();
while req.is_waiting() {
if let AppControlFlow::Exit = self.update(true) {
break;
}
}
}
pub fn has_exited(&self) -> bool {
self.app.has_exited()
}
}
pub struct APP;
impl APP {
pub fn multi_app_enabled(&self) -> bool {
cfg!(feature = "multi_app")
}
pub fn is_started(&self) -> bool {
LocalContext::current_app().is_some()
}
pub fn is_running(&self) -> bool {
self.is_started() && !APP_PROCESS_SV.read().exit
}
pub fn id(&self) -> Option<AppId> {
LocalContext::current_app()
}
#[cfg(not(feature = "multi_app"))]
fn assert_can_run_single() {
use std::sync::atomic::*;
static CAN_RUN: AtomicBool = AtomicBool::new(true);
if !CAN_RUN.swap(false, Ordering::SeqCst) {
panic!("only one app is allowed per process")
}
}
fn assert_can_run() {
#[cfg(not(feature = "multi_app"))]
Self::assert_can_run_single();
if APP.is_running() {
panic!("only one app is allowed per thread")
}
}
pub fn window_mode(&self) -> WindowMode {
if VIEW_PROCESS.is_available() {
if VIEW_PROCESS.is_headless_with_render() {
WindowMode::HeadlessWithRenderer
} else {
WindowMode::Headed
}
} else {
WindowMode::Headless
}
}
pub async fn wait_view_process(&self) {
if VIEW_PROCESS.is_available() {
view_process::VIEW_PROCESS_INITED_EVENT.wait_match(|_| true).await
}
}
pub fn device_events_filter(&self) -> Var<DeviceEventsFilter> {
APP_PROCESS_SV.read().device_events_filter.clone()
}
}
impl APP {
pub fn minimal(&self) -> AppBuilder {
zng_env::init_process_name("app-process");
#[cfg(debug_assertions)]
print_tracing(tracing::Level::INFO, false, |_| true);
assert_not_view_process();
Self::assert_can_run();
spawn_deadlock_detection();
let _ = INSTANT.now();
let scope = LocalContext::start_app(AppId::new_unique());
AppBuilder {
view_process_exe: None,
view_process_env: HashMap::new(),
with_defaults: false,
_cleanup: scope,
}
}
pub fn defaults(&self) -> AppBuilder {
let mut app = self.minimal();
app.with_defaults = true;
app
}
}
pub struct AppBuilder {
view_process_exe: Option<PathBuf>,
view_process_env: HashMap<Txt, Txt>,
with_defaults: bool,
_cleanup: AppScope,
}
impl AppBuilder {
fn run_impl(self, start: std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>) {
let app = RunningApp::start(
self._cleanup,
true,
true,
self.view_process_exe,
self.view_process_env,
!self.with_defaults,
);
UPDATES.run(start).perm();
app.run_headed();
}
fn run_headless_impl(self, with_renderer: bool) -> HeadlessApp {
if with_renderer {
unsafe {
std::env::set_var("ZNG_VIEW_TIMEOUT", "false");
}
}
let app = RunningApp::start(
self._cleanup,
false,
with_renderer,
self.view_process_exe,
self.view_process_env,
!self.with_defaults,
);
HeadlessApp { app }
}
}
impl AppBuilder {
pub fn view_process_exe(mut self, view_process_exe: impl Into<PathBuf>) -> Self {
self.view_process_exe = Some(view_process_exe.into());
self
}
pub fn view_process_env(mut self, name: impl Into<Txt>, value: impl Into<Txt>) -> Self {
self.view_process_env.insert(name.into(), value.into());
self
}
pub fn run<F: Future<Output = ()> + Send + 'static>(self, start: impl IntoFuture<IntoFuture = F>) {
let start = start.into_future();
self.run_impl(Box::pin(start))
}
pub fn run_headless(self, with_renderer: bool) -> HeadlessApp {
self.run_headless_impl(with_renderer)
}
}
mod running;
pub use running::*;
use zng_view_api::DeviceEventsFilter;
mod private {
pub trait Sealed {}
}
pub fn print_tracing(max: tracing::Level, span_events: bool, filter: impl Fn(&tracing::Metadata) -> bool + Send + Sync + 'static) -> bool {
print_tracing_impl(max, span_events, Box::new(filter))
}
fn print_tracing_impl(
max: tracing::Level,
span_events: bool,
filter: Box<dyn Fn(&tracing::Metadata) -> bool + Send + Sync + 'static>,
) -> bool {
use tracing_subscriber::prelude::*;
let layers = tracing_subscriber::registry().with(FilterLayer(max, filter));
#[cfg(target_os = "android")]
let layers = layers.with(tracing_android::layer(&zng_env::about().pkg_name).unwrap());
#[cfg(target_os = "android")]
let _ = span_events;
#[cfg(not(target_os = "android"))]
let layers = {
let mut fmt_layer = tracing_subscriber::fmt::layer().without_time();
if span_events {
fmt_layer = fmt_layer.with_span_events(tracing_subscriber::fmt::format::FmtSpan::ACTIVE);
}
#[cfg(target_arch = "wasm32")]
let fmt_layer = fmt_layer.with_ansi(false).with_writer(tracing_web::MakeWebConsoleWriter::new());
layers.with(fmt_layer)
};
layers.try_init().is_ok()
}
struct FilterLayer(tracing::Level, Box<dyn Fn(&tracing::Metadata) -> bool + Send + Sync>);
impl<S: tracing::Subscriber> tracing_subscriber::Layer<S> for FilterLayer {
fn enabled(&self, metadata: &tracing::Metadata<'_>, _: tracing_subscriber::layer::Context<'_, S>) -> bool {
print_tracing_filter(&self.0, metadata, &self.1)
}
fn max_level_hint(&self) -> Option<tracing::metadata::LevelFilter> {
Some(self.0.into())
}
#[cfg(any(test, feature = "test_util"))]
fn on_event(&self, event: &tracing::Event<'_>, _ctx: tracing_subscriber::layer::Context<'_, S>) {
if event.metadata().level() == &tracing::Level::ERROR && APP.is_running() && TEST_LOG.get() {
struct MsgCollector<'a>(&'a mut String);
impl tracing::field::Visit for MsgCollector<'_> {
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
use std::fmt::Write;
write!(self.0, "\n {} = {:?}", field.name(), value).unwrap();
}
}
let meta = event.metadata();
let file = meta.file().unwrap_or("");
let line = meta.line().unwrap_or(0);
let mut msg = format!("[{file}:{line}]");
event.record(&mut MsgCollector(&mut msg));
panic!("[LOG-ERROR]{msg}")
}
}
}
pub fn print_tracing_filter(level: &tracing::Level, metadata: &tracing::Metadata, filter: &dyn Fn(&tracing::Metadata) -> bool) -> bool {
if metadata.level() > level {
return false;
}
if metadata.level() == &tracing::Level::INFO && level < &tracing::Level::TRACE {
if metadata.target() == "zng_webrender::device::gl" {
return false;
}
if metadata.target() == "zng_webrender::renderer::init" {
return false;
}
} else if metadata.level() == &tracing::Level::WARN && level < &tracing::Level::DEBUG {
if metadata.target() == "zng_webrender::device::gl" {
if metadata.line() == Some(4647) {
return false;
}
}
if metadata.target() == "font_kit::loaders::freetype" {
if metadata.line() == Some(734) {
return false;
}
}
}
filter(metadata)
}
#[cfg(any(test, feature = "test_util"))]
pub fn test_log() {
TEST_LOG.set(true);
}
#[cfg(any(test, feature = "test_util"))]
zng_app_context::app_local! {
static TEST_LOG: bool = false;
}
#[doc(hidden)]
pub fn name_from_pkg_name(name: &'static str) -> Txt {
let mut n = String::new();
let mut sep = "";
for part in name.split(&['-', '_']) {
n.push_str(sep);
let mut chars = part.char_indices();
let (_, c) = chars.next().unwrap();
c.to_uppercase().for_each(|c| n.push(c));
if let Some((i, _)) = chars.next() {
n.push_str(&part[i..]);
}
sep = " ";
}
n.into()
}
#[doc(hidden)]
pub fn txt_from_pkg_meta(value: &'static str) -> Txt {
value.into()
}