Files
GacUI/.github/KnowledgeBase/KB_GacUI_Design_AddingNewControl.md
2025-10-30 22:39:15 -07:00

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) 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
  • Override lifecycle methods from GuiControl:
    • BeforeControlTemplateUninstalled_() - cleanup before template removal
    • AfterControlTemplateInstalled_(bool initialize) - setup after template installation
    • OnParentLineChanged() - handle parent hierarchy changes
    • OnActiveAlt() - handle ALT key activation
    • IsTabAvailable() - 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::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 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)
  • Destructor: Cleanup (usually minimal, automatic cleanup happens)
  • BeforeControlTemplateUninstalled_(): Clear template-specific state
  • AfterControlTemplateInstalled_(bool initialize): Sync state to template
    • Call template methods: TypedControlTemplateObject(true)->SetState(controlState)
  • 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, or TypedControlTemplateObject(false) without check

Key patterns from GuiButton:

  • State machine pattern: controlState variable tracks visual state (Normal, Active, Pressed)
  • Event chaining: BeforeClickedClickedAfterClicked
  • 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.

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 leftButtonDown if 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

  • 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 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 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