DVX_GUI/dvx/widgets/widgetClass.c

536 lines
18 KiB
C

// widgetClass.c -- Widget class vtable definitions
//
// This file implements a C vtable pattern for the widget type system.
// Each widget type has a static const WidgetClassT struct that defines
// its behavior -- paint, layout, mouse handling, keyboard handling, etc.
// A master table (widgetClassTable[]) maps WidgetTypeE enum values to
// their class definitions, enabling O(1) dispatch.
//
// Why a vtable approach instead of switch statements:
// 1. Adding a new widget type is purely additive -- define a new class
// struct and add one entry to the table. No existing switch
// statements need modification, reducing the risk of forgetting
// a case.
// 2. NULL function pointers indicate "no behavior" naturally. A box
// container has no paint function because the framework paints
// its children directly. A label has no onKey handler because
// it's not interactive. This is cleaner than empty case blocks.
// 3. The flags field combines multiple boolean properties into a
// single bitmask, avoiding per-type if-chains in hot paths
// like hit testing and layout.
//
// Class flags:
// WCLASS_FOCUSABLE -- widget can receive keyboard focus (Tab order)
// WCLASS_BOX_CONTAINER -- uses the generic box layout engine (VBox/HBox)
// WCLASS_HORIZ_CONTAINER -- lays out children horizontally (HBox variant)
// WCLASS_PAINTS_CHILDREN -- widget handles its own child painting (TabControl,
// TreeView, ScrollPane, Splitter). The default paint
// walker skips children for these widgets.
// WCLASS_NO_HIT_RECURSE -- hit testing stops at this widget instead of
// recursing into children. Used by widgets that
// manage their own internal click regions (TreeView,
// ScrollPane, ListView, Splitter).
#include "widgetInternal.h"
// ============================================================
// Per-type class definitions
// ============================================================
//
// Containers (VBox, HBox, RadioGroup, TabPage, StatusBar, Toolbar)
// typically have NULL for most function pointers because their
// behavior is handled generically by the box layout engine and
// the default child-walking paint logic.
//
// Leaf widgets (Button, Label, TextInput, etc.) override paint,
// calcMinSize, and usually onMouse/onKey for interactivity.
static const WidgetClassT sClassVBox = {
.flags = WCLASS_BOX_CONTAINER,
.paint = NULL,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassHBox = {
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
.paint = NULL,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassLabel = {
.flags = 0,
.paint = widgetLabelPaint,
.paintOverlay = NULL,
.calcMinSize = widgetLabelCalcMinSize,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = widgetLabelGetText,
.setText = widgetLabelSetText
};
static const WidgetClassT sClassButton = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetButtonPaint,
.paintOverlay = NULL,
.calcMinSize = widgetButtonCalcMinSize,
.layout = NULL,
.onMouse = widgetButtonOnMouse,
.onKey = widgetButtonOnKey,
.destroy = NULL,
.getText = widgetButtonGetText,
.setText = widgetButtonSetText
};
static const WidgetClassT sClassCheckbox = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetCheckboxPaint,
.paintOverlay = NULL,
.calcMinSize = widgetCheckboxCalcMinSize,
.layout = NULL,
.onMouse = widgetCheckboxOnMouse,
.onKey = widgetCheckboxOnKey,
.destroy = NULL,
.getText = widgetCheckboxGetText,
.setText = widgetCheckboxSetText
};
static const WidgetClassT sClassRadioGroup = {
.flags = WCLASS_BOX_CONTAINER,
.paint = NULL,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassRadio = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetRadioPaint,
.paintOverlay = NULL,
.calcMinSize = widgetRadioCalcMinSize,
.layout = NULL,
.onMouse = widgetRadioOnMouse,
.onKey = widgetRadioOnKey,
.destroy = NULL,
.getText = widgetRadioGetText,
.setText = widgetRadioSetText
};
// TextInput and TextArea have destroy functions because they dynamically
// allocate their text buffer and undo buffer. Most other widgets store
// all data inline in the WidgetT union and need no cleanup.
static const WidgetClassT sClassTextInput = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetTextInputPaint,
.paintOverlay = NULL,
.calcMinSize = widgetTextInputCalcMinSize,
.layout = NULL,
.onMouse = widgetTextInputOnMouse,
.onKey = widgetTextInputOnKey,
.destroy = widgetTextInputDestroy,
.getText = widgetTextInputGetText,
.setText = widgetTextInputSetText
};
static const WidgetClassT sClassTextArea = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetTextAreaPaint,
.paintOverlay = NULL,
.calcMinSize = widgetTextAreaCalcMinSize,
.layout = NULL,
.onMouse = widgetTextAreaOnMouse,
.onKey = widgetTextAreaOnKey,
.destroy = widgetTextAreaDestroy,
.getText = widgetTextAreaGetText,
.setText = widgetTextAreaSetText
};
static const WidgetClassT sClassListBox = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetListBoxPaint,
.paintOverlay = NULL,
.calcMinSize = widgetListBoxCalcMinSize,
.layout = NULL,
.onMouse = widgetListBoxOnMouse,
.onKey = widgetListBoxOnKey,
.destroy = widgetListBoxDestroy,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassSpacer = {
.flags = 0,
.paint = NULL,
.paintOverlay = NULL,
.calcMinSize = widgetSpacerCalcMinSize,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassSeparator = {
.flags = 0,
.paint = widgetSeparatorPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSeparatorCalcMinSize,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassFrame = {
.flags = WCLASS_BOX_CONTAINER,
.paint = widgetFramePaint,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
// Dropdown and ComboBox use paintOverlay to draw their popup lists
// on top of the entire widget tree. This is rendered in a separate
// pass after the main paint, so the popup can overlap sibling widgets.
static const WidgetClassT sClassDropdown = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetDropdownPaint,
.paintOverlay = widgetDropdownPaintPopup,
.calcMinSize = widgetDropdownCalcMinSize,
.layout = NULL,
.onMouse = widgetDropdownOnMouse,
.onKey = widgetDropdownOnKey,
.destroy = NULL,
.getText = widgetDropdownGetText,
.setText = NULL
};
static const WidgetClassT sClassComboBox = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetComboBoxPaint,
.paintOverlay = widgetComboBoxPaintPopup,
.calcMinSize = widgetComboBoxCalcMinSize,
.layout = NULL,
.onMouse = widgetComboBoxOnMouse,
.onKey = widgetComboBoxOnKey,
.destroy = widgetComboBoxDestroy,
.getText = widgetComboBoxGetText,
.setText = widgetComboBoxSetText
};
static const WidgetClassT sClassProgressBar = {
.flags = 0,
.paint = widgetProgressBarPaint,
.paintOverlay = NULL,
.calcMinSize = widgetProgressBarCalcMinSize,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassSlider = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetSliderPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSliderCalcMinSize,
.layout = NULL,
.onMouse = widgetSliderOnMouse,
.onKey = widgetSliderOnKey,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
// TabControl has both PAINTS_CHILDREN and a custom layout function
// because it needs to show only the active tab page's children and
// position them inside the tab content area (below the tab strip).
static const WidgetClassT sClassTabControl = {
.flags = WCLASS_FOCUSABLE | WCLASS_PAINTS_CHILDREN,
.paint = widgetTabControlPaint,
.paintOverlay = NULL,
.calcMinSize = widgetTabControlCalcMinSize,
.layout = widgetTabControlLayout,
.onMouse = widgetTabControlOnMouse,
.onKey = widgetTabControlOnKey,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassTabPage = {
.flags = WCLASS_BOX_CONTAINER,
.paint = NULL,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassStatusBar = {
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
.paint = widgetStatusBarPaint,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassToolbar = {
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
.paint = widgetToolbarPaint,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
// TreeView uses all three special flags:
// PAINTS_CHILDREN -- it renders tree items itself (with indentation,
// expand/collapse buttons, and selection highlighting)
// NO_HIT_RECURSE -- mouse clicks go to the TreeView widget, which
// figures out which tree item was clicked based on scroll position
// and item Y coordinates, rather than letting the hit tester
// recurse into child TreeItem widgets
static const WidgetClassT sClassTreeView = {
.flags = WCLASS_FOCUSABLE | WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE,
.paint = widgetTreeViewPaint,
.paintOverlay = NULL,
.calcMinSize = widgetTreeViewCalcMinSize,
.layout = widgetTreeViewLayout,
.onMouse = widgetTreeViewOnMouse,
.onKey = widgetTreeViewOnKey,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
// TreeItem has no paint/mouse/key handlers -- it's a data-only node.
// The TreeView parent widget handles all rendering and interaction.
// TreeItem exists as a WidgetT so it can participate in the tree
// structure (parent/child/sibling links) for hierarchical data.
static const WidgetClassT sClassTreeItem = {
.flags = 0,
.paint = NULL,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = widgetTreeItemGetText,
.setText = widgetTreeItemSetText
};
static const WidgetClassT sClassImage = {
.flags = 0,
.paint = widgetImagePaint,
.paintOverlay = NULL,
.calcMinSize = widgetImageCalcMinSize,
.layout = NULL,
.onMouse = widgetImageOnMouse,
.onKey = NULL,
.destroy = widgetImageDestroy,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassImageButton = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetImageButtonPaint,
.paintOverlay = NULL,
.calcMinSize = widgetImageButtonCalcMinSize,
.layout = NULL,
.onMouse = widgetImageButtonOnMouse,
.onKey = widgetImageButtonOnKey,
.destroy = widgetImageButtonDestroy,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassCanvas = {
.flags = 0,
.paint = widgetCanvasPaint,
.paintOverlay = NULL,
.calcMinSize = widgetCanvasCalcMinSize,
.layout = NULL,
.onMouse = widgetCanvasOnMouse,
.onKey = NULL,
.destroy = widgetCanvasDestroy,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassListView = {
.flags = WCLASS_FOCUSABLE | WCLASS_NO_HIT_RECURSE,
.paint = widgetListViewPaint,
.paintOverlay = NULL,
.calcMinSize = widgetListViewCalcMinSize,
.layout = NULL,
.onMouse = widgetListViewOnMouse,
.onKey = widgetListViewOnKey,
.destroy = widgetListViewDestroy,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassAnsiTerm = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetAnsiTermPaint,
.paintOverlay = NULL,
.calcMinSize = widgetAnsiTermCalcMinSize,
.layout = NULL,
.onMouse = widgetAnsiTermOnMouse,
.onKey = widgetAnsiTermOnKey,
.destroy = widgetAnsiTermDestroy,
.getText = NULL,
.setText = NULL
};
// ScrollPane, Splitter: PAINTS_CHILDREN because they clip/position
// children in a custom way; NO_HIT_RECURSE because they manage their
// own scrollbar/divider hit regions.
static const WidgetClassT sClassScrollPane = {
.flags = WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE,
.paint = widgetScrollPanePaint,
.paintOverlay = NULL,
.calcMinSize = widgetScrollPaneCalcMinSize,
.layout = widgetScrollPaneLayout,
.onMouse = widgetScrollPaneOnMouse,
.onKey = widgetScrollPaneOnKey,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassSplitter = {
.flags = WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE,
.paint = widgetSplitterPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSplitterCalcMinSize,
.layout = widgetSplitterLayout,
.onMouse = widgetSplitterOnMouse,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassTimer = {
.flags = 0,
.paint = NULL,
.paintOverlay = NULL,
.calcMinSize = widgetTimerCalcMinSize,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = widgetTimerDestroy,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassSpinner = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetSpinnerPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSpinnerCalcMinSize,
.layout = NULL,
.onMouse = widgetSpinnerOnMouse,
.onKey = widgetSpinnerOnKey,
.destroy = NULL,
.getText = widgetSpinnerGetText,
.setText = widgetSpinnerSetText
};
// ============================================================
// Class table -- indexed by WidgetTypeE
// ============================================================
//
// This array is the central dispatch table for the widget system.
// Indexed by the WidgetTypeE enum, it provides O(1) lookup of any
// widget type's class definition. Every WidgetT stores a pointer
// to its class (w->wclass) set at allocation time, so per-widget
// dispatch doesn't even need to index this table -- it's a direct
// pointer dereference through the vtable.
//
// Using C99 designated initializers ensures the array slots match
// the enum values even if the enum is reordered. If a new enum
// value is added without a table entry, it will be NULL, which
// callers check for before dispatching.
const WidgetClassT *widgetClassTable[] = {
[WidgetVBoxE] = &sClassVBox,
[WidgetHBoxE] = &sClassHBox,
[WidgetLabelE] = &sClassLabel,
[WidgetButtonE] = &sClassButton,
[WidgetCheckboxE] = &sClassCheckbox,
[WidgetRadioGroupE] = &sClassRadioGroup,
[WidgetRadioE] = &sClassRadio,
[WidgetTextInputE] = &sClassTextInput,
[WidgetTextAreaE] = &sClassTextArea,
[WidgetListBoxE] = &sClassListBox,
[WidgetSpacerE] = &sClassSpacer,
[WidgetSeparatorE] = &sClassSeparator,
[WidgetFrameE] = &sClassFrame,
[WidgetDropdownE] = &sClassDropdown,
[WidgetComboBoxE] = &sClassComboBox,
[WidgetProgressBarE] = &sClassProgressBar,
[WidgetSliderE] = &sClassSlider,
[WidgetTabControlE] = &sClassTabControl,
[WidgetTabPageE] = &sClassTabPage,
[WidgetStatusBarE] = &sClassStatusBar,
[WidgetToolbarE] = &sClassToolbar,
[WidgetTreeViewE] = &sClassTreeView,
[WidgetTreeItemE] = &sClassTreeItem,
[WidgetImageE] = &sClassImage,
[WidgetImageButtonE] = &sClassImageButton,
[WidgetCanvasE] = &sClassCanvas,
[WidgetAnsiTermE] = &sClassAnsiTerm,
[WidgetListViewE] = &sClassListView,
[WidgetSpinnerE] = &sClassSpinner,
[WidgetScrollPaneE] = &sClassScrollPane,
[WidgetSplitterE] = &sClassSplitter,
[WidgetTimerE] = &sClassTimer
};