LunaUI is a generic Win32 + WebView2 application shell: one top-level window, one WebView2 controller, optional local virtual-host content mapping, and a typed JSON message bridge between page JS and native code. WebView2 initialization is asynchronous but ready/error/closed lifecycle callbacks are explicit, and incoming messages are accepted only from an exact configured origin allowlist.
- Standard resizable, DPI-aware window with an optional dark title bar and always-on-top.
- Frameless mode (
borderless): the page draws its own title bar. The shell keeps an 8-direction native resize strip (resize_borderpx) around the WebView viaWM_NCHITTEST(edges + corners), clamps maximize to the work area, and optionally rounds the client corners on Windows 11. - Optional window protocol manager (
luna_ui_window_manager.hpp): page → nativehost:messages start native gestures (host:drag-move,host:resize-<left|right|top|bottom|top-left|…>) and run window commands (host:minimize-window,host:maximize-window,host:restore-window,host:toggle-maximize,host:close-window). Structured JSON commands use{"kind":"webui-command","command":"set-always-on-top","payload":{"value":true}}(alsoset-window-background-color,query-window-state,set-window-transparent— live DWM glass toggle — andset-backdrop-mode). - Backdrop modes (native transparency):
host:cycle-backdrop-modeor theset-backdrop-modecommand cycles throughnone → glass → layered-97 → layered-85 → mica → acrylic → blur → acrylic-blur. Modes map toDwmExtendFrameIntoClientArea,WS_EX_LAYERED+SetLayeredWindowAttributes,DWMWA_SYSTEMBACKDROP_TYPE(Mica/Acrylic), and the composition accent API (blur / acrylic-blur); the current mode is announced to the page ashost:backdrop-mode:<label>. The WebView background is transparent (transparent_background), so the chosen backdrop shows through transparent page pixels. - Optional multi-window (
LunaUIWindowManager::OpenWindow, page commandopen-window): child windows share the shell's WebView2 environment, are owned by the main window (they close with it), and can be transparent real glass frames (transparent):DwmExtendFrameIntoClientArea({-1,-1,-1,-1})plus a fully transparent WebView background so the page draws floating rounded cards over the desktop. Child pages control their window through{"type":"window","action":…}JSON (drag,minimize,maximize,close,alwaysOnTop{enabled},resize{edge},state); the shell replies with{"type":"windowState",…}and{"type":"windowLifecycle", "state":"closing"|"minimizing"|"restoring"|"maximizing"}. Page-side commands:open-window,close-window,hide-window,destroy-window,window-list; native pushes{"kind":"host:window-event","event":"opened"|"ready"|"hidden"|"closed"|"error"|"list",…}back to the main page.BroadcastJsonfans one JSON out to every window. - Window manager (window lifecycle kinds): every child window has a
kindinopen-window's payload —transient(default): created on open, destroyed on close.persistent(palette-like, e.g. a command-search popup): created once and kept alive —open-windowshows/focuses it (optionallyshow:falseprefetches it hidden at startup, so it is loaded on launch),close-windowjust hides it (host:window-event "hidden"), and its page state (query, scroll) survives across shows;hide-windowhides,destroy-windowtears it down, and the child page itself can post{"type":"window", "action":"close"|"hide"|"destroy"}.open-windowalso acceptsauto_hide_on_focus_lost:truefor palette-like windows: when the window loses focus (the user clicked elsewhere) it dismisses itself — persistent windows hide (page stays alive), transient windows close. A short deactivation guard prevents an instant hide during the show/activate hand-off.window-list/window-evententries now carry"kind"and"visible"so the page can render a full window-manager panel.
- Window state push (native → page): the optional manager sends
host:window-shown, childwindowLifecycleevents, and{"kind":"host:window-state",…}JSON. - WebView2 lifecycle: environment with a per-app user data folder (optionally
isolated per process via
isolate_user_data_folder_per_processso concurrent instances of the same app never share one WebView2 profile — two processes on one folder fight over the browser process and blank each other's windows), controller sizing on resize/DPI change, settings (DevTools, context menu, zoom). Navigate(url)/NavigateToString(html)to load content.- Optional local content: map a folder to a
https://<virtual-host>/origin. - JSON message bridge: page
window.chrome.webview.postMessage(json)reaches the typed native callback;PostJson(json)sends JSON into the page. Object fields may be reordered and string values are decoded with standard JSON escaping. - Handler subscriptions can be added before or after
Start()and removed by id without replacing another consumer's callback.on_readymeans the WebView2 controller is initialized;on_navigation_completedreports the later page navigation result.
luna_ui_example opens a borderless window hosting the LunaUI control
gallery at example/ui/gallery.html — a control and interaction pattern
collection demonstrating the framework's bridge and window APIs:
- custom title bar (drag to move, double-click to maximize, minimize / maximize-restore / close chips, pin-to-top), 8-way native resize edges and card-style page corner grips (sticky to the content card's corners, styled as small rounded card tabs with an accent arc, starting native resize gestures); maximized state removes the content's max-width so the card stack fills the whole window width
- transparent window mode: with
transparent_backgroundthe frameless window is a real DWM glass frame from creation — the page background is fully transparent (.app-shellhas no tint) and solid cards/masthead float directly over the desktop; the gallery's transparent switch (set-window-transparent) turns the glass off/on live, andset-backdrop-mode/host:cycle-backdrop-modecycle the full backdrop family (glass / layered alpha / Mica / Acrylic / blur) - window lifecycle animations (close / minimize / restore sequences with the native handshake; the close chip closes directly with animation), live window-state badges, native event log
- buttons (gradient CTA, chips, hover label swap, progress sweep), inputs, custom select with a "jelly" hover block, toggles with scope tags, segmented controls with sliding indicator, tabs, vertical/horizontal sliders, activity list with scroll-fade masks, modal dialog, command palette (staggered open, keyboard navigation, filtering), toasts
- Timeline preview (
片段条segment strip + transport): a 20-segment timeline strip with wheel-scroll momentum + snap, and a two-mode drag-to-scroll — inside the window the strip follows the cursor 1:1, and once the cursor crosses a window edge the control switches to distance-based: the farther past the edge it is pushed, the faster the strip scrolls (pulling back toward the window slows it, crossing back inside resumes 1:1). A velocity-matched auto-scroll covers the case where the cursor is clamped at the edge (window fills the screen / remote-desktop sessions). - full motion language:
revealentrances, masthead piece animations, elastic easing, clip-path icon morphs,prefers-reduced-motionsupport; light/dark themes viadata-themeCSS variables. - Multi-window demo + window manager: the gallery opens floating
transparent child windows — an analytics panel
(
example/ui/dashboard.html, transient: created on open, destroyed on close) and the command palette (example/ui/command-palette.html, persistent: prefetched hidden at startup, loaded once, close only hides it and its query/scroll state survives). The window-management card lists every window with itskindand visibility and drives them withopen-window/hide-window/close-window/destroy-window(显示 / 隐藏 / 关闭 / 销毁). The command palette forwards the picked command back to the main page through{"type":"command",…}; the main window can also be turned into a real DWM glass frame withset-window-transparent/set-backdrop-mode. Child pages speak the{"type":"window",…}protocol (drag / minimize / maximize / close / always-on-top / corner-resize) and animate onwindowLifecycleevents.
Requirements: Visual Studio 2022 C++ tools, CMake, a WebView2 SDK, and the
WebView2 Runtime. WEBVIEW2_ROOT is required and is intentionally not
hardcoded to a machine-specific path.
cmake -S . -B build -A x64 -DWEBVIEW2_ROOT="<path-to-webview2-sdk>" -DLUNA_UI_BUILD_TESTS=ON
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failureThe demo is opt-in: add -DLUNA_UI_BUILD_EXAMPLE=ON when building the gallery.
Produces luna_ui.lib, the optional luna_ui_window_manager.lib, and the
luna_ui_example demo in build/Release/.
To install the reusable targets and headers:
cmake --install build --config Release --prefix <install-prefix>add_subdirectory this project (or use the installed package) and link the
core target:
add_subdirectory("<path-to-LunaUI>" LunaUI)
target_link_libraries(my_app PRIVATE luna_ui)Then open a window:
#include "luna_ui.hpp"
#include <utility>
luna_ui::LunaUIConfig config;
config.title = L"My App";
config.initial_url = L"https://example.com";
luna_ui::LunaUIEvents events;
events.on_error = [](HRESULT, const std::wstring& detail) {
// report initialization failure
};
luna_ui::LunaUI shell;
config.allowed_origins = {luna_ui::UrlOrigin(config.initial_url)};
if (!shell.Start(instance, config, std::move(events))) return 1;
return shell.RunMessageLoop();The core target does not interpret application commands. Add
luna_ui_window_manager when the optional host: / child-window protocol is
needed. The manager is a protocol adapter over the core's private child-window
primitives; applications that only need one top-level window can omit it:
target_link_libraries(my_app PRIVATE luna_ui_window_manager)#include "luna_ui_window_manager.hpp"
luna_ui::LunaUIWindowManager windows(shell);
windows.Attach();For an installed package, set LunaUI_WEBVIEW2_ROOT and use
find_package(LunaUI CONFIG REQUIRED) with LunaUI::luna_ui or
LunaUI::luna_ui_window_manager.
See include/luna_ui.hpp for the full config surface and the example in
example/main.cpp.
The first external consumer is the desktop shell in the companion
dsh-desktop repository.