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.
| 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.
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:
Windows, weil es derselbe Code ist.
keine Systemvorgabe, die dazwischenfunkt.
Plattform nicht.
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.
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.
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.
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.
Align — alTop, alBottom, alLeft, alRight,
alClient. Das Control bekommt die ganze Kante.
Anchors — Bitmaske aus ANCHOR_LEFT/TOP/RIGHT/BOTTOM.Bestimmt, was bei einer Größenänderung des Elternteils passiert.
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.