corundum-framework 0.5.0

The application framework shared by sapphire and asteria apps - single-dependency facade over the corundum-framework-* crates
Documentation

corundum-framework

Language: English | 日本語

The application framework shared by the sapphire apps and asteria: what an egui app is made of before any of its own screens, on the desktops and on phones.

Crates

Crate What it is Status
corundum-framework-gui The look: the shell, theme, fonts, icons, translations and window unreleased
corundum-framework-platform Without egui: the app's directories (mobile included), the system's languages, the JNI / Objective-C bridging unreleased
corundum-framework-update Newer GitHub releases of an app's binary: finding them, and replacing the binary with one unreleased
corundum-framework-package The app as a Velopack package on the desktops: its start and install hooks (start), its updates (Updater), its command line on PATH (cli_path) and the Windows console shim (console) unreleased
corundum-framework-service The app's background service registered with the OS's user-level service manager: a systemd user unit, a LaunchAgent, a scheduled task (install, uninstall, status, and state, stop, start for a GUI) unreleased
corundum-framework-keys API keys for an app's server: a store of named keys, one file each, in a directory the app chooses (a synced one works), their verification, an axum layer and a clap subcommand unreleased
corundum-framework-build For an app's build.rs: the app icon embedded in a Windows executable unreleased
corundum-framework The facade over the crates above unreleased
corundum-framework-demo A demo app showing every component planned

corundum-framework-gui

  • Shell: the app's screens on a rail (a bottom bar when the window is narrow, as on a phone), with a footer for Settings and a slot of the app's own; wide, a status bar along the bottom, the app's from the left (a log line, the services' state, the track playing), its slot and the detail pane's toggle at the right end. Screens and actions mix on the rail in any order (a "new entry" action first, say). When some are left off it — no room, or hidden — a "More" button takes the footer's place, its menu holding the footer first, then those left off. A settings page (settings::RailPage) orders them and chooses which the rail shows.
  • Theme: light and dark, three contrast levels and an accent colour, built in OKLCH; a settings widget and a tint picker (palette, hue/chroma disk, preview) to choose them.
  • Fonts: finds a CJK fallback font on the system (CoreText on iOS, the system font directories elsewhere).
  • Icons: a set of Lucide icons and the SVG loader that draws them.
  • Translations: every string looked up by id through Fluent in the user's language (tr!), with English and Japanese bundled and a language picker.
  • Settings: the Settings screen, a list of pages beside the open one, into which the app and the frameworks under it put their pages; tabs for the scopes a setting can be kept in (a device and its workgroup, a client and its server); and an Appearance page, the theme and the language over those tabs. The pages store nothing: the app keeps the values.
  • Look: the theme and the language in effect, kept with the rail's order in corundum.toml in the app's config directory (look and store, with the serde feature) and installed before the first frame, so the app opens in them. An app that merges settings from elsewhere (a workgroup, a server) applies the result there; an app with nothing else to keep them in stores them there alone, as the demo does.
  • Detail pane: the side pane every shell has (Shell::show, filled by the app's ShellApp::side_ui), beside every screen: what the main view's item holds (the playlist playing, the journal open) or a new entry begun from the rail's action, in a right column that a button on the status bar hides, on a card over the middle of the screen when the column is hidden, or on a phone in its place.
  • File explorer: explorer::FileExplorer, a folder's contents the app lists and hands in (a page at a time if it likes) — under a trail of the folders above, as a list sorted by a column's heading or a grid of thumbnails, and on a phone as tall rows a finger can tap. Entries are picked with clicks, Ctrl and Shift, and the keys; a right click (a long press) opens Open, Rename and the app's own items; picked entries dragged onto a folder ask to be moved, and F2 renames in place. The explorer touches no files: it returns ExplorerEvents, and the app moves, renames and opens.
  • Platform: what a window asks of the system and egui cannot give: the system's dialogs to pick files (platform::FilePicker, with filters such as IMAGE_EXTENSIONS) and folders (pick_folder), the file manager (open_in_file_manager), and on Android the clipboard and the insets. The dialogs are the desktops' for now: on a phone file_picker_available says there are none. What a phone has instead is its photo album: platform::MediaPicker (media-picker feature) picks photos and videos there (PHPicker on iOS, the Photo Picker on Android) and from the file dialog on a desktop, answering through a Pending the app polls each frame. platform::scan_qr (qr-scan) reads a QR code with the phone's camera the same way.
  • QR codes: qr::QrCode (qr-code feature) draws text as a QR code, for a phone to read off a desktop's screen: an invite ticket, a token.
  • Window: window::run opens a desktop window on wgpu the same way for every app; window::run_android does the same on an Android phone (GameActivity). On iOS the window is put in a scene, which iOS 27 requires (the app's Info.plist declares one with CorundumSceneDelegate as its delegate: see window), and takes the keyboards that compose (Japanese, Chinese, Korean), which winit 0.30 alone does not.
  • App menu entry: WindowOptions::launcher(spec) puts the app in the desktop's app menu at start (and sets the window's app id and icon), and settings::LauncherRow, a row for an About page, removes it and adds it back; with a store the removal is kept, and the entry is not written again on the next start. A Windows app also sets #![cfg_attr(windows, windows_subsystem = "windows")] in its main.rs, or the console opens with it.
  • First run: first_run::FirstRun (with the serde feature) shows the app's first-run pages (FirstRunPage, each answering a PageAction) over the window on the first start, remembers in corundum.toml that they were done, and runs them again from Settings. FirstRun::new(app_name, pages) makes it, done(ctx) says whether it ran, reset(ctx) shows it again.
  • Background services (service feature, desktops only): first_run::ServicePage, the first-run question whether to keep the app's services running in the background, and settings::ServiceRow, an About-page row per service that says what it is doing and stops, starts, removes or repairs it. A cargo build in target/ registers nothing: both say it is a development build instead.
Feature What it enables Default
eframe The desktop window (window::run) and its wgpu setup (gpu) yes
android The Android window (window::run_android), with logcat and the platform calls no
serde Serialize / Deserialize on the theme and language settings, store and first_run no
service first_run::ServicePage and settings::ServiceRow, over corundum-framework-service (brings serde; desktops only) no
update update::UpdatesPanel, the Settings → About rows that look for a newer release and say where it is (brings corundum-framework-update) no
self-update The panel also downloads the release and replaces the running binary (brings update). A store build leaves it off, as the store updates the app no
media-picker platform::MediaPicker: photos and videos from a phone's album, the file dialog on a desktop. An Android app's activity forwards onActivityResult to corundum (see corundum_framework_platform::media::available); iOS needs no Info.plist key no
qr-scan platform::scan_qr: a QR code read with the phone's camera (AVFoundation on iOS, Google's code scanner on Android); nothing on a desktop. iOS needs NSCameraUsageDescription, Android the app's play-services-code-scanner dependency (see corundum_framework_platform::qr::available). Without it, iOS links no camera API no
qr-code qr::QrCode, a QR code drawn from text (brings the qrcode crate) no
keys keys::KeysPage, the API keys page, over a store on this machine (LocalKeys) or the app's own KeysBackend (brings corundum-framework-keys) no
media-session media_session: the platform crate's media session, and media_session::start, which makes one for an eframe window and repaints it when the OS sends an event no
single-instance WindowOptions::single_instance: the desktop app runs once; window::run of a second start brings the first's window to the front (unminimized and focused) and returns without opening one. WindowOptions::instance takes a lock the app took itself, before applying an update at start no

The components depend on egui alone. An app that renders egui without eframe turns default features off; an Android app turns on android (eframe's default window does not build there).

corundum-framework-platform

What an app asks of the OS, without egui, so the crates without a window (a server, a CLI) can use it too, and no app has to call JNI or Objective-C itself.

  • Directories: dirs::{config_dir, data_dir, cache_dir} and an app's own AppDirs under them: the dirs crate's on the desktops, the sandbox's on iOS (NSFileManager) and Android (the context's files and cache directories). AppDirs is <root>/<app name> on the desktops and the sandbox's roots themselves on a phone. On Windows all of them are in Local (%LOCALAPPDATA%), not Roaming, which a domain carries to the user's other PCs, and AppDirs keeps the cache in <app>\cache there, apart from the settings and data. An app that kept its directory in Roaming calls AppDirs::migrate_from_roaming once at start to move it (merged into a Local directory that is already there, entry by entry).
  • Languages: locale::system_languages, the system's preferred languages (Android's whole LocaleList, where sys-locale gives only one).
  • Launcher (feature launcher, on by default): launcher, the app's entry in the desktop's app menu (a .desktop file and icon on Linux, an app bundle on macOS, a Start menu shortcut on Windows), written from a LauncherSpec, and the process's Windows taskbar id. Skipped for a build that runs from a cargo target directory.
  • Icon (feature icon, on by default; launcher uses it): icon, an SVG rendered to PNG, icns and ico with resvg.
  • Headless use: a crate that needs only the directories, the languages or package (the update and package crates do) depends on the platform crate with default-features = false, which builds neither resvg nor, on Windows, the shell's COM bindings.
  • Window support: the clipboard and the window insets on Android, which the gui crate's platform reads.
  • The system's screens (features, off by default): media, the phone's photo picker (media-picker), its picks copied into the cache, and qr, its QR code scanner (qr-scan), each answering through a pending::Pending.
  • Media session (feature media-session, off by default): media_session::MediaSession, a music player under the OS's media keys and in its "now playing": the app tells it the track (title, artist, album, length, a cover as a file or bytes) and whether it plays and where, and reads back play, pause, toggle, stop, next, previous and seeks as events, without holding its frame. An MPRIS player over zbus on Linux (no libdbus), the window's SystemMediaTransportControls on Windows, MediaPlayer's now-playing and remote commands on macOS and iOS, with the audio session on iOS (interruptions, headphones pulled out). Android has none yet: there the session does nothing. An iOS app that turns it on lists audio under UIBackgroundModes in its Info.plist, and is shown only while it plays sound.
  • Single instance (feature single-instance, off by default): instance::acquire, the app's lock (instance.lock in its data directory, held with the OS's file lock) and a local socket, the user's own (a named pipe on Windows; a socket file in the data directory on macOS, in $XDG_RUNTIME_DIR or else the data directory on Linux): the first start holds it and hears Instance::on_activate, a second is told AlreadyRunning and asks the first to come to the front. acquire_or_activate is both, and opens in place of a first start that is closing. A lock that cannot be taken for another reason never stops the app; on iOS and Android it does nothing.
  • Android: android::with_env, the process's JavaVM attached with Java exceptions caught, and the application context and activity, for an app's own JNI calls.

corundum-framework

One dependency for the crates above, as sapphire-framework's facade is: each feature re-exports a crate as a module (corundum_framework::platform, ::gui), and the rest pass through to the gui crate.

Feature What it enables Default
platform platform: corundum-framework-platform yes
gui gui and egui: corundum-framework-gui without eframe (brings platform) no
update update: corundum-framework-update no
self-update update with the download and the replacement; a store build leaves it off no
package package: corundum-framework-package (brings platform) no
service service: corundum-framework-service (brings platform) no
keys keys: corundum-framework-keys no
keys-axum keys::require_key, the layer that checks a router's requests (brings keys) no
keys-clap keys::KeyCommand, the subcommand that manages a store (brings keys) no
gui-keys The gui crate's keys page (brings gui and keys) no
gui-update The gui crate's update panel (brings gui and update) no
gui-self-update The gui crate's self-update (brings gui-update and self-update) no
gui-package The gui crate's package: the panel's row for the app's own package (brings gui-update and package) no
gui-service The gui crate's service: gui::first_run::ServicePage and gui::settings::ServiceRow (brings gui, service and serde) no
eframe gui::window::run, the desktop window (brings gui) no
android gui::window::run_android, the Android window (brings gui) no
serde The gui crate's serde no
media-picker gui::platform::MediaPicker, the phone's photo picker (brings gui) no
qr-scan gui::platform::scan_qr, the phone's QR code scanner (brings gui) no
qr-code gui::qr, QR codes drawn from text (brings gui) no
media-session platform::media_session, and gui::media_session with gui no
single-instance platform::instance, and gui::window::WindowOptions::single_instance with gui no

The default leaves egui out, for a server or a CLI; a desktop app turns on eframe, an Android app android.

Demo

demo/ is an app that shows every shared piece — the shell, the theme, the fonts, the icons, the translations and the platform calls — to try them on each OS apart from the apps: cargo run -p corundum-framework-demo on a desktop, and an Xcode and a Gradle project for the iOS simulator and the Android emulator. It is not published.

Status

Unreleased. The first release, 0.1.0, waits for the demo app and a manual check on each desktop and phone OS, so there is no release workflow yet. Until then, use it as a git dependency:

[dependencies]
corundum-framework-gui = { git = "https://github.com/sapphire-turtle/corundum-framework" }

Use the egui it re-exports (corundum_framework_gui::egui), so your app and the components share one.

The gui crate used to live in sapphire-framework as crates/corundum-gui; its history came along. Issue and pull request numbers in that history refer to sapphire-framework.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option. The bundled Lucide icons are under the ISC license, those derived from Feather under the MIT license (crates/corundum-framework-gui/assets/icons/LICENSE-Lucide).