Architektur
Vega ist in vier Schichten aufgebaut. Jede kennt nur die unter ihr — das ist die Regel, an der sich entscheidet, ob eine neue Datei richtig liegt.
Die vier Schichten
| Schicht | Units | Aufgabe |
|---|---|---|
| 1 — Grundlagen | Types, Events, Strings, Lists | Farben, Rechtecke, Ereignisarten, Puffer, Listen |
| 2 — Zeichnen | Canvas, Font, TrueType, Picture, Theme, Style | Pixel, Schrift, Bilder, Aussehen |
| 3 — Steuerelemente | Control, StdCtrls, Edits, ListCtrls, … | Der Baum aus Controls und der Dispatcher |
| 4 — Fenster | Backend, Backend/*, Forms | Die einzige Stelle mit Kontakt zum Betriebssystem |
Eine Unit aus Schicht 2 darf nichts aus Schicht 3 wissen. Wo das doch nötig wird, steht ein Funktionszeiger-Haken statt eines Imports — siehe unten.
Eigenes Zeichnen
Jedes Control malt sich selbst in einen Pixelpuffer im Format TColor
(Byte 0 = Blau, 1 = Grün, 2 = Rot, 3 = Alpha). Erst der fertige Puffer geht
über Present ans Fenstersystem.
Das kostet Arbeit und bringt drei Dinge:
- Gleiches Aussehen überall. Ein Knopf sieht unter X11 aus wie unter
Windows, weil es derselbe Code ist.
- Themes bestimmen alles, bis hinunter zur Titelleiste — es gibt
keine Systemvorgabe, die dazwischenfunkt.
- Die HAL bleibt klein. 21 Methoden, mehr braucht eine neue
Plattform nicht.
Die HAL
Vega/Backend.lyx beschreibt den Vertrag als Interface IVegaBackend:
Fenster anlegen und zerstören, Titel, Lage und Größe, Rahmen an/aus,
Maximieren, Ziehen am Rand, Present, PollEvent, Zeigerform,
Zeichenflächengröße, Monitore, Bildschirmgröße, Zwischenablage,
Skalierung.
Fensterhandles sind opake int64. Was dahintersteckt, weiß nur das
Backend (X11: Window-XID, Win32: HWND). Der geteilte Code reicht sie durch
und deutet sie nie.
Mehr dazu: Backends.
Der Dispatcher
Vega/Dispatch.lyx verteilt die Ereignisse eines Fensters auf die
Controls: Treffertest, Fokus, Maus einfangen, Doppelklick, Tastatur samt
Umschaltern, Ziehen und Ablegen, Kontextmenü, Hinweisfenster.
Er ist der Grund, warum ein Control nur DoMouseDown überschreiben muss
und nichts über Fenster wissen darf.
Zwei Muster, die man kennen muss
Funktionszeiger-Haken gegen Zyklen
Lyx hat keine Methodenzeiger und keine zyklischen Imports. Wenn eine untere Schicht etwas aus einer oberen braucht, wird eine Funktionsvariable gesetzt, statt zu importieren:
// In der unteren Unit:
var _kontextmenueOeffner: TOeffnerFn;
pub fn SetContextMenuOpener(f: TOeffnerFn): void {
_kontextmenueOeffner := f;
}
Die obere Unit trägt sich beim Start ein. Benutzt an drei Stellen:
Backend-Verweis in TCustomEdit, SetActionRunner zwischen Menüs und
Aktionen, SetContextMenuOpener zwischen Dispatcher und Formularen.
Verwandt: ActionPtr, PopupMenuPtr und ImagesPtr liegen als
int64 an TControl — dieselbe Lösung für dasselbe Problem.
Eigenschaften als Getter/Setter
Lyx kennt kein property. Was Delphi als
property Left: Integer read FLeft write SetLeft;
schreibt, ist hier ein Paar GetLeft / SetLeft — genau das, was der
Pascal-Compiler intern ohnehin daraus macht. Die Felder bleiben öffentlich
und lesbar; für Schleifen und Rechnungen ist der direkte Zugriff schneller
und völlig in Ordnung.
Der Unterschied: ctl.Left := 20; verschiebt das Control, ohne neu
zu zeichnen. ctl.SetLeft(20) zeichnet neu.
Gebündelt im Interface IControlProps. Das zahlt sich zweimal aus: beim
Formatieren fremder Controls und beim LFM-Format, wo eine einzige Funktion
SetCommonProp jedes Control bedient, ohne dessen Klasse zu kennen.
Layout
Align—alTop,alBottom,alLeft,alRight,
alClient. Das Control bekommt die ganze Kante.
Anchors— Bitmaske ausANCHOR_LEFT/TOP/RIGHT/BOTTOM.
Bestimmt, was bei einer Größenänderung des Elternteils passiert.
- Größengrenzen —
MinWidth,MaxWidth,MinHeight,
MaxHeight.
Die Ankerrechnung merkt sich die vorige Größe am Elternteil
(_layoutW, _layoutH) statt vier Werte je Kind — ein Feld pro
Container gegen Delphis vier pro Control.
Ohne Anker gilt eine Regel, die überrascht und trotzdem richtig ist: das Control wandert halb mit. Sonst klebte ein mittig gedachtes Control beim Vergrößern oben links fest.
