Follow this guide to build your own application on Vortex.
A Vortex app is a consumer of the framework: it vendors Vortex, declares its interface in its own styles, provides one entry function, and adds a thin per-platform shell. This guide walks the whole contract. The bundled demo (examples/demo/) is a complete, minimal reference; it is built exactly the way any external app is.
Vendor Vortex as a git submodule and add it to your CMake build:
git submodule add https://codeberg.org/HuntrSoftware/vortex.git ext/vortex
git submodule update --init --recursive
add_subdirectory(ext/vortex)
This provides the vortex_core library plus the platform reconcilers (vortex_desktop on desktop builds). Vortex's own demo and tests are skipped automatically when it is not the top-level project.
Every platform shell calls one function at startup, defined by your app:
#include "vortex/vapp.h"
#include "vortex/vshell.h"
void Vortex::build_app(VApp& app) {
app.set_root_style("App");
wire_main_window_shell(app);
// bind content + wire app-specific interaction here
}
build_app() applies the app's root style, reuses the framework shell, and then binds whatever content and interaction the app needs. Everything in it is portable; it runs unchanged on every platform.
Your app ships its own style directory, loaded additively on top of the framework's library styles and un-namespaced, so your styles can inherit any library style through a Vortex/-qualified reference. The convention is an "App" root style listing the app's top-level widgets:
{
"App": {
"->": "S:Vortex/Box",
"Layout.Direction": "Column",
"Layout.Align": "Stretch",
"Main Window": {
"->": "S:Vortex/Main Window",
"Layout.Grow": 1
},
"Theme Directories Dialog": "S:Vortex/Theme Directories Dialog"
}
}
An app usually wants its own window rather than the stock one. Define a window style that inherits the framework's Main Window under your app's name: the whole shell structure (titlebar, tabs, settings menu) comes with it, and your style overrides or extends what it needs. Then pass that name to the shell wiring:
app.set_root_style("MyApp");
wire_main_window_shell(app, "MyApp", "MyApp Window");
See the styling guide for inheritance, path appendages, and theming in depth.
wire_main_window_shell() installs the standard window behavior: single-select titlebar tabs mapped to body panes (App Tab → App Pane, Settings Tab → Settings Menu), the theme/appearance states, and the theme-directories dialog open/close.
It returns a WindowTabs controller, pre-populated with the framework tabs. Apps with their own tabs use it two ways:
extraTabs: wire_main_window_shell(app, "App", "Main Window", {{"My Tab", "My Pane"}}).tabs->add(tabPath, panePath, stateName) / tabs->remove(...) / tabs->activate(...). Static and dynamic tabs share one selection: activating either kind deselects the other.A desktop main() fills a DesktopAppConfig and hands off to the framework runner:
#include "run_desktop.h"
int main(int argc, char** argv) {
Vortex::DesktopAppConfig config;
config.orgName = "myorg"; // QSettings identity:
config.appName = "MyApp"; // per-app theme persistence
config.windowTitle = "MyApp";
config.defaultTheme = "Dark (huntrsoftwarellc)";
config.frameworkAssetDir = VORTEX_FRAMEWORK_DIR; // the framework's styles/themes/icons
config.appStyleDirs = {VORTEX_APP_DIR "/styles"};
return Vortex::run_desktop(argc, argv, config);
}
add_executable(myapp platforms/desktop/main.cpp app/myapp_app.cpp)
target_link_libraries(myapp PRIVATE vortex_core vortex_desktop)
target_compile_definitions(myapp PRIVATE
VORTEX_FRAMEWORK_DIR="${CMAKE_SOURCE_DIR}/ext/vortex"
VORTEX_APP_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
run_desktop() owns the QApplication, theme persistence, the frameless window, and the headless screenshot harness; a consumer's main() stays a few lines.
DesktopAppConfig also takes appIconDirs (searched before the framework's icons, so an app can override a shell icon by name or add its own) and appMediaDirs (searched for Image/Video sources).
The web analog is one CMake call, made from an Emscripten build (emcmake cmake ...):
include(ext/vortex/platforms/web/VortexWebApp.cmake)
vortex_web_app(myapp-web
BUILD_APP app/myapp_app.cpp
APP_STYLES ${CMAKE_SOURCE_DIR}/styles
OUT_DIR ${CMAKE_SOURCE_DIR}/site
APP_ICONS ${CMAKE_SOURCE_DIR}/icons # optional
APP_MEDIA ${CMAKE_SOURCE_DIR}/media) # optional
This builds vortex.js + vortex.wasm from the framework bridge plus your build_app(), embeds the framework styles/themes and your app styles into the WASM filesystem, and copies the reusable runtime JS (the DOM reconciler) and icons next to your page. Your OUT_DIR holds an index.html that loads vortex.js; serve it over HTTP.
Subclass the framework's host Activity and supply only your identity:
import com.huntrsoftware.vortex.VortexActivity
class MainActivity : VortexActivity() {
override val prefsName = "myapp_prefs"
override val defaultTheme = "Dark (huntrsoftwarellc)"
}
The vortex library module owns the reconciler, JNI bridge, theme persistence, and asset plumbing. Apps that need seams backed by app-only libraries (e.g. Play Billing for in-app purchases) override onInstallAppSeams(bridge) to install them.
App code never talks to platform APIs directly. Each platform shell injects handlers into the core; portable app code consumes them through VApp:
| Seam | App code calls | Provided by |
|---|---|---|
| HTTP | app.http(request, callback) |
Qt Network / fetch / OkHttp-style transport |
| File pick | app.pick_path(...) |
native file dialogs / SAF |
| File upload | app.pick_and_upload(...) |
pick + POST, per platform |
| Open URL | app.open_url(url) |
external browser / new tab |
| Timers | app.after(ms, callback) |
Qt timers / async calls / handlers |
| Config in | app.env(key) |
set by the shell (e.g. base_url) |
| Persistence | app.store(key, value) |
QSettings / localStorage / SharedPreferences |
| Purchases | app.start_purchase(...), app.query_purchases() |
app-installed (e.g. Play Billing) |
A platform that doesn't provide a seam simply never delivers it; portable code stays portable.
Beyond the shell, apps drive their widgets through VApp's runtime binds: bind_text, bind_click, bind_visible, set_list, the editor and checkbox APIs, and more. See the widgets guide for the full tour.