Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

AppShell banner

Typing SVG


C++ CMake GLFW WebGPU Platforms


Status Rendering UI


Important

AppShell is a starting point, not a finished app. src/app/ is scaffolded with demo screens and a live settings panel so you can see every part of the shell working together — replace it with your own screens. The rendering core (window/, gpu/, ui/) is the part meant to stay.


Table of Contents


What is AppShell?

AppShell is a small, native, GPU-accelerated UI boilerplate for Windows and Linux desktop apps. There's no Electron, no Chromium, no browser engine, no garbage collector — just C++, one window, and direct GPU calls through WebGPU.

It exists for one specific situation: you want a desktop app with a fully custom look (not the OS's native widget toolkit) and native performance, without paying the cost of an embedded browser to get there.

GLFW window  →  wgpu-native GPU surface  →  immediate-mode UI, redrawn every frame

Clone it, build it, and you get a resizable borderless window with a real custom title bar, a dashboard full of working demo widgets (Kanban board, timeline, mind-map canvas, activity heatmap, bento grid), and a settings screen that actually controls the rest of the app — a working reference for how every piece fits together, not just a blank canvas.


Why This Project Exists

If you want a desktop UI that looks nothing like the OS's native widgets, and you don't want to pay Electron's or CEF's memory and binary-size cost to get it, your remaining options are usually "learn a native GUI toolkit's widget model" or "write directly against the GPU." AppShell takes the second path and does the unglamorous groundwork first, so a new project can start from "add my own screen" instead of "get a triangle on screen without crashing on resize."

That groundwork is what most from-scratch attempts stall on: a borderless window with a real custom title bar (drag-to-move, double-click-to-maximize, min/max/close — on the primary window and any window a tab gets detached into), DPI-safe rendering, real TrueType text instead of a bitmap font, image loading, scrolling and clipping regions, drag-and-drop, and multi-window support. AppShell ships all of it working together, plus enough demo screens to prove the shell can carry a real app's worth of UI, not just a static mockup.


Feature Overview

Everything below reflects what's implemented in the current source, not a wishlist.

Window & chrome

Feature Notes
Borderless window, custom title bar Hand-drawn min/max/close, drag-to-move, double-click-to-maximize — identical behavior on the primary window and any detached tab window
Multi-window Detach a tab into its own OS window, with a matching title bar and a "dock" button to bring it back
Responsive chrome Sidebar auto-collapses to an icon rail below ~900px width; the dashboard grid reflows from 4 columns down to 1 as the window narrows
Two layout modes Sidebar or top-nav, swapped live from Settings

Rendering core

Feature Notes
Immediate-mode 2D renderer (UiRenderer) Rects, rounded rects, circles, arcs, lines, and clipped scrolling regions, redrawn from state every frame
Real TrueType text JetBrains Mono baked into a glyph atlas via stb_truetype, with five derived style variants (regular/bold/condensed/oblique/wide)
Image loading Arbitrary PNG/JPEG textures via stb_image, cached by path, drawn as tinted quads or UV sub-rects for sprite sheets and icon atlases
4x MSAA on every draw call Shape edges, circles, and text are anti-aliased rather than jagged — not free with raw immediate-mode rendering

Widget library (widgets.h)

Widget Notes
Scroll panels Clipped, momentum-free scrolling regions used throughout
Tab bar Rename, add, and detach-to-window, per tab
Kanban board Draggable cards across columns; swimlanes modeled in state (demo renders one lane)
Timeline Pan/zoom with tooltips
Heatmap Activity-grid visualization
Node-graph canvas Add/rename/delete/connect nodes, zoom-to-cursor
Bento grid Responsive card grid
Workload chart, countdown timer Additional dashboard-ready widgets

Live settings screen

Not a static mockup — theme (dark/light), layout (sidebar vs. top nav), density (comfortable vs. compact), font style, and feature toggles (animations, shadows) are wired to real state that every other screen reads on every frame.

Optional GPU-heavy demo pipelines

Kept as separate, additive subsystems so you can delete what you don't need:

Subsystem What it demonstrates
gpu/renderer3d A depth-tested 3D pipeline (indexed mesh + MVP uniform) — starting point for any 3D/model-viewer content
gpu/instanced_quad_renderer One draw call for thousands of quads — particle systems, point clouds, tile grids
gpu/signal_level_compute A real WGSL compute shader (parallel-reduction RMS/peak) — a template for GPU-side DSP or other compute workloads

Architecture

flowchart TD
    A["GLFW window<br/>(borderless, custom title bar, drag/resize)"] --> B["GpuContext<br/>wgpu-native instance/adapter/device/surface"]
    A --> C["Input events<br/>mouse, keyboard, scroll, file-drop"]
    C --> D["main.cpp<br/>orchestrator: wiring input into AppState"]
    D --> E["UiRenderer<br/>solid shapes · textured text · textured images<br/>shared clip/scissor + batching, 4x MSAA"]
    E --> B
    D --> F["widgets.h<br/>ScrollPanel · TabBar · KanbanBoard · Timeline ·<br/>GraphCanvas · Heatmap · Bento grid"]
    F --> E
    D --> G["app/ (AppState + screens.h)<br/>replace this with your app"]
    G --> F
    D -.optional, delete if unused.-> H["gpu/renderer3d<br/>gpu/instanced_quad_renderer<br/>gpu/signal_level_compute"]
    H --> B

    style D fill:#0D1526,stroke:#38BDF8,stroke-width:2px,color:#E2E8F0
    style E fill:#0D1526,stroke:#4B8BBE,stroke-width:2px,color:#E2E8F0
    style F fill:#0D1526,stroke:#10B981,stroke-width:1.5px,color:#E2E8F0
    style G fill:#0D1526,stroke:#F59E0B,stroke-width:1.5px,color:#E2E8F0
    style H fill:#0D1526,stroke:#6366F1,stroke-width:1.5px,color:#E2E8F0
Loading

main.cpp owns the window, the GPU device, and the per-frame render loop; it drives the UI by wiring input into AppState and dispatching to a Draw*Screen function per nav section, keeping the loop itself small and mostly free of app-specific logic. src/gpu/gpu_context.* owns the one wgpu-native instance/adapter/device/surface for the window, plus the MSAA color target and depth target every render pass uses — RenderFrame() is the entire per-frame render path. The optional 3D/instancing/compute subsystems under src/gpu/ are independent of the 2D UI path and of each other; none of them are required for the shell to run.


Tech Stack & Rationale

LayerChosenWhy
Windowing / input GLFW 3.4 (fetched from source) Small, well-understood, cross-platform surface for window creation and raw input, without pulling in a full application framework.
GPU / rendering wgpu-native (WebGPU → D3D12 on Windows, Vulkan on Linux) One graphics API surface instead of maintaining separate D3D12 and Vulkan backends by hand, while still talking to the GPU directly — no engine, no scene graph, no retained-mode widget toolkit.
UI rendering Hand-written immediate-mode renderer, real TrueType text via stb_truetype The entire UI is redrawn from application state every frame, batched into vertex buffers and submitted in one render pass — no DOM, no retained widget tree to keep in sync, and no bitmap-font ceiling on text quality.
Images stb_image Single-header, dependency-light decoding for arbitrary PNG/JPEG textures, cached by path.
Build system CMake 3.20+, GLFW fetched at configure time Nothing to preinstall beyond a compiler, CMake, and the one precompiled dependency (wgpu-native) that CMake can't reasonably build from source itself.

Repository Structure

appshell/
├── CMakeLists.txt
├── src/
│   ├── main.cpp                 # orchestrator: window/GPU/UI setup, input, main loop
│   ├── window/
│   │   ├── window.cpp/.h        # GLFW wrapper + input callbacks
│   ├── gpu/
│   │   ├── gpu_context.cpp/.h           # wgpu-native instance/adapter/device/surface
│   │   ├── renderer3d.cpp/.h            # optional: depth-tested 3D pipeline
│   │   ├── instanced_quad_renderer.cpp/.h  # optional: instanced quad rendering
│   │   ├── signal_level_compute.cpp/.h  # optional: WGSL compute shader demo
│   │   └── mat4.h
│   ├── ui/
│   │   ├── ui_renderer.cpp/.h   # shapes, text, images - the 2D rendering core
│   │   ├── widgets.h            # scroll panels, tabs, kanban, timeline, graph...
│   │   ├── theme.h              # dark/light color palette
│   │   ├── anim.h               # eased-interpolation helpers
│   │   ├── font_style.h         # style variants derived from one TTF
│   │   ├── text_input.h         # minimal single-line editor (renaming)
│   │   └── geom.h
│   ├── app/                     # <-- replace this with your app
│   │   ├── app_state.h          # persistent state, Screen enum, live settings
│   │   ├── screens.h            # one draw function per nav section (demo content)
│   │   ├── detached_window.h    # hosts a tab in a second OS window
│   │   └── asset_paths.h
│   └── assets/
│       ├── fonts/JetBrainsMono-Regular.ttf
│       └── images/demo_gradient.png
└── .gitignore

Getting Started

Prerequisites

  • CMake 3.20+
  • A C++17 compiler (MSVC on Windows, GCC/Clang on Linux)
  • Linux only: libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libgl1-mesa-dev
  • The matching wgpu-native binary for your platform — this is the one dependency CMake can't fetch and build from source (it's a precompiled Rust binary). Download it and place it under a shared Project_Dependency/ folder; see the comment block at the top of CMakeLists.txt for the exact expected layout, or override the location with -DPROJECT_DEPS_DIR=....

Build

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release

GLFW is fetched from source automatically. Fonts and image assets are copied next to the built executable as a post-build step, and main.cpp pins the process's working directory to the executable's own folder at startup, so relative asset paths resolve the same way regardless of how the binary is launched.


Extending This

  • Your app's screens: add a case to the switch in main.cpp's DrawFrame and a matching Draw*Screen function in src/app/screens.h. Add persistent state to AppState.
  • Reskinning: edit theme::Dark() / theme::Light() in src/ui/theme.h.
  • A new font style: add an entry to FontStylePresets() in font_style.h — no new glyph art needed, styles are derived from the one TTF.
  • A new layout mode: extend the LayoutMode enum, add a case in ChromeSidebarWidth() / ChromeTopBandHeight(), and add a DrawYourNav function in screens.h.
  • More 2D primitives: add methods to UiRenderer next to DrawCircle / DrawLine / etc.
  • Don't need 3D / compute / instancing? Delete the corresponding file(s) under src/gpu/, drop the matching lines from CMakeLists.txt's add_executable, and remove their setup calls and screen cases from main.cpp / screens.h. Each is self-contained and doesn't affect the 2D UI path.
  • A real UI toolkit instead: swap UiRenderer for Dear ImGui once you outgrow immediate-mode — GpuContext already exposes the raw device() / queue() / surface_format() its WebGPU backend needs.
  • macOS: add a WGPUSurfaceSourceMetalLayer branch to gpu_context.cpp. Everything else (GLFW, CMake, the UI/widget layer) is already platform-generic.

Known Limitations

Being direct about scope, so you know what you're picking up:

  • Text input is intentionally minimal — no cursor movement, no selection, no copy/paste, ASCII only. Fine for renaming a tab or a node; not a real text field. See text_input.h's header comment before extending it.
  • Kanban columns scroll as one region, not independently per column. Swimlanes are modeled (KanbanSwimlane) but the demo board only renders one lane.
  • SignalLevelCompute::Compute() blocks synchronously on the GPU readback — fine for an occasional demo call, not for a real-time hot loop. A production audio/DSP path should double-buffer and poll instead.
  • No macOS backend yetgpu_context.cpp has Windows (D3D12) and Linux (Vulkan/X11) surface creation only.
  • No native Wayland surface — GLFW is built with X11 only, which covers XWayland (the common case). Flip GLFW_BUILD_WAYLAND on in CMakeLists.txt if you specifically need native Wayland.

Roadmap

  • WGPUSurfaceSourceMetalLayer branch for macOS
  • Native Wayland surface option, built and documented
  • Cursor movement, selection, and copy/paste in text_input.h
  • Independent per-column scrolling and multi-swimlane rendering in the Kanban demo
  • Double-buffered, non-blocking readback for SignalLevelCompute

Contributing

Issues and pull requests are welcome — especially around the rendering core (ui_renderer.cpp, widgets.h) and the optional GPU subsystems, since those are the parts most projects will build directly on top of.


footer

No browser runtime. Just a window, a GPU, and your app.

About

Starter shell for GPU-accelerated native desktop apps. C++17, GLFW windowing, wgpu-native (WebGPU→D3D12/Vulkan), immediate-mode UI with real TrueType text, and a working widget library (Kanban, timeline, node graph, bento grid) — no browser runtime, no game engine.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages