====== Sviluppo controlli ====== Workflow per la creazione di nuovi controlli UI per la suite WvdS FPC RAD. I controlli devono funzionare sia in **design-time** (VS Code Designer) che in **runtime** (Desktop/TUI/Web). ===== Panoramica ===== Un controllo e composto da diversi strati: ^ Strato ^ Scopo ^ Percorso ^ | Runtime Model | Logica target-neutrale | ''~/sources/common/ui/controls/{category}/'' | | Target Renderer | Rappresentazione target-specifica | ''~/sources/common/ui/targets/{target}/renderers/'' | | Pack Manifest | Mapping tag PXAML → classe | ''~/packs/controls/{control_id}/'' | | Designer Metadata | Toolbox, properties, eventi | ''~/sources/extensions/wvds.vscode.ui.meta/'' | ===== Variabili ===== Definire queste variabili durante la creazione di un controllo: ^ Variabile ^ Esempio ^ Descrizione ^ | ''{CONTROL_ID}'' | ''dateedit'' | Identificatore univoco (minuscolo) | | ''{CONTROL_CLASS}'' | ''TWvdSDateEdit'' | Nome classe Pascal | | ''{PXAML_TAG}'' | ''DateEdit'' | Nome tag nel PXAML | | ''{PXAML_NS}'' | ''wvds'' | Namespace (wvds per controlli interni) | | ''{TARGETS}'' | ''tui,desktop,web'' | Target supportati | ===== Workflow (7 passaggi) ===== ==== Passaggio 1: Contract Freeze ==== Definire l'API pubblica del controllo: **Properties:** (* Set minimo di properties pubbliche *) 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:** (* Eventi richiesti *) property OnValueChanged: TWvdSNotifyEvent read FOnValueChanged write FOnValueChanged; property OnValidating: TWvdSValidateEvent read FOnValidating write FOnValidating; **TargetCaps:** ^ Capability ^ TUI ^ Desktop ^ Web ^ | Keyboard | Richiesto | Richiesto | Richiesto | | Mouse | Opzionale | Richiesto | Richiesto | | Touch | N/A | Opzionale | Opzionale | | Popup | Opzionale | Richiesto | Richiesto | ==== Passaggio 2: Unit Runtime Model ==== Creare la unit model target-neutrale: **Percorso:** ''~/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(Campo di input data con popup calendario) Model target-neutrale per DateEdit. NON contiene dipendenze di rendering o host. *) 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 (* Implementazione senza codice target-specifico *) end. **Nessuna dipendenza target-specifica!** No DOM, no Terminal I/O, no unit LCL. ==== Passaggio 3: Unit Target Renderer ==== Creare un renderer per ogni target in ''{TARGETS}'': === Renderer TUI === **Percorso:** ''~/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 (* Gestione tastiera per modifica data *) end; end. === Renderer Desktop === **Percorso:** ''~/sources/common/ui/targets/gui/renderers/WvdS.UI.Desktop.Renderers.DateEdit.pas'' === Renderer Web === **Percorso:** ''~/sources/common/ui/targets/web/renderers/WvdS.UI.Web.Renderers.DateEdit.pas'' ==== Passaggio 4: Pack Manifest ==== Creare il manifesto pack per il resolver PXAML: **Percorso:** ''~/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"] } } ==== Passaggio 5: Designer Metadata ==== Creare metadati designer per toolbox e properties: **Aggiungere descriptor a:** ''~/sources/common/core/meta/WvdS.Meta.Controls.Editors.pas'' procedure RegisterDateEditMeta(ARegistry: TMetaRegistry); var TypeMeta: TTypeMeta; begin TypeMeta := CreateTypeMeta('DateEdit', 'TWvdSDateEdit', 'Editors'); TypeMeta.Description := 'Campo di input data con popup calendario'; TypeMeta.Icon := 'calendar'; (* Properties *) TypeMeta.AddProperty(CreatePropertyMeta('Value', pkDateTime, 'Valore data corrente')); TypeMeta.AddProperty(CreatePropertyMeta('Format', pkString, 'Formato visualizzazione (es. yyyy-mm-dd)')); TypeMeta.AddProperty(CreatePropertyMeta('MinDate', pkDateTime, 'Data minima consentita')); TypeMeta.AddProperty(CreatePropertyMeta('MaxDate', pkDateTime, 'Data massima consentita')); TypeMeta.AddProperty(CreatePropertyMeta('ReadOnly', pkBoolean, 'Modalita sola lettura')); (* Events *) TypeMeta.AddEvent(CreateEventMeta('OnValueChanged', 'Attivato quando Value cambia')); TypeMeta.AddEvent(CreateEventMeta('OnValidating', 'Attivato prima del cambio valore')); ARegistry.RegisterType(TypeMeta); end; ==== Passaggio 6: Registrazione Registry ==== Registrare il controllo nelle registry runtime: === PXAML Resolver === Il compilatore ''pxamlc'' legge automaticamente ''pack.wvds.json'' da ''~/packs/''. === Target Runtime Registry === Aggiornare la registry renderer per ogni target: (* In WvdS.UI.TUI.Render.Registry.pas *) procedure RegisterTUIRenderers; begin (* ... altri renderer ... *) RegisterRenderer(TWvdSDateEdit, TWvdSTUIDateEditRenderer); end; ==== Passaggio 7: Documentazione ==== Creare documentazione API per il controllo: **Percorso:** ''~/docs/api/controls/dateedit.md'' # TWvdSDateEdit Campo di input data con popup calendario opzionale. ## Quando usare - L'utente deve inserire una data - Limitare l'intervallo date (MinDate/MaxDate) - Richiesta visualizzazione data formattata ## Properties | Property | Tipo | Descrizione | |----------|------|-------------| | Value | TDateTime | Valore data corrente | | Format | string | Formato visualizzazione | | MinDate | TDateTime | Data minima consentita | | MaxDate | TDateTime | Data massima consentita | | ReadOnly | Boolean | Modalita sola lettura | ## Events | Evento | Descrizione | |--------|-------------| | OnValueChanged | Attivato quando Value cambia | | OnValidating | Permette validazione prima del cambio valore | ## Esempio ```xml ``` ===== Criteri di completamento ===== Un controllo e completo quando: [ ] VS Code Designer puo posizionare {PXAML_NS}:{PXAML_TAG} [ ] Properties modificabili nel designer [ ] Preview nel designer funzionante [ ] Build target (tui/desktop/web) compila con successo [ ] Pack manifest presente e riconosciuto dal resolver [ ] Documentazione API scritta ===== Vedi anche ===== * [[.:pxaml-pipeline|Pipeline PXAML]] * [[.:pack-struktur|Struttura pack]] * [[.:targets|Target di build]] * [[.:meta-api|Meta API]]