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 base. The convention is an App root layer that inherits Vortex/App and lists the app's top-level widgets:
App << Vortex/App:
Icon: "logo_myapp"
Name: "MyApp"
Main Window << Vortex/Main Window
Theme Directories Dialog << Vortex/Theme Directories Dialog
Icon names an icon file and Name is the app's display name. The titlebar's app tab links to both, so these two attributes are all it takes to brand the shell. Keep the root named App: that's the name those links look for.
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.
App << Vortex/App:
Icon: "logo_myapp"
Name: "MyApp"
MyApp Window << MyApp Window
Theme Directories Dialog << Vortex/Theme Directories Dialog
MyApp Window << Vortex/Main Window:
App Pane/Content:
Greeting << Vortex/Text:
Text: "Hello, MyApp"
Then pass the window's name to the shell wiring:
app.set_root_style("App");
wire_main_window_shell(app, "App", "MyApp Window");
See the styling guide for inheritance, path overrides, 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.