Skip to main content

AppLauncher

Struct AppLauncher 

Source
pub struct AppLauncher { /* private fields */ }
Expand description

Platform-agnostic application launcher.

Platform-agnostic application launcher.

This builder provides a unified API for launching Compose applications on different platforms (desktop, Android, Web) with proper inversion of control. It abstracts away the differences between window creation, event loops, and surface initialization.

§When to use

Use AppLauncher as the standard entry point for any Cranpose application. It handles the boilerplate of:

  • Creating a window or attaching to a view.
  • Initializing the graphics context (WGPU instance, Surface, Adapter, Device).
  • Setting up the main event loop.
  • Bridging platform events to the Cranpose runtime.

§Example

use cranpose::AppLauncher;

// Desktop
#[cfg(all(
    feature = "desktop-shell",
    feature = "renderer-wgpu",
    not(target_os = "android")
))]
fn main() {
    AppLauncher::new()
        .with_title("My App")
        .with_size(1024, 768)
        .run(|| {
            // Your composable UI here
        });
}

// Android
#[cfg(all(feature = "android", target_os = "android"))]
#[unsafe(no_mangle)]
fn android_main(app: android_activity::AndroidApp) {
    AppLauncher::new().with_title("My App").run(app, || {
        // Your composable UI here
    });
}

#[cfg(not(any(
    all(
        feature = "desktop-shell",
        feature = "renderer-wgpu",
        not(target_os = "android")
    ),
    all(feature = "android", target_os = "android")
)))]
fn main() {}

Implementations§

Source§

impl AppLauncher

Source

pub fn new() -> Self

Create a new application launcher with default settings.

Source

pub fn with_title(self, title: impl Into<String>) -> Self

Set the window title.

§Arguments
  • title - The string to display in the window title bar (Desktop/Web) or the activity label (Android).
Source

pub fn with_capabilities(self, capabilities: &Capabilities<'static>) -> Self

State what this application asks of a device.

The value comes from the build script, through app_capabilities!:

ⓘ
cranpose::app_capabilities!();

AppLauncher::new().with_capabilities(&CAPABILITIES)

The platform builds read the same declaration, so the permissions in the manifest and the answers a service gives at run time cannot drift apart.

Source

pub fn with_application_id(self, application_id: impl Into<String>) -> Self

Set the id every framework-owned storage path is scoped by.

Use the same reverse-DNS identifier the app is packaged under, so a desktop build writes beside its own data rather than beside every other Cranpose application.

§Arguments
  • application_id - for example com.example.notes.
Source

pub fn with_size(self, width: u32, height: u32) -> Self

Set the initial window size.

Desktop uses this as the initial primary window size. Android applies it as a best-effort host-window request only when the activity starts in a multi-window mode (freeform / desktop windowing such as DeX); fullscreen activities ignore it and keep the display-sized, edge-to-edge surface, because shrinking the fullscreen window would leave uncovered (black) strips of display around the surface. Web ignores it: the canvas takes the box the host page lays it out in, or the viewport under Self::with_web_fill_viewport.

§Arguments
  • width - The initial width in logical pixels.
  • height - The initial height in logical pixels.
Source

pub fn with_window_wrapping_content(self) -> Self

Desktop only: keep the primary window the size of what it lays out, measured after every update, so a window that holds a stack of panes is exactly that stack and shrinks when a pane leaves for a window of its own. The window keeps its top-left corner as it resizes. The initial size applies until the first layout; other platforms ignore this.

Source

pub fn with_web_fill_viewport(self, fill: bool) -> Self

Web only: size the canvas to the full browser viewport and keep it tracking that viewport’s size as the window resizes, instead of taking the box the host page’s stylesheet lays the canvas out in. Other platforms ignore this.

Source

pub fn with_custom_cursor_size(self, size: CustomCursorSize) -> Self

Desktop and web: how big the app’s own cursor images appear when the person has enlarged the system pointer. See CustomCursorSize.

Source

pub fn with_fonts(self, fonts: &'static [&'static [u8]]) -> Self

Set fonts to use for text rendering.

§Arguments
  • fonts - A slice of static byte slices, each representing a font file (e.g., .ttf or .otf).
§Example
use cranpose::AppLauncher;

// In specialized environments, you might include bytes:
// static REGULAR: &[u8] = include_bytes!("../assets/MyFont.ttf");
static DUMMY_FONT: &[u8] = &[];
static FONTS: &[&[u8]] = &[DUMMY_FONT];

AppLauncher::new().with_fonts(FONTS);
Source

pub fn with_font_family(self, family: &FontFamily) -> Self

Register a font family from files on disk.

Each FontFile declares the weight and style its file provides, and a TextStyle naming the same family picks between them. The files are read and parsed here, once, before the app runs — nothing re-reads them per frame or per string.

A family whose files cannot be read is reported and skipped; text asking for it falls back to the default face rather than disappearing.

§Example
use cranpose::{
    AppLauncher,
    text::{FontFamily, FontFile, FontWeight},
};

let roboto = FontFamily::file_backed(vec![
    FontFile::new("/system/fonts/Roboto-Regular.ttf"),
    FontFile::new("/system/fonts/Roboto-Regular.ttf").with_weight(FontWeight::MEDIUM),
    FontFile::new("/system/fonts/Roboto-Regular.ttf").with_weight(FontWeight::BOLD),
])
.expect("a family needs at least one file");

let launcher = AppLauncher::new().with_font_family(&roboto);
Source

pub fn with_font_face_bytes( self, family: &FontFamily, weight: FontWeight, style: FontStyle, bytes: impl Into<Vec<u8>>, ) -> Self

Register a font family from bytes the app already holds.

Use this for fonts that are not files on disk — an archive entry, a download cache, or an asset cranpose-assets resolved out of a desktop bundle (its load_bytes returns exactly what this wants). For an APK asset use AppLauncher::with_android_asset_font instead: APK entries are not filesystem paths, so a path resolver cannot reach them.

use cranpose::{
    AppLauncher,
    text::{FontFamily, FontStyle, FontWeight},
};

let launcher = AppLauncher::new().with_font_face_bytes(
    &FontFamily::named("Roboto"),
    FontWeight::NORMAL,
    FontStyle::Normal,
    bytes,
);
Source

pub fn with_system_font_family( self, directory: impl AsRef<Path>, family: &FontFamily, ) -> Self

Bind a generic family (FontFamily::SansSerif, Serif, Monospace, Cursive) to the platform’s own typeface for it, at Regular, Medium and Bold.

Styles keep naming the generic family; they simply stop resolving to the framework’s bundled fallback. On Android this is how an app matches what Jetpack Compose draws for FontFamily.SansSerif, because the platform backs that alias with its own Roboto.

directory is the platform’s font directory — ANDROID_SYSTEM_FONT_DIR on Android. If nothing there backs the family, the failure is reported and the bundled fallback keeps serving.

Source

pub fn with_system_fonts( self, family: &FontFamily, weights: &[FontWeight], ) -> Self

Registers a family from the fonts this platform ships, without the application naming a directory.

Where a system keeps its fonts is the platform’s business, and an application that spells the path out has target-specific code in it and draws in the wrong typeface on the target it did not spell out. Every weight in weights is resolved the way the platform resolves it, so a weight the system has no file for lands on the face it would have returned rather than being skipped.

Platforms with no readable font directory — the browser, which has no filesystem and draws with the fonts the page already has — register nothing and leave the app on its own faces.

Source

pub fn with_log_tag(self, tag: impl Into<String>) -> Self

The tag this application’s log lines carry.

Android routes every line through one tag and adb logcat -s <tag> is how anyone reads them. Naming it here means an application never initialises a platform logger of its own to get its name onto its lines.

Source

pub fn with_window_icon(self, icon: ImageBitmap) -> Self

Desktop only: the picture every window the application opens carries, in the title bar, taskbar and task switcher of Windows and Linux.

macOS, Android, iOS and the web take the application’s icon from what the platform packaged and ignore it. A square picture of 64 to 256 pixels a side reads well at every size those desktops draw it.

Source

pub fn with_developer_inspector(self, enabled: bool) -> Self

Enables the developer inspector independently of application semantics.

Enabled by default in debug builds and disabled in release builds. Robot drivers default to disabled; this override wins in either builder order. Open it with the floating Inspector control; drag the control or panel title to move it.

Source

pub fn with_fonts_from( self, register: impl FnOnce(&mut SoftwareTextFontRegistry) -> Result<(), FontLoadError>, ) -> Self

Register fonts through the registry directly, for apps that want the per-face Result rather than a logged warning.

Source

pub fn with_android_use_system_fonts(self, use_system_fonts: bool) -> Self

Enable system font loading on Android (default: false).

When false, only fonts provided via with_fonts(), with_font_family() and friends are used. When true, the platform’s sans-serif, serif and monospace faces are registered from ANDROID_SYSTEM_FONT_DIR in addition, so styles naming those generic families draw in the system typeface.

Android backs those aliases with variable fonts on modern builds; the registry instances them per weight on their wght axis rather than drawing every weight at the file’s default.

Text that names no family at all also lands on the system face, because registered faces outrank the plain with_fonts() bytes on a tie — the same thing Compose does, where FontFamily.Default is sans-serif on Android. An app that wants its own bundled font for unnamed text should leave this off and register its family by name instead.

Source

pub fn with_android_gpu_backend(self, backend: AndroidGpuBackend) -> Self

Selects the Android graphics API. Other platforms ignore this setting.

The default is Vulkan. OpenGL ES uses the same WGPU renderer through the device’s GLES driver. Profiling overrides can replace this choice.

Source

pub fn with_android_overlay_window( self, options: AndroidOverlayWindowOptions, ) -> Self

Render the Android root into a floating TYPE_APPLICATION_OVERLAY surface.

This Android-only mode requires the host app to declare android.permission.SYSTEM_ALERT_WINDOW, include Cranpose’s Android Java helper sources, and obtain overlay permission before launch. Other platforms ignore this setting and keep their normal primary surface.

Source

pub fn with_headless(self, headless: bool) -> Self

Enable headless mode for robot testing.

When headless mode is enabled, the window is created but not shown. This allows robot tests to:

  • Run in parallel without windows overlapping or stealing focus
  • Run in CI environments without a display server (using Xvfb or similar)
  • Execute faster by skipping window decoration rendering

Note: The app still creates a full WGPU surface for accurate rendering tests.

§Example
use cranpose::AppLauncher;

#[cfg(all(
    feature = "desktop-shell",
    feature = "renderer-wgpu",
    not(target_os = "android")
))]
{
    let launcher = AppLauncher::new()
        .with_title("Robot Test")
        .with_size(800, 600)
        .with_headless(true);

    #[cfg(feature = "robot")]
    let launcher = launcher.with_test_driver(|robot| {
        robot.wait_for_idle().unwrap();
        robot.click(100.0, 100.0).unwrap();
        robot.exit().unwrap();
    });

    launcher.run(|| {
        // Your composable UI here
    });
}
Source

pub fn with_fps_counter(self, enabled: bool) -> Self

Enable FPS counter overlay (desktop only).

When enabled, displays a real-time FPS counter in the top-right corner. This is rendered directly by the renderer (not via composition) so it doesn’t affect performance measurements.

§Example
use cranpose::AppLauncher;

#[cfg(all(
    feature = "desktop-shell",
    feature = "renderer-wgpu",
    not(target_os = "android")
))]
{
    AppLauncher::new()
        .with_title("My App")
        .with_fps_counter(true)
        .run(|| {
            // Your composable UI here
        });
}
Source

pub fn with_frame_pacing_mode(self, mode: FramePacingMode) -> Self

Set the initial desktop frame pacing mode.

This controls whether the desktop surface uses vsync or no-vsync presentation and, for hard caps, limits redraw scheduling to the requested frame rate.

Source

pub fn with_frame_pacing_controls(self, enabled: bool) -> Self

Enable clickable frame pacing controls in the desktop development overlay.

Enabling the controls also enables the FPS overlay because the controls are rendered as part of that overlay.

Source

pub fn with_recording(self, path: impl Into<PathBuf>) -> Self

Enable input recording mode.

When enabled, all mouse and keyboard events are recorded with precise timestamps. On app exit, a robot test file is generated that can replay the exact interaction sequence.

§Example
use cranpose::AppLauncher;

AppLauncher::new()
    .with_title("My App")
    .with_recording(".cranpose-tmp/my_test.rs")
    .run(|| {
        // Interact with the app, then close
        // Recording is saved automatically
    });
Source

pub fn try_run(self, content: impl FnMut() + 'static) -> Result<(), LaunchError>

Run the application (Desktop platform).

This method blocks the current thread and starts the platform event loop. It should be the last call in your main function.

§Arguments
  • content - The root composable function of your application.
Source

pub fn run(self, content: impl FnMut() + 'static) -> !

Run the application (Desktop platform).

Use AppLauncher::try_run when the caller needs a typed launch failure.

Trait Implementations§

Source§

impl Default for AppLauncher

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync> ⓘ

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> NoneValue for T
where T: Default,

Source§

type NoneType = T

Source§

fn null_value() -> T

The none-equivalent value.
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more