Reflex is the portable C++ framework for building graphical multimedia applications and audio plugins, featuring:
Reflex is not a DSP library or an audio-processing toolkit. It provides low-level abstraction over platform and plug-in APIs, but does not include DSP algorithms, synthesis engines, processing graphs, or other domain-specific audio layers. Those systems are expected to be built on top when needed.
The most common way to use Reflex is as a framework. Reflex Creator template projects and the Bootstrap namespace provide the standard starting point for applications, with state/view separation, startup wiring, UI initialisation, and project scaffolding already in place.
Due to its modular nature, Reflex may also be used as a library. Lower-level modules can be embedded into existing host architectures and initialised independently, allowing integration into other frameworks such as JUCE.
Reflex is organised into a set of small, focused sub-libraries with strict one-directional dependencies. Each directory contains a 'require.h' that aggregates the headers of its dependencies.
The source tree is divided into two major parts, reflex and reflex_ext.
The core, canonical framework layer containing the fundamental primitives and primary APIs of Reflex.
Reflex: Core object model, containers, and reference system. Lightweight primitives forming the foundation for all Reflex modules.
System: Cross-platform OS abstraction (files, memory, threading, timing, SIMD detection).
SIMD: Portable SIMD layer exposing unified vector types over platform intrinsics.
Data: Structured data system (PropertySet), serialization, formats (JSON/XML/RIFF), encoding, compression, hashing.
File: File IO utilities, path handling, virtual file system, and shared resource management.
GLX: UI framework: layout, styling, events, and animation system.
VM: Scripting system: VM, compiler, runtime, module loading, and C++ bindings.
Contains higher-level extension modules, helper systems, widgets, tooling, and application scaffolding built on top of reflex/.
Async: Task system with worker threads, scheduling, and HTTP utilities.
File: Extensions to file system (e.g. monolithic archive format, extra utilities).
GLX: Additional UI widgets and helpers built on GLX.
IDE: Integrated debugging, live editing and hot-reload for stylesheets and scripts.
Bootstrap: Application scaffolding, lifecycle management, VFS setup, and project template foundation.
Reflex is built around a small set of core concepts which are used consistently throughout the framework. It is imperative to understand these concepts before diving deeper into the framework:
Object model and lifetime: Reflex > Object
Intrusive hierarchies and traversal: Reflex > Intrusive
Containers, views, and allocation: Reflex > Containers
Strings and text processing: Reflex > String
Keys and properties: Reflex > Data > PropertySet
Initialisation and globals: Reflex > Initialisation
A good starting point is the examples/Notes project. It is a small but complete working application demonstrating the separation between UI and application logic, recommended project structure, and typical usage of Reflex modules across GLX, Data, and Bootstrap.
Once familiar with the example, create your own project using Reflex Project Creator (bin/tools), which sets up the required modules, entry point, and Bootstrap scaffolding automatically.
From there, the natural development path is:
Reflex provides a compact suite of container types designed around predictable behaviour, explicit allocation control, contiguous storage, and lightweight non-owning views.
Array<T> is the primary dynamic container in Reflex.
It provides a contiguous dynamically-sized buffer with explicit allocation control and fast iteration.
CString and WString are typedefs of Array<char> and Array<wchar_t>.
Array<Int32> values;
values.Push(10);
values.Push(20);
CString and WString are specialised Array types with guaranteed null termination after all modifications.
This allows direct interoperability with traditional C APIs while preserving the Array API model.
CString text = "hello";
FILE * file = fopen(path.GetData(), "r");
Unlike generic Array<T>, string containers always maintain an additional null terminator internally.
ArrayView<T> provides a lightweight non-owning slice into contiguous memory.
CString::View and WString::View are typedefs of ArrayView<char> and ArrayView<wchar_t>.
Views are passed by value, never allocate, and are commonly used for substring operations, tokenization, temporary references, and zero-copy APIs.
auto [a, b] = Splice(str, 5);
CString copy = a; // promote to owning string if needed
The referenced memory must remain valid for the lifetime of the view.
Sequence<TKey, TValue> is a contiguous associative container where keys are not required to be unique.
It is useful when insertion order matters or when multiple entries may share the same identifier.
Map<TKey, TValue> is a simplified unique-key associative container built on top of Sequence.
Queue<T> is a fixed-size SPSC (single producer / single consumer) ring buffer.
It is designed for real-time and multi-threaded systems such as audio processing pipelines.
Reflex containers use allocator objects at runtime rather than allocator template parameters.
This allows containers to move across API and module boundaries without allocator-dependent types.
Write operations such as Push() and Insert() accept explicit allocation policies.
Common policies include:
Reflex containers support range-based iteration:
for (auto & value : array)
{
}
Reverse iteration helpers are also provided via rbegin() and rend().
Array, ArrayRegion, ArrayView, Map, Queue, Sequence
Reflex uses a controlled runtime initialisation model. Some systems, including allocators, modules, null-instances, and global state, are not safe to use before Reflex has initialised.
Avoid global objects which allocate memory or depend on Reflex runtime state.
Allocating types such as Array, CString, WString, Reference, TRef, and many Object-derived types may depend on systems which are not yet initialised during static construction.
If a non-trivial global object is unavoidable, ensure the default allocator is instantiated in the same translation unit before the global object.
REFLEX_INSTANTIATE_DEFAULT_ALLOCATOR;
CString g_name = "example";
In general, prefer explicit initialisation over global construction.
Reflex provides a small set of intrusive container primitives: Item, List, and Node.
These form the structural foundation of many higher-level framework systems including GLX::Object, GLX::Style, property hierarchies, and other tree-based APIs.
Most applications will not implement these classes directly, however you will interact with them implicitly when iterating over children, siblings, or hierarchical structures throughout the framework.
An intrusive container stores linkage directly inside the object itself rather than inside external wrapper nodes.
Objects therefore remain allocated exactly where they were created while simultaneously participating in lists or hierarchies.
This model provides:
Reflex uses intrusive structures extensively for UI trees, style trees, property hierarchies, and related systems.
List<TYPE, RETAIN, BASE> is a doubly-linked intrusive list.
The list itself does not allocate nodes - linkage exists inside the contained objects.
Common operations include:
for (auto & item : list)
{
}
Item represents a single element participating inside a List.
Objects which should appear in intrusive lists inherit from Item.
Common navigation helpers include:
Node combines both Item and List to form a hierarchical tree node.
A Node may simultaneously:
This forms the basis of Reflex hierarchical systems such as UI trees and style trees.
for (auto & child : object)
{
// ...
}
Depending on template configuration, intrusive lists may optionally retain contained objects automatically.
Many higher-level Reflex systems therefore combine intrusive hierarchy management with the Object reference counting system.
The exact ownership behaviour depends on the RETAIN template parameter and the surrounding framework subsystem.
Intrusive lists and nodes support standard range-based iteration:
for (auto & child : object)
{
}
Reverse iteration helpers are also available via rbegin() and rend().
Reflex provides two lightweight notification primitives that together cover the majority of notification and change-tracking use cases. Both are intentionally minimal and give explicit control over notification semantics, lifetime, and cost.
Pull-based change tracking.
Extremely lightweight and allocation-free. Clients explicitly poll a State for changes using a State::Monitor. Best suited for high-frequency or frame-based updates where control and predictability are critical, as well as multi-threaded scenarios.
Push-based notification.
Observers register callbacks (including lambdas) and are notified synchronously when the signal is emitted. Listener lifetime is managed automatically via reference-counted handles, making the client passive and convenient. Typically the caller will store the object received CreateListener via a Reference to keep it alive.
Signal is generally more expensive than State and should be avoided for very frequent notifications.
Reflex makes a primary, explicit distinction between object-types and value-types. Object types derive from Reflex::Object, are shared, reference counted, typically heap-based (although they can also be instantiated on the stack), and non-copyable by default.
Value types (non-Objects such as UInt32 or Array) are lightweight stack-based primitives. To promote a value into an object so it can participate in reference counting, heap allocation, dynamic properties, or generic object APIs, use ObjectOf<TYPE>.
Reflex::Object provides two foundational capabilities:
Reflex uses an intrusive reference counting model built directly into Object.
Unlike std::shared_ptr, COM, or Objective-C, there are no external control blocks or hidden allocations - the reference count lives inside the object itself.
Reference<T> is the primary ownership wrapper for Reflex objects. It retains on construction, releases on destruction, and guarantees a valid object reference over its lifetime.
See Reflex::Reference for full semantics and usage.
TRef<T> is the lightweight non-owning counterpart to Reference<T>. It does not retain or release, but still follows the Reflex convention of representing a valid object reference rather than a nullable pointer.
See Reflex::TRef for full semantics and usage.
Reflex objects should never be created with raw new/delete directly.
Instead, object creation is performed through the Reflex helper APIs:
auto a = New<MyClass>(args...);
auto b = Make<MyClass>(args...);
Make<T>() is generally the most convenient form when immediate ownership is required.
AutoRelease() provides a shorthand for constructing temporary Reference<T> wrappers.
The following examples are equivalent:
Reference<HttpConnection> conn = HttpConnection::Create(...);
auto conn = AutoRelease(HttpConnection::Create(...));
auto conn = Make<HttpConnection>(...);
Unlike traditional COM-style systems, Reflex objects may be instantiated directly on the stack.
Data::PropertySet node;
Stack objects ignore Retain() and Release() calls and are destroyed normally via scope lifetime.
This flexibility is useful and efficient for temporaries and embedded members, however ownership must remain explicit: any retained reference must not outlive the stack object it refers to. For example, storing a Reference or TRef to a stack object beyond its lifetime is invalid:
Data::PropertySet node;
auto props = New<Data::PropertySet>();
props->SetProperty("child", node); // invalid after node leaves scope
Reference<T> and TRef<T> default construct using the type's null-instance rather than nullptr.
If a type does not define a null-instance, the wrappers cannot be default-constructed, enforcing validity at compile time.
User-defined object types therefore often require explicit construction:
Reference <View> view1 = New<View>();
Reference <View> view2 = kNewObject; //shorthand helper
TRef<T> also supports construction using kNoValue for deferred assignment, however this should only be used sparingly.
Object exposes Retain() and Release(), however direct usage is uncommon.
If manual lifetime management is required:
Reflex is not a garbage collected system. Circular references will leak unless explicitly broken:
auto a = New<Data::PropertySet>();
auto b = New<Data::PropertySet>();
a->SetProperty("foo", b);
b->SetProperty("foo", a);
Some advanced systems inside Reflex (such as Reflex::VM) provide explicit cycle-breaking mechanisms, however application code should generally avoid circular ownership through design.
ObjectOf<T> wraps a value type inside an Object, allowing values to participate in reference counting, dynamic properties, and generic object-based APIs.
Reflex::Object defines a virtual interface for attaching, querying, and removing typed properties dynamically via Object::SetProperty, Object::QueryProperty, and Object::UnsetProperty.
This allows sub-objects and arbitrary typed values to be attached to objects at runtime, reducing the need for deep subclassing and enabling highly data-driven systems.
Properties are identified by both id AND type, rather than by id alone. This complements C++'s strong typing model by allowing multiple typed views of the same conceptual property.
Important: Object Does Not Implement the Interface
Reflex::Object itself does not provide a concrete property implementation. Calling SetProperty() on a derived type which does not implement the property callbacks will silently discard the property.
Typically, dynamic properties are implemented through Data::PropertySet, which fully implements the property system for arbitrary types.
Many primary framework classes (such as GLX::Object) derive from Data::PropertySet and therefore support dynamic properties automatically.
For more information on dynamic properties, see Data::PropertySet.
AcquireProperty, AutoRelease, GetAbstractProperty, GetProperty, Make, New, SetAbstractProperty, UnsetAbstractProperty
ConstReference, ConstTRef, ObjectOf, Reference, TRef
In Reflex, strings are arrays.
CString and WString are typedefs of Array<char> and Array<wchar_t> respectively, while CString::View and WString::View are typedefs of ArrayView<char> and ArrayView<wchar_t>.
Because of this, most string utilities operate generically on Array and ArrayView rather than on dedicated string-only types.
Functions such as Split, Splice, Search, Replace, Trim, and tokenization helpers therefore work uniformly across strings, byte arrays, and other contiguous sequences.
CString and WString are specialised Array types which guarantee null termination after all modifications.
This allows safe interoperability with traditional APIs expecting const char* or const wchar_t* buffers.
CString text = "hello";
SomeAPI(text.GetData());
Generic Array<T> containers do not provide this guarantee.
Many string operations return lightweight non-owning views rather than allocating new strings.
This allows substring and tokenization operations to occur without copying or heap allocation.
CString str = "HelloWorld";
auto [a, b] = Splice(str, 5);
In this example, both a and b reference memory owned by str.
If str is modified or destroyed, the views immediately become invalid.
This behaviour is intentional and allows high-performance zero-copy APIs throughout the framework.
When ownership of a substring or slice is required, the view must be explicitly copied into an owning string:
CString str = "HelloWorld";
auto [a, b] = Splice(str, 5);
CString owned = b;
Explicit copying keeps ownership semantics visible and avoids hidden allocations in performance-sensitive code paths.
Because strings are simply specialised arrays, most helper functions are implemented generically for contiguous sequences rather than specifically for text.
This keeps the API surface small and consistent while avoiding duplicate implementations for strings, byte buffers, and related container types.
Strings and views support standard range-based iteration:
for (auto c : text)
{
}
Reverse iteration helpers are also available via rbegin() and rend().
CString, CString::View, WString, WString::View
Join, Left, Lowercase, Merge, Mid, RawStringCopy, RawStringLength, Remove, Replace, ReverseSearch, ReverseSplice, Right, Search, Splice, Split, ToCString, ToFloat32, ToFloat64, ToInt32, ToInt64, ToUInt32, ToUInt64, ToWString, Uppercase
Address, Float32, Float64, Function, Idx, Int16, Int32, Int64, Int8, Key, NullType, Pair, Point, Rect, Size, Tuple, UInt16, UInt32, UInt64, UInt8, WChar
Async is a small util library built on top of the low-level System::Task and System::Thread primitives, providing high-level and convenient access to asynchronous operations. The API is designed to simplify correct async usage while keeping control over execution, lifetime, and thread safety explicit.
It covers the most common async use cases, including background execution with progress reporting and cancellation, HTTP requests, safe transfer of payloads across thread boundaries, and lifetime management of running tasks.
Async tasks are reference-counted. Clients typically hold a Reference <Async::Task>. Destroying this reference automatically requests cancellation of the task (assuming one-owner).
For custom workers, task cancellation is cooperative. Worker implementations must explicitly observe and respect the task’s run flag via calling Cancelled() and if true, return early. In this case typically you should also ctx.SetResult(false) to indicate failure.
Progress and completion are commonly monitored via polling using clocks created with CreateClock() or CreatePeriodic().
In UI code, do not use Async::CreateClock or Async::CreatePeriodic. Use the GLX equivalents (GLX::CreateAnimationClock, GLX::CreatePeriodicClock, or GLX::Object::OnClock), which guarantee a valid UI context for each callback.
//start a background task
auto task_ref = Make<Async::Worker>([](Async::Worker::Context & ctx)
{
auto result = New<Data::UInt32Property>(); //use whatever payload type is suitable
UInt n = 0;
while (!ctx.Cancelled() && n < 100)
{
System::SuspendThread(10); //emulate work
ctx.SetProgress(Float(n) / 99.0f); //publish progress for UI
n++;
}
result->value = n;
ctx.SetResult(n == 100, result);
});
// monitor progress
m_clock_ref = Async::CreatePeriodicClock(0.1f, [task_ref]() //!by capturing task_ref, the Task is kept alive as long as needed
{
switch (task_ref->GetStatus())
{
case Async::Task::kStatusCompleted:
if (auto result = Cast<Data::UInt32Property>(task_ref->GetResult())) //use DynamicCast if your worker returns different types
{
//do something with result
}
break;
default:
break;
}
});
void AttachAwait(GLX::Object & object, Key32 id, TRef <Async::Task> task, const Function <void(bool ok, Reflex::Object & result)> & callback)
{
GLX::AttachPeriodicClock(object, id, 0.25f, 0.25f, [&object, id, task = AutoRelease(task), callback]()
{
auto status = task->GetStatus();
if (status == Async::Task::kStatusPending)
{
return; //exit early, no callback
}
else
{
Reflex::Detail::WeakRef <GLX::Object> weakref(object); //object could die during callback, but we need to access again on last line
callback(status == Async::Task::kStatusCompleted, task->GetResult());
GLX::DetachClock(weakref.Load(), id); //also releases the task, because was captured in a Reference
}
});
}
CreateClock, CreatePeriodicClock
The Reflex::Data namespace provides the core infrastructure for representing, transforming, and persisting structured data in a platform-independent way.
It sits beneath higher-level systems and is used throughout Reflex wherever data needs to be serialized, stored, transmitted, hashed, or converted between representations.
Data covers three main concerns: structured data representation, format-level serialization, and low-level data transformation utilities.
You should expect to use the Data namespace extensively in application logic. When used correctly, it removes the need to roll your own data containers, serialization layers, encoding utilities, or hashing functions, as these concerns are already handled in a consistent and well-integrated way across Reflex.
CompressionAlgorithm, DecompressionAlgorithm
BytesToHex, DecodeUCS2, DecodeUTF8, DecodeUrlSegment, EncodeUCS2, EncodeUTF8, EncodeUrlSegment, HexToBytes, IsHttps, MakeUrl, SplitUrl, SplitUrlResource
Data::Format defines the interface for encoding and decoding structured data. A given Format implements how to serialize a PropertySet into bytes and how to reconstruct it back into a PropertySet.
Reflex provides several global, constant Format instances covering the common persistence use cases.
Data::PropertySet data;
Data::SetInt32(data, "version", 1);
auto child = Data::AcquirePropertySet(data, "child");
Data::SetFloat32Array(child, "values", {1.0f, 2.0f});
auto blob = Data::EncodePropertySet(Data::kPropertySetFormat, data);
File::Save(path, blob);
auto bytes = File::Open(path);
Data::PropertySet root = Data::DecodePropertySet(Data::kPropertySetFormat, bytes);
if (root)
{
auto v = Data::GetInt32(root, "version");
auto sub = Data::GetPropertySet(root, "child");
auto arr = Data::GetFloat32Array(sub, "values");
}
kBinaryFormat, kJsonFormat, kPropertySetFormat, kPropertySheetFormat, kReflexMarkupFormat, kReflexXmlFormat, kRiffFormat
CopyPropertySet, DecodePropertySet, EncodePropertySet, ResetPropertySet
Reflex provides a small set of non-cryptographic hash functions for use in lookup keys, content identifiers, and integrity checks.
These are designed for speed and practicality, not for cryptographic security.
All functions operate on Archive::View and return an Archive (for SHA) or UInt32/UInt64.
CRC32, FNV1a32, FNV1a64, SHA1, SHA256
Data::PropertySet is Reflex’s generic container for structured, tree-based data.
PropertySet allows arbitrary data to be attached to objects at runtime, without modifying class definitions or introducing ad-hoc subclasses.
Instead of encoding every variation in a type hierarchy, behavior and state can be composed dynamically by attaching properties as needed.
object.SetProperty("hover_time", 0.0f);
object.SetProperty("user_data", some_ref);
This bridges the performance and safety of C++ with the flexibility typically associated with dynamic languages. It enables rapid iteration, late-bound features, and data-driven behavior while remaining fully type-aware and debuggable.
PropertySet implements the root Reflex::Object property interface (OnSetProperty / OnUnsetProperty / OnQueryProperty).
This provides a uniform, generic property mechanism across the entire framework.
Properties are identified by id AND type, rather than id alone.
This complements C++'s strong typing model by allowing multiple, type-safe views of the same conceptual property without collapsing everything into a single loosely-typed value.
ps.SetProperty("my_id", 1);
ps.SetProperty("my_id", "string");
The example above creates two distinct properties:
Under the hood, this (id + type) pair is represented by Reflex::Address.
PropertySet supports hierarchical data by allowing nested PropertySet instances.
This makes it suitable for representing structured trees of runtime state, configuration, or metadata when needed.
Unlike rigid schemas, PropertySet allows structure to evolve organically as systems interact.
PropertySet is serializable, via the Format system.
This makes it suitable for storing presets, state snapshots, configuration files, and interchange data.
For persistent data, values should be written using the Data::SetXXX family of functions, which define the set of interoperable, format-safe property types. Using the Reflex::Data:: suite of property accessors also avoids template bloat occuring from use of the Reflex:: templated ones.
Data::SetFloat32(ps, "gain", 0.75f);
Data::SetCString(ps, "name", "Preset A");
PropertySet can be serialized using any Data::Format implementation:
auto blob = Data::EncodePropertySet(Data::kPropertySetFormat, ps);
//or
auto json = Data::DecodePropertySet(Data::kJsonFormat, ps);
Note that when writing text-based formats (JSON, Reflex PropertySheets) you need to register the key-strings, via Data::AcquireKeyMap and Data::RegisterKey.
Use Iterate<TYPE>() to iterate over all properties of TYPE
Each item will have a [key,value] where key is Reflex::Address (comprising of the property id and type_id) and value a Reference <TYPE>
for (auto & [adr, ref] : test.Iterate<Data::CStringProperty>())
{
output.Log(id.value, ref->value);
}
Float32Property, Float64Property, Int32Property, Int64Property, KeyMap, UInt32Property, UInt64Property
AcquireKeyMap, AcquirePropertySet, AcquirePropertySetArray, AddPropertySet, Assimilate, GetBool, GetFloat32, GetFloat64, GetInt32, GetInt64, GetKey, GetKey32, GetKeyMap, GetPropertySet, GetPropertySetArray, GetUInt32, GetUInt64, GetUInt8, Merge, RegisterKey, SetBinary, SetBool, SetCString, SetFloat32, SetFloat64, SetInt32, SetInt64, SetKey32, SetPropertySet, SetUInt32, SetUInt64, SetUInt8, SetWString, UnsetBinary, UnsetBool, UnsetCString, UnsetFloat32, UnsetFloat64, UnsetInt32, UnsetInt64, UnsetKey32, UnsetPropertySet, UnsetPropertySetArray, UnsetUInt32, UnsetUInt64, UnsetUInt8, UnsetWString
ArchiveObject, ObjectArray, PropertySet
Reflex serialization provides a lightweight, explicit way to encode and decode strongly-typed data to and from a binary stream.
It is designed for deterministic layouts, low overhead, and full control over what is written.
Unlike PropertySet serialization (which targets structured, schema-light data), this API is intended for **compact, ordered, binary serialization** of known data layouts.
Data::Archive is a byte container used as the backing store for serialization.
It is a simple typedef over Array<UInt8>, and can be treated as a writable or readable byte stream.
Data::Archive outstream;
To read from an archive, use Data::Archive::View, which maintains a read cursor without copying data:
Data::Archive::View instream = outstream;
Archive::View performs deserialization directly from the underlying byte buffer. For simple value types, data is read by copying from the current stream position and advancing an internal read cursor. This avoids additional heap allocations and minimizes intermediate copying, making the operation suitable for performance-sensitive code paths.
Use Data::Serialize to append strongly-typed values to an archive in sequence.
Data::Serialize(stream, 1, 2.0f);
Data::SerializeUTF8(stream, L"wide");
Values are written in the order provided. The resulting stream is compact and contains no metadata beyond what is required to decode variable-length data (such as strings).
Use Data::Deserialize to read values back in the same order they were written.
auto [i, f] = Data::Deserialize<Int32, Float32>(stream);
auto wstring = Data::DeserializeUTF8(stream);
Deserialization advances the read cursor stored in the Archive::View.
The template parameters define the expected types and enforce strong typing at the call site.
Serialization is order-dependent.
The reader must deserialize values in the exact order and type they were written.
This explicitness is intentional:
This makes the system well suited for file formats, IPC, network packets, caches, and other performance-sensitive data paths.
String serialization is explicit.
Use SerializeUTF8 / DeserializeUTF8 to encode wide strings into UTF-8 byte sequences.
Encoding is handled deterministically and is independent of platform wchar size.
Use direct serialization when the data layout is known and fixed.
Use PropertySet serialization (EncodePropertySet) when flexibility, flexible version evolution, or dynamic composition is required.
Both systems share the same Data namespace but target different problem spaces.
Data::Pack and Data::Unpack provide a lower-level mechanism for converting a single value into a binary representation and restoring it again.
These functions operate on types that have a well-defined, “toll-free” binary representation - meaning the value can be viewed as a contiguous block of bytes without transformation. This typically includes:
UInt32 t;
Data::Archive::View view = Data::Pack(t);
auto restored = Data::Unpack<UInt32>(view);
For types with a compile-time known binary size (for example, UInt32 is always 4 bytes), Pack / Unpack and Serialize / Deserialize produce the same binary representation. Internally, Serialize uses Pack / Unpack for these cases.
The distinction becomes important for variable-sized data.
Serialize is designed for writing *sequences* of values to a stream. As such, it includes any additional information required to reconstruct the data when reading, such as encoding the length of strings or arrays before their contents.
In contrast, Pack produces only the binary representation of the value itself. For example, when packing a string, the length is not prepended, as the size of the binary view implicitly defines the data extent.
Deserialize, DeserializePropertySet, DeserializeUCS2, DeserializeUTF8, Pack, ReadLine, Serialize, SerializePropertySet, SerializeUCS2, SerializeUTF8, Unpack, WriteLine
The Reflex::File namespace provides a unified, cross-platform interface for working with files, paths, and shared file-backed resources.
It is built on top of the System layer and is intended to be the primary API for file and resource access in application code.
File covers three closely related concerns: path manipulation, file I/O, and virtualised/shared resource access.
Copy, CreateMemoryReader, CreateMemoryWriter, GetRemainder, Open, Peek, ReadBytes, ReadLine, ReadValue, Save, WriteBytes, WriteLine, WriteValue
PersistentPropertySet, ResourcePool, VirtualFileSystem
CheckExtension, CorrectExtension, CorrectStrokes, CorrectTrailingStroke, Delete, DeleteDirectoryContent, DeletePath, Exists, GetSystemPath, GetVolumes, IsDirectory, MakeDirectory, MakePath, MakeRelativePath, RemoveDuplicateStrokes, RemoveTrailingStroke, Rename, ResolveExistingFolder, ResolveRelativePath, SplitExtension, SplitFilename
GLX is Reflex's UI framework. It combines a retained object tree, a declarative layout system, stylesheet-based rendering, an event model, and time-based animation helpers into one consistent API surface.
Most GLX work falls into a small set of concepts which are used repeatedly across the framework:
See the following guides before diving into specific widgets or helpers:
Object tree and layout: Layout
Stylesheets, layers, and states: Styling
Input dispatch and custom behavior: Events
Time-based transitions, clocks, and procedural updates: Animation
auto panel = New<GLX::Object>();
GLX::SetFlow(panel, GLX::kFlowY);
auto title = New<GLX::Label>(L"Overview");
auto body = New<GLX::Object>();
auto button = New<GLX::Button>(L"Run");
GLX::AddInline(panel, title);
GLX::AddInlineFlex(panel, body);
GLX::AddFloat(body, button, GLX::kAlignmentTopRight);
GLX::BindClick(button, [](){});
GLX animation covers two closely related systems:
In many cases the simplest animation is declarative. A stylesheet can define @State variants and a transition time, and code only needs to push or clear the relevant state.
For hover, selected, inactive, and similar UI feedback, prefer stylesheet-driven transitions first.
Button:
{
transition: 0.25;
@State hover:
{
bg: Fill(colour: 228);
};
}
This keeps simple UI motion in the style layer rather than in imperative code.
When code needs to decide target values dynamically, create an animation object and run it against a property or state on a target object.
auto fade = GLX::CreateColourPropertyAnimation("colour", GLX::kWhite, GLX::kBlack);
GLX::Run(object, "colour", 0.25f, fade);
Common helpers in this module include CreateStateAnimation, CreateColourPropertyAnimation, CreateMarginPropertyAnimation, and CreateCallbackAnimation.
Use clocks when you need procedural updates over time rather than interpolation between two values.
This is commonly used for polling async task status, updating drag visuals, driving custom canvas effects, or other time-based UI behavior that is not well described as a simple property tween.
AttachAnimationClock, AttachPeriodicClock, CreateAnimationClock, CreateCallbackAnimation, CreateColourPropertyAnimation, CreateFloatPropertyAnimation, CreateInterpolatedAnimation, CreateLogarithmicAnimation, CreateMarginPropertyAnimation, CreateMaxBoundsAnimation, CreateOpacityAnimation, CreatePeriodicClock, CreatePointPropertyAnimation, CreatePositionAnimation, CreateSizePropertyAnimation, CreateStateAnimation, CreateWaitAnimation, DetachClock, Enter, Exit, Run, Stop
Animation, ContainerAnimation, InterpolatedAnimation, Multi, PlayList
The GLX Event System provides a unified way to send and receive input and custom messages across all UI objects.
Events are lightweight, dynamically extensible objects that bubble up through the view hierarchy until handled or consumed.
GLX::Event derives from Data::PropertySet, allowing arbitrary name-value pairs to be attached dynamically.
It also has a Key32 id member for fast event type comparisons.
GLX::Object::Emit delivers an event to the target object, then bubbles it up through its parent hierarchy until a handler traps or consumes it.
Use GLX::Object::ProcessEvent when you want to dispatch an event directly to a single object without bubbling.
It is the callers responsiblity to ensure the event is retained before calling Emit or ProcessEvent.
The low-level approach is to create and populate an Event instance manually and call Emit() on the target object.
auto e = Make<Event>("MyCustomEvent");
Data::SetBool(e, "selected", true);
my_view->Emit(e);
A more concise helper exists:
GLX::Emit(*this, "MyCustomEvent", "selected", true);
This ensures compile-time checking that arguments are provided in name-value pairs and that names decay to Key32.
The lowest-level way to respond to events is by overriding bool GLX::Object::OnEvent(GLX::Object & src, GLX::Event & e).
bool MyView::OnEvent(GLX::Object & src, GLX::Event & e)
{
switch (e.id.value)
{
case GLX::kMouseDown:
// handle click
if (GLX::GetClickFlags(e) & GLX::kClickFlagDbl)
{
}
return true; // trap event
case K32("MyCustomEvent"):
// handle custom event
return true;
}
return GLX::Object::OnEvent(src, e); // Always forward to base
}
Combined with the dispatch helpers, it's common to use if/else checks instead of a switch:
if (e.id == GLX::kMouseDown)
{
return true;
}
else if (auto menu = GLX::GetMenu(e))
{
menu->AddItem(L"Option 1");
return true;
}
Always forward unhandled events to the base OnEvent implementation, or delegates and default behavior will not run.
GLX supports inline delegate binding to avoid subclassing.
auto btn = GLX::AddInline(*this, GLX::Init(New<GLX::Button>(L"Click Me"), m_button_style));
GLX::BindClick(btn, []()
{
// Handle click
});
BindEvent and BindEventVoid provide binding to a specific event id, while SetEventDelegate allows full forwarding of all events.
As BindEvent uses the event id also for the delegate id, each usage of BindEvent with the same event id will replace any previous delegate for that event id. To attach multiple event handlers use SetEventDelegate with different ids.
Some useful helpers for dispatching events include...
UInt8 GetClickFlags(const Event & e); //Returns click state flags.
bool IsLeftClick(const Event & e); //True if left mouse button was used.
bool IsRightClick(const Event & e); //True if right mouse button was used.
bool IsDoubleClick(const Event & e); //True if a double click was detected.
Point GetMouseDelta(const Event & e); //Mouse drag or wheel delta.
UInt8 GetModifierKeys(const Event & e); //Modifier key flags.
KeyCode GetKeyCode(const Event & e); //Key code from key events.
WChar GetKeyCharacter(const Event & e); //Character from key events.
TRef<Menu> GetMenu(Event & e); //Returns Menu from kMenuOpen event.
TRef<Menu> GetMenu(Event & e, Key32 context); //Same, filtered by context.
Transaction GetTransactionStage(const Event & e); //Retrieve transaction stage.
TRef<Object> GetDragSource(Event & e); //Retrieve drag source.
// Emit a custom event with a property
GLX::Emit(*this, K32("VolumeChanged"), "value", 0.75f);
// Handle it in a parent view
bool MyView::OnEvent(GLX::Object & src, GLX::Event & e)
{
if (e.id == K32("VolumeChanged"))
{
auto v = Data::GetFloat32(e, "value");
ApplyVolume(v);
return true;
}
return Base::OnEvent(src, e);
}
kCharacter, kFocus, kKeyDown, kKeyUp, kLoseFocus, kMouseDown, kMouseDrag, kMouseEnter, kMouseLeave, kMouseUp, kMouseWheel, kTransaction
BindClick, BindEvent, BindEventVoid, Emit, EnableMouse, EnableMouseCapture, FocusBranch, GetClickFlags, GetKeyCharacter, GetKeyCode, GetModifierKeys, GetMousePosition, IsDoubleClick, IsLeftClick, IsRightClick, QueryAntecedent, RedirectFocus, ScaleDelta, Send, SetEventDelegate, TransformPosition, UnbindEvent
GLX drag and drop passes a Reflex::Object payload through the event system. The payload can be any Object, including GLX::Object, Data::PropertySet, ObjectOf <TYPE>, or any custom object-type.
auto payload = Make<Data::PropertySet>();
Data::SetWString(payload, "path", L"example.txt");
GLX::StartDragDrop(payload, GLX::kMouseCursorPointer, GLX::kMouseCursorBlock);
To receive kMouseDrag and kMouseUp events on an object, first enable mouse capture:
GLX::EnableMouseCapture(object, true);
The typical pattern is to watch kMouseDrag and use ExceedsDragThreshold(GetMouseDelta(e)) to decide when the pointer movement is large enough to begin a drag operation.
bool MyView::OnEvent(GLX::Object & src, GLX::Event & e)
{
if (e.id == GLX::kMouseDrag)
{
if (GLX::ExceedsDragThreshold(GLX::GetMouseDelta(e)))
{
GLX::StartDragDrop(Make<MyDragData>(), GLX::kMouseCursorPointer, GLX::kMouseCursorBlock);
return true;
}
}
return Base::OnEvent(src, e);
}
A potential drop target becomes active by handling kDragDropTender and returning true. Once a target accepts kDragDropTender, it will then receive kDragDropEnter, kDragDropLeave, and kDragDropReceive events for that drag.
bool MyView::OnEvent(GLX::Object & src, GLX::Event & e)
{
switch (e.id.value)
{
case GLX::kDragDropTender:
return GLX::QueryDragDropData<MyDragData>(e);
case GLX::kDragDropEnter:
src.SetState("dragover");
return true;
case GLX::kDragDropLeave:
src.ClearState("dragover");
return true;
case GLX::kDragDropReceive:
if (auto data = GLX::QueryDragDropData<MyDragData>(e))
{
ConsumeDrop(*data);
return true;
}
return false;
}
return Base::OnEvent(src, e);
}
If you want to prevent this object and its parents from accepting the current drag, you can intercept the tender event, clear its id, and then allow it to continue upward as a non-drag event:
case GLX::kDragDropTender:
e.id = kNullKey; //change the Event id to effectively hide the event from parents
return false; //return false as this object doesnt want to receive the drop
QueryDragDropData<TYPE>(e) is the standard typed helper. Internally it performs a DynamicCast on the object stored in the event's drag payload.
struct CustomDragData : public Reflex::Object
{
REFLEX_OBJECT(CustomDragData, Reflex::Object);
Array <WString> filepaths;
};
template <class TYPE> inline TYPE * Reflex::GLX::QueryDragDropData(GLX::Event & e)
{
return DynamicCast<TYPE>(GetDragDropData(e));
}
If you use a custom payload type, make it object-castable with REFLEX_OBJECT so DynamicCast can recognise it.
When checking against multiple possible payload types, it is slightly more efficient to fetch the payload once and then test it manually:
auto drag_data = GLX::GetDragDropData(e);
if (auto custom = DynamicCast<CustomDragData>(drag_data))
{
}
else if (auto generic = DynamicCast<Data::PropertySet>(drag_data))
{
}
The drag API takes two cursors: the cursor shown while hovering an accepting target, and the cursor shown when the current target does not accept the drag. A simple setup is:
GLX::StartDragDrop(data, GLX::kMouseCursorPointer, GLX::kMouseCursorBlock);
For richer UI, a common pattern is to hide the OS drag cursor and render your own drag-preview object in the window foreground. Start the drag with invisible cursors:
GLX::StartDragDrop(data, GLX::kMouseCursorInvisible, GLX::kMouseCursorInvisible);
A typical custom-cursor implementation uses a global begin-listener to construct the preview from the drag payload type, attaches that object to the window foreground, disables mouse handling on it, and updates its position every frame.
A key rule is to exclude the visual cursor from hit-testing. Use GLX::EnableMouse(cursor, false, true) so it ignores mouse-over events.
m_drag_drop_visualiser = GLX::CreateDragDropBeginListener([this](Reflex::Object & drag_data)
{
auto origin = GLX::Core::desktop->GetMouseOver();
TRef <GLX::WindowClient> window = origin->GetWindow();
//create drag cursor
auto cursor = New<GLX::Object>();
GLX::SetText(cursor, GLX::GetText(origin));
cursor->SetStyle(GLX::FindStyle(origin, "DragCursor"));
GLX::EnableMouse(cursor, false, true); //ensure cursor, or any children on cursor, do not doesnt intercept mouse (see EnableMouse for details)
GLX::Enter(cursor, GLX::kEnterAnimationFade);
GLX::AddAbsolute(window->GetForeground(), cursor, window->GetMousePosition());
//attach callbacks to window, so if window is destroyed they wont be called
SetAbstractProperty(window, "dragdrop_clock", GLX::CreateAnimationClock([window, cursor](Float)
{
cursor->SetPosition(window->GetMousePosition());
}));
auto run_opacity_animation = [cursor](GLX::Object & drop_target)
{
auto fade = GLX::CreateOpacityAnimation("opacity", 0.75f, 0.9f);
if (IsNull(drop_target)) fade->Flip();
GLX::Run(cursor, "opacity", 0.25f, fade);
};
run_opacity_animation(Null<GLX::Object>()); //call now to apply initial fade to cursor
SetAbstractProperty(window, "dragdrop_target", GLX::CreateDragDropTargetListener([this, run_opacity_animation](GLX::Object & drop_target)
{
m_drop_target.Load()->ClearState("dragover"); //using Reflex::Detail::WeakRef to cover case drop_target might be stack-based
m_drop_target.Store(drop_target);
drop_target.SetState("dragover");
run_opacity_animation(drop_target);
}));
SetAbstractProperty(window, "dragdrop_end", GLX::CreateDragDropEndListener([this, window, cursor]()
{
GLX::Exit(cursor, true); //detach cursor with fade
m_drop_target.Clear();
UnsetAbstractProperty(window, "dragdrop_clock");
UnsetAbstractProperty(window, "dragdrop_target");
UnsetAbstractProperty(window, "dragdrop_end"); //important always delete containing lambda last
}));
});
The above is stil a fairly basic solution, if you support multiple payload types, a convenient pattern is to register a small factory per drag-data type and choose the preview object in CreateDragDropBeginListener. This lets file drags, object drags, and generic property payloads each supply their own visual cursor
In a multi-instance plugin environment, take care to do this once, otherwise each window would create a mousecursor.
kDragDropEnter, kDragDropLeave, kDragDropReceive, kDragDropReceiveExternal, kDragDropTender
CancelDragDrop, CreateDragDropBeginListener, CreateDragDropEndListener, CreateDragDropTargetListener, ExceedsDragThreshold, GetDragDropData, QueryDragDropData, StartDragDrop
GLX layout is handled by a layout model attached to each Object. By default every Object uses the standard layout model (kStandardLayout), which supports common “box layout” behaviors: inline flow, inline flex, float (pin), stretch (fill), plus absolute positioning.
You typically don’t write layout code directly. Instead:
A useful mental model:
Each Object owns a layout model (GLX::Detail::LayoutModel), which can be changed via Object::SetLayoutModel.
The following standard models are provided:
Custom layout models can be implemented by deriving from GLX::Detail::LayoutModel, but this is a secondary API. Most UIs should use the standard model and helpers described below.
The parent controls how *inline* children are arranged. This includes the primary axis, optional inversion, and optional centering of the inline group.
auto panel = New<GLX::Object>();
GLX::SetFlow(panel, GLX::kFlowY); //vertical stack
//GLX::SetFlow(panel, GLX::kFlowY | GLX::kFlowCenter); //stack centered as a group
Flow only affects children in *inline* mode. Float and absolute children ignore flow entirely.
Auto-fit is enabled by default. This means an object will grow its content size to accommodate its children.
A child’s contribution to content size is derived from:
GLX::EnableAutoFit(panel, true, true); //fit width + height to children
//GLX::EnableAutoFit(panel, true, false); //fit width only
Auto-fit is commonly disabled on large scrolling containers, but useful for popups, pills, menus, and small panels.
Each child has a positioning mode that is interpreted by its parent’s layout model.
In the standard model, this is set either:
The AddXXX helpers are convenience functions that:
As positioning mode and position are child-properties, they persist when changing the parent:
auto parent = New<GLX::Object>();
auto another_parent = New<GLX::Object>();
auto child = New<GLX::Object>();
GLX::AddFloat(parent, child, GLX::kAlignmentTopRight);
child->Detach();
child->SetParent(another_parent); //child is still pinned top-right on the new parent
GLX::EnableFloat(child, GLX::kAlignmentBottomLeft); //now moved to bottom left
Inline children participate in the parent’s flow. They are laid out sequentially along the flow axis.
The Orientation parameter controls placement on the *orthogonal axis* (perpendicular to flow):
auto row = New<GLX::Object>();
GLX::SetFlow(row, GLX::kFlowX);
auto icon = New<GLX::Object>();
auto label = New<GLX::Label>(L"Settings");
GLX::AddInline(row, icon, GLX::kOrientationCenter); //y centered (ortho axis) on row
GLX::AddInline(row, label); //fit to row height
Using flex allows the child to expand along the *primary axis* to consume remaining space.
Conceptually similar to “flex: 1”.
auto header = New<GLX::Object>();
GLX::SetFlow(header, GLX::kFlowX);
auto title = New<GLX::Label>(L"Project");
auto spacer = New<GLX::Object>();
auto close = New<GLX::Button>(L"X");
GLX::AddInline(header, title, GLX::kOrientationCenter);
GLX::AddInlineFlex(header, spacer);
GLX::AddInline(header, close, GLX::kOrientationCenter);
The remaining space is distributed across all children with flex enabled evenly.
To achieve an exact 50% / 50% horizontal split, enable flex on both children and disable horizontal auto-fit so their size is not influenced by content:
auto container = New<GLX::Object>();
GLX::SetFlow(header, GLX::kFlowX);
auto left = New<GLX::Label>(L"Left");
auto right = New<GLX::Button>(L"Right");
GLX::EnableAutoFit(left, false, true); //Important: prevent content from affecting width
GLX::EnableAutoFit(right, false, true);
GLX::AddInlineFlex(container, left);
GLX::AddInlineFlex(container, right);
Float positions a child relative to the parent’s bounds but removes it from inline flow. Floated children do not affect layout of inline siblings.
Float positioning can be specified using:
auto view = New<GLX::Object>();
auto badge = New<GLX::Label>(L"NEW");
GLX::AddFloat(view, badge, GLX::kAlignmentTopRight);
auto spinner = New<GLX::Object>();
GLX::AddFloat(view, spinner, GLX::kAlignmentCenter);
auto sticky_footer = New<GLX::Object>();
GLX::AddFloat(view, sticky_footer, GLX::kOrientationFit, GLX::kOrientationFar); //note use of 2nd variant with Orientation enum
Orientation values in float mode mean:
Stretch is a convenience mode for the most common float case: filling the parent on both axes.
It is shorthand for enabling float with kOrientationFit on X and Y.
GLX::AddStretch(parent, child); //same as GLX::AddFloat(parent, child, GLX::kOrientationFit, GLX::kOrientationFit)
Absolute places a child at an explicit position in the parent’s coordinate space.
auto canvas = New<GLX::Object>();
auto node = New<GLX::Object>();
GLX::AddAbsolute(canvas, node, {120.0f, 80.0f});
Genuine use-cases for absolute positioning are very rare, and should generally be avoided.
The majority of cases where you may think you need absolute positioning are likely covered by use of GLX::Scroller / GLX::Zoomable or custom drawing.
Furthermore, typical genuine cases for absolute positioning will require a custom layout model, to ensure the child positions are updated in response to the parent size (during the Align phase)
When used correctly the standard layout model will cover most use cases in typical UI layouts. The additional standard layout variants cover most remaining use cases:
To allow inline children to wrap, set the parent’s layout model to kStandardLayoutWrapped.
Child APIs remain unchanged.
Typical use cases:
AddAbsolute, AddFloat, AddInline, AddInlineFlex, AddStretch, BranchContains, CalculateAbs, CalculateAbsoluteRect, CalculateRelativeRect, EnableAutoFit, GetBounds, LookupBranchIndex, LookupChildAtIndex, LookupIndex, QueryChildById, QueryElementById, SetFlow
Alignment, FlowFlags, Orientation
GLX supports a powerful visual styling system designed to clearly separate presentation from layout. Layout logic is implemented in C++ or Reflex VM code, while stylesheets remain no-code, making them accessible to designers without impacting development.
Stylesheets avoid the unpredictable inheritance rules of CSS while still enabling reuse via includes, states, and the inherit: property.
The unique bg/fg layer concept allows highly rich designs to be expressed almost entirely through vector primitives. With a wide set of predefined render layers, you can build fully resizable, vectorised UIs that eliminate reliance on pre-baked bitmaps and bloated binaries. Layers support advanced anti-aliasing for crisp visuals at any resolution.
Dynamic property binding (&property) further extends styles into the runtime, powering fluid, data-driven animations and live effects.
Load a stylesheet from disk or resources using RetrieveStyleSheet.
A StyleSheet is itself a Style, so you typically apply it to your root view.
auto sheet = GLX::RetrieveStyleSheet(L":res:MyApp/styles.txt");
view->SetStyle(sheet);
Recommended: use Bootstrap::SetStyleSheet for hot-reload during development.
Bootstrap::SetStyleSheet(view, L":res:MyApp/styles.txt");
Whenever a style is set or hot-reloaded, the framework calls OnSetStyle(const Style & style) on that object.
This is the right place to style sub-objects so they also update on hot-reload.
void MyApp::View::OnSetStyle(const GLX::Style & style)
{
auto header_style = style["Header"];
auto tab_style = header_style["Tab"];
m_header->SetStyle(header_style);
for (auto & tab : m_header) tab->SetStyle(tab_style);
}
Styles support a core set of properties that define sizing, spacing, colors, layers, and state transitions.
The 'margin' and 'padding' properties accept 1, 2, or 4 float values.
By default, values use Reflex ordering:
margin: 24; // all edges
margin: 16,8; // width, height
margin: 12,8,12,8; // left, top, right, bottom
You can opt into CSS-style ordering:
#option margin_syntax css
margin: 24; // all edges
margin: 16,8; // vertical, horizontal
margin: 12,8,12,8; // top, right, bottom, left
Margins define spacing outside the object's bounds, while padding defines spacing inside, around its content.
While margin and padding are set on Style's, most layers also accept an 'indent' property. e.g:
bg: Fill(indent: 8,12; corner: 4);
'size' and 'max' accept 1 or 2 floats:
size: 64; // 64 x 64
size: 200,48; // width 200, height 48
max: 400,200; // maximum size
'size' sets the minimum content size, while 'max' constrains it.
The 'color' (or 'colour') property sets the "pen" colour, whereas 'bg_color' (or 'bg_colour') defines a rectangular fill colour. Colors always use 0-255 ranges for components, including the alpha channel.
colour: 128; // greyscale
colour: 128,128; // greyscale + alpha
colour: 255,128,0; // RGB
bg_colour: 0,128,255,192; // RGBA
You can also set colour via hex codes:
colour: $FF0000;
These map to normalized (0.0f - 1.0f) floats.
Most layers also support a colour property, which is multiplied by the "pen" colour.
bg: Border(width: 2; corner: 4; colour: 255,0,0);
The 'opacity' property is a normalized single float value (0.0 - 1.0), applied multiplicatively to all layers and children of an object.
opacity: 0.75;
Each object supports two layer arrays: 'bg' (drawn before children) and 'fg' (drawn after children). Typically use bg, but for cases where rendering "on top of" content is required, use fg (e.g. InnerShadow).
Both properties take an array of layers. Layers can be freely combined, but the draw order is strictly in the order specified.
bg: Fill(colour: 240), Border(colour: 0, width: 1);
fg: Text(value: &label, font: Small, colour: 0);
Some layers (such as Mask and Align) are grouping layers that include a 'content' property, which is itself a nested array of layers:
bg:
Mask
(
mask: Fill(corner: 16);
content:
Image(source: artwork; fit: cover),
Border(corner: 16; width: 2);
);
The 'transition' property combined with @State selection is the simplest way to do a basic animation. It sets the blending time in seconds when switching between states.
transition: 0.25; // quarter second blend
States are style variants that override properties when active. They appear as @State blocks inside a style and are applied in top-to-bottom order, so later states override earlier ones when both are active.
Push and clear states in code with Object::SetState(Key32) and Object::ClearState(Key32). Hover and focus are the only built-in states and are managed by GLX::Object automatically.
Button:
{
transition: 0.25;
size: 64;
@State hover:
{
bg: Fill(colour: 236);
};
@State selected:
{
bg: Fill(colour: 80,176,240);
fg: Text(colour: 255);
};
@State inactive:
{
fg: Text(colour: 128);
};
}
Nested states let you specify combinations explicitly.
Button:
{
@State inactive:
{
@State selected:
{
bg: Fill(colour: 96,120,140);
};
};
}
The Select, SelectChildren, SelectBranch helper functions set/unset the "selected" id. Activate(object, false) pushes "inactive" and disables input on that object.
Stylesheets can include other sheets for reuse and structure.
include: ["common.txt", "controls.txt"];
Stylesheets can define resources for use in layers like Text, Image, TextEdit.
@Font app_font:
{
path: ":res:MyApp/fonts/Inter-Regular.ttf";
size: 14;
};
@Bitmap logo:
{
path: ":res:MyApp/bitmaps/logo.png"; // png,bmp supported; jpg mostly supported
};
Header:
{
fg: Text(font: app_font; colour: 32; value: &title);
bg: Image(source: logo; fit: contain);
}
Resources can be declared at the root of the sheet for global access or nested inside a style for specificity.
Stylesheets support reuse and composition through the inherit property and the special @Alias directive.
Using inherit, a style copies all properties from another style before applying its own overrides. This allows you to define a base style and then extend or specialize it with minimal duplication:
BigButton:
{
inherit: Button;
size: 128;
};
The inherit property accepts either a single style ID, or a path to a nested sub-style. For example, to inherit from a sub-style defined inside another style:
inherit: Dialog > Header;
Additionally, the @Alias style variant lets you reuse a style as a named property of another style. Unlike inherit, aliasing does not copy values, and instead directly references the source definition:
Popup:
{
inherit: Button;
@Alias menu; // alias a menu property defined previously
};
This makes aliasing useful when you want multiple styles to share the same reference (for example, to the same menu definition or resource) rather than duplicating properties.
Any style property value can bind to a code-side variable by using &name. This allows live updates and animations without reapplying styles.
Commonly used for Text value or colours.
// Stylesheet
Flashing:
{
size: 64;
bg: Fill(corner: 12; colour: &dynamic_colour);
}
// C++
View::View()
{
m_flashing = GLX::Init(New<GLX::Object>(), style["Flashing"]);
SetProperty(m_flashing, "dynamic_color", New<GLX::ColorObject>(GLX::kWhite));
GLX::AddFloat(*this, m_flashing, GLX::kAlignmentCenter);
EnableOnClock(); // enable OnClock(delta) callback
}
void View::OnSetStyle(const GLX::Style & style)
{
m_flashing->SetStyle(style["Flashing"]);
}
void View::OnClock(Float delta_seconds)
{
auto colour = QueryProperty<GLX::ColorObject>(m_flashing, "dynamic_color");
colour->r = /* update channel over time */;
colour->g = /* update channel over time */;
colour->b = /* update channel over time */;
m_flashing->Redraw(); // trigger layer repaint
}
Activate, ActivateBranch, ClearText, FindStyle, GetClip, GetOpacity, GetText, IsActive, IsSelected, RGB, Rescale, RetrieveStyleSheet, Rotate, Select, SelectBranch, SelectChildren, SetBounds, SetClip, SetOnStyle, SetOpacity, SetState, SetText, ToggleState, Translate, UnsetBounds, UnsetClip, UnsetOpacity
Reflex GLX provides three canvas entry points for custom, code-driven drawing, ordered from simplest and fastest to most advanced:
In stylesheets, these correspond to:
These canvas callbacks are not immediate-mode draw calls in the traditional sense. They are not invoked every frame just because the object is visible.
Instead, the callback prepares geometry for the renderer, effectively building VBO data that can be reused until something changes.
To request a new canvas build, call Redraw() on the object. For example:
m_object.Redraw();
Other state changes that already imply a visual refresh, such as Update(), Realign(), and related layout-reaccommodation paths, also schedule a redraw.
For animated content, a typical pattern is to trigger redraws from OnClock(Float):
void MyView::OnClock(Float dt)
{
Redraw();
}
Prefer Canvas over ColourCanvas over GraphicCanvas, in that order, for performance and simplicity.
Use Canvas() when you are drawing simple monochrome paths and shapes.
Use ColourCanvas() when your geometry needs per-point colour.
Use GraphicCanvas() only when you need the extra power geometry plus textures (eg image/font) composition (which are non primary APIs).
Canvas bindings are usually declared in styles with an optional id. Use the plain form when there is only one binding for the object:
bg: Canvas();
When you have multiple canvas bindings on the same object, give each one an id and bind them explicitly in code:
bg: Canvas(id: logo), Canvas(id: clouds);
SetCanvas(m_obj, {.id = K32("logo")}, draw_logo);
SetCanvas(m_obj, {.id = K32("clouds")}, draw_clouds);
The same pattern applies to colour and graphic canvases:
SetColourCanvas(m_obj, {}, draw_coloured_geometry);
SetGraphicCanvas(m_obj, {}, draw_advanced_graphic);
For Canvas and ColourCanvas, do not clear ctx.output. Append to it only. The system may batch multiple canvases together and compact them into a single VBO.
A minimal Canvas demo can draw a star shape by appending a closed path to ctx.output:
void StarView::OnSetStyle(const GLX::Style & style)
{
GLX::SetCanvas(*this, {}, [](GLX::CanvasContext & ctx)
{
auto cx = ctx.size.w * 0.5f;
auto cy = ctx.size.h * 0.5f;
auto outer = Min(ctx.size.w, ctx.size.h) * 0.42f;
auto inner = outer * 0.45f;
GLX::Point star[10];
for (UInt i = 0; i < 10; i++)
{
auto a = -kPif * 0.5f + kPif * 0.2f * Float(i);
auto r = (i & 1) ? inner : outer;
star[i] = { cx + Cos(a) * r, cy + Sin(a) * r };
}
GLX::AddPath(ctx.output, star, true, 2.0f, GLX::kPathJoinRound, GLX::kPathCapRound);
});
}
This demo uses SetCanvas because the geometry is monochrome. As with most layers, it will be drawn at the style colour, modulated by its own colour property. Use ColourCanvas only when you need per-point colour, and keep GraphicCanvas for the advanced cases where you need the full Graphic pipeline.
See examples/Custom Drawing for a practical demonstration of the Canvas API in use.
AddDottedLine, AddEllipseFill, AddEllipseOutline, AddPath, AddPointsWithColour, AddPolygonFill, AddRectFill, AddRectOutline, AddRoundedFill, AddRoundedOutline, AddRoundedTriangleFill, AddRoundedTriangleOutline, AddTriangleFill, AddTriangleOutline, SetCanvas, SetColourCanvas, SetGraphicCanvas, UnsetCanvas
CanvasContext, ColourCanvasContext, GraphicCanvasContext
Reflex provides native SVG support, treating vector assets as first-class resources alongside bitmaps. SVGs can be imported and used in stylesheets via the @SVG resource declaration, or decoded in code for custom drawing via the geometry pipeline.
SVG usage in stylesheets is consistent with @Bitmap resources and the Image layer. Declare an SVG resource, then reference it in a style property.
Declare an SVG resource using the @SVG type:
@SVG my_svg_id:
{
path: "assets/some.svg";
};
Then use it as an Image source:
bg: Image(source: my_svg_id; fit: contain; anchor: center);
@SVG also supports a size property. If the SVG file contains intrinsic dimensions, those will be used by default. However, some SVGs do not specify a size, in which case you must set one explicitly using standard size syntax:
@SVG my_svg_id:
{
path: "assets/some.svg";
size: 128;
};
Because SVGs are vector-based, size does not affect rendering quality in the way it does for bitmaps. However, the initial size is important as it affects the initial geometry preparation, including corner steps and other tessellation details.
Reflex supports icon sets where multiple icons are defined in a single SVG file using the `<symbol>` tag with unique IDs. Declare the SVG resource once, then select individual icons using the `>` frame selector - the same syntax used for bitmap sprite frames.
@SVG icons:
{
path: "assets/icons.svg";
};
Reference a specific icon by its symbol ID:
bg: Image(source: icons > circle_icon; fit: contain; anchor: center);
As per-standard stylesheet syntax, if the symbol ID contains characters that are not a single token (such as a dash), wrap it in single quotes:
bg: Image(source: icons > 'circle-icon'; fit: contain; anchor: center);
The examples/SVG Demo project demonstrates parsing an SVG file and coupling it with custom drawing. The basic workflow is:
void ViewImpl::OnUpdate()
{
auto xml_bytes = File::Open(m_path_to_svg);
auto xml = Make<Data::PropertySet>(Data::DecodePropertySet(Data::kReflexXmlFormat, xml_bytes));
if (auto svgs = GLX::Detail::InspectSVG(xml))
{
GLX::SetColourCanvas(m_icon, {}, {[xml, svg = svgs.GetFirst()](GLX::ColourCanvasContext & ctx)
{
GLX::Detail::DecodeSVG(ctx.output, svg, size);
}});
}
else
{
GLX::UnsetCanvas(m_icon, {});
}
}
void ViewImpl::OnSetStyle(const GLX::Style & style)
{
m_icon.SetStyle(style["Icon"]);
}
Note: In the example, capturing 'xml' as a Reference (produced by Make) in the lambda is critical to keep the PropertySet alive for the lifetime of the canvas binding.
Due to the complexity of the SVG format, decoding is expensive and should be avoided per-frame. In the example, the size_z guard in the example ensures the geometry is only re-decoded when the target size actually changes.
Icon:
{
bg: ColourCanvas(); //use Canvas() for monochrome geometry, ColourCanvas() for colour-point geometry
};
Widgets are the higher-level interactive controls built on top of GLX::Object and a small set of reusable behaviours.
Most of them are event-driven composites rather than standalone rendering systems: they emit requests such as select, open, load, remove, and transaction updates, and they usually apply named substyles to internal parts such as header, body, footer, item, tab, prev, or next.
A recurring pattern in this module is that visual feedback comes from ordinary GLX state flags such as selected, hover, open, and reorder, so stylesheet state handling is a big part of how these controls are customised.
Many of the classes here are also intended to be assembled together. Form, Selector, Button, and Menu all act as building blocks for larger widgets rather than being isolated controls.
CloseContextMenu, OpenContextMenu
AbstractList, AbstractViewBar, AbstractViewPort, Form, Label, List, Menu, Popup, RangeBar, RotarySlider, Scroller, Selector, Split, TabGroup, TextArea, VirtualList, Zoomable
The Reflex::SIMD namespace provides a high-performance, cross-platform abstraction for Single Instruction, Multiple Data (SIMD) operations. It allows for data-parallel processing of 4-element vectors using platform-specific hardware acceleration (such as SSE, AVX, or NEON) through a unified C++ interface.
The library centers around the TypeV4<T> template, which represents a vector of four elements. Specialized typedefs are provided for common data types:
Vectors can be initialized via broadcasting a single value, specifying four distinct values, or loading from memory.
using namespace Reflex::SIMD;
FloatV4 a(1.0f); // Broadcast: [1.0, 1.0, 1.0, 1.0]
FloatV4 b(1.0f, 2.0f, 3.0f, 4.0f); // Explicit: [1.0, 2.0, 3.0, 4.0]
Float32 data[4] = { 5.0f, 6.0f, 7.0f, 8.0f };
FloatV4 c = LoadUnaligned(data); // Load from memory
Accessing individual elements can be done via the [] operator or by retrieving a Quad<T> structure. Use GetFirst() to efficiently grab the element at index 0.
Standard arithmetic operators are overloaded to perform component-wise operations across all four lanes simultaneously.
Because SIMD lanes cannot branch independently, conditional logic is handled via masking and the Select function.
// Choose between 'a' and 'b' based on a condition
BoolV4 mask = a > b;
FloatV4 result = Select(mask, a, b); // result[i] = mask[i] ? a[i] : b[i]
You can also analyze the state of a mask using these helpers:
The library provides tools to reorder data within vectors or between two different vectors.
// Reorder elements within a vector
auto swapped = Shuffle<1, 0, 3, 2>(v);
// Interleave values from two vectors
auto lo = InterleaveLo(a, b);
// Transpose a 4x4 matrix represented by four vectors
Transpose(row0, row1, row2, row3);
While most operations are vertical (lane-to-lane), some operations combine values across a single vector:
Float32 total = Sum(my_v4); // Adds all 4 components into a single scalar
Explicit conversion between integer and floating-point vectors is required:
Abs, And, Any, ClipNormal, Count, Empty, Exp, Exp2, Full, GetFlags, GetFree, Invert, Log, Log2, Max, Min, Modulo, Not, Or, Pow, Reciprocal, RoundDown, RoundNearest, Select, SelectNot, Sign, SquareRoot, operator!=, operator&, operator*, operator+, operator-, operator/, operator<, operator<=, operator==, operator>, operator>=, operator|
Reflex is designed so that application code is completely platform-agnostic. When developing with Reflex, there is no need to interact with OS-specific APIs directly - all system functionality is abstracted and implemented in a fully cross-platform way.
The System namespace is responsible for this abstraction layer. It provides the low-level, platform-independent primitives that Reflex builds upon.
The System namespace is not intended to be the primary API used by application code.
Instead:
In most cases, you should use the higher-level APIs rather than System directly.
For example, instead of working with System::FileHandle, use the File namespace for more convenient helper functions,
//Low-level (usually unnecessary)
auto file_handle = Make<System::FileHandle>(path);
Array <UInt8> bytes1(file_handle.GetSize());
auto bytes_read = file_handle->Read(bytes1.GetData(), bytes1.GetSize());
bytes1.SetSize(bytes_read);
//Recommended
auto bytes2 = File::Open(path);
Direct use of the System namespace is appropriate in advanced or specialized cases, such as:
In these cases, System provides a stable, cross-platform foundation without leaking OS-specific concepts into application code.
As most System APIs are defined as pure-abstract interfaces, the System layer acts as an extensibility point as well as an OS abstraction. This allows you to provide alternate implementations (for example, a FileHandle that reads from an in-memory blob, a cloud stream, or a virtual file system) and pass them directly to other APIs operating on the System primitives.
ColourPoint, fPoint, fRect, fSize
Delete, Exists, GetElapsedTime, GetNumProcessor, GetOperatingSystemVersion, GetPath, GetSystemID, GetTime, IsDirectory, MakeDirectory, Open, Rename
KeyCode, ModifierKeys, MouseCursor, Path, Response
DirectoryIterator, DiskIterator, DynamicLibrary, FileHandle, HttpConnection, Process, Renderer, Renderer::Canvas, Renderer::Graphic, Task, Thread, Window
Colour, ReceiveDataFn, ReceiveHeaderFn
using CString = Array <char>;
using CString::View = ArrayView <char>;
Key32 is Reflex's 32-bit identifier type. It is used extensively throughout the framework anywhere a compact, comparable id is needed.
Common uses include property ids, property addresses, GLX event ids, style ids, and other lightweight keys used to identify behavior or data without storing strings at runtime.
When constructed from a string literal or const char source in normal code, the hash is typically produced at compile time via the consteval constructor path. This makes Key32 convenient to write and very cheap to compare.
Data::SetFloat32(object, "gain", 0.75f);
if (e.id == MakeKey32("MyEvent"))
{
}
auto button_style = style["Button"];
Key32 is also useful when you need to branch on received text without doing repeated string comparisons:
switch (MakeKey32(some_received_string))
{
case MakeKey32("one"):
break;
case MakeKey32("two"):
break;
}
Because Key32 stores only the hash value, reverse lookup to the original text is not available unless you explicitly maintain that mapping elsewhere, for example in a KeyMap.
using Key32 = Key <UInt32>;
using WString = Array <WChar>;
using WString::View = ArrayView <WChar>;
Reference <TYPE> AcquireProperty(Object& object, Key32 id);
Reference <TYPE> AcquireProperty(Object& object, Key32 id, VARGS...);
Reference <TYPE> AutoRelease(TYPE& object);
TRef <Object> GetAbstractProperty(Object& object, Key32 id);
ConstTRef <Object> GetAbstractProperty(const Object& object, Key32 id);
ConstTRef <TYPE> GetProperty(Object& object, Key32 id);
Array <TYPE> Join(const VARGS...& ...);
Varadic function concatenating 'args' returning an Array.
ArrayView <TYPE> Left(const ArrayView <TYPE>& view, UInt32 position);
void Lowercase(const TYPE& string);
char Lowercase(char character);
WChar Lowercase(WChar character);
Reference <TYPE> Make(VARGS... args);
void Merge(const ArrayView <TYPE>& view, const ArrayView <TYPE>& delimiter);
ArrayView <TYPE> Mid(const ArrayView <TYPE>& view, UInt32 position);
ArrayView <TYPE> Mid(const ArrayView <TYPE>& view, UInt32 position, UInt32 length);
TRef <TYPE> New(VARGS... args);
UInt32 RawStringCopy(const TYPE* from, TYPE* to, UInt32 capacity);
UInt32 RawStringLength(const TYPE* string);
UInt32 RawStringLength(const TYPE* string, UInt32 capacity);
void Remove(Array <TYPE>& array, const TYPE& element_or_array);
Array <TYPE> Replace(const ArrayView <TYPE>& view, const TYPE& element_or_array);
Idx ReverseSearch(const ArrayView <TYPE>& view, const TYPE& element_or_array);
Pair < ArrayView <TYPE> , ArrayView <TYPE> > ReverseSplice(const ArrayView <TYPE>& view, UInt32 position);
ArrayView <TYPE> Right(const ArrayView <TYPE>& view, UInt32 position);
Idx Search(const ArrayView <TYPE>& view, const TYPE& element_or_array);
void SetAbstractProperty(Object& object, Key32 id, TRef <Object> property);
Pair < ArrayView <TYPE> , ArrayView <TYPE> > Splice(const ArrayView <TYPE>& view, UInt32 position);
Array < ArrayView <TYPE> > Split(const ArrayView <TYPE>& view, const TYPE& delimiter);
CString ToCString(const WString::View& string);
CString ToCString(const WString& string);
CString ToCString(UInt32 value);
CString ToCString(UInt64 value);
CString ToCString(Int32 value);
CString ToCString(Int64 value);
CString ToCString(Float32 value, UInt32 precision, bool discard_zeros);
CString ToCString(Float64 value, UInt32 precision, bool discard_zeros);
Converts a value to a CString.
Supported conversions include:
Floating-point overloads format the value using the specified precision and optional trailing-zero trimming.
Float32 ToFloat32(const CString::View& string);
Float32 ToFloat32(const WString::View& string);
Converts a string to a Float32.
Supported conversions include:
The string is interpreted as a base-10 floating-point value. Behaviour is undefined if the string does not contain a valid numeric representation.
Float64 ToFloat64(const CString::View& string);
Float64 ToFloat64(const WString::View& string);
Converts a string to a Float32.
Supported conversions include:
The string is interpreted as a base-10 floating-point value. Behaviour is undefined if the string does not contain a valid numeric representation.
Int32 ToInt32(const CString::View& string);
Int32 ToInt32(const WString::View& string);
Int64 ToInt64(const CString::View& string);
Int64 ToInt64(const WString::View& string);
ArrayRegion <TYPE> ToRegion(const ArrayRegion <TYPE>& value);
ArrayRegion <TYPE> ToRegion(Allocation <TYPE>& allocation);
ArrayRegion <TYPE> ToRegion(Array <TYPE>& value);
UInt32 ToUInt32(const CString::View& string);
UInt32 ToUInt32(const WString::View& string);
Converts a string to a UInt32.
Supported conversions include:
The string is interpreted as a base-10 integer. Behaviour is undefined if the string does not contain a valid unsigned integer representation.
UInt64 ToUInt64(const CString::View& string);
UInt64 ToUInt64(const WString::View& string);
Converts a string to a UInt32.
Supported conversions include:
The string is interpreted as a base-10 integer. Behaviour is undefined if the string does not contain a valid unsigned integer representation.
ArrayView <TYPE> ToView(const ArrayView <TYPE>& value);
ArrayView <TYPE> ToView(const ArrayRegion <TYPE>& value);
ArrayView <TYPE> ToView(const TYPE& value);
ArrayView <TYPE> ToView(const Allocation <TYPE>& allocation);
ArrayView <TYPE> ToView(const Array <TYPE>& value);
WString ToWString(const CString::View& string);
WString ToWString(const CString& string);
WString ToWString(UInt32 value);
WString ToWString(UInt64 value);
WString ToWString(Int32 value);
WString ToWString(Int64 value);
WString ToWString(Float32 value, UInt32 precision, bool discard_zeros);
WString ToWString(Float64 value, UInt32 precision, bool discard_zeros);
Converts a value to a WString.
Supported conversions include:
Floating-point overloads format the value using the specified precision and optional trailing-zero trimming.
void UnsetAbstractProperty(Object& object, Key32 id);
void Uppercase(const TYPE& string);
char Uppercase(char character);
WChar Uppercase(WChar character);
Reflex::Address is the internal key used to identify a property address. It combines the property id and property type into a single value.
You do not usually use it directly in normal code, because the type portion is typically selected implicitly via the property template or typed accessor you call.
Lower-level object property callbacks such as Object::OnSetProperty receive a Reflex::Address to describe which typed property is being accessed.
Key32 Address::id;
UInt32 Address::type_id;
Type-safe dynamic contiguous container.
Array stores elements in contiguous memory with explicit size and capacity management. It supports value types and object types, with optimized paths for raw-copyable items.
Array owns its storage through an Allocator reference.
Clear() destroys active elements and keeps capacity. The destructor clears elements and releases owned memory.
Array supports SetSize(...), Expand(...), Shrink(...), Push(...), Pop(), Insert(...), Remove(...), and Append(...).
For non-trivial element types, constructors and destructors are called correctly during growth, shrink, and relocation.
For raw-constructible/copyable element types, operations use bulk memory paths for performance.
If TYPE is marked null-terminated, Array keeps an additional trailing terminator element that is maintained after each mutation.
This mode enables string-like usage from the same container type while preserving explicit size.
Array provides indexed access, first/last accessors, direct data pointer access, and forward/reverse iteration.
Bounds are validated with assertions for index-based operations.
Array<int> values;
values.Push(10);
values.Push(20);
values.Insert(1, 15);
values.Remove(0);
for (auto & v : values)
{
// v = 15, then 20
}
void Array::Allocate(UInt32 capacity);
UInt32 Array::GetCapacity();
void Array::Compact();
void Array::Clear();
TYPE& Array::Push();
TYPE& Array::Push(const TYPE& value);
void Array::Pop();
TYPE& Array::Insert(UInt32 idx);
TYPE& Array::Insert(UInt32 idx, const TYPE& value);
void Array::SetSize(UInt32 size);
UInt32 Array::GetSize();
TYPE& Array::operator[](UInt32 idx);
const TYPE& Array::operator[](UInt32 idx);
bool Array::operator bool();
TYPE* Array::GetData();
const TYPE* Array::GetData();
void Array::Append(const ArrayView <TYPE>& values);
bool Array::operator<(const ArrayView <TYPE>& value);
bool Array::operator==(const ArrayView <TYPE>& value);
bool Array::operator!=(const ArrayView <TYPE>& value);
TYPE* ArrayRegion::data;
UInt32 ArrayRegion::size;
const TYPE* ArrayView::data;
UInt32 ArrayView::size;
Type-erased callable wrapper with semantics similar to std::function, but always safe to invoke.
An empty Function is implicitly bound to a null function that returns a default-constructed value. As a result, calling an unset Function is well-defined and never undefined behavior.
When the return type is not default-constructible, the function must be bound at construction and cannot exist in the empty (null-bound) state. This is enforced at compile time.
Clear() resets the function to the same null-bound state. This operation is likewise unavailable for non-default-constructible return types.
The function converts to bool, evaluating to true when a callable is bound and false when in the null-bound state.
Function <Int32(Int32)> fn;
output.Log(True(fn)); //logs false
auto v = fn(10); //safe, returns default int (0)
fn = [](Int32 x)
{
return x * 2;
};
output.Log(True(fn)); //logs true
fn.Clear(); //resets to null function
void Function::Clear();
RTN Function::Invoke(VARGS...);
RTN Function::operator()(VARGS...);
Lightweight integral index type used to represent either a valid position within an indexable container or an invalid/unset state.
Idx is typically used as the return type for search and lookup operations. A special sentinel value represents an invalid or not-found index.
if (Idx idx = Search(array, value))
{
auto & item = array[idx.value];
}
UInt32 Idx::value;
bool Idx::operator bool();
Intrusive list node base type.
Item provides link storage and membership operations for objects that participate in List.
An Item belongs to at most one List at a time.
Attaching an item automatically detaches it from any current list before linking into the target list.
Destroying an attached item unlinks it safely from its list.
Item supports:
GetPrev(), GetNext(), and GetList() expose neighborhood and ownership context.
OnAttach() and OnDetach() can be implemented by derived item types.
These hooks are invoked during normal attach/detach operations and allow custom side effects.
struct Entry : public Item<Entry>
{
using Item::Attach;
using Item::Detach;
using Item::InsertBefore;
void OnAttach() { /* became linked */ }
void OnDetach() { /* became unlinked */ }
};
List<Entry> entries;
Entry x, y;
x.Attach(entries);
y.InsertBefore(x);
y.Detach();
List <TYPE>* Item::GetList();
const List <TYPE>* Item::GetList();
Item <TYPE>* Item::GetPrev();
const Item <TYPE>* Item::GetPrev();
Item <TYPE>* Item::GetNext();
const Item <TYPE>* Item::GetNext();
TYPE Key::value;
Intrusive doubly-linked list container.
List manages ordering and traversal of items that derive from Item<TYPE,...>. The list does not allocate per-node wrappers; link fields live inside each item.
Item can be configured with RETAIN = true or false.
List exposes GetFirst(), GetLast(), GetNumItem(), Empty(), and boolean conversion.
Item-side APIs (Attach/InsertBefore/InsertAfter/Detach) update list links and counts atomically with respect to list invariants.
List destruction clears membership by detaching all linked items.
Forward and reverse iterators are provided.
In debug builds, iterator dereference validates modification state to catch invalid iteration across structural mutation.
SafeIterate(...) is available for mutation-tolerant traversal patterns.
UInt32 List::GetNumItem();
bool List::Empty();
Item <TYPE>* List::GetFirst();
const Item <TYPE>* List::GetFirst();
Item <TYPE>* List::GetLast();
const Item <TYPE>* List::GetLast();
void Map::Clear();
TYPE1* Map::Search(const TYPE1& key, TYPE2* fallback);
const TYPE2* Map::Search(const TYPE1& key, const TYPE2* fallback);
TYPE2& Map::Set(const TYPE1& key, const TYPE2& value);
void Map::Unset(const TYPE1& key);
TYPE2& Map::operator[](const TYPE1& idx);
const TYPE2& Map::operator[](const TYPE1& idx);
Lightweight observer used to detect changes in a State via polling.
A Monitor is connected to a State and records the last observed change count. Calling Poll() reports whether the associated State has changed since the previous poll.
State::Monitor does not manage the lifetime of the observed State. It is the caller’s responsibility to ensure that the State remains alive for the duration of the Monitor’s lifetime.
For cases where strong lifetime coupling is required, clients would typically wrap a Monitor together with an owning reference to the State’s owner.
struct RetainingMonitor
{
RetainingMonitor(Object & owner, State & owner_state)
: m_owner(owner)
, m_monitor(owner_state)
{
}
Reference <Object> m_owner; // retains state owner
State::Monitor m_monitor;
};
void Monitor::Connect(const State& state);
void Monitor::Reconnect();
void Monitor::Disconnect();
bool Monitor::Poll();
Hierarchical intrusive node built from Item + List.
Node combines sibling linkage (as an Item in a parent list) and child ownership/view (as a List of children), enabling tree structures without external wrapper nodes.
Each node can have:
Attach(parent) links a node as a child of parent.
GetParent() returns the parent node or null for roots.
Node provides parent and branch traversal utilities.
LookupBranchIndex(root, node) returns the top-level child index beneath root that contains node.
BranchContains(parent, child) returns true if child lies in parent's ancestry chain.
struct TreeItem : public Node<WidgetNode>
{
using Node::Attach;
};
TreeItem root, child, grandchild;
child.Attach(root);
grandchild.Attach(child);
bool inside = BranchContains(root, grandchild);
ObjectOf<T> wraps a value type inside a Reflex::Object so that it can participate in object-based APIs without designing a dedicated object class.
This is commonly useful when you want to heap-allocate a plain value, store it in a Reference, attach it as a typed property, pass it through generic object channels, or return it from async/task-style APIs.
auto offset = Make<ObjectOf<GLX::Point>>();
object.SetProperty("offset", New<ObjectOf<GLX::Point>>({10, 20}));
auto payload = New<ObjectOf<Function<void()>>>(callback);
The wrapped value is stored in the public .value member.
A useful detail is that ObjectOf<T> automatically provides a null-instance when T is trivially constructible. This makes wrappers such as ObjectOf<bool>, ObjectOf<UInt32>, ObjectOf<Key32>, and similar simple value types integrate cleanly with Reference<T> and TRef<T> default construction.
void Output::Log(VARGS... args);
void Output::Warn(VARGS... args);
void Output::Error(VARGS... args);
TYPE Point::x;
TYPE Point::y;
Lock-free single-producer single-consumer queue.
Queue is a ring-buffer queue designed for SPSC workloads. It uses atomic read/write positions and does not take locks.
Queue is intended for one producer thread and one consumer thread.
SIZE must be a power of two.
For fixed-size queues, storage is TYPE m_data[SIZE] and index wrap uses bitmasking.
Push(...) fails and returns false when full.
Pop(...) fails and returns false when empty.
Flush(value) reads the latest queued value and advances read position to write position, effectively dropping older pending entries.
Flush() clears all pending entries without returning a value.
Queue<int, 1024> q;
// producer thread
q.Push(42);
// consumer thread
int v = 0;
if (q.Pop(v))
{
// consumed value
}
void Queue::Push(const TYPE& value);
void Queue::Push(TYPE temp);
bool Queue::Pop(TYPE& out);
bool Queue::Flush(TYPE& last_out);
Point <TYPE> Rect::origin;
Size <TYPE> Rect::size;
Reference<T> is the primary ownership wrapper for Reflex objects.
It automatically retains on construction and releases on destruction, giving deterministic RAII-style lifetime management while guaranteeing a valid object reference over its lifetime.
Reference<T> default-constructs to the type's null-instance rather than nullptr. If a type does not define a null-instance, Reference<T> cannot be default-constructed.
Reference<MyClass> ref = New<MyClass>();
ref->DoSomething();
This is broadly equivalent to manual Retain()/Release() management:
MyClass * ptr = New<MyClass>();
ptr->Retain();
ptr->DoSomething();
ptr->Release();
In practice, Reference<T> is safer, clearer, and strongly preferred over manual retain/release handling.
Idx Sequence::Search(const KEY& key);
void Sequence::Clear();
TYPE2* Sequence::SearchValue(const TYPE1& key, TYPE2* fallback);
const TYPE2* Sequence::SearchValue(const TYPE1& key, const TYPE2* fallback);
TYPE2& Sequence::Set(const TYPE1& key, const TYPE2& value);
TYPE2& Sequence::Insert(const TYPE1& key, const TYPE2& value);
TYPE2& Sequence::Acquire(const TYPE1& key, VARGS... ...);
void Sequence::Remove(UInt32 idx);
void Sequence::Remove(UInt32 idx, UInt32 n);
UInt32 Sequence::GetSize();
TYPE2& Sequence::operator[](UInt32 idx);
const TYPE2& Sequence::operator[](UInt32 idx);
Type-safe push-based notification primitive.
Signal represents an event that can be emitted with a fixed argument signature. Observers register callbacks (listeners) which are invoked synchronously when the signal is notified.
Signals manage listener registration internally and support multiple independent listeners per signal.
Signal stores listeners in an intrusive list and does not retain them. Listener objects are owned by the client.
CreateListener(...) returns an Object representing a listener handle. As notifications are delivered only while this handle remains alive, the receiver would typically store the received Object in a Reference. When the handle is destroyed, the listener automatically disconnects and will no longer be called.
This design guarantees that Notify(...) never calls into a listener that has already been destroyed, without requiring the signal to manage listener lifetimes or the client to explicitly disconnect.
Signal and listener lifetimes are fully independent: it is safe for a Signal to be destroyed before its listeners, and for listeners to be destroyed before the Signal.
At the start of a notification, the current listener list is copied into a temporary dispatch buffer using strong references. This guarantees safe iteration in the presence of re-entrancy and list mutation.
Listeners added while a Notify(...) call is in progress will not receive that notification, and will be invoked starting from the next Notify(...).
Listener removal during dispatch takes effect immediately: if a listener is detached or released such that it is only retained by the internal dispatch buffer, it is skipped and will not be invoked.
Supports temporary muting via Signal::Mute.
If a signal is muted, notifications are suppressed until the mute scope is released. Nested mutes are supported.
struct DataStream
: public Signal<const Packet &>
{
using Signal<const Packet &>::CreateListener;
void Receive(const Packet & packet)
{
Notify(packet);
}
};
m_client.SetProperty(K32("listener"), data_stream.CreateListener([](const Packet & packet)
{
// process incoming data
}));
//disconnect
m_client.UnsetProperty<Reflex::Object>(K32("listener"));
TYPE Size::h;
TYPE Size::w;
Lightweight change-tracking primitive for pull-based notification.
State represents a mutable condition that can be marked as changed. Observers do not receive callbacks; instead, they poll for changes using a State::Monitor.
State is intended to be used as a base class. Its primary mutation method, Notify(), is protected to enforce strict semantics: only the state owner may publish its own changes. External code can observe state transitions via State::Monitor, but cannot trigger them directly.
struct MyObject :
public Reflex::Object,
public Reflex::State
{
void SetSomething()
{
State::Notify();
}
};
Each call to Notify() increments an internal change counter. Clients use State::Monitor instances to poll for changes since the last observation.
TRef<T> is Reflex's lightweight non-owning object reference.
Unlike Reference<T>, TRef<T> does not retain or release. Its role is primarily semantic: it expresses temporary access to a valid object without taking ownership.
Like Reference<T>, TRef<T> binds to the type's null-instance rather than nullptr, so it follows the Reflex convention of valid object references instead of nullable smart pointers.
Reference<MyClass> owner = New<MyClass>();
TRef<MyClass> handle = owner;
handle->DoSomething();
Typical uses include:
TYPE1 Tuple::a;
TYPE2 Tuple::b;
VARGS... Tuple::c (etc);
Inherits from Object
Fixed-size contiguous object allocation.
Allocation stores a contiguous block of elements with a size chosen at creation time. Unlike Array, Allocation does not track capacity or support growth. The size can only stay the same or be reduced via Shrink(...).
Because the allocation size cannot increase after creation, Allocation is suitable for sharing fixed-size primitive buffers across multiple threads when callers need stable storage and a stable element count.
This does not provide atomic read/write synchronization. Concurrent access still requires the usual external synchronization when multiple threads may read and write the same elements.
Allocation provides indexed access, direct data pointer access, and begin/end iteration in a compact object-owned buffer.
const UInt32 Allocation::size;
void Allocation::Shrink(UInt32 n);
TYPE& Allocation::operator[](UInt32 idx);
const TYPE& Allocation::operator[](UInt32 idx);
TYPE* Allocation::GetData();
const TYPE* Allocation::GetData();
Inherits from Object
Reflex makes a primary, explicit distinction between object-types and value-types. Object types derive from Reflex::Object, are shared, reference counted, typically heap-based (although they can also be instantiated on the stack), and non-copyable by default.
Value types (non-Objects such as UInt32 or Array) are lightweight stack-based primitives. To promote a value into an object so it can participate in reference counting, heap allocation, dynamic properties, or generic object APIs, use ObjectOf<TYPE>.
Reflex::Object provides two foundational capabilities:
Reflex uses an intrusive reference counting model built directly into Object.
Unlike std::shared_ptr, COM, or Objective-C, there are no external control blocks or hidden allocations - the reference count lives inside the object itself.
Reference<T> is the primary ownership wrapper for Reflex objects. It retains on construction, releases on destruction, and guarantees a valid object reference over its lifetime.
See Reflex::Reference for full semantics and usage.
TRef<T> is the lightweight non-owning counterpart to Reference<T>. It does not retain or release, but still follows the Reflex convention of representing a valid object reference rather than a nullable pointer.
See Reflex::TRef for full semantics and usage.
Reflex objects should never be created with raw new/delete directly.
Instead, object creation is performed through the Reflex helper APIs:
auto a = New<MyClass>(args...);
auto b = Make<MyClass>(args...);
Make<T>() is generally the most convenient form when immediate ownership is required.
AutoRelease() provides a shorthand for constructing temporary Reference<T> wrappers.
The following examples are equivalent:
Reference<HttpConnection> conn = HttpConnection::Create(...);
auto conn = AutoRelease(HttpConnection::Create(...));
auto conn = Make<HttpConnection>(...);
Unlike traditional COM-style systems, Reflex objects may be instantiated directly on the stack.
Data::PropertySet node;
Stack objects ignore Retain() and Release() calls and are destroyed normally via scope lifetime.
This flexibility is useful and efficient for temporaries and embedded members, however ownership must remain explicit: any retained reference must not outlive the stack object it refers to. For example, storing a Reference or TRef to a stack object beyond its lifetime is invalid:
Data::PropertySet node;
auto props = New<Data::PropertySet>();
props->SetProperty("child", node); // invalid after node leaves scope
Reference<T> and TRef<T> default construct using the type's null-instance rather than nullptr.
If a type does not define a null-instance, the wrappers cannot be default-constructed, enforcing validity at compile time.
User-defined object types therefore often require explicit construction:
Reference <View> view1 = New<View>();
Reference <View> view2 = kNewObject; //shorthand helper
TRef<T> also supports construction using kNoValue for deferred assignment, however this should only be used sparingly.
Object exposes Retain() and Release(), however direct usage is uncommon.
If manual lifetime management is required:
Reflex is not a garbage collected system. Circular references will leak unless explicitly broken:
auto a = New<Data::PropertySet>();
auto b = New<Data::PropertySet>();
a->SetProperty("foo", b);
b->SetProperty("foo", a);
Some advanced systems inside Reflex (such as Reflex::VM) provide explicit cycle-breaking mechanisms, however application code should generally avoid circular ownership through design.
ObjectOf<T> wraps a value type inside an Object, allowing values to participate in reference counting, dynamic properties, and generic object-based APIs.
Reflex::Object defines a virtual interface for attaching, querying, and removing typed properties dynamically via Object::SetProperty, Object::QueryProperty, and Object::UnsetProperty.
This allows sub-objects and arbitrary typed values to be attached to objects at runtime, reducing the need for deep subclassing and enabling highly data-driven systems.
Properties are identified by both id AND type, rather than by id alone. This complements C++'s strong typing model by allowing multiple typed views of the same conceptual property.
Important: Object Does Not Implement the Interface
Reflex::Object itself does not provide a concrete property implementation. Calling SetProperty() on a derived type which does not implement the property callbacks will silently discard the property.
Typically, dynamic properties are implemented through Data::PropertySet, which fully implements the property system for arbitrary types.
Many primary framework classes (such as GLX::Object) derive from Data::PropertySet and therefore support dynamic properties automatically.
For more information on dynamic properties, see Data::PropertySet.
void Object::RetainSt();
void Object::ReleaseSt();
void Object::RetainMt();
void Object::ReleaseMt();
void Object::UnsetProperty(Key32 id);
void Object::SetProperty(Key32 id, TYPE& property);
TYPE* Object::QueryProperty(Key32 id, TYPE& property);
Allocator* Object::GetAllocator();
kStatusPending
kStausFailed
kStatusCompleted
TRef <Object> CreateClock(const Function <void()>& callback);
TRef <Object> CreatePeriodicClock(Float32 interval, const Function <void()>& callback);
bool Context::Cancelled();
void Context::SetProgress(Float32 value_normalized);
void Context::SetResult(bool result, TRef <Object> payload);
Inherits from System::Thread
void Task::Cancel();
Float32 Task::GetProgress();
Task::Status Task::GetStatus();
TRef <Object> Task::GetResult();
Inherits from System::Thread
const kBinaryFormat kBinaryFormat;
While Reflex’s native formats (text-based PropertySheet and the binary PropertySet format) provide better type support, performance, and integration with the Reflex pipeline, JSON remains the natural choice when exchanging data with external systems - especially web services, scripting environments, and tools that expect standard JSON encoding.
Reflex::Data provides full support for mapping JSON to/from PropertySet, allowing seamless interchange while retaining the benefits of PropertySet’s typed accessors and structured hierarchy.
Use DecodePropertySet with the Data::kJsonFormat format to parse a JSON blob into a standard PropertySet:
Data::Archive json_blob = File::Open(path);
auto json = Data::DecodePropertySet(Data::kJSON, json_blob);
Once decoded, use the dynamic property system to access the properties
For readability/convienence and to minimise template bloat, Data implements helpers for common property types:
auto f = Data::GetFloat32(json, "my_key");
auto s = Data::GetCString(json, "my_string");
auto sub = Data::GetPropertySet(json, "sub_node");
Nested structure, typed values, and optional presence checks behave exactly like any other PropertySet.
Use the Data::Get...Array helpers to retrieve arrays, including Data::GetPropertySetArray for object arrays.
auto items = Data::GetFloat32Array(json, "numbers");
for (auto & i : items)
{
}
PropertySet is a map, not an array.
To read a JSON document whose root is an array, use:
auto items = Data::GetPropertySetArray(json, {}); //or kNullKey for constexpr hash of ""
for (auto & i : items)
{
}
Each entry in the returned array is a PropertySet representing one array element.
JSON decoding supports configurable options, such as:
These are controlled via the optional Decode options structure passed to `DecodePropertySet`.
Encoding follows the same Format interface as all other Data formats, but one key rule applies:
PropertySet keys are integer IDs, so each ID must be mapped to a string name.
Before setting values on a JSON PropertySet for encoding, acquire a key map on the root:
Data::PropertySet json;
auto keymap = Data::AcquireKeyMap(json); // only needed on the root
Then register each string key once:
auto my_key = Data::RegisterKey(keymap, "my_key");
Data::SetFloat32(json, my_key, 22.0f);
After registration, the same integer ID is used for subsequent sets - no repeated string hashing.
auto blob = Data::EncodePropertySet(Data::kJSON, json);
File::Save(path, blob);
The resulting `Data::Archive` contains a UTF-8 JSON document.
const kJsonFormat kJsonFormat;
const kLZ4 kLZ4;
const kPropertySetFormat kPropertySetFormat;
const kPropertySheetFormat kPropertySheetFormat;
const kReflexMarkupFormat kReflexMarkupFormat;
Reflex XML support is for interoperability (eg SVG, simple config).
XML is decoded into a PropertySet tree where:
Data::Archive blob = File::Open(path);
auto xml = Data::DecodePropertySet(Data::kReflexXmlFormat, blob);
The returned PropertySet is the root element.
if (Data::GetXmlTag(*xml) == "svg")
{
}
XML documents have a single root element.
Child elements are stored as a PropertySetArray at kNullKey.
for (auto & child : Data::GetXmlNodes(xml))
{
auto tag = Data::GetXmlTag(*child);
}
XML attribute values decode as strings and are stored directly on the node PropertySet.
Use the dynamic property iterator (or your string helpers) to read them.
auto keymap = Data::GetKeyMap(xml);
for (auto & child : Data::GetXmlNodes(xml))
{
for (auto & [adr, prop] : child->Iterate<Data::CStringProperty>())
{
auto name = Data::GetKey(keymap, adr.id);
auto value = prop->value;
}
}
Note: "tag" is also a string property on the node. Skip it if you only want attributes.
To build an XML structure, create a root PropertySet and attach child nodes via the kNullKey array.
Data::PropertySet root;
auto nodes = Data::AcquireXmlNodes(root); // = AcquirePropertySetArray(root, kNullKey)
auto rect = Data::AddXmlNode(*nodes, "rect"); // creates a PropertySet + sets ktag
Data::SetCString(rect, "x", "2");
Data::SetCString(rect, "y", "3");
Data::SetCString(rect, "width", "10");
Data::SetCString(rect, "height", "10");
To add grandchildren, call AcquireXmlNodes on the child and keep going.
auto g = Data::AddXmlNode(*nodes, "g");
Data::SetCString(g, "transform", "translate(12 12)");
auto g_nodes = Data::AcquireXmlNodes(*g);
auto path = Data::AddXmlNode(*g_nodes, "path");
Data::SetCString(path, "d", "M0 0 L10 0");
auto blob = Data::EncodePropertySet(Data::kReflexXmlFormat, root);
File::Save(path, blob);
const kReflexXmlFormat kReflexXmlFormat;
const kRiffFormat kRiffFormat;
using Archive = Array <UInt8>;
using Archive::View = ArrayView <UInt8>;
using Float32Property = ObjectOf <Float32>;
using Float64Property = ObjectOf <Float64>;
using Int32Property = ObjectOf <Int32>;
using Int64Property = ObjectOf <Int64>;
using KeyMap = ObjectOf < Map < Key <UInt32> , Array <char> > >;
using UInt32Property = ObjectOf <UInt32>;
using UInt64Property = ObjectOf <UInt64>;
TRef < ObjectOf < Map < Key <UInt32> , Array <char> > > > AcquireKeyMap(PropertySet& root);
TRef <PropertySet> AcquirePropertySet(PropertySet& propertyset, Key32 id);
TRef <PropertySet> AcquirePropertySet(PropertySet& propertyset, const ArrayView < Key <UInt32> >& path);
TRef < ObjectArray <PropertySet> > AcquirePropertySetArray(PropertySet& propertyset, Key32 id);
TRef <PropertySet> AddPropertySet(ObjectArray <PropertySet>& array);
void Assimilate(PropertySet& target, const PropertySet& source);
CString BytesToHex(const Archive::View& bytes);
UInt32 CRC32(const Archive::View& bytes, UInt32 previous);
Computes a standard 32-bit CRC (Cyclic Redundancy Check) using the IEEE 802.3 polynomial. Intended primarily for error detection or compatibility with external systems that use CRC32.
Note, if 'previous' is non-zero, the function will continue from that value, allowing incremental CRC accumulation over multiple buffers. This is useful for streaming or chunked data validation.
UInt32 crc = CRC32(chunk1);
crc = CRC32(chunk2, crc);
crc = CRC32(chunk3, crc);
Archive Compress(const CompressionAlgorithm& algorithm, const Archive::View& bytes);
PropertySet CopyPropertySet(const Format& format, const PropertySet& propertyset);
PropertySet DecodePropertySet(const Format& format, const Archive::View& bytes, UInt32 options);
void DecodeUCS2(WString& output, const Archive::View& bytes);
WString DecodeUCS2(const Archive::View& bytes);
void DecodeUTF8(WString& output, const Archive::View& bytes);
WString DecodeUTF8(const Archive::View& bytes);
CString DecodeUrlSegment(const CString::View& view);
Archive Decompress(const DecompressionAlgorithm& algorithm, const Archive::View& bytes);
void Deserialize(Archive::View& stream, VARGS...& ...);
TYPE Deserialize(Archive::View& stream);
Tuple <VARGS...> Deserialize(Archive::View& stream);
Restores one or more values from a binary [code Archive::View], in the same order they were previously stored. Two forms are provided for flexibility.
void Deserialize(Archive::View & stream, VARGS & ...)
Restores values in-place by writing into references.
Int32 a;
CString b;
Restore(stream, a, b);
template <class ...VARGS> Tuple<VARGS...> Deserialize(Archive::View & stream)
Restores values and returns them as a [code Tuple]. If only one type is requested, the tuple decays to that type automatically.
auto [id, name] = Deserialize<Int32,CString>(in);
CString str = Deserialize<CString>(in); // returns single value directly
The input [code stream] is advanced as each value is decoded. The number and order of types must match what was passed to [code Store].
void DeserializePropertySet(Archive::View& stream, const SerializableFormat& format, PropertySet& out);
void DeserializeUCS2(Archive::View& stream, WString& string_out);
WString DeserializeUCS2(Archive::View& stream);
void DeserializeUTF8(Archive::View& stream, WString& string_out);
WString DeserializeUTF8(Archive::View& stream);
Archive EncodePropertySet(const Format& format, const PropertySet& propertyset);
void EncodeUCS2(Archive& output, const WString::View& string);
Archive EncodeUCS2(const WString::View& string);
void EncodeUTF8(Archive& output, const WString::View& string);
Archive EncodeUTF8(const WString::View& string);
CString EncodeUrlSegment(const CString::View& view);
CString EncodeUrlSegment(const WString::View& view);
UInt32 FNV1a32(const Archive::View& bytes, UInt32 previous);
UInt64 FNV1a64(const Archive::View& bytes, UInt64 previous);
Computes a fast non-cryptographic hash using the FNV-1a algorithm. Suitable for hashing structured binary data (strings, file names, serialized values).
auto file = Make<System::FileHandle>(path_to_file);
auto h64 = FNV1a64({});
while (auto chunk = File::ReadBytes(file, kMaxUInt16))
{
}
bool GetBool(const Object& propertyset, Key32 id, bool fallback_opt);
Float32 GetFloat32(const Object& propertyset, Key32 id, Float32 fallback_opt);
Float64 GetFloat64(const Object& propertyset, Key32 id, Float64 fallback_opt);
Int32 GetInt32(const Object& propertyset, Key32 id, Int32 fallback_opt);
Int64 GetInt64(const Object& propertyset, Key32 id, Int64 fallback_opt);
CString::View GetKey(const KeyMap& keymap, Key32 key);
Key32 GetKey32(const Object& propertyset, Key32 id, Key32 fallback_opt);
ConstTRef < ObjectOf < Map < Key <UInt32> , Array <char> > > > GetKeyMap(const PropertySet& root);
ConstTRef <PropertySet> GetPropertySet(const Object& propertyset, Key32 id);
ConstTRef <PropertySet> GetPropertySet(const PropertySet& propertyset, const ArrayView < Key <UInt32> >& path);
ArrayView < ConstReference <PropertySet> > GetPropertySetArray(const PropertySet& propertyset, Key32 id);
UInt32 GetUInt32(const Object& propertyset, Key32 id, UInt32 fallback_opt);
UInt64 GetUInt64(const Object& propertyset, Key32 id, UInt64 fallback_opt);
UInt8 GetUInt8(const Object& propertyset, Key32 id, UInt8 fallback_opt);
Archive HexToBytes(const CString::View& hex);
bool IsHttps(const CString::View& url);
CString MakeUrl(const Url& url);
PropertySet Merge(const PropertySet& a, const PropertySet& b);
Archive::View Pack(const TYPE& value);
Converts a value into a binary view without transformation.
Pack operates only on *raw-packable types* - types whose binary representation is stable, contiguous, and platform-independent.
If TYPE is not raw-packable, compilation will fail with an error such as “Non raw-packable type”.
Pack performs no encoding, metadata emission, or length prefixing. The size of the returned view is determined entirely by the value’s binary representation.
UInt32 u32 = 42;
auto view = Data::Pack(u32); //OK
CString value; //typedef of Array<char>
auto view = Data::Pack(value); //OK
WString value;
auto view = Data::Pack(value); //ERROR wchar_t size is platform dependent, use Data::EncodeUTF8 instead
This struct is raw-packable:
struct Foo
{
UInt32 a;
CString b;
};
Foo foo;
auto view = Data::Pack(foo); //OK
Whereas this struct is not:
struct Foo
{
UInt32 a;
WString b;
};
Foo foo;
auto view = Data::Pack(foo); //ERROR
bool ReadLine(Archive::View& stream, WString& line_out);
bool ReadLine(Archive::View& stream, CString& line_out);
Reads a single line of text from a memory stream and decodes it as either raw ASCII (CString overload) or UTF-8 (WString overload).
//read entire ASCII text file
Data::Archive archive = File::Open(path);
Data::Archive::View stream = archive; //use ToView for shorthand
CString buffer;
while (Data::ReadLine(stream, buffer))
{
//process line
}
Key32 RegisterKey(KeyMap& keymap, const CString::View& string);
void ResetPropertySet(const Format& format, PropertySet& propertyset);
Archive SHA1(const Archive::View& bytes);
Computes a 160-bit SHA-1 hash of the input data. Suitable for content identification or integrity checks where a stronger hash than CRC32 is required.
Note, SHA-1 is not considered secure for cryptographic purposes but remains useful for non-security-critical fingerprinting or deduplication.
Archive hash = SHA1(fileData);
Archive SHA256(const Archive::View& bytes);
Computes a 256-bit SHA-2 (SHA-256) hash of the input data. Provides strong collision resistance for security-sensitive applications such as signing, verification, or integrity validation.
Note, SHA-256 is suitable for cryptographic use cases, including secure hashing of content, signatures, and tokens.
Archive hash = SHA256(fileData);
void Serialize(Archive& stream, const VARGS...& ...);
Serializes one or more values into a binary [code Archive]. Supports any combination of raw, structured, or user-defined types with appropriate [code Encode] overloads.
This function is the core of Reflex's structured serialization system. It can encode basic types, arrays, containers, strings, and custom structures, in order.
Archive out;
Serialize(out, Int32(42), CString("example"), my_object);
The data can later be recovered using [code Restore] with the same types and order.
Archive::View in = out;
Int32 a;
CString b;
MyType c;
Deserialize(in, a, b, c);
void SerializePropertySet(Archive& stream, const SerializableFormat& format, const PropertySet& in);
void SerializeUCS2(Archive& stream, const WString::View& string);
void SerializeUTF8(Archive& stream, const WString::View& string);
void SetBinary(Object& propertyset, Key32 id, const Archive::View& value);
void SetBool(Object& propertyset, Key32 id, bool value);
void SetCString(Object& propertyset, Key32 id, const CString::View& value);
void SetFloat32(Object& propertyset, Key32 id, Float32 value);
void SetFloat64(Object& propertyset, Key32 id, Float64 value);
void SetInt32(Object& propertyset, Key32 id, Int32 value);
void SetInt64(Object& propertyset, Key32 id, Int64 value);
void SetKey32(Object& propertyset, Key32 id, Key32 value);
void SetPropertySet(Object& propertyset, Key32 id, TRef <PropertySet> value);
void SetUInt32(Object& propertyset, Key32 id, UInt32 value);
void SetUInt64(Object& propertyset, Key32 id, UInt64 value);
void SetUInt8(Object& propertyset, Key32 id, UInt8 value);
void SetWString(Object& propertyset, Key32 id, const WString::View& value);
Url SplitUrl(const CString::View& url);
Pair < ArrayView <char> , ArrayView <char> > SplitUrlResource(const CString::View& url);
void Unpack(const Archive::View& bytes, TYPE& output);
TYPE Unpack(const Archive::View& bytes);
Restores a value from a binary view, previously produced by Pack.
Unpack operates only on *raw-packable types* and reconstructs the value by reading its binary representation.
TYPE must match the type used when packing. If TYPE is not raw-packable, compilation will fail with an error similar to "Non raw-packable type".
Unpack performs no decoding, validation, or metadata processing. It simply interprets the referenced bytes as TYPE.
Unpack assumes the binary layout in ref exactly matches TYPE. Mismatched types or incompatible layouts result in undefined behavior.
Use Unpack only with data produced by Pack or when the binary layout is known and stable.
UInt32 original = 42;
auto view = Data::Pack(original);
UInt32 restored;
Data::Unpack(view, restored); //OK
UInt32 restored = Data::Unpack<UInt32>(view); //OK
struct Foo
{
UInt32 a;
CString b;
};
Foo foo;
auto view = Data::Pack(foo);
Foo restored = Data::Unpack<Foo>(view);
auto restored = Data::Unpack<WString>(view); //FAIL WString size is platform dependent
void UnsetBinary(Object& propertyset, Key32 id);
void UnsetBool(Object& propertyset, Key32 id);
void UnsetCString(Object& propertyset, Key32 id);
void UnsetFloat32(Object& propertyset, Key32 id);
void UnsetFloat64(Object& propertyset, Key32 id);
void UnsetInt32(Object& propertyset, Key32 id);
void UnsetInt64(Object& propertyset, Key32 id);
void UnsetKey32(Object& propertyset, Key32 id);
void UnsetPropertySet(Object& propertyset, Key32 id);
void UnsetPropertySetArray(PropertySet& propertyset, Key32 id);
void UnsetUInt32(Object& propertyset, Key32 id);
void UnsetUInt64(Object& propertyset, Key32 id);
void UnsetUInt8(Object& propertyset, Key32 id);
void UnsetWString(Object& propertyset, Key32 id);
void WriteLine(Archive& stream, const WString::View& line);
void WriteLine(Archive& stream, const CString::View& line);
Writes a single line of text to a Data::Archive (byte array), encoding the input as either raw ASCII (CString::View overload) or UTF-8 (WString::View overload).
A line terminator is automatically appended. The input string should not include a newline.
Data::Archive stream;
//write plain ASCII
Data::WriteLine(stream, "first line");
Data::WriteLine(stream, "second line");
//write multibyte UTF-8
Data::WriteLine(stream, L"UTF-8 text");
Data::WriteLine(stream, L"日本語");
CString Url::domain;
CString Url::fragment;
UInt16 Url::port;
CString Url::resource;
CString Url::scheme;
Pair < Array <char> , Array <char> > Url::user;
Inherits from Object
Inherits from DecompressionAlgorithm
UInt32 CompressionAlgorithm::GetMaxCompressedSize(UInt32 size);
UInt32 CompressionAlgorithm::Compress(const Archive::View& input, UInt8* output);
Inherits from Object
bool DecompressionAlgorithm::Decompress(const Archive::View& input, Archive& output);
Inherits from Object
Data::Format defines the interface for encoding and decoding structured data. A given Format implements how to serialize a PropertySet into bytes and how to reconstruct it back into a PropertySet.
Reflex provides several global, constant Format instances covering the common persistence use cases.
Data::PropertySet data;
Data::SetInt32(data, "version", 1);
auto child = Data::AcquirePropertySet(data, "child");
Data::SetFloat32Array(child, "values", {1.0f, 2.0f});
auto blob = Data::EncodePropertySet(Data::kPropertySetFormat, data);
File::Save(path, blob);
auto bytes = File::Open(path);
Data::PropertySet root = Data::DecodePropertySet(Data::kPropertySetFormat, bytes);
if (root)
{
auto v = Data::GetInt32(root, "version");
auto sub = Data::GetPropertySet(root, "child");
auto arr = Data::GetFloat32Array(sub, "values");
}
void Format::Reset(PropertySet& propertyset);
bool Format::Encode(Archive& out, const PropertySet& data);
bool Format::Decode(PropertySet& out, const Archive::View& data, UInt32 options);
Inherits from Object
Inherits from Object
Data::PropertySet is Reflex’s generic container for structured, tree-based data.
PropertySet allows arbitrary data to be attached to objects at runtime, without modifying class definitions or introducing ad-hoc subclasses.
Instead of encoding every variation in a type hierarchy, behavior and state can be composed dynamically by attaching properties as needed.
object.SetProperty("hover_time", 0.0f);
object.SetProperty("user_data", some_ref);
This bridges the performance and safety of C++ with the flexibility typically associated with dynamic languages. It enables rapid iteration, late-bound features, and data-driven behavior while remaining fully type-aware and debuggable.
PropertySet implements the root Reflex::Object property interface (OnSetProperty / OnUnsetProperty / OnQueryProperty).
This provides a uniform, generic property mechanism across the entire framework.
Properties are identified by id AND type, rather than id alone.
This complements C++'s strong typing model by allowing multiple, type-safe views of the same conceptual property without collapsing everything into a single loosely-typed value.
ps.SetProperty("my_id", 1);
ps.SetProperty("my_id", "string");
The example above creates two distinct properties:
Under the hood, this (id + type) pair is represented by Reflex::Address.
PropertySet supports hierarchical data by allowing nested PropertySet instances.
This makes it suitable for representing structured trees of runtime state, configuration, or metadata when needed.
Unlike rigid schemas, PropertySet allows structure to evolve organically as systems interact.
PropertySet is serializable, via the Format system.
This makes it suitable for storing presets, state snapshots, configuration files, and interchange data.
For persistent data, values should be written using the Data::SetXXX family of functions, which define the set of interoperable, format-safe property types. Using the Reflex::Data:: suite of property accessors also avoids template bloat occuring from use of the Reflex:: templated ones.
Data::SetFloat32(ps, "gain", 0.75f);
Data::SetCString(ps, "name", "Preset A");
PropertySet can be serialized using any Data::Format implementation:
auto blob = Data::EncodePropertySet(Data::kPropertySetFormat, ps);
//or
auto json = Data::DecodePropertySet(Data::kJsonFormat, ps);
Note that when writing text-based formats (JSON, Reflex PropertySheets) you need to register the key-strings, via Data::AcquireKeyMap and Data::RegisterKey.
Use Iterate<TYPE>() to iterate over all properties of TYPE
Each item will have a [key,value] where key is Reflex::Address (comprising of the property id and type_id) and value a Reference <TYPE>
for (auto & [adr, ref] : test.Iterate<Data::CStringProperty>())
{
output.Log(id.value, ref->value);
}
bool PropertySet::Empty();
bool PropertySet::operator bool();
PropertySet::PropertyIterator <TYPE> PropertySet::Iterate();
Inherits from Format
void SerializableFormat::Serialize(Archive& stream, const PropertySet& propertyset);
void SerializableFormat::Deserialize(Archive::View& stream, PropertySet& propertyset);
The standard path separator used by Reflex. This is an alias of System::kPathDelimiter and represents the canonical delimiter for all paths handled within Reflex APIs.
Reflex paths must always use this delimiter, regardless of the underlying OS. The System layer will translate paths when interacting with native APIs, ensuring portability across platforms. Any path provided by a Reflex::System API is guaranteed to use kPathDelimiter.
Although the current value is a forward slash ('/'), this should not be relied upon for future compatibility. All path construction should reference File::kPathDelimiter explicitly.
Folder paths in Reflex typically include the trailing delimiter.
For example, when calling File::List(path), folder entries in the returned list will include the trailing separator.
Use Join(...) or manually insert File::kPathDelimiter when building paths:
auto user_docs_path = File::GetSystemPath(File::kPathUserDocuments); //includes trailing stroke
auto path = Join(user_docs_path, L"sub_folder", File::kPathDelimiter, L"sub_sub_folder", File::kPathDelimiter);
This ensures consistent behaviour across all platforms, correct interoperation with the VirtualFileSystem, and predictable results when listing or resolving paths.
const kPathDelimiter kPathDelimiter;
bool CheckExtension(const WString::View& path, const WString::View& extension);
bool Copy(const WString& from, const WString& to);
bool Copy(System::FileHandle& from, System::FileHandle& to, UInt32 chunksize_opt);
WString CorrectExtension(const WString::View& path, const WString::View& extension);
WString CorrectStrokes(const WString::View& path);
WString CorrectTrailingStroke(const WString::View& path);
TRef <System::FileHandle> CreateMemoryReader(ConstTRef <Data::ArchiveObject> data);
TRef <System::FileHandle> CreateMemoryWriter(TRef <Data::ArchiveObject> data);
bool Delete(const WString& path);
Deletes a file or directory. To delete a directory, the directory must be empty.
This is an alias of System::Delete.
void DeleteDirectoryContent(const WString& path);
Deletes all content of a directory, recursively, but does not the directory itself.
void DeletePath(const WString& path);
Deletes a directory, including all content.
bool Exists(const WString& path);
Returns true is path is a file or directory/folder.
This is an alias of System::Exists.
UInt64 GetRemainder(const System::FileHandle& file_handle);
WString GetSystemPath(System::Path path);
Returns the actual location of a system defined 'special' path.
Alias of System::GetPath.
Array < Pair < Array <WChar> , Array <WChar> > > GetVolumes();
bool IsDirectory(const WString& path);
Returns true is path is a directory/folder.
This is an alias of System::IsDirectory.
bool MakeDirectory(const WString& path);
Creates a directory (folder) at the given path. Returns true on success, false on failure.
This is an alias of System::MakeDirectory.
void MakePath(const WString& path);
WString MakeRelativePath(const WString::View& base_dir, const WString::View& path);
Computes a relative path from 'base_dir' to 'path' using purely lexical (string) path comparison.
The result is formed by:
auto rel = Reflex::File::MakeRelativePath(L"/a/b/c/", L"/a/b/d/e.txt");
// rel == "../d/e.txt"
auto rel = Reflex::File::MakeRelativePath(L"/project/assets/", L"/project/assets/textures/wood.png");
// rel == "textures/wood.png"
Data::Archive Open(const WString& path);
Reads the entire contents of a file into a Data::Archive. Open is a convenience wrapper around System::FileHandle for cases where a file needs to be loaded into memory at once.
The archive is sized to the file length, and the file is read in a single operation.
auto bytes = File::Open(filename);
if (bytes.GetSize() > 4)
{
Data::Archive::View stream = bytes;
if (Data::Deserialize<UInt32>(stream) == kMagicBytes)
{
//deserialize remaining data according to the expected format
}
}
This helper is suitable for loading small–medium resources, configuration files, text files, and binary assets that fit comfortably in memory.
Data::Archive Peek(System::FileHandle& file_handle, UInt32 bytes);
Data::Archive ReadBytes(System::FileHandle& file_handle);
Data::Archive ReadBytes(System::FileHandle& file_handle, UInt32 bytes);
Reads raw bytes from a file into a Data::Archive. Two overloads are provided: one that reads all remaining bytes, and one that reads up to a specified number of bytes.
auto handle = Make<System::FileHandle>(path);
if (handle->GetSize() > 32)
{
handle->SetPosition(16);
auto region = File::ReadBytes(handle, 16);
//use region
}
bool ReadLine(System::FileHandle& file_handle, CString& line_out);
bool ReadLine(System::FileHandle& file_handle, WString& line_out);
Reads a single line of text from the file and decodes it as either raw ASCII (CString overload) or UTF-8 (WString overload).
//read entire ASCII text file
auto file_handle = Make<System::FileHandle>(path);
CString buffer;
while (File::ReadLine(file_handle, buffer))
{
//process line
}
bool ReadValue(System::FileHandle& file_handle, TYPE& value_out);
TYPE ReadValue(System::FileHandle& file_handle);
Reads a fixed-size value from the file into a raw-packable type. This is a low-level binary read intended for POD / trivially copyable types with a stable binary layout.
TYPE must be raw-packable (compile-time enforced). Non raw-packable types will fail to compile.
auto file_handle = Make<System::FileHandle>(path);
UInt32 a = 0;
if (File::ReadValue(*file_handle, a))
{
//use a
}
auto b = File::ReadValue<Float32>(*file_handle);
WString RemoveDuplicateStrokes(const WString::View& path);
WString::View RemoveTrailingStroke(const WString::View& path);
bool Rename(const WString& from, const WString& to);
WString::View ResolveExistingFolder(const WString::View& path);
WString ResolveRelativePath(const WString::View& path);
bool Save(const WString& filename, const Data::Archive::View& data);
Writes the contents of a byte buffer to a file. Save is the inverse of File::Open: instead of loading the entire file into an Archive, it writes an Archive (or view of one) out to disk (typically).
Save performs a single write of the provided data range. No additional formatting or metadata is added.
Data::Archive stream;
Data::Serialize(stream, 123, 4.5f);
Data::SerializeUTF8(stream, L"hello");
if (!File::Save(L"output.bin", out))
{
//handle save failure
}
Pair < ArrayView <WChar> , ArrayView <WChar> > SplitExtension(const WString::View& path);
Pair < ArrayView <WChar> , ArrayView <WChar> > SplitFilename(const WString::View& path);
UInt32 WriteBytes(System::FileHandle& file_handle, const Data::Archive::View& bytes);
Writes the given byte view to the file at the current cursor position.
A short write (return < bytes.GetSize()) indicates an I/O failure, full disk, or write restrictions.
void WriteLine(System::FileHandle& file_handle, const CString::View& line);
void WriteLine(System::FileHandle& file_handle, const WString::View& line);
Writes a single line of text to the file, encoding the input as either raw ASCII (CString::View overload) or UTF-8 (WString::View overload).
A line terminator is automatically appended. The input string should not include a newline.
auto file_handle = Make<System::FileHandle>(path);
//write plain ASCII
File::WriteLine(file_handle, "first line");
File::WriteLine(file_handle, "second line");
//write multibyte UTF-8
File::WriteLine(file_handle, L"UTF-8 text");
File::WriteLine(file_handle, L"日本語");
bool WriteValue(System::FileHandle& file_handle, TYPE value);
Writes a single raw value directly to the file. TYPE must be a raw-packable type - i.e., a type whose binary representation is stable, contiguous, and platform-independent.
WriteValue performs no encoding or metadata emission. The bytes of the value are written exactly as they exist in memory.
auto f = Make<System::FileHandle>(path, File::kWrite);
UInt32 counter = 123;
File::WriteValue(f, counter); //writes 4 bytes
struct Foo
{
UInt32 a;
Float32 b;
};
Foo foo = { 10, 2.5f };
File::WriteValue(f, foo); //OK if Foo is trivially copyable
Inherits from Data::PropertySet
PersistentPropertySet brings together Data::PropertySet and Data::Format to create a PropertySet that can be stored and restored.
It is used primarily by the Bootstrap layer for the prefs object and the app session object, and it is the underlying mechanism behind the Bootstrap::Streamable persistence system used by the app, views, and other clients.
You can subscribe to changes by creating a listener using the Signal::CreateListener pattern. The receiver must store the returned object to keep the connection alive.
See also
Inherits from Object
ResourcePool is the higher-level shared resource cache built on top of the file layer.
It resolves resources by address or path, ensures the underlying file is decoded only once, and then keeps the in-memory representation available for reuse across the application.
In practice this is used extensively by GLX for stylesheets, fonts, bitmaps, and other UI resources, but it was originally designed around audio/sample loading and is equally well proven there.
ResourcePool typically sits above File::VirtualFileSystem, which handles the lower-level path resolution.
Inherits from Object
VirtualFileSystem is the lower-level path resolution layer used to map a path onto a concrete file source.
Rather than being limited to disk files, it works through attachable locator handlers for different domains and storage types. This allows special paths such as ":res:Project/styles.glx" to resolve through the appropriate handler instead of directly through the OS filesystem.
In practice this is used for embedded resources, monolithic containers, web-backed content, and other custom storage schemes. Most application code uses higher-level APIs on top of it rather than talking to the VirtualFileSystem directly.
const kCharacter kCharacter;
const kDragDropEnter kDragDropEnter;
const kDragDropLeave kDragDropLeave;
const kDragDropReceive kDragDropReceive;
const kDragDropReceiveExternal kDragDropReceiveExternal;
const kDragDropTender kDragDropTender;
const kFocus kFocus;
const kKeyDown kKeyDown;
const kKeyUp kKeyUp;
const kLoseFocus kLoseFocus;
const kMouseDown kMouseDown;
const kMouseDrag kMouseDrag;
const kMouseEnter kMouseEnter;
const kMouseLeave kMouseLeave;
const kMouseUp kMouseUp;
const kMouseWheel kMouseWheel;
"Transaction" / kTransaction is a standard event used by interactive widgets to report value changes in a structured begin -> perform -> end sequence. It is emitted during continuous user interactions such as dragging, scrolling, typing, and other adjustments where you typically want live updates plus a single undo/commit at the end.
Widgets that emit transactions include RotarySlider, RangeBar, and TextArea.
The transaction stage is stored on the event and can be read using GLX::GetTransactionStage(e).
The following properties may be attached to the event:
index and value are optional; some widgets emit only the stage (and modifiers) and expect the receiver to query widget state directly.
Typical handling is to bind to GLX::kTransaction, switch on the stage, apply live updates on kTransactionStagePerform, and push a single undo/commit on kTransactionStageEnd.
GLX::BindEvent(widget, GLX::kTransaction, [](GLX::Object & src, GLX::Event & e)
{
auto stage = GLX::GetTransactionStage(e);
switch (stage)
{
case GLX::kTransactionStageBegin:
{
//optional: capture initial state for undo / preview
break;
}
case GLX::kTransactionStagePerform:
{
//live update (engine/app state)
//optional: use GLX::GetIndex(e) / GLX::GetValue(e) if provided
break;
}
case GLX::kTransactionStageEnd:
{
//commit once (e.g. add undo step)
break;
}
case GLX::kTransactionStageCancel:
{
//optional: revert to initial state
break;
}
default: break;
}
return true;
});
Transactions are typically emitted via the helper EmitTransaction
const kTransaction kTransaction;
kFlowX
kFlowY
kFlowInvert
kFlowCenter
kOrientationNear
kOrientationCenter
kOrientationFar
kOrientationFit
kAlignmentTopLeft
kAlignmentTop
kAlignmentTopRight
kAlignmentLeft
kAlignmentCenter
kAlignmentRight
kAlignmentBottomLeft
kAlignmentBottom
kAlignmentBottomRight
kClickFlagRmb
kClickFlagDbl
kLinear
kEaseIn2x
kEaseIn3x
kEaseOut2x
kEaseOut3x
kEaseInOutCos
kEaseInOut2x
kTransactionStageNone
kTransactionStageBegin
kTransactionStagePerform
kTransactionStageEnd
kTransactionStageCancel
kSelectionModeSingle
kSelectionModeMulti
kSelectionModeMultiToggle
using Colour = System::Colour;
using ColourPoints = Array < Tuple < Point <Float32> ,System::Colour> >;
using KeyCode = System::KeyCode;
Convenience alias of System::ModifierKeys. Use GLX::kModifierKeyPrimary to identify Ctrl (Windows) or Cmd (macOS).
if (GLX::GetModifierKeys(e) & GLX::kModifierKeyShift)
{
//handle shift
}
using ModifierKeys = System::ModifierKeys;
using MouseCursor = System::MouseCursor;
using Point = System::fPoint;
using Points = Array < Point <Float32> >;
using Rect = System::fRect;
using Size = System::fSize;
void Activate(Object& object, bool state);
void ActivateBranch(Object& object, bool state);
TRef <Object> AddAbsolute(Object& parent, Object& child);
TRef <Object> AddAbsolute(Object& parent, Object& child, Point position);
Places the child at a fixed position within the parent's content rect, without using the layout system.
This function should generally not be used in application code, typical use-cases are for building complex widgets (such as ViewPort).
The real power of the layout system comes from using inline and float modes, which provide dynamic, responsive layouts. AddAbsolute bypasses those mechanisms and should be considered a low-level escape hatch.
void AddDottedLine(Points& points, Point from, Point to, Size pixel_size);
void AddEllipseFill(Points& points, const Rect& rect, Float32 start, Float32 sweep);
void AddEllipseOutline(Points& points, const Rect& rect, Size width, Float32 start, Float32 sweep);
TRef <Object> AddFloat(Object& parent, Object& child, Orientation x_position, Orientation y_position);
TRef <Object> AddFloat(Object& parent, Object& child, Alignment alignment);
Adds child to parent with float positioning, independent of the inline flow. The childs is positioned either by specifying Orientation values for both axes, or by using a single Alignment value as shorthand.
In the shorthand form, the alignment parameters selects one of nine standard positions (top-left, top etc...)
In the full form, Orientation gives fine-grained control along X and Y:
Common patterns include:
AddFloat does not consume inline space - it layers the child over the parent's content area. Combine with margin and parent padding for refined placement.
TRef <Object> AddInline(Object& parent, Object& child, Orientation ortho_position);
Adds child to parent with inline positioning (horizontal or vertical depending on parent layout). The child is positioned on the parents ortho-axis according to the orientation paremeter.
TRef <Object> AddInlineFlex(Object& parent, Object& child, Orientation ortho_position);
Adds child to parent with inline positioning, similar to AddInline, but the child expands to fill available space along the main axis. When multiple flex children are present, the available space is distributed evenly among them.
void AddPath(Points& points, const ArrayView < Point <Float32> >& path, bool& closed);
void AddPointsWithColour(ColourPoints& colour_points, const ArrayView < Point <Float32> >& colour, const Colour& input);
void AddPolygonFill(Points& points, const ArrayView < Point <Float32> >& input);
void AddRectFill(Points& points, const Rect& rect);
void AddRectFill(ColourPoints& colour_points, const Colour& colour, const Rect& rect);
void AddRectOutline(Points& points, const Rect& rect, const Margin& width, Size pixel_size);
void AddRectOutline(ColourPoints& colour_points, const Colour& colour, const Rect& rect, const Margin& width, Size pixel_size);
void AddRoundedFill(Points& points, const Rect& rect, const Size* corners[4], Float32 corner_step);
void AddRoundedFill(Points& points, const Rect& rect, const Margin& corners, Float32 corner_step);
void AddRoundedOutline(Points& points, const Rect& rect, const Margin& width, const Size* corners[4], Float32 corner_step);
void AddRoundedOutline(Points& points, const Rect& rect, const Margin& width, const Margin& corners, Float32 corner_step);
void AddRoundedTriangleFill(Points& points, const Rect& rect, Float32 corner, Alignment direction, Size pixel_size);
void AddRoundedTriangleOutline(Points& points, const Rect& rect, Float32 width, Float32 corner, Alignment direction, Alignment pixel_size);
TRef <Object> AddStretch(Object& parent, Object& child);
Shorthand for AddFloat with both axes set to kOrientationFit.
Adds the child stretched to fill the entire parent's content area. Useful for overlays, background layers, or full-size panels where the child should always match the parent's size.
void AddTriangleFill(Points& points, const Rect& rect, Alignment direction);
void AddTriangleOutline(Points& points, const Rect& rect, Float32 width, Alignment direction, Size pixel_size);
void AttachAnimationClock(Object& object, Key32 id, const Function <void(Float32)>& callback);
void AttachPeriodicClock(Object& object, Key32 id, Float32 delay, Float32 interval, const Function <void()>& callback);
TRef <Object> BindClick(Object& object, const Function <void()>& callback);
void BindEvent(Object& object, Key32 event_id, const Function <bool(Object&, Event&)>& callback);
Attaches a delegate to the specified object which filters and forwards only events matching the given event-id to the supplied callback.
BindEvent replaces any previous delegate with the same event_id on this object.
The callback should return true if the event is fully handled, to stop propagation, or false to allow further delegates to receive it.
The following example shows how to intercept and trap mouse clicks. (BindClick is a shorthand for this).
GLX::BindEvent(button, GLX::Button::kMouseDown, [](GLX::Object & src, GLX::Event & e)
{
Log("Button clicked!");
return true; // stop propagation
});
void BindEventVoid(Object& object, Key32 event_id, const Function <void()>& callback);
Convenience version of BindEvent for simple handlers.
Invokes the supplied callback when the specified event-id occurs, and always traps the event.
The callback receives no parameters and does not see the source object or event payload.
bool BranchContains(const Object& parent, const Object& child);
Pair < Point <Float32> , Size <Float32> > CalculateAbs(const Object& object);
Pair < Point <Float32> , Size <Float32> > CalculateAbs(const Object& parent, const Object& object);
Rect CalculateAbsoluteRect(const Object& object);
Rect CalculateRelativeRect(const Object& parent, const Object& object);
void CancelDragDrop();
void ClearText(Object& object, Key32 id_opt);
void CloseContextMenu();
TRef <Object> CreateAnimationClock(const Function <void(Float32)>& callback);
TRef <Animation> CreateCallbackAnimation(const Function <void(Object&)>& callback);
TRef <InterpolatedAnimation> CreateColourPropertyAnimation(Key32 property_id, const Colour& from, const Colour& to);
TRef <Object> CreateDragDropBeginListener(const Function <void(Object&)>& callback);
TRef <Object> CreateDragDropEndListener(const Function <void()>& callback);
TRef <Object> CreateDragDropTargetListener(const Function <void(Object&)>& callback);
TRef <InterpolatedAnimation> CreateFloatPropertyAnimation(Key32 property_id, Float32 from, Float32 to);
TRef <InterpolatedAnimation> CreateInterpolatedAnimation(const Function <void(Object&, Float32)>& callback);
TRef <Animation> CreateLogarithmicAnimation(Float32 from, Float32 to, const Function <void(Object&, Float32)>& callback, Float32 decay_factor);
TRef <InterpolatedAnimation> CreateMarginPropertyAnimation(Key32 property_id, const Margin& from, const Margin& to);
TRef <Animation> CreateMaxBoundsAnimation(Key32 bounds_id, bool yaxis, Float32 from, Float32 to);
TRef <InterpolatedAnimation> CreateOpacityAnimation(Object& target, Key32 id, Float32 from, Float32 to);
TRef <Object> CreatePeriodicClock(Float32 delay, Float32 interval, const Function <void()>& callback);
TRef <InterpolatedAnimation> CreatePointPropertyAnimation(Key32 property_id, Point from, Point to);
TRef <InterpolatedAnimation> CreatePositionAnimation(bool y, Float32 from, Float32 to);
TRef <InterpolatedAnimation> CreateSizePropertyAnimation(Key32 property_id, Size from, Size to);
TRef <Animation> CreateStateAnimation(Key32 state);
TRef <InterpolatedAnimation> CreateWaitAnimation();
void DetachClock(Object& object, Key32 id);
bool Emit(Object& src, Key32 id, VARGS... id_value_pairs);
Posts a custom event upward from the specified object. This is the recommended helper for emitting events without manually constructing an Event object.
Emit(object, "my_event", "value", 1.0f);
The call above is equivalent to:
auto e = Make<Event>("my_event");
Data::SetFloat(e, "value", 1.0f);
object.Emit(e);
This helper is intended for simple transactional or notification-style events. For advanced cases (preconfigured payloads or reuse), construct and emit an Event explicitly.
void EnableAutoFit(Object& object, bool x, bool y);
void EnableMouse(Object& object, bool enable, bool intercept);
Controls how an object participates in mouse hit-testing.
//obj receives mouse events, if a child doesnt have priorty (default behaviour)
GLX::EnableMouse(obj, true);
//obj receives all mouse events, stealing from children
GLX::EnableMouse(obj, true, true);
//obj is excluded from hit-testing, but its children can still receive mouse events normally
GLX::EnableMouse(obj, false)
//the object and its entire subtree are invisible to the mouse
GLX::EnableMouse(obj, false, true)
void EnableMouseCapture(Object& object, bool enable, bool incremental);
void Enter(Object& object, UInt8 flags);
bool ExceedsDragThreshold(Point drag, Float32 sens);
void Exit(Object& object, bool detach, UInt8 or_flags_opt);
ConstTRef <Style> FindStyle(const Object& object, Key32 id);
ConstTRef <Style> FindStyle(const Style& style, Key32 id);
Finds a style by path relative to 'start'.
Resolution order:
Returns null if no match is found.
void FocusBranch(Object& branch_root);
const Pair < Size <Float32> , Size <Float32> >& GetBounds(const Object& object, Key32 id);
UInt8 GetClickFlags(const Event& e);
Pair <bool,bool> GetClip(const Object& object, Key32 id);
TRef <Object> GetDragDropData(Event& e);
WChar GetKeyCharacter(const Event& e);
KeyCode GetKeyCode(const Event& e);
UInt8 GetModifierKeys(const Event& e);
Point GetMousePosition(const Object& object);
Float32 GetOpacity(const Object& object, Key32 id);
WString::View GetText(const Object& object, Key32 id_opt);
bool IsActive(const Object& object);
bool IsDoubleClick(const Event& e);
bool IsLeftClick(const Event& e);
bool IsRightClick(const Event& e);
bool IsSelected(const Object& object);
Idx LookupBranchIndex(const Object& parent, const Object& child);
TRef <Object> LookupChildAtIndex(Object& parent, UInt32 idx);
Idx LookupIndex(const Object& child);
Reference <Menu> OpenContextMenu(Object& src, Key32 context_opt, Key32 style_opt);
Attaches a Menu widget to the window foreground.
See Menu for more details.
const Event* QueryAntecedent(const Event& e, Key32 id, const Event* fallback);
Object* QueryChildById(Object& parent, Key32 id, Object* fallback);
Searches 'object' for the first direct child (i.e. no recursion) with the given 'id'.
TYPE* QueryDragDropData(Event& e);
Object* QueryElementById(Object& object, Key32 id, Object* fallback);
Searches 'object' recursively for the first child with the given 'id'.
Colour RGB(UInt8 grey);
Colour RGB(UInt8 grey, UInt8 alpha);
Colour RGB(UInt8 red, UInt8 green, UInt8 blue);
Colour RGB(UInt8 red, UInt8 green, UInt8 blue, UInt8 alpha);
void RedirectFocus(Object& branch_root, Object& object);
void Rescale(const ArrayRegion < Point <Float32> >& points, Size scale);
void Rescale(const ArrayRegion < Tuple < Point <Float32> ,System::Colour> >& colour_points, Size scale);
ConstTRef <StyleSheet> RetrieveStyleSheet(const WString::View& path, const Data::PropertySet& options_opt);
void Rotate(const ArrayRegion < Point <Float32> >& points, Point origin, Float32 angle_normalised);
void Rotate(const ArrayRegion < Tuple < Point <Float32> ,System::Colour> >& colour_points, Point origin, Float32 angle_normalised);
void Run(Object& target, Key32 id, TRef <Animation> animation);
void Run(Object& target, Key32 id, Float32 time, TRef <Animation> animation);
void Run(Object& target, Key32 id, Float32 time, InterpolatedAnimation::Easing easing, TRef <InterpolatedAnimation> animation);
Point ScaleDelta(const Object& object, Point window_coordinates_delta);
void Select(Object& object, bool select);
Sets or clears the GLX::kSelectedState ("selected") on the target object
void SelectBranch(Object& object, bool select);
void SelectChildren(Object& object, bool select);
bool Send(Object& src, Key32 id, VARGS... id_value_pairs);
Sends a custom event directly to the specified object without propagating it up the object hierarchy.
Send(object, "my_event", "value", 1.0f);
The call above is equivalent to:
auto e = Make<Event>("my_event");
Data::SetFloat(e, "value", 1.0f);
object.ProcessEvent(e);
This helper is intended for simple transactional or notification-style events. For advanced cases (preconfigured payloads or reuse), construct an Event explicitly.
void SetBounds(Object& object, Key32 id, const Size& min, const Size& max);
void SetCanvas(Object& object, Key32 id, const Function <void(GLX::CanvasContext&)>& callback);
void SetClip(Object& object, Key32 id, bool x, bool y);
void SetColourCanvas(Object& object, Key32 id, const Function <void(GLX::ColourCanvasContext&)>& callback);
void SetEventDelegate(Object& object, Key32 delegate_id, const Function <bool(Object&, Event&)>& callback);
Attaches a delegate to the specified object which forwards all events to the supplied callback.
SetEventDelegate replaces any previous delegate with the same delegate_id on this object.
The callback should return true if the event is fully handled, to stop propagation. Return false to allow further delegates to receive it.
The following example shows how to intercept and trap mouse clicks. (BindClick is a shorthand for this).
GLX::SetEventDelegate(button, "my_delegate", [](GLX::Object & src, GLX::Event & e)
{
if (e.id == GLX::kMouseDown)
{
Log("Button clicked!");
return true; // stop propagation of mouse down
}
return false; //allow propogation of other events
});
void SetFlow(Object& object, FlowFlags flags);
void SetGraphicCanvas(Object& object, Key32 id, const Function <void(GLX::GraphicCanvasContext&)>& callback);
void SetOnStyle(Object& object, const Function <void(const Style &)>& callback, Key32 delegate_id_opt);
Attaches a delegate which invokes the supplied callback whenever the object's style is changed.
This is a callback-based alternative to overriding the GLX::Object::OnSetStyle method, which can be used to apply sub-styles to child elements when not sub-classing GLX::Object.
auto container = New<GLX::Object>();
auto button = GLX::AddFloat(container, New<GLX::Button>("Click Me"));
GLX::SetOnStyle(container, [button](const GLX::Style & style)
{
button->SetStyle(style["button"]);
});
void SetOpacity(Object& object, Key32 id, Float32 opacity);
void SetState(Object& object, Key32 state, bool value);
void SetText(Object& object, const WString& value, Key32 id_opt);
void StartDragDrop(TRef <Object> data, MouseCursor dragover, MouseCursor block);
void Stop(Object& target, Key32 id);
bool ToggleState(Object& object, Key32 state);
Point TransformPosition(const Object& object, Point window_coordinates_position);
void Translate(const ArrayRegion < Point <Float32> >& points, Point offset);
void Translate(const ArrayRegion < Tuple < Point <Float32> ,System::Colour> >& colour_points, Point offset);
void UnbindEvent(Object& object, Key32 event_id);
void UnsetBounds(Object& object, Key32 id);
void UnsetCanvas(Object& object, Key32 id);
void UnsetClip(Object& object, Key32 id);
void UnsetOpacity(Object& object, Key32 id);
Size Margin::far;
Size Margin::near;
Float32 Range::length;
Float32 Range::start;
Inherits from Object
AbstractList is the shared selection and navigation base for list-like widgets.
It handles click, double-click, drag-start, keyboard navigation, Ctrl+A / Ctrl+D selection helpers, and Delete / Backspace remove requests, while derived classes provide item storage and visual updates.
The key events to know are ListSelect, ListLoad, ListStartDrag, and ListRequestRemove. Selection mode can be single, multi, or toggle.
void AbstractList::SetSelectionMode(AbstractList::SelectionMode mode);
UInt32 AbstractList::GetNumItem();
void AbstractList::SelectAll();
void AbstractList::SelectNone();
bool AbstractList::Select(UInt32 idx, bool multi);
void AbstractList::Deselect(UInt32 idx);
bool AbstractList::SelectNext(bool extend);
bool AbstractList::SelectPrev(bool extend);
void AbstractList::EnumerateSelection(UInt32 start, UInt32 range, const Function <void(UInt idx, UInt n)>& callback);
void AbstractList::Reveal(UInt32 idx);
Inherits from Object
Inherits from Object
void AbstractViewPort::SetContent(TRef <Object> content, Key32 style_id_opt);
TRef <Object> AbstractViewPort::GetContent();
ConstTRef <Object> AbstractViewPort::GetContent();
void AbstractViewPort::InvertScrollAxis(bool invert);
TRef <Object> AbstractViewPort::CreateListener(const Function <void()>& callback);
void AbstractViewPort::SetMinView(Size size);
Size AbstractViewPort::GetMinView();
Size AbstractViewPort::GetExtent();
void AbstractViewPort::SetView(const Rect& view);
const Rect& AbstractViewPort::GetView();
Size AbstractViewPort::GetPixelsPerUnit();
void AbstractViewPort::StartScroll(bool yaxis, Float32 offset);
void AbstractViewPort::StopScroll(bool yaxis);
void AbstractViewPort::Reveal(bool yaxis, Float32 offset, Float32 range, Float32 padding, bool animate);
void AbstractViewPort::EnableAutoScroll(Float32 amount, bool scoped);
void AbstractViewPort::DisableAutoScroll();
ConstTRef <Object> AbstractViewPort::GetBody();
TRef <AbstractViewBar> AbstractViewPort::GetViewBar(bool yaxis);
Inherits from Object
GLX animation covers two closely related systems:
In many cases the simplest animation is declarative. A stylesheet can define @State variants and a transition time, and code only needs to push or clear the relevant state.
For hover, selected, inactive, and similar UI feedback, prefer stylesheet-driven transitions first.
Button:
{
transition: 0.25;
@State hover:
{
bg: Fill(colour: 228);
};
}
This keeps simple UI motion in the style layer rather than in imperative code.
When code needs to decide target values dynamically, create an animation object and run it against a property or state on a target object.
auto fade = GLX::CreateColourPropertyAnimation("colour", GLX::kWhite, GLX::kBlack);
GLX::Run(object, "colour", 0.25f, fade);
Common helpers in this module include CreateStateAnimation, CreateColourPropertyAnimation, CreateMarginPropertyAnimation, and CreateCallbackAnimation.
Use clocks when you need procedural updates over time rather than interpolation between two values.
This is commonly used for polling async task status, updating drag visuals, driving custom canvas effects, or other time-based UI behavior that is not well described as a simple property tween.
void Animation::SetTime(Float32 time);
void Animation::SetTarget(Object& target);
void Animation::Play();
Inherits from Animation
void ContainerAnimation::Clear();
void ContainerAnimation::Add(Animation& animation);
Inherits from Data::PropertySet
Key32 Event::id;
TRef <Event> Event::Clone();
Inherits from Object
Simple non-interactive container widget composed of two sub-objects: header and body.
The header is laid out inline, followed by the body with inline-flex. The default layout direction is vertical (GLX::kFlowY).
Applies the header style block to the header object and the body style block to the body object.
FormExample:
{
size: 200,300;
header:
{
size: 32;
bg_colour: 255,0,0;
};
body:
{
padding: 8;
bg_colour: 0,255,0;
};
}
const TRef <Object> Form::body;
const TRef <Label> Form::header;
Inherits from Animation
Inherits from Object
Label is the lightweight text-holding widget.
It stores a Text property under a chosen property id, so it can be used both for ordinary labels and for small value displays embedded inside larger widgets.
Inherits from AbstractList
List is the concrete child-backed implementation of AbstractList.
Selection is reflected directly through each child item's selected state, and it also adds optional drag-reordering through the ListReorder transaction event sequence.
Use it when you already have concrete child objects for each row and want the list to manage selection, reveal, focus, and reorder interaction.
Inherits from Scroller
The Menu widget provides a standard floating popup menu, typically used for context menus and drop-down menus.
It is built on top of Scroller, so it supports a single scrollable content region and automatically shows scrollbars when needed.
Menus are typically not opened directly. For most cases, use OpenContextMenu which attaches the menu to the window foreground (always-on-top) and triggers a MenuOpen event to allow menu population by the target object and its parents.
Alternatively, use the Popup widget for simple option-enumeration widgets.
auto menu = GLX::OpenContextMenu(*this);
GLX::BindClick(menu->AddItem(L"Option 1"), []()
{
//handle option
});
menu->AddSeparator();
auto sub = menu->AddSubMenu(L"More");
GLX::BindClick(sub->AddItem(L"Option 2"), [](){});
Menu inherits Scroller, so implements the same style-schema (body, content, x, y). Additionally, Menu applies item styles by applying these ids:
style
menu:
{
size: 256,0; //min width
bg: Shadow(width: 16; indent: -8; color: 0,32),Fill(corner: 4);
content:
{
padding: 4;
};
item:
{
bg: Text(font: MyFont; value: &value; indent: 8,4; color: 0);
};
//y: see Scroller
};
Typically use OpenContextMenu to create and attach a menu instance.
When the menu is attached, the menu emits Menu::kMenuOpen on src, with the created menu stored as an event property. This allows src and its parents to intercept the open and add items.
To fully support context-menus created on child objects, populate the menu in a seperate handler from creation, e.g:
bool SubView::OnEvent(GLX::Object & src, GLX::Event & e)
{
if (e.id == GLX::kMouseDown && (GLX::GetClickFlags(e) & GLX::kClickFlagRmb))
{
GLX::OpenContextMenu(*this);
return true;
}
else if (auto menu = GLX::GetMenu(e)) //e.id == Menu::kMenuOpen, reads "menu" property
{
GLX::BindClick(menu->AddItem(L"Option 1"), []()
{
//handle option
});
//return true; //optional: trap to prevent parents adding items
}
return GLX::Object::OnEvent(src, e);
}
If you don’t need to support interception of menus from child objects, you can open and populate together:
bool SubView::OnEvent(GLX::Object & src, GLX::Event & e)
{
if (GLX::IsRightClick(e)) //helper
{
auto menu = GLX::OpenContextMenu(*this);
GLX::BindClick(menu->AddItem(L"Option 1"), []()
{
//handle option
});
return true;
}
return GLX::Object::OnEvent(src, e);
}
Binding a click handler on every item (BindClick) is often convenient, but for some cases you may prefer a unified handler.
Menu emits Menu::kMenuSelect whenever an item is selected, allowing you to handle all selections in one place. This is useful for large menus, or for simple option/enumeration menus where you only need the selected index.
In this case, use the GetIndex and GetItem helpers to read the index and source item properties from the event:
GLX::BindEvent(menu, GLX::kMenuSelect, [](GLX::Object & src, GLX::Event & e)
{
UInt idx = GLX::GetIndex(e);
auto item = GLX::GetItem(e);
//handle selection (idx / item)
return true;
});
void Menu::Clear();
TRef <Object> Menu::AddItem(const WString::View& label);
TRef <Object> Menu::AddItem(TRef <Object> item);
TRef <Object> Menu::AddSeparator();
TRef <Object> Menu::AddSeparator(TRef <Object> item);
TRef <Object> Menu::AddSubMenu(const WString::View& label);
TRef <Object> Menu::AddSubMenu(TRef <Object> item);
TRef <Object> Menu::GetParentItem();
bool Menu::OpenSubMenu(Object& item);
Inherits from ContainerAnimation
Inherits from Data::PropertySet
void Object::SetParent(Object& child);
void Object::Clear();
void Object::InsertBefore(Object& child);
void Object::InsertAfter(Object& child);
void Object::Detach();
void Object::SendBottom();
void Object::SendTop();
void Object::SetMouseCursor(MouseCursor mousecursor);
MouseCursor Object::GetMouseCursor();
void Object::SetStyle(const Style& style);
ConstTRef <Style> Object::GetStyle();
void Object::ClearState(Key32 state);
void Object::SetState(Key32 state);
bool Object::CheckState(Key32 state);
void Object::Focus();
bool Object::ProcessEvent(Object& src, Event& e);
bool Object::Emit(Event& e);
void Object::Accommodate();
void Object::Realign();
void Object::Update();
void Object::OnAttachWindow();
void Object::OnDetachWindow();
void Object::OnClock(Float32 delta);
void Object::OnUpdate();
void Object::OnSetStyle(const Style& style);
bool Object::OnEvent(Object& src, Event& e);
Inherits from ContainerAnimation
void PlayList::EnableLoop(bool enable);
Inherits from Object
The Popup widget is a lightweight “click-to-open” menu control. When clicked, it opens a floating Menu anchored to the Popup’s screen position (typically displayed underneath the widget if there is space, otherwise positioned to fit).
Popup is built around Menu: it opens a Menu instance and reuses Menu’s item model, styling, and selection events.
See the GLX::Menu documentation for details on populating a menu (items, sub-menus etc) and handling selection events.
GLX::BindEvent(m_popup, GLX::Popup::kMenuOpen, [](GLX::Object & src, GLX::Event & e)
{
auto menu = GLX::GetMenu(e); //get handle to Menu created by the Popup
GLX::BindClick(menu->AddItem(L"Option 1"), []()
{
//handle option
});
return true;
});
When the Popup is clicked, it opens a Menu and then forwards the menu’s Menu::kMenuOpen event to the Popup itself (emitting upward). This allows the Popup, or any of its parents, to intercept the open and populate the menu.
Typical patterns:
Example: populate from a parent handler
bool ViewImpl::OnEvent(GLX::Object & src, GLX::Event & e)
{
if (src == m_popup && e.id == GLX::Popup::kMenuOpen)
{
auto menu = GLX::GetMenu(e);
menu->AddItem(L"Parent option");
return true; //typically trap to prevent ancestors adding items
}
return GLX::Object::OnEvent(src, e);
}
Popup itself is a widget (Object) that opens a Menu; menu styling is controlled via the Menu style schema. Define a menu sub-style that implements the Menu style schema.
Assuming you have a 'menu' defined previously in your stylesheet, a typical Popup style might look like this:
Popup:
{
color: 0;
bg: Border(),Text(font: FontID; value: &value; indent: 16,8); //&value set in code via GLX::SetText
@Alias menu; //re-use an existing 'menu' style
}
As selecting a menu item will typically lead to a state change, the typical pattern is to display the current value in OnUpdate, for example:
void ViewImpl::OnUpdate()
{
Key32 mode = app->GetMode(); //some state property
GLX::SetText(m_popup, GetModeString(mode)); //GetModeString is some helper to stringify enum values
}
Inherits from AbstractViewBar
Emits a "Transaction" event, see GLX::kTransaction.
Inherits from Object
RotarySlider is the base numeric drag control used by knob-like widgets such as DragEdit.
It exposes krange and kvalue properties, emits transaction-style updates while dragging, supports keyboard stepping, and provides built-in reset behaviour via right click or double click when those actions are not otherwise handled.
void RotarySlider::SetSensitivity(Float32 pixels);
bool RotarySlider::SetRange(Float32 min, Float32 max, Float32 step);
Pair <Range,Float32> RotarySlider::GetRange();
void RotarySlider::SetDefault(Float32 value);
Float32 RotarySlider::GetDefault();
void RotarySlider::Reset();
bool RotarySlider::SetValue(Float32 value);
Float32 RotarySlider::GetValue();
Inherits from AbstractViewPort
The Scroller widget provides a scrollable container.
It hosts a single content object and automatically manages x and y scrollbars which appear dynamically when content exceeds the visible region.
Its often used with the List and VirtualList widgets for the content. It also forms the basis for the standard Menu implementation.
auto scroller = New<GLX::Scroller>();
auto content = New<GLX::Object>();
// Set content size (normally comes from content layout)
GLX::SetBounds(content, {}, { 512.0f, 1024.0f });
scroller->SetContent(content);
Once the content is assigned, ViewPort automatically calculates the visible region and shows the relevant scrollbars as needed.
Scroller applies the following sub-styles:
Example style block:
List:
{
clip: true; //alternatively you can set on body (for slightly different clipping behaviour)
content:
{
bg: Tile(stride: 32; axis: y; content: Line(position: bottom; colour: 128; pattern: dashed));
};
y:
{
size: 32;
bg:
Fill(colour: 0,32),
Bar(range: ⦥ region: ®ion; content: Border(colour: 0,64; corner: 4));
};
};
The 'Bar' layer is particularly useful for scrollbar styling.
By binding to 'range' and 'region' (published by the scrollbars), you can draw the visible "trackbar" area that represents the current view region within the total content.
By default, Scroller applies kFlowY to its own flow.
The flow direction determines how scrollbars are arranged.
The flow direction of the content (not the ViewPort itself) affects navigation behaviour (mouse wheel, keyboard PageUp/Down, Home/End).
Inherits from Object
Selector is a one-panel-at-a-time content switcher.
Panels are registered up front, then SelectPanel swaps the active content and emits a selection event with both the selected item and index. Depending on style, the content swap can be animated.
EnableContentAutoFit is useful when the selected panel should determine the container size from its real content rather than from style constraints alone.
void Selector::EnableContentAutoFit(bool enable);
void Selector::Clear();
void Selector::AddPanel(TRef <Object> item, Key32 style_id_opt);
void Selector::RemovePanel(UInt32 idx);
UInt32 Selector::GetNumPanel();
TRef <Object> Selector::GetPanel(UInt32 idx);
void Selector::SelectPanel(UInt32 idx);
Idx Selector::GetCurrentIndex();
Inherits from Object
Split is a thin GLX::Object wrapper around SplitBehaviour for resizable split layouts.
The public API stays intentionally small: you typically set, clear, or query the split size for child items and let the attached behaviour handle the interaction details.
Inherits from Data::PropertySet
const Key32 Style::id;
void Style::SetParent(Style& child);
void Style::Clear();
void Style::InsertBefore(Style& child);
void Style::InsertAfter(Style& child);
void Style::Detach();
void Style::Attach(Style& child);
Inherits from Style
const Key32 StyleSheet::path;
Inherits from Form
Container widget that extends GLX::Form to provide a tab-based layout.
TabGroup uses a GLX::Selector as its body and populates the form header with tab buttons. Selecting a tab activates the corresponding panel in the underlying selector.
Panels are added with 'AddPanel(label, content, style_id, tab_style_id)'. The 'style_id' is applied to the panel in the selector body, and the 'tab_style_id' is applied to the corresponding tab in the header.
By default, tabs use the header > tab style, and panels use the body > content style.
Clicking a tab selects the matching panel.
Keyboard navigation is also supported: Tab moves to the next tab and shift+Tab moves to the previous.
The underlying selector can animate panel changes by setting the body > animate property to true.
TabGroup does not define any custom event IDs of its own.
Selection is driven by the underlying GLX::Selector, and TabGroup forwards the selector's panel-selection notification.
To observe selection changes, either listen on the selector returned by `GetSelector()`, or handle `GLX::Selector::kSelectPanel` on the TabGroup itself.
Extends the Form style schema.
The header > align_content property controls how tabs are positioned in the header, via the standard orientation keys (near|far|center|fit)
TabGroup
{
header:
{
align_content: center;
};
};
In the simple case, all tabs use the same 'header > tab' style and all panels use 'body > content'.
TabGroup:
{
fg: InnerShadow(width: 0,3,0,0; colour: 0,32);
header:
{
align_content: center;
bg_colour: 128;
fg: Line(position: bottom; colour: 200);
tab:
{
transition: 0.25;
bg:
[
Text(indent: 8,4; font: Medium; value: &value; colour: kTextColour)
];
@State hover:
{
bg:
[
Fill(indent: 2,4; colour: 240,128; corner: 4),
Text(indent: 8,4; font: Medium; value: &value; colour: 0)
];
};
@State selected:
{
bg:
[
Fill(indent: 2,4; colour: 240; corner: 4),
Text(indent: 8,4; font: Medium; value: &value; colour: 0)
];
};
};
};
body:
{
animate: true; //GLX::Selector property for ease/in animation
content:
{
bg: Fill(colour: 252);
};
};
};
To style each tab/panel individually, define additional styles under header and pass their ids through the tab_style_id parameter of 'AddPanel(...)'.
tabs.AddPanel(L"Overview", overview_panel, "OverviewPanel", "OverviewTab");
tabs.AddPanel(L"Logs", logs_panel, "LogsPanel", "LogsTab");
tabs.AddPanel(L"Settings", settings_panel, "SettingsPanel", "SettingsTab");
TabGroup:
{
header:
{
align_content: fit;
@SVG TabIcons:
{
path: "icons/tabs.svg";
};
OverviewTab:
{
size: 36;
bg:
[
Image(source: TabIcons > overview; fit: contain; anchor: top; indent: 8,0; colour: 96),
Text(font: Medium; value: &value; anchor: bottom; colour: 96)
];
};
LogsTab:
{
size: 36;
bg:
[
Image(source: TabIcons > logs; fit: contain; anchor: left; indent: 8,0; colour: 96),
Text(indent: 30,4; font: Medium; value: &value; colour: 96)
];
};
SettingsTab:
{
size: 36;
bg:
[
Image(source: TabIcons > settings; fit: contain; anchor: left; indent: 8,0; colour: 96),
Text(indent: 30,4; font: Medium; value: &value; colour: 96)
];
};
};
body:
{
animate: true;
OverviewPanel:
{
bg_colour: 200;
};
};
};
void TabGroup::Clear();
TRef <Object> TabGroup::AddPanel(const WString::View& label, TRef <Object> content, Key32 style_id, Key32 tab_style_id);
void TabGroup::RemovePanel(UInt32 idx);
TRef <Selector> TabGroup::GetSelector();
ConstTRef <Selector> TabGroup::GetSelector();
Inherits from Scroller
TextArea is a scrollable wrapper around TextEditBehaviour.
It owns a content object with a Text property, installs the editing behaviour for that property, and exposes simple ClearText, SetText, and GetText helpers.
The constructor's multi_line flag changes both text flow and scrolling setup, so single-line and multi-line usage are configured differently from the start.
void TextArea::ClearText();
void TextArea::SetText(const WString& label);
WString::View TextArea::GetText();
Inherits from AbstractList
VirtualList is the large-data counterpart to List.
Instead of owning one child object per row, it materialises only the currently visible items and repopulates them through a callback as the viewport moves. Selection is tracked separately and re-applied when rows come back into view.
This makes it a better fit for long or dynamic datasets, but note that GetItem only returns objects that are currently visible.
void VirtualList::SetPopulateCallback(const Function <void(UInt start, ArrayRegion <Reference<GLX::Object>> items, const GLX::Style & style)>& callback);
void VirtualList::ClearItems();
void VirtualList::SetNumItem(UInt32 n, bool force_refresh);
void VirtualList::Rebuild();
TRef <Object> VirtualList::GetItem(UInt32 idx);
Inherits from Object
Inherits from AbstractViewPort
kBooleanFalse
kBooleanTrue
using BoolV4 = TypeV4 <Boolean>;
using FloatV4 = TypeV4 <Float32>;
using IntV4 = TypeV4 <Int32>;
FloatV4 Abs(const FloatV4& value);
IntV4 Abs(const IntV4& value);
BoolV4 And(const BoolV4& a, const BoolV4& b);
bool Any(const BoolV4& value);
FloatV4 ClipNormal(const FloatV4& value);
Int32 Count(const BoolV4& value);
bool Empty(const BoolV4& value);
FloatV4 Exp(const FloatV4& value);
FloatV4 Exp2(const FloatV4& value);
bool Full(const BoolV4& value);
Int32 GetFlags(const BoolV4& value);
UInt32 GetFree(const BoolV4& value);
FloatV4 Invert(const FloatV4& value);
FloatV4 Log(const FloatV4& value);
FloatV4 Log2(const FloatV4& value);
FloatV4 Max(const FloatV4& a, const FloatV4& b);
IntV4 Max(const IntV4& a, const IntV4& b);
FloatV4 Min(const FloatV4& a, const FloatV4& b);
IntV4 Min(const IntV4& a, const IntV4& b);
FloatV4 Modulo(const FloatV4& a, const FloatV4& b);
BoolV4 Not(const BoolV4& a);
BoolV4 Or(const BoolV4& a, const BoolV4& b);
FloatV4 Pow(const FloatV4& x, const FloatV4& y);
FloatV4 Reciprocal(const FloatV4& value);
FloatV4 RoundDown(const FloatV4& value);
FloatV4 RoundNearest(const FloatV4& value);
TypeV4 <TYPE> Select(const BoolV4& mask, const TypeV4 <TYPE>& true_value, const TypeV4 <TYPE>& false_value);
TypeV4 <TYPE> Select(const BoolV4& mask, const TypeV4 <TYPE>& true_value);
TYPE SelectNot(const BoolV4& a, const TYPE& b);
FloatV4 Sign(const FloatV4& value);
FloatV4 SquareRoot(const FloatV4& x);
BoolV4 operator!=(const FloatV4& a, const FloatV4& b);
BoolV4 operator!=(const IntV4& a, const IntV4& b);
BoolV4 operator!=(const BoolV4& a, const BoolV4& b);
IntV4 operator&(const IntV4& a, const IntV4& b);
FloatV4 operator*(const FloatV4& a, const FloatV4& b);
IntV4 operator*(const IntV4& a, const IntV4& b);
FloatV4 operator+(const FloatV4& a, const FloatV4& b);
IntV4 operator+(const IntV4& a, const IntV4& b);
FloatV4 operator-(const FloatV4& value);
FloatV4 operator-(const FloatV4& a, const FloatV4& b);
IntV4 operator-(const IntV4& a, const IntV4& b);
FloatV4 operator/(const FloatV4& a, const FloatV4& b);
BoolV4 operator<(const FloatV4& a, const FloatV4& b);
BoolV4 operator<(const IntV4& a, const IntV4& b);
BoolV4 operator<=(const FloatV4& a, const FloatV4& b);
BoolV4 operator==(const FloatV4& a, const FloatV4& b);
BoolV4 operator==(const IntV4& a, const IntV4& b);
BoolV4 operator==(const BoolV4& a, const BoolV4& b);
BoolV4 operator>(const FloatV4& a, const FloatV4& b);
BoolV4 operator>(const IntV4& a, const IntV4& b);
BoolV4 operator>=(const FloatV4& a, const FloatV4& b);
IntV4 operator|(const IntV4& a, const IntV4& b);
TYPE TypeV4::data;
TypeV4 <TYPE>& TypeV4::operator=(const TypeV4 <TYPE>& value);
TypeV4 <TYPE>& TypeV4::operator=(TYPE value);
void TypeV4::Set(TYPE value);
void TypeV4::Set(TYPE a, TYPE b, TYPE c, TYPE d);
TypeV4 <TYPE>& TypeV4::operator+=(const TypeV4 <TYPE>& value);
TypeV4 <TYPE>& TypeV4::operator-=(const TypeV4 <TYPE>& value);
TypeV4 <TYPE>& TypeV4::operator*=(const TypeV4 <TYPE>& value);
TypeV4 <TYPE>& TypeV4::operator/=(const TypeV4 <TYPE>& value);
TYPE& TypeV4::operator[](UInt32 idx);
const TYPE& TypeV4::operator[](UInt32 idx);
TYPE* TypeV4::GetData();
const TYPE* TypeV4::GetData();
TYPE TypeV4::ReadFirst();
kPathTemp
kPathDesktop
kPathApplicationData
kPathUserData
kPathUserDocuments
kResponseAborted
kResponseNoConnection
kResponseOK
kResponsePartialContent
kResponseMovedPermanently
kResponseFound
kResponseBadRequest
kResponseUnauthorized
kResponseForbidden
kResponseNotFound
kResponseInternalServerError
kResponseServiceUnavailable
kMouseCursorInvisible
kMouseCursorArrow
kMouseCursorWait
kMouseCursorMove
kMouseCursorLeftRight
kMouseCursorTopBottom
kMouseCursorTopLeftBottomRight
kMouseCursorBottomLeftTopRight
kMouseCursorPointer
kMouseCursorDrag
kMouseCursorText
kMouseCursorBlock
kMouseCursorZoom
kNumMouseCursor
kKeyCodeNull
kKeyCodeF1
kKeyCodeF12
kKeyCodeTab
kKeyCodeEnter
kKeyCodeEscape
kKeyCodeSpace
kKeyCodeBackspace
kKeyCodeInsert
kKeyCodeDelete
kKeyCodeHome
kKeyCodeEnd
kKeyCodePageUp
kKeyCodePageDown
kKeyCodeUp
kKeyCodeDown
kKeyCodeLeft
kKeyCodeRight
kKeyCodeNumericDivide
kKeyCodeNumericMultiply
kKeyCodeNumericMinus
kKeyCodeNumericPlus
kKeyCode1
kKeyCode0
kKeyCodeMinus
kKeyCodePlus
kKeyCodeSlash
kKeyCodeA
kKeyCodeZ
kKeyCodeBracketOpen
kKeyCodeBracketClose
kNumKeyCode
kModifierKeyShift
kModifierKeyCtrl
kModifierKeyAlt
kModifierKeySystem
using ColourPoint = Tuple < Point <Float32> ,Colour>;
using fPoint = Point <Float32>;
using fRect = Rect <Float32>;
using fSize = Size <Float32>;
bool Delete(const WString& path);
bool Exists(const WString& path);
Float64 GetElapsedTime();
UInt32 GetNumProcessor();
CString GetOperatingSystemVersion();
WString GetPath(Path path_id);
UInt64 GetSystemID();
UInt64 GetTime();
bool IsDirectory(const WString& path);
bool MakeDirectory(const WString& path);
bool Open(const WString& path);
bool Rename(const WString& from, const WString& to);
Float32 Colour::a;
Float32 Colour::b;
Float32 Colour::g;
Float32 Colour::r;
Inherits from Object
Inherits from Object
Inherits from Object
Inherits from Object
bool FileHandle::Status();
bool FileHandle::IsWriteable();
UInt64 FileHandle::GetSize();
void FileHandle::SetPosition(UInt64 position);
UInt64 FileHandle::GetPosition();
UInt32 FileHandle::Read(void* bytes, UInt32 buffer_capacity);
UInt32 FileHandle::Write(const void* bytes, UInt32 size);
Inherits from Object
void HttpConnection::SetTimeout(Float32 connection, Float32 transfer);
HttpConnection::Response HttpConnection::Request(const CString::View& method, const CString::View& resource, const ArrayView < Pair < Array <char> , Array <char> > >& headers, const ArrayView <UInt8>& body, const HttpConnection::ReceiveHeaderFn& receive_header, const HttpConnection::ReceiveDataFn& receive_data);
Inherits from Thread
Inherits from Object
Inherits from Object
Inherits from Object
Inherits from Object
Inherits from Task
Inherits from Object