Vortex 2.5 is archived. The latest version is 3.0. Read the 3.0 docs

Follow this guide to build your own application on Vortex.

App Development Guide - 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.

Project setup

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.

The app entry

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.

The App root style

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.

The shared shell

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:

  • Static tabs: add the tab and its pane in your styles, then pass them as extraTabs: wire_main_window_shell(app, "App", "Main Window", {{"My Tab", "My Pane"}}).
  • Dynamic tabs: register tabs created at runtime with tabs->add(tabPath, panePath, stateName) / tabs->remove(...) / tabs->activate(...). Static and dynamic tabs share one selection: activating either kind deselects the other.

Platform shells

Desktop

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).

Web

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.

Android

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.

Platform seams

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.

Binding data and interaction

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.

Appearance
Theme
—