// 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 };