====== Control Development ====== Workflow for creating new UI controls for the WvdS FPC RAD Suite. Controls must work for **design-time** (VS Code Designer) and **runtime** (Desktop/TUI/Web). ===== Overview ===== A control consists of multiple layers: ^ Layer ^ Purpose ^ Path ^ | Runtime Model | Target-neutral logic | ''~/sources/common/ui/controls/{category}/'' | | Target Renderer | Target-specific rendering | ''~/sources/common/ui/targets/{target}/renderers/'' | | Pack Manifest | PXAML tag → class mapping | ''~/packs/controls/{control_id}/'' | | Designer Metadata | Toolbox, Properties, Events | ''~/sources/extensions/wvds.vscode.ui.meta/'' | ===== Variables ===== When creating a control, define these variables: ^ Variable ^ Example ^ Description ^ | ''{CONTROL_ID}'' | ''dateedit'' | Unique identifier (lowercase) | | ''{CONTROL_CLASS}'' | ''TWvdSDateEdit'' | Pascal class name | | ''{PXAML_TAG}'' | ''DateEdit'' | Tag name in PXAML | | ''{PXAML_NS}'' | ''wvds'' | Namespace (wvds for internal controls) | | ''{TARGETS}'' | ''tui,desktop,web'' | Supported targets | ===== Workflow (7 Steps) ===== ==== Step 1: Contract Freeze ==== Define the public API of the control: **Properties:** (* Minimal set of public properties *) property Value: TDateTime read FValue write SetValue; property Format: string read FFormat write SetFormat; property MinDate: TDateTime read FMinDate write FMinDate; property MaxDate: TDateTime read FMaxDate write FMaxDate; property ReadOnly: Boolean read FReadOnly write FReadOnly; **Events:** (* Required events *) property OnValueChanged: TWvdSNotifyEvent read FOnValueChanged write FOnValueChanged; property OnValidating: TWvdSValidateEvent read FOnValidating write FOnValidating; **TargetCaps:** ^ Capability ^ TUI ^ Desktop ^ Web ^ | Keyboard | Required | Required | Required | | Mouse | Optional | Required | Required | | Touch | N/A | Optional | Optional | | Popup | Optional | Required | Required | ==== Step 2: Runtime Model Unit ==== Create the target-neutral model unit: **Path:** ''~/sources/common/ui/controls/{category}/WvdS.UI.Controls.{Category}.{ControlId}.pas'' unit WvdS.UI.Controls.Editors.DateEdit; {$mode objfpc}{$H+} interface uses WvdS.UI.Core.Base, WvdS.UI.Core.Events; type (* @abstract(Date input field with calendar popup) Target-neutral model for DateEdit. Contains NO rendering or host dependencies. *) TWvdSDateEdit = class(TWvdSControl) private FValue: TDateTime; FFormat: string; FOnValueChanged: TWvdSNotifyEvent; procedure SetValue(AValue: TDateTime); protected procedure DoValueChanged; virtual; public constructor Create(AOwner: TWvdSComponent); override; property Value: TDateTime read FValue write SetValue; property Format: string read FFormat write FFormat; property OnValueChanged: TWvdSNotifyEvent read FOnValueChanged write FOnValueChanged; end; implementation (* Implementation without target-specific code *) end. **No target-specific dependencies!** No DOM, no terminal I/O, no LCL units. ==== Step 3: Target Renderer Units ==== Create a renderer for each target in ''{TARGETS}'': === TUI Renderer === **Path:** ''~/sources/common/ui/targets/tui/renderers/WvdS.UI.TUI.Renderers.DateEdit.pas'' unit WvdS.UI.TUI.Renderers.DateEdit; {$mode objfpc}{$H+} interface uses WvdS.UI.TUI.Renderer.Base, WvdS.UI.Controls.Editors.DateEdit; type TWvdSTUIDateEditRenderer = class(TWvdSTUIRenderer) public procedure Measure(AControl: TWvdSControl; out AWidth, AHeight: Integer); override; procedure Paint(AControl: TWvdSControl; ACanvas: TWvdSTUICanvas); override; procedure HandleInput(AControl: TWvdSControl; AEvent: TWvdSInputEvent); override; end; implementation procedure TWvdSTUIDateEditRenderer.Measure(AControl: TWvdSControl; out AWidth, AHeight: Integer); begin AWidth := 12; (* [YYYY-MM-DD] *) AHeight := 1; end; procedure TWvdSTUIDateEditRenderer.Paint(AControl: TWvdSControl; ACanvas: TWvdSTUICanvas); var DateEdit: TWvdSDateEdit; begin DateEdit := AControl as TWvdSDateEdit; ACanvas.DrawText(0, 0, FormatDateTime(DateEdit.Format, DateEdit.Value)); end; procedure TWvdSTUIDateEditRenderer.HandleInput(AControl: TWvdSControl; AEvent: TWvdSInputEvent); begin (* Keyboard handling for date changes *) end; end. === Desktop Renderer === **Path:** ''~/sources/common/ui/targets/gui/renderers/WvdS.UI.Desktop.Renderers.DateEdit.pas'' === Web Renderer === **Path:** ''~/sources/common/ui/targets/web/renderers/WvdS.UI.Web.Renderers.DateEdit.pas'' ==== Step 4: Pack Manifest ==== Create the pack manifest for the PXAML resolver: **Path:** ''~/packs/controls/{control_id}/pack.wvds.json'' { "id": "dateedit", "vendor": "wvds", "version": "0.1.0", "xml": { "namespace": "wvds", "tag": "DateEdit" }, "model": { "unit": "WvdS.UI.Controls.Editors.DateEdit", "class": "TWvdSDateEdit" }, "renderers": { "tui": { "unit": "WvdS.UI.TUI.Renderers.DateEdit", "class": "TWvdSTUIDateEditRenderer" }, "desktop": { "unit": "WvdS.UI.Desktop.Renderers.DateEdit", "class": "TWvdSDesktopDateEditRenderer" }, "web": { "unit": "WvdS.UI.Web.Renderers.DateEdit", "class": "TWvdSWebDateEditRenderer" } }, "targetCaps": { "requires": ["keyboard"], "optional": ["mouse", "popup"] } } ==== Step 5: Designer Metadata ==== Create designer metadata for Toolbox and Properties: **Add descriptor to:** ''~/sources/common/core/meta/WvdS.Meta.Controls.Editors.pas'' procedure RegisterDateEditMeta(ARegistry: TMetaRegistry); var TypeMeta: TTypeMeta; begin TypeMeta := CreateTypeMeta('DateEdit', 'TWvdSDateEdit', 'Editors'); TypeMeta.Description := 'Date input field with calendar popup'; TypeMeta.Icon := 'calendar'; (* Properties *) TypeMeta.AddProperty(CreatePropertyMeta('Value', pkDateTime, 'Current date value')); TypeMeta.AddProperty(CreatePropertyMeta('Format', pkString, 'Display format (e.g. yyyy-mm-dd)')); TypeMeta.AddProperty(CreatePropertyMeta('MinDate', pkDateTime, 'Earliest allowed date')); TypeMeta.AddProperty(CreatePropertyMeta('MaxDate', pkDateTime, 'Latest allowed date')); TypeMeta.AddProperty(CreatePropertyMeta('ReadOnly', pkBoolean, 'Read-only mode')); (* Events *) TypeMeta.AddEvent(CreateEventMeta('OnValueChanged', 'Fired when Value changes')); TypeMeta.AddEvent(CreateEventMeta('OnValidating', 'Fired before value change')); ARegistry.RegisterType(TypeMeta); end; ==== Step 6: Registry Registration ==== Register the control in the runtime registries: === PXAML Resolver === The ''pxamlc'' compiler reads ''pack.wvds.json'' automatically from ''~/packs/''. === Target Runtime Registry === Update the renderer registry for each target: (* In WvdS.UI.TUI.Render.Registry.pas *) procedure RegisterTUIRenderers; begin (* ... other renderers ... *) RegisterRenderer(TWvdSDateEdit, TWvdSTUIDateEditRenderer); end; ==== Step 7: Documentation ==== Create API documentation for the control: **Path:** ''~/docs/api/controls/dateedit.md'' # TWvdSDateEdit Date input field with optional calendar popup. ## When to Use - User needs to enter a date - Restrict date range (MinDate/MaxDate) - Formatted date display required ## Properties | Property | Type | Description | |----------|------|-------------| | Value | TDateTime | Current date value | | Format | string | Display format | | MinDate | TDateTime | Earliest allowed date | | MaxDate | TDateTime | Latest allowed date | | ReadOnly | Boolean | Read-only mode | ## Events | Event | Description | |-------|-------------| | OnValueChanged | Fired when Value changes | | OnValidating | Allows validation before value change | ## Example ```xml ``` ===== Completion Criteria ===== A control is complete when: [ ] VS Code Designer can place {PXAML_NS}:{PXAML_TAG} [ ] Properties editable in Designer [ ] Preview in Designer works [ ] Target build (tui/desktop/web) compiles successfully [ ] Pack manifest present and recognized by resolver [ ] API documentation written ===== See Also ===== * [[.:pxaml-pipeline|PXAML Pipeline]] * [[.:pack-struktur|Pack Structure]] * [[.:targets|Build Targets]] * [[.:meta-api|Meta API]]