cranpose 0.1.97

Cranpose runtime and UI facade
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
#![deny(unsafe_code)]
#![deny(missing_docs)]

//! High level utilities for running Cranpose applications with minimal boilerplate.

#[cfg(all(feature = "android", target_os = "android"))]
mod android_file_picker;
#[cfg(all(feature = "android", target_os = "android"))]
pub use android_file_picker::open_content_uri;
#[cfg(any(
    test,
    all(feature = "desktop-shell", feature = "renderer-wgpu"),
    all(feature = "android", feature = "renderer-wgpu", target_os = "android"),
    all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"),
    all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32")
))]
mod accessibility;
#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
mod android_accessibility;
/// The Android accessibility wire format behind `CranposeActivity`'s
/// `AccessibilityNodeProvider`. Built on the host as well so its encoding
/// tests run everywhere.
#[cfg(any(
    test,
    all(feature = "android", feature = "renderer-wgpu", target_os = "android")
))]
mod android_accessibility_wire;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_app_info;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_camera;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_media;
// `android_main!` expands to nothing off Android, so the macro itself is always
// compiled: an application writes the invocation once and every target accepts
// it.
#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
mod android_display;
mod android_entry;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_finish;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_font_scale;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_frame_rate;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_frame_telemetry;
/// The bounded command queue behind the Android haptics delivery thread.
/// Built on the host as well so its ordering/coalescing test runs everywhere.
#[cfg(any(
    test,
    all(feature = "android", feature = "renderer-wgpu", target_os = "android")
))]
mod android_haptics_queue;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_host;
#[cfg_attr(not(all(feature = "android", target_os = "android")), allow(dead_code))]
mod android_host_window;
mod android_input;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_jni;
#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
mod android_keyboard;
/// The Android intent-extra wire format behind `cranpose_services::launch_args`.
/// Built on the host as well so its decoding tests run everywhere.
#[cfg(any(test, all(feature = "android", target_os = "android")))]
mod android_launch_args;
#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
mod android_overlay_window;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_perf_hint;
/// The Play Billing wire format behind `cranpose_services::purchases`. Built on
/// the host as well so its decoding tests run everywhere.
#[cfg(any(
    test,
    all(feature = "android", feature = "playbilling", target_os = "android")
))]
mod android_purchase_wire;
#[cfg(all(
    feature = "android",
    feature = "playbilling",
    feature = "renderer-wgpu",
    target_os = "android"
))]
mod android_purchases;
#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
mod android_services;
#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
mod android_surface;
#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
mod android_text_input;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_vsync;
#[cfg(all(feature = "android", target_os = "android"))]
mod android_writable_folder;
mod app_launcher;
mod host_environment;
#[cfg(all(feature = "ios", target_os = "ios"))]
mod ios_host;
mod native_window;
/// The activity handle `NativeActivity` hands to the entry point. Re-exported so
/// an application declares its entry point with [`android_main!`] and never
/// depends on `android_activity` for a parameter type.
#[cfg(all(feature = "android", target_os = "android"))]
pub use android_activity::AndroidApp;
#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
pub use android_host_window::{
    rememberAndroidHostWindowState, AndroidHostWindowPositionError, AndroidHostWindowSizeError,
    AndroidHostWindowSizeStatus, AndroidHostWindowState,
};
#[cfg(all(
    feature = "renderer-wgpu",
    any(feature = "desktop-shell", all(feature = "ios", target_os = "ios"))
))]
pub use app_launcher::LaunchError;
pub use app_launcher::{AndroidOverlayWindowOptions, AppLauncher, AppSettings};
/// Font registration vocabulary named by [`AppLauncher`]'s font methods:
/// the platform font directory [`AppLauncher::with_system_font_family`] wants,
/// the weight set it registers, and the registry and error
/// [`AppLauncher::with_fonts_from`] hands out.
pub use cranpose_render_common::font_source::{
    FontLoadError, SoftwareTextFontRegistry, ANDROID_SYSTEM_FONT_DIR, DEFAULT_SYSTEM_FAMILY_WEIGHTS,
};
pub use host_environment::{host_density, system_font_directory};
pub use native_window::{
    current_native_window_surface_origin, rememberWindowState, Window, WindowAttachPolicy,
    WindowConfig, WindowGroup, WindowId, WindowModifierExt, WindowMoveMode, WindowNode,
    WindowResizeDirection, WindowState,
};
#[cfg(all(
    feature = "renderer-wgpu",
    any(
        feature = "desktop-shell",
        all(feature = "android", target_os = "android"),
        all(feature = "ios", target_os = "ios"),
        all(feature = "web", target_arch = "wasm32")
    )
))]
mod present_mode;
#[cfg(all(
    feature = "renderer-wgpu",
    any(
        feature = "desktop-shell",
        all(feature = "android", target_os = "android"),
        all(feature = "ios", target_os = "ios"),
        all(feature = "web", target_arch = "wasm32")
    )
))]
mod surface_format;
#[cfg(all(
    feature = "renderer-wgpu",
    any(
        feature = "desktop-shell",
        all(feature = "android", target_os = "android"),
        all(feature = "ios", target_os = "ios"),
        all(feature = "web", target_arch = "wasm32")
    )
))]
mod wgpu_surface;

/// Re-export framework services (HTTP, URI, etc.) from the dedicated services crate.
pub use cranpose_services::*;
/// Re-export the UI crate so applications can depend on a single crate.
pub use cranpose_ui::*;

/// Liquid UI — the first-party glass component library
/// (`use cranpose::liquid::prelude::*;`).
pub use cranpose_liquid as liquid;

/// The real-time audio engine that backs `cranpose_services::audio`. Call
/// [`install_audio`] once at startup; Android installs it automatically.
#[cfg(feature = "audio")]
pub use cranpose_audio::{install as install_audio, AudioEngine};

/// The desktop media backend that backs `cranpose_services::media`. Installed
/// automatically by the desktop shell; Android, iOS and the web install their
/// own platform backend instead. [`uri_for_path`] builds the `file:` URI a
/// [`cranpose_services::MediaItem`] takes from a path.
#[cfg(feature = "media-desktop")]
pub use cranpose_media::{path_from_uri, uri_for_path, DesktopMediaPlayer};

/// Core runtime helpers commonly used by applications.
pub use cranpose_core::{
    delay, interval, launchBlocking, mutableStateOf, produceState, remember,
    rememberCoroutineScope, rememberMutableStateOf, rememberMutableStateOfNeverEqual,
    rememberUpdatedState, CoroutineScope, MutableState, SnapshotStateList, SnapshotStateMap, State,
};

static KEEP_SCREEN_ON_EFFECTS: std::sync::atomic::AtomicUsize =
    std::sync::atomic::AtomicUsize::new(0);

/// Keeps the platform display awake while this call remains in composition and
/// `enabled` is true. Multiple active callers are reference-counted.
#[allow(non_snake_case)]
pub fn KeepScreenOn(enabled: bool) {
    cranpose_core::DisposableEffect!(enabled, move |scope| {
        if !enabled {
            return cranpose_core::DisposableEffectResult::default();
        }
        if KEEP_SCREEN_ON_EFFECTS.fetch_add(1, std::sync::atomic::Ordering::AcqRel) == 0 {
            cranpose_services::set_keep_screen_on(true);
        }
        scope.on_dispose(move || {
            if KEEP_SCREEN_ON_EFFECTS.fetch_sub(1, std::sync::atomic::Ordering::AcqRel) == 1 {
                cranpose_services::set_keep_screen_on(false);
            }
        })
    });
}

/// Installs a declared bundled-asset set on a worker and returns the outcome
/// on the UI runtime. Work is cancelled with the owning composition.
#[cfg(not(target_arch = "wasm32"))]
#[allow(non_snake_case)]
pub fn BundledAssetInstallEffect<K: std::hash::Hash>(
    keys: K,
    spec: cranpose_services::BundledAssetInstallSpec,
    on_result: impl FnOnce(
            Result<
                cranpose_services::BundledAssetInstallOutcome,
                cranpose_services::BundledAssetError,
            >,
        ) + 'static,
) {
    cranpose_core::LaunchedEffect!(keys, move |scope| {
        scope.launch_background(
            move |_token| async move { cranpose_services::install_bundled_asset_set(&spec) },
            on_result,
        );
    });
}

/// Registers a lifecycle observer for the lifetime of the current composition.
///
/// Screens that only need the current state read
/// [`cranpose_services::local_lifecycle_state`] instead; this is for work that
/// must react to a *transition*.
#[allow(non_snake_case)]
pub fn LifecycleEffect<K: std::hash::Hash + 'static>(
    keys: K,
    observer: impl FnMut(cranpose_services::LifecycleEvent) + 'static,
) {
    let transitions = cranpose_services::rememberLifecycleEvents();
    cranpose_core::CollectEvents(transitions, keys, observer);
}

static ACTIVE_BACK_HANDLERS: std::sync::atomic::AtomicUsize =
    std::sync::atomic::AtomicUsize::new(0);

/// Handles platform back requests on the UI thread while `enabled` is true.
/// Nested handlers follow stack order: the innermost active handler receives
/// the request and dropping it restores the handler beneath it.
#[allow(non_snake_case)]
pub fn BackHandler(enabled: bool, mut on_back: impl FnMut() + 'static) {
    let requests = cranpose_core::rememberEventStream(enabled, move |sender| {
        if !enabled {
            return None;
        }
        if ACTIVE_BACK_HANDLERS.fetch_add(1, std::sync::atomic::Ordering::AcqRel) == 0 {
            cranpose_services::set_back_interception(true);
        }
        let registration = cranpose_services::observe_back_requests(move || {
            let count = cranpose_services::take_back_requests();
            if count > 0 {
                sender.send(count);
            }
        });
        Some(BackInterception {
            _registration: registration,
        })
    });
    // Only a handler that is switched on collects. A collector is a runtime
    // task parked on a stream for as long as it is composed, and the shell
    // composes a `BackHandler` at the root of every application — so collecting
    // while disabled meant every Cranpose app carried one parked task for its
    // whole life, for a handler that had nothing to receive.
    if enabled {
        cranpose_core::CollectEvents(requests, enabled, move |count: usize| {
            for _ in 0..count {
                on_back();
            }
        });
    }
}

/// Holds the platform back registration and releases the interception flag when
/// the last handler leaves the composition.
struct BackInterception {
    _registration: cranpose_services::BackRequestObserver,
}

impl Drop for BackInterception {
    fn drop(&mut self) {
        if ACTIVE_BACK_HANDLERS.fetch_sub(1, std::sync::atomic::Ordering::AcqRel) == 1 {
            cranpose_services::set_back_interception(false);
        }
    }
}

/// Remembers observable application update state for the current composition.
#[allow(non_snake_case)]
pub fn rememberAppUpdateState() -> cranpose_core::State<cranpose_services::AppUpdateStatus> {
    let updates = cranpose_core::rememberEventStream((), |sender| {
        cranpose_services::observe_app_update_status(move |status| sender.send(status))
    });
    cranpose_core::collectAsState(updates, (), cranpose_services::app_update_status())
}

/// Runs `on_frame` on every animation frame while `running` is true.
///
/// This is the framework-owned animation loop for games and other custom-drawn
/// content. `running` is ordinary observable state the caller derives — a
/// simulation flag, "the window is visible", "a gesture is in progress" — and
/// the loop starts and stops with it. There is no wake handle to hold and no
/// scheduler to poke: stopping is a state change like any other.
#[allow(non_snake_case)]
pub fn FrameEffect<K: std::hash::Hash>(
    keys: K,
    running: bool,
    on_frame: impl FnMut(u64) + 'static,
) {
    let on_frame: std::rc::Rc<std::cell::RefCell<dyn FnMut(u64)>> =
        std::rc::Rc::new(std::cell::RefCell::new(on_frame));
    let on_frame = cranpose_core::rememberUpdatedState(on_frame);
    cranpose_core::LaunchedEffectAsync!((keys, running), move |scope| {
        Box::pin(async move {
            if !running {
                return;
            }
            let clock = scope.runtime().frame_clock();
            while scope.is_active() {
                let now = clock.next_frame().await;
                if !scope.is_active() {
                    break;
                }
                (on_frame.value().borrow_mut())(now);
            }
        })
    });
}

#[doc(hidden)]
pub use cranpose_core::{
    debug_label_current_scope, location_key, with_current_composer, CallbackHolder, Composer,
    ParamState, ReturnSlot,
};

#[cfg(all(
    feature = "desktop-shell",
    feature = "robot",
    feature = "renderer-wgpu"
))]
#[doc(hidden)]
pub type RobotAppHook = dyn FnMut(String, String) -> Result<Option<String>, String>;

/// Convenience imports for Cranpose applications.
pub mod prelude {
    #[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
    pub use crate::{
        rememberAndroidHostWindowState, AndroidHostWindowPositionError, AndroidHostWindowSizeError,
        AndroidHostWindowSizeStatus, AndroidHostWindowState,
    };
    pub use crate::{
        rememberWindowState, AndroidOverlayWindowOptions, AppLauncher, AppSettings, Window,
        WindowAttachPolicy, WindowConfig, WindowGroup, WindowId, WindowModifierExt, WindowMoveMode,
        WindowNode, WindowResizeDirection, WindowState,
    };
    pub use cranpose_core::{
        delay, interval, mutableStateOf, produceState, remember, rememberCoroutineScope,
        rememberMutableStateOf, rememberMutableStateOfNeverEqual, rememberUpdatedState,
        CoroutineScope, MutableState, SnapshotStateList, SnapshotStateMap, State,
    };
    pub use cranpose_services::*;
    pub use cranpose_ui::*;
}

// Platform-specific runtime modules
#[cfg(any(
    all(feature = "desktop-shell", feature = "renderer-wgpu"),
    all(feature = "android", feature = "renderer-wgpu", target_os = "android"),
    all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"),
    all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32")
))]
pub(crate) mod platform_env;

#[cfg(all(feature = "android", feature = "renderer-wgpu", target_os = "android"))]
pub mod android;
#[cfg(feature = "renderer-wgpu")]
#[cfg_attr(
    not(any(
        all(feature = "android", target_os = "android"),
        all(feature = "ios", target_os = "ios")
    )),
    allow(dead_code)
)]
pub(crate) mod gpu_limits;

#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
pub mod desktop;
#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
mod desktop_accessibility;
#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
mod desktop_bundled_assets;
#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
mod desktop_host_surface;
#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
mod desktop_incoming;
#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
mod desktop_input;
#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
mod desktop_power;

/// What this process is using: the memory and processor-time readings whose
/// platform calls need `libc`, so no application writes them. Compiled with
/// the platform shells that install it — a target with no shell has nothing to
/// install it into.
#[cfg(all(
    unix,
    feature = "renderer-wgpu",
    any(
        feature = "desktop-shell",
        all(feature = "android", target_os = "android"),
        all(feature = "ios", target_os = "ios")
    )
))]
mod process_info;

/// Compiled with the shells that translate winit input. Both need a renderer:
/// a shell with nothing to draw with never opens a window to receive a pointer
/// event, so building this without one leaves it with no callers at all.
#[cfg(any(
    all(feature = "desktop-shell", feature = "renderer-wgpu"),
    all(feature = "ios", feature = "renderer-wgpu", target_os = "ios")
))]
mod winit_pointer;

/// Multi-touch id routing for the winit ingress. Only the iOS shell consumes it
/// today (desktop pointers are single-finger), but it is built on every target
/// that compiles the winit translation so its tests run on the host.
#[cfg(any(
    all(feature = "desktop-shell", feature = "renderer-wgpu"),
    all(feature = "ios", feature = "renderer-wgpu", target_os = "ios")
))]
#[cfg_attr(not(all(feature = "ios", target_os = "ios")), allow(dead_code))]
mod winit_touch;

/// winit's mouse wheel, normalized into the shell's shared wheel sample.
#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
mod winit_wheel;

/// Renderer-agnostic robot testing harness shared by the desktop shells.
#[cfg(all(
    feature = "robot",
    feature = "desktop-shell",
    feature = "renderer-wgpu"
))]
mod robot;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
pub mod ios;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_accessibility;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_file_picker;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_uri_handler;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_clipboard;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_share_sheet;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_image_picker;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_notifier;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_haptics;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_media;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_app_info;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_device_info;

/// Thermal pressure, which both Apple shells read from the same Foundation call.
#[cfg(any(
    all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"),
    all(
        feature = "desktop-shell",
        feature = "renderer-wgpu",
        target_os = "macos"
    )
))]
mod apple_thermal;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_writable_folder;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_camera;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_keyboard;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_back_gesture;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_background;

#[cfg(all(feature = "ios", feature = "renderer-wgpu", target_os = "ios"))]
mod ios_bundled_assets;

#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
pub mod recorder;

#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
pub mod web;

// The canvas drawing-buffer sizing policy is pure arithmetic shared with the
// web runtime. Compile it for the wasm web build (where `web` consumes it) and
// under `test` so its HiDPI regression guards run in the host test suite.
#[cfg(any(
    all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"),
    test
))]
mod web_surface_scale;

// The browser's wheel units and sign convention are likewise pure arithmetic,
// and likewise silent when wrong: compile them under `test` so the host suite
// pins the direction the web scrolls.
#[cfg(any(
    all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"),
    test
))]
mod web_wheel;

#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
mod web_accessibility;

#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
mod web_clipboard;

#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
mod web_host_surface;

#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
mod web_media;

#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
mod web_services;

#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
mod web_power;

#[cfg(all(feature = "web", feature = "renderer-wgpu", target_arch = "wasm32"))]
mod web_drop;

// Re-export the renderer-agnostic robot harness so applications and the
// testing crate can drive either desktop shell through a single path.
#[cfg(all(
    feature = "desktop-shell",
    feature = "robot",
    feature = "renderer-wgpu"
))]
pub use robot::{
    Robot, RobotScreenshot, RobotTimelineAction, RobotTimelineStep, SemanticElement, SemanticRect,
};

/// Development frame pacing and FPS statistics types.
#[cfg(all(feature = "desktop-shell", feature = "renderer-wgpu"))]
pub use cranpose_app_shell::{DevOptions, FpsStats, FramePacingMode};

/// A unique, empty directory under the workspace `target/test-output` for a
/// test in this crate that needs real files. See
/// [`cranpose_core::test_scratch_dir`].
///
/// Gated the way its callers are: the tests that need real files are the
/// desktop shell's own, and an iOS or web build compiles neither them nor this.
#[cfg(all(test, feature = "desktop-shell", feature = "renderer-wgpu"))]
pub(crate) fn test_scratch_dir(tag: &str) -> std::path::PathBuf {
    cranpose_core::test_scratch_dir(env!("CARGO_MANIFEST_DIR"), tag)
}