16 KiB
Adding a New Control
Overview
Adding a new control to GacUI requires coordinated changes across multiple files to integrate the control into the class hierarchy, template system, theme management, reflection system, and XML compiler. This document provides a comprehensive guide covering control classes, templates, inheritance patterns, registration, and a minimal working example.
Control Class Definition
Header File Structure (Source/Controls/*.h)
The control class must:
- Inherit from base class:
GuiControl(or another control) andDescription<YourControl>for reflection - Specify template type: Use
GUI_SPECIFY_CONTROL_TEMPLATE_TYPE(TemplateName, BaseControlType)macro - Declare state variables: Member variables to track control state
- Implement the generated template hooks in the
.cppfile:BeforeControlTemplateUninstalled_()- cleanup before template removalAfterControlTemplateInstalled_(bool initialize)- setup after template installation- Do not redeclare these underscore functions in the class:
GUI_SPECIFY_CONTROL_TEMPLATE_TYPEdeclares them and generates the actualBeforeControlTemplateUninstalled()andAfterControlTemplateInstalled(bool)overrides
- Override feature-specific virtual methods only when needed, such as
OnParentLineChanged(),OnActiveAlt(), orIsTabAvailable() - Attach event handlers: In constructor to
boundsComposition->GetEventReceiver()for mouse/keyboard events - Define public events: Using
compositions::GuiNotifyEvent - Define properties: With getters/setters
- Accept theme parameter: Constructor takes
theme::ThemeNameparameter and passes to base class
Example pattern from GuiButton:
class GuiButton : public GuiControl, public Description<GuiButton>
{
GUI_SPECIFY_CONTROL_TEMPLATE_TYPE(ButtonTemplate, GuiControl)
protected:
ButtonState controlState;
void OnMouseDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments);
void UpdateControlState();
public:
GuiButton(theme::ThemeName themeName);
~GuiButton();
compositions::GuiNotifyEvent BeforeClicked;
compositions::GuiNotifyEvent Clicked;
compositions::GuiNotifyEvent AfterClicked;
bool GetAutoFocus();
void SetAutoFocus(bool value);
};
Control Implementation
Implementation File (Source/Controls/*.cpp)
Implement:
- Constructor: Initialize state, set up event handlers on
boundsComposition- Pattern:
GuiYourControl(theme::ThemeName themeName) : GuiControl(themeName) - Set up events:
eventName.SetAssociatedComposition(boundsComposition) - Attach handlers:
boundsComposition->GetEventReceiver()->eventName.AttachMethod(this, &GuiYourControl::Handler)
- Pattern:
- Destructor: Cleanup (usually minimal, automatic cleanup happens)
- Generated hook definition
BeforeControlTemplateUninstalled_(): Clear template-specific state; the definition may be empty - Generated hook definition
AfterControlTemplateInstalled_(bool initialize): Sync state to template; the definition may be empty when no synchronization is needed- Call template methods:
TypedControlTemplateObject(true)->SetState(controlState)
- Call template methods:
- Event handlers: Mouse events (
mouseDown,mouseUp,mouseEnter,mouseLeave), keyboard events (keyDown,keyUp). FilterGuiMouseEventArgs::buttonwhen a control should react to only one button. - Property getters/setters: Update state and notify template
- Template access: Use
TypedControlTemplateObject(true)to get typed template with existence check, orTypedControlTemplateObject(false)without check
Key patterns from GuiButton:
- State machine pattern:
controlStatevariable tracks visual state (Normal, Active, Pressed) - Event chaining:
BeforeClicked→Clicked→AfterClicked - Mouse tracking: Separate flags for
mousePressingDirect,mousePressingIndirect,mouseHoving - Keyboard support: SPACE and ENTER keys trigger click
- Focus integration:
SetFocusableComposition(boundsComposition)andautoFocusproperty - Template communication: Call template setters in
AfterControlTemplateInstalled_
Control Template System
Template Declaration (Source/Controls/Templates/GuiControlTemplates.h)
Three required entries:
1. Add to GUI_CONTROL_TEMPLATE_DECL macro:
F(GuiYourTemplate, GuiBaseTemplate)
2. Define template properties macro:
#define GuiYourTemplate_PROPERTIES(F)\
F(GuiYourTemplate, PropertyType, PropertyName, DefaultValue)
Properties are defined using the F macro with: template class name, property type, property name, default value.
3. Forward declaration and class declaration:
The macros GUI_TEMPLATE_CLASS_FORWARD_DECL and GUI_TEMPLATE_CLASS_DECL are applied to GUI_CONTROL_TEMPLATE_DECL to generate these automatically.
Template Implementation (Source/Controls/Templates/GuiControlTemplates.cpp)
The template implementation is auto-generated by:
GUI_CONTROL_TEMPLATE_DECL(GUI_TEMPLATE_CLASS_IMPL)
This macro expands to create:
- Property getter/setter implementations with change events
- Constructor that initializes event handlers
- Destructor that calls
FinalizeAggregation()
Theme Integration
Theme Name Registration (Source/Application/Controls/GuiThemeManager.h)
Add the control to GUI_CONTROL_TEMPLATE_TYPES macro:
#define GUI_CONTROL_TEMPLATE_TYPES(F) \
... existing entries ...\
F(YourTemplate, YourControl)
This generates the corresponding theme::ThemeName::YourControl enum value used in control constructors.
Refreshing Installed Templates
vl::presentation::controls::GuiApplication::RefreshThemes() (GacUI/Source/Application/Controls/GuiApplication.h/.cpp) refreshes every live registered window, including hidden popups. vl::presentation::controls::GuiControl::RefreshThemes() (GacUI/Source/Application/Controls/GuiBasicControls.h/.cpp) refreshes one control and its descendants. Both are reflected, synchronous, no-argument instance methods returning void; call them on the UI thread after changing the theme or palette used by template factories.
A control with no explicitly assigned ControlTemplate rebuilds its template from the current theme. An explicit factory preserves that control's own template, but its descendants are still visited. Refresh does not replace application-owned controls or their container compositions. Template replacement preserves its position among bounds-composition children, so retained overlays such as TUI column resize handles remain above the template for painting and hit testing. Template-owned objects can be destroyed, so application and child traversal use snapshots with GuiDisposedFlag, and focus is restored only to a surviving control. Do not retain template-object pointers across refresh.
Control authors must support repeated template installation: detach old template-specific handlers in BeforeControlTemplateUninstalled_(), then reapply the existing control state in AfterControlTemplateInstalled_(bool initialize). Use initialize for first-installation defaults rather than resetting models, edit history, selection or scrolling every time a theme changes. Preserve owner-provided factories when recreating realized item styles or column-header templates.
For example, after changing a TuiSkin palette, call GetApplication()->RefreshThemes(). If the change originates inside an input callback, queue palette installation and refresh together with InvokeInMainThread so the callback finishes before its template is replaced. Refreshing themes is available to GUI applications too. Explicit custom templates and values already captured by application-owned Color-eval elements do not automatically become reactive.
See the TUI palette and state-preservation details and the layout and skin guideline.
Reflection Registration
Three-Step Registration Process
Step 1: Add to type list (Source/Reflection/TypeDescriptors/GuiReflectionPlugin.h):
Add to GUIREFLECTIONCONTROLS_CLASS_TYPELIST macro:
F(presentation::controls::GuiYourControl)
Step 2: Register control class (Source/Reflection/TypeDescriptors/GuiReflectionControls.cpp):
BEGIN_CLASS_MEMBER(GuiYourControl)
CLASS_MEMBER_BASE(GuiBaseControl)
CONTROL_CONSTRUCTOR_CONTROLT_TEMPLATE(GuiYourControl)
CLASS_MEMBER_GUIEVENT(EventName)
CLASS_MEMBER_PROPERTY_FAST(PropertyName)
CLASS_MEMBER_PROPERTY_GUIEVENT_FAST(PropertyWithEvent)
CLASS_MEMBER_METHOD(MethodName, {L"param1" _ L"param2"})
END_CLASS_MEMBER(GuiYourControl)
Step 3: Template registration (Source/Reflection/TypeDescriptors/GuiReflectionTemplates.cpp):
Templates are auto-registered via the GUI_CONTROL_TEMPLATE macro expansion applied to all templates declared in GUI_CONTROL_TEMPLATE_DECL.
XML Compiler Integration
Instance Loader Registration (Source/Compiler/InstanceLoaders/GuiInstanceLoader_Plugin.cpp)
Add to loader registration in IGuiPlugin::Load():
For a normal control:
ADD_TEMPLATE_CONTROL(GuiYourControl, YourControl);
For a virtual control (uses another control's implementation with different theme):
ADD_VIRTUAL_CONTROL(VirtualName, GuiActualControl, ThemeName);
This uses GuiTemplateControlInstanceLoader<T> to register the control with the instance loader manager, making it available in GacUI XML files.
Header File Organization
Include Files
Add includes to:
Source/Controls/IncludeForward.h- forward declarationSource/Controls/IncludeAll.h- full header inclusion
This ensures proper compilation order and accessibility throughout the framework.
Creating a Control that Inherits from Another Control
When creating a control that inherits from another control (e.g., GuiSelectableButton inherits from GuiButton):
Class Definition Differences
- Inherit from parent control:
class GuiDerivedControl : public GuiParentControl, public Description<GuiDerivedControl> - Specify parent in template macro:
GUI_SPECIFY_CONTROL_TEMPLATE_TYPE(DerivedTemplate, GuiParentControl) - Do NOT re-attach parent event handlers: Don't re-attach
mouseDownif the parent already handles it - Attach to parent's events instead: Example -
GuiSelectableButtonattaches toGuiButton::AfterClicked
Constructor Pattern
- Call parent constructor:
GuiDerivedControl(theme::ThemeName themeName) : GuiParentControl(themeName) - Initialize additional events: On
boundsComposition(inherited from parent) - Attach handlers to parent's events: If extending behavior
Template Inheritance
- Template inherits from parent template: In
GuiControlTemplates.h, add:F(GuiDerivedTemplate, GuiParentTemplate) - Define additional properties: Separate
GuiDerivedTemplate_PROPERTIES(F)macro with new properties
Reflection Registration
- Use parent as base:
CLASS_MEMBER_BASE(GuiParentControl)instead ofCLASS_MEMBER_BASE(GuiControl) - Still use standard constructor macro:
CONTROL_CONSTRUCTOR_CONTROLT_TEMPLATE(GuiDerivedControl) - Only register new members: Properties/events/methods introduced by derived class
Minimal Changes Approach
- Define both generated hooks:
GUI_SPECIFY_CONTROL_TEMPLATE_TYPEdeclaresBeforeControlTemplateUninstalled_()andAfterControlTemplateInstalled_(bool), so provide definitions even when they are empty - Parent handles the actual lifecycle overrides: The macro-generated overrides invoke the derived hook and chain to the parent control in the required order
- Parent's event handlers inherited: No need to re-implement
- Focus on new functionality: Don't repeat parent's work
Example Pattern from GuiSelectableButton
- Extends
GuiButtonwith selection state - Attaches to parent's
AfterClickedevent to toggle selection - Adds new properties:
GroupController,AutoSelection,Selected - Adds new template property:
SelectedinSelectableButtonTemplate - Template calls both:
SetState()(from parent) andSetSelected()(new)
Template Property Access Pattern
Controls access template properties via:
TypedControlTemplateObject(true)- gets the typed template with existence checkTypedControlTemplateObject(false)- gets the typed template without check- Template properties have auto-generated
Get/Setmethods andChangedevents
Control Template Macro System
The macro system provides:
GUI_TEMPLATE_CLASS_DECL: Generates class declaration with propertiesGUI_TEMPLATE_CLASS_IMPL: Generates implementation (constructor, destructor, property accessors)GUI_SPECIFY_CONTROL_TEMPLATE_TYPE: Links a control to its template type, declares the two underscore hook functions, generates the lifecycle overrides, and provides typed template access- Property macros: Generate private field, getter, setter, and change event
Minimal Working Example
This example demonstrates the essential code structure for creating a custom control:
Step 1: Header File (Source/Controls/GuiMyControls.h)
class GuiMyControl : public GuiControl, public Description<GuiMyControl>
{
GUI_SPECIFY_CONTROL_TEMPLATE_TYPE(MyControlTemplate, GuiControl)
protected:
// State variables
bool myState = false;
// Event handlers
void OnMouseUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments);
public:
GuiMyControl(theme::ThemeName themeName);
~GuiMyControl();
// Events
compositions::GuiNotifyEvent StateChanged;
// Properties
bool GetMyState();
void SetMyState(bool value);
};
Step 2: Implementation File (Source/Controls/GuiMyControls.cpp)
void GuiMyControl::BeforeControlTemplateUninstalled_()
{
// Cleanup before template removal
}
void GuiMyControl::AfterControlTemplateInstalled_(bool initialize)
{
// Sync state to template
TypedControlTemplateObject(true)->SetMyState(myState);
}
void GuiMyControl::OnMouseUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments)
{
if (arguments.button != NativeMouseButton::Left) return;
SetMyState(!myState);
}
GuiMyControl::GuiMyControl(theme::ThemeName themeName)
: GuiControl(themeName)
{
StateChanged.SetAssociatedComposition(boundsComposition);
boundsComposition->GetEventReceiver()->mouseUp.AttachMethod(this, &GuiMyControl::OnMouseUp);
}
GuiMyControl::~GuiMyControl()
{
}
bool GuiMyControl::GetMyState()
{
return myState;
}
void GuiMyControl::SetMyState(bool value)
{
if (myState != value)
{
myState = value;
TypedControlTemplateObject(true)->SetMyState(myState);
StateChanged.Execute(compositions::GuiEventArgs(boundsComposition));
}
}
Step 3: Template Declaration (Source/Controls/Templates/GuiControlTemplates.h)
// Add to GUI_CONTROL_TEMPLATE_DECL macro:
F(GuiMyControlTemplate, GuiControlTemplate)
// Define template properties:
#define GuiMyControlTemplate_PROPERTIES(F)\
F(GuiMyControlTemplate, bool, MyState, false)
Step 4: Theme Registration (Source/Application/Controls/GuiThemeManager.h)
// Add to GUI_CONTROL_TEMPLATE_TYPES macro:
F(MyControlTemplate, MyControl)
Step 5: Reflection (Source/Reflection/TypeDescriptors/GuiReflectionControls.cpp)
BEGIN_CLASS_MEMBER(GuiMyControl)
CLASS_MEMBER_BASE(GuiControl)
CONTROL_CONSTRUCTOR_CONTROLT_TEMPLATE(GuiMyControl)
CLASS_MEMBER_GUIEVENT(StateChanged)
CLASS_MEMBER_PROPERTY_EVENT_FAST(MyState, StateChanged)
END_CLASS_MEMBER(GuiMyControl)
Step 6: XML Loader (Source/Compiler/InstanceLoaders/GuiInstanceLoader_Plugin.cpp)
// Add in IGuiPlugin::Load():
ADD_TEMPLATE_CONTROL(GuiMyControl, MyControl);
Core Pattern Summary
Each control has:
- A C++ class managing state and events
- A template defining visual properties
- Reflection for runtime access
- XML loader for declarative usage
- Theme integration for consistent styling