13 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
- Override lifecycle methods from
GuiControl:BeforeControlTemplateUninstalled_()- cleanup before template removalAfterControlTemplateInstalled_(bool initialize)- setup after template installationOnParentLineChanged()- handle parent hierarchy changesOnActiveAlt()- handle ALT key activationIsTabAvailable()- control TAB navigation availability
- 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 BeforeControlTemplateUninstalled_() override;
void AfterControlTemplateInstalled_(bool initialize) override;
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)
BeforeControlTemplateUninstalled_(): Clear template-specific stateAfterControlTemplateInstalled_(bool initialize): Sync state to template- Call template methods:
TypedControlTemplateObject(true)->SetState(controlState)
- Call template methods:
- Event handlers: Mouse events (
leftButtonDown,leftButtonUp,mouseEnter,mouseLeave), keyboard events (keyDown,keyUp) - 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.
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
leftButtonDownif 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
- Parent handles lifecycle: Override
BeforeControlTemplateUninstalled_, etc. only if needed - 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 control to its template type with automatic casting- 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;
// Lifecycle overrides
void BeforeControlTemplateUninstalled_() override;
void AfterControlTemplateInstalled_(bool initialize) override;
// Event handlers
void OnMouseClick(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::OnMouseClick(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments)
{
SetMyState(!myState);
}
GuiMyControl::GuiMyControl(theme::ThemeName themeName)
: GuiControl(themeName)
{
StateChanged.SetAssociatedComposition(boundsComposition);
boundsComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiMyControl::OnMouseClick);
}
GuiMyControl::~GuiMyControl()
{
}
bool GuiMyControl::GetMyState()
{
return myState;
}
void GuiMyControl::SetMyState(bool value)
{
if (myState != value)
{
myState = value;
TypedControlTemplateObject(false)->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_GUIEVENT_FAST(MyState)
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