Files
GacUI/.github/KnowledgeBase/KB_GacUI_Design_AddingNewControl.md
T

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) and Description<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 .cpp file:
    • BeforeControlTemplateUninstalled_() - cleanup before template removal
    • AfterControlTemplateInstalled_(bool initialize) - setup after template installation
    • Do not redeclare these underscore functions in the class: GUI_SPECIFY_CONTROL_TEMPLATE_TYPE declares them and generates the actual BeforeControlTemplateUninstalled() and AfterControlTemplateInstalled(bool) overrides
  • Override feature-specific virtual methods only when needed, such as OnParentLineChanged(), OnActiveAlt(), or IsTabAvailable()
  • 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::ThemeName parameter 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)
  • 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)
  • Event handlers: Mouse events (mouseDown, mouseUp, mouseEnter, mouseLeave), keyboard events (keyDown, keyUp). Filter GuiMouseEventArgs::button when 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, or TypedControlTemplateObject(false) without check

Key patterns from GuiButton:

  • State machine pattern: controlState variable 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) and autoFocus property
  • 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 declaration
  • Source/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 mouseDown if the parent already handles it
  • Attach to parent's events instead: Example - GuiSelectableButton attaches to GuiButton::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 of CLASS_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_TYPE declares BeforeControlTemplateUninstalled_() and AfterControlTemplateInstalled_(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 GuiButton with selection state
  • Attaches to parent's AfterClicked event to toggle selection
  • Adds new properties: GroupController, AutoSelection, Selected
  • Adds new template property: Selected in SelectableButtonTemplate
  • Template calls both: SetState() (from parent) and SetSelected() (new)

Template Property Access Pattern

Controls access template properties via:

  • TypedControlTemplateObject(true) - gets the typed template with existence check
  • TypedControlTemplateObject(false) - gets the typed template without check
  • Template properties have auto-generated Get/Set methods and Changed events

Control Template Macro System

The macro system provides:

  • GUI_TEMPLATE_CLASS_DECL: Generates class declaration with properties
  • GUI_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