Conventions¶
The rules that keep the tree navigable. They are the C++ side; the rules a library user meets when writing specs are in the reference's Conventions.
Include Mirror¶
include/winui/<dir>/ mirrors src/<dir>/ one to one. A header and its .cpp sit in matching directories, and a header-only component sits in the directory of the subsystem it serves.
include/winui/ src/
core/ Geometry, Color, Input, Property, Callback, ... core/
render/ Renderer, Describe, Text, Bitmap, ... render/
ui/ Element, Window, one header per control and its spec ui/
themes/ Theme, the palettes, the theme source themes/
spec/ the shared spec machinery (Spec, Control, ...) spec/
testing/ HeadlessUI, TestRenderer, the test doubles, testing/
OffscreenCapture (the winui::testing library)
platform/windows/ (private)
winui.hpp umbrella header
testing.hpp the testing tier's umbrella header
When you add a public header, put it in the include/ directory that matches where its .cpp goes in src/. A new .cpp also goes into LIBWINUI_CORE_SOURCES or, for Windows-only code, LIBWINUI_WIN32_SOURCES in the top-level CMakeLists.txt (LIBWINUI_TESTING_SOURCES for the testing tier, which builds into its own library, winui::testing), and a new control's header into include/winui/winui.hpp.
Private Windows headers that expose backend internals (src/platform/windows/D2DRenderer.hpp, DWriteMeasurer.hpp, Win32Launcher.hpp, ...) stay in src/ and are never installed.
Two directories are special:
core/,render/,ui/andtesting/compile on any platform, and their headers never include<windows.h>. The few source files there that call the OS directly (the command line, the thread check, tracing, the performance counters) do it behind#ifdef _WIN32.src/testing/OffscreenCapture.cppdraws through the backend's privateD2DRendereron Windows and is an empty capture elsewhere.- No public header names the operating system. The Windows backend's headers are private, under
src/platform/windows/.
Fully-Qualified Includes¶
Every include of a libwinui header spells the full path from include/:
#include "winui/ui/Element.hpp" // yes
#include "Element.hpp" // no
#include "../render/Renderer.hpp" // no
A header then resolves the same way from anywhere, and moving a file is a search-and-replace of one string. clang-format regroups includes into blocks: quoted project headers first, then <winui/...>, then standard headers, then the C headers (<cstddef>, <cstdint>, ...).
Namespaces¶
| Namespace | Holds |
|---|---|
ui |
Everything a user writes: the specs (ui::Button), the controls they build (ui::ButtonControl), Element, Window, Property, Theme, the core types, the render interface and the headless host. The library is called libwinui and its headers live under winui/, but the namespace is ui. |
ui::win32 |
The private Win32 backend classes under src/platform/windows/. Nothing in it is public. |
ui::testing |
The testing tier, opt-in through <winui/testing.hpp>: HeadlessUI, TestRenderer, the test doubles for the platform seams, OffscreenCapture, seams(). |
ui::detail |
Implementation details that have to live in a header. Never documented. |
Small nested namespaces group constants and helpers: ui::colors, ui::glyphs, ui::styles, ui::easing, ui::literals.
Naming¶
| Kind | Style | Examples |
|---|---|---|
| Types, enums, concepts | PascalCase |
StackControl, DirtyFlags, SpecLike |
| Functions, variables, parameters | snake_case |
set_text(), measure_override(), final_rect |
| Private data members | snake_case_ with a trailing underscore |
value_, bounds_, clipboard_ |
| Constants | PascalCase |
Dot, MaxHeight |
| Enum values | PascalCase |
Alignment::Stretch, ThemeVariant::Luna |
| Files | PascalCase, after the type they hold; a topic name when no type leads |
InfoBadge.hpp (ui::InfoBadge and ui::InfoBadgeControl), VisualId.hpp, Geometry.hpp |
| Tests, examples, tools | snake_case |
test_info_badge.cpp, pages_basic_input.cpp, gen_reference.py |
| Directories | lowercase | ui/input/, platform/windows/ |
Name a thing after what it is. A control keeps its common name (Button, Slider, TreeView), and a name that only makes sense inside another framework gets a plain one (Text, Stack, Dialog). A public enum sits at namespace scope, never inside a class: ui:: plus one name, prefixed by the owner's spec name when it belongs to one control (SliderTickPlacement, PipsPagerWrapMode, ButtonVariant). ui::Orientation and ui::Alignment are shared by every control; an enum that is only internal state stays nested and private. A control class is its spec's name plus Control (ui::InfoBadge builds an InfoBadgeControl, ui::Rating a RatingControl). The few pairs that do not follow the rule (ui::VerticalStack and ui::HorizontalStack both build a StackControl) are listed in the reference conventions.
Implementations of one interface follow one pattern:
| Role | Pattern | Examples |
|---|---|---|
| The interface | the plain noun | Clipboard, SpellChecker, TextMeasurer, ContextMenuHost |
| The test double | Test<Noun> |
TestClipboard, TestSpellChecker, TestRenderer |
| The Win32 implementation | win32::Win32<Noun> |
win32::Win32Clipboard, win32::Win32SpellChecker |
| The headless implementation | Headless<Noun> |
HeadlessTextMeasurer, HeadlessWindowHost |
Test tags are the control's name in kebab case ([info-badge], [dropdown-button]), plus the area ([layout], [render], [input]).
Ownership¶
Every node in the tree has exactly one owner, a std::unique_ptr in its parent. There is no shared_ptr anywhere in the UI tree.
- A method that adopts a child takes
std::unique_ptr<T>and returns a plainT*, a non-owning observer for wiring up behavior. - References that are not ownership (the focus ring, a window's pointer to its clipboard) are raw pointers or
std::vector<Element*>into memory the tree already owns. - An
Elementis not copyable, and only movable before it has a parent. - COM objects live in
Microsoft::WRL::ComPtr, and platform state behind a pimplstd::unique_ptr. The library never reaches into Windows' object lifetimes, and Windows never reaches into the tree's.
A setter compares before assigning, and marks only what changed:
void InfoBadgeControl::set_severity(InfoBadgeSeverity severity)
{
if (severity == severity_)
{
return;
}
severity_ = severity;
mark_dirty(DirtyFlags::Visual); // the color changed, the size did not
}
Use DirtyFlags::Layout when the size can change, Arrange when only the placement of children changes, and Visual for appearance only. Invalidating more than needed costs a relayout, and invalidating less leaves a stale screen.
Callback for Handlers¶
A handler a control stores is a ui::Callback<Sig>, which is std::move_only_function<Sig>. ui::Action is Callback<void()>. std::function requires a copyable target, so it cannot hold a lambda that captures a std::unique_ptr or any other move-only state.
void on_click(Action handler); // yes
void on_value_changed(Callback<void(double)> handler); // yes
void on_click(std::function<void()> handler); // no, for a control's handler
The exception is an item type that callers put in a braced std::vector (MenuItem, ThumbnailButton, ...). std::initializer_list only hands out copies, so those handlers have to be copyable and use std::function.
Use the unqualified signature, so a caller may pass a mutable lambda that carries state. A handler that must run through a const path keeps it mutable instead of const-qualifying the signature: CommandHandler (a Command's execute) runs from the const Command* that every button, menu item and shortcut pointing at the command shares.
Strings¶
Every string in the library is a std::string holding UTF-8, and every text offset is a byte offset on a code-point boundary.
std::u8stringis not used: it has nostd::formatand no iostreams support, andchar8_twould need a cast at every seam.- A
static_assertininclude/winui/core/Utf8.hppfails a translation unit compiled without/utf-8(MSVC, clang-cl), since string literals would otherwise not be UTF-8. - The UTF-8/UTF-16 helpers in
Utf8.hppare hand-written rather thanMultiByteToWideChar, so they behave the same on every platform without<windows.h>. - Malformed input (an unpaired surrogate, a truncated or overlong sequence) becomes U+FFFD instead of an error, so a file name with one bad byte still displays. A truncated sequence swallows only its own bytes.
At the Win32 boundary, where every ...W call takes UTF-16, widen() and narrow() in src/platform/windows/Win32String.hpp are the only crossing points. They follow the same U+FFFD policy (no MB_ERR_INVALID_CHARS/WC_ERR_INVALID_CHARS), so anything coming back from the OS, such as clipboard text or UI Automation values, is valid UTF-8 by the time it reaches the tree.
Comment Style¶
Doxygen reads only ///, and the reference is generated from what it reads.
///on every public declaration: classes, structs, enums, functions, members.///<for a short comment trailing a member or enum value.//everywhere else: the SPDX line,// ---- section ----dividers, private members, function bodies.- Other comments are rare: one or two lines that explain a surprising why, or name something the reader can look up, such as a Win32 API. Longer explanations belong on this site, and code never points at markdown files.
The full set of rules, with examples, is on Documentation.
Clang-Format¶
The checked-in .clang-format decides formatting. Its main points:
- 4 spaces, no tabs, 120 columns.
- Braces on their own line for classes, functions, namespaces and control statements, and braces around every
if/for/whilebody, even a one-liner (InsertBraces). - Consecutive declarations and assignments aligned in columns.
Type* nameandType& name(pointer and reference bind to the type).- One argument or parameter per line once a call does not fit.
Run it with cmake --build build --target format. See Building for the version it needs.