Pas des liaisons vers une boîte à outils, pas une enveloppe autour d'une toile écrite en C. Le rasteriseur, le moteur TrueType, la mise en page, les widgets, le thème et le docking sont des fichiers .keal. Le C en dessous ouvre une fenêtre, rapporte ce que l'utilisateur a fait, et pose à l'écran un tampon de pixels déjà fini. Il ne dessine rien.
import "keal-view/keal-view.keal" val clicks = state(0) runApp("Hello", 320, 200, { -> column([ label("Clicked ${clicks.get()} times").fontSize(20.0).centered(), button("Click me", { -> clicks.set(clicks.get() + 1) }).kindOf(primary) ]).gaps(12.0).padAll(24.0).aligned(mainCenter, crossCenter).grows() })

Lequel des deux dépend du backend contre lequel il a été construit, et aucun des 11 à 13 % restants ne pose un pixel où que ce soit. Toute la surface C tient dans un en-tête : un tampon d'image, un bloc d'octets, une structure d'événement, et les accesseurs de chacun.
Un seul fait du compilateur décide de toute la conception. native """…""" colle son C dans la même unité de traduction que le programme compilé, donc une fonction static inline déclarée là est inlinée par le compilateur C dans le code de Keal lui-même. Un appel à travers la frontière n'est pas un appel — c'est l'instruction qu'il contient.
extern func kvPxSet(x: Int, y: Int, argb: Int): Int = "kv_set"
Cette ligne est une seule écriture avec vérification de bornes. Mesurée à environ une demi-nanoseconde par pixel depuis une boucle Keal, ce qui est le prix d'un rasteriseur C, parce qu'après inlining c'est le rasteriseur C. Il n'y avait donc aucune raison d'écrire le dessin en C, et toutes les raisons de ne pas le faire.
Étiquettes, boutons, cases à cocher, boutons radio, interrupteurs, contrôles segmentés, listes déroulantes, curseurs, incréments, progression, champs, onglets, cartes, panneaux, zones défilantes — et custom, à qui l'on remet une toile déjà découpée, son rectangle, les polices, le thème et son propre état de survol.
openMenu, openDialog, openSheet et .tip("…"). Rien à installer : la boucle compose cette couche au-dessus de la surcouche de l'application.
Un axe de flexbox et pas davantage. Il n'y a pas de compression : ce qui ne rentre pas est découpé plutôt que serré, parce qu'une mise en page qui compresse en silence ne casse que sur les petites fenêtres, l'endroit où personne ne regarde.
Un analyseur et un rasteriseur TrueType écrits en Keal : formats cmap 0, 4, 6 et 12, glyf avec les glyphes composites, les collections. Les glyphes sont remplis par la méthode de l'aire signée et rasterisés à la taille où ils seront réellement dessinés.
L'anti-crénelage est analytique, pas échantillonné : la couverture d'un bord droit est son recouvrement exact avec la cellule du pixel. Il n'y a pas de réglage de qualité, ni d'image discrètement moins chère que la précédente.
Sombre et clair, construits depuis une rampe de surfaces, un accent et trois teintes d'état plutôt qu'une liste de codes hexadécimaux. useTheme(lightTheme()) est tout le changement, y compris pour les widgets écrits après.
Le rasteriseur est la seule chose qui sache ce qu'est un pixel. Une mise en page est juste sur un écran Retina et sur un projecteur ; les nombres ne changent pas, l'échelle si.
Entre deux images, la boucle est dans kvWait, qui bloque jusqu'à ce que le système ait quelque chose à dire. Pas un faible taux d'activité — aucun. Sur un portable, ça vaut plus que la vitesse de dessin.
L'agencement est un arbre binaire à exactement deux cas — une feuille qui tient des onglets, et une coupe qui tient deux enfants et une fraction — ce qui est assez peu pour que chaque état possible soit un état sur lequel on peut raisonner.
Un panneau se traîne par son onglet ; pendant le déplacement, la zone mise en évidence sous le pointeur dit où le lâcher le poserait — à gauche, à droite, au-dessus, en dessous, ou comme onglet de plus — avant qu'on lâche. Un panneau est une fonction de vue ordinaire et ignore qu'il est ancré.
val dock = Dock( beside( leaf(["files"]), above( beside(leaf(["editor"]), leaf(["props", "preview"]), 0.68), leaf(["terminal"]), 0.72), 0.19)) dock.add(panelOf("files", "Files", { -> filesPanel() }))
Les trois backends ont été écrits contre la même douzaine de fonctions, et rien au-dessus de runtime/ n'a changé pour ajouter le deuxième et le troisième — ce qui est tout l'intérêt d'avoir tracé la frontière là.
| Plateforme | Backend | État |
|---|---|---|
| macOS | runtime/kv_cocoa.m | Cocoa. Vérifié — développé ici |
| Windows | runtime/kv_win32.c | Win32 et un DIB BI_RGB. Vérifié sur Windows 10 22H2 et 11 22H2, MinGW-w64 |
| Linux | runtime/kv_x11.c | Xlib seul, aucune boîte à outils. Vérifié sur Ubuntu 26.04 aarch64, sous Wayland à travers XWayland |
Windows et Linux ont chacun été vérifiés par quelqu'un sur une vraie machine, en suivant la liste de portage ligne à ligne, et à eux deux cela a coûté dix-neuf défauts — dont douze au-dessus du backend, donc présents sur toutes les plateformes, y compris celle où tout a été écrit. Quatre choses restent non vérifiées et sont consignées plutôt que passées sous silence.
keal-view a besoin du compilateur Keal à côté de lui. Il n'y a pas de système de construction ici : tools/build.sh choisit le backend de la plateforme, le compile une fois, et remet le fichier objet à keal build.
# the compiler, then the framework git clone https://github.com/geneacta/keal git clone https://github.com/geneacta/keal-view cd keal && cargo build --release && cd ../keal-view tools/build.sh examples/tour.keal && build/tour # start here tools/build.sh examples/gallery.keal && build/gallery # every widget tools/test.sh # 194 checks, no display
La première n'a besoin d'aucun écran, ce qui est la façon dont le framework est vérifié en intégration continue et dont chaque image de ce site a été faite.
build/studio --snapshot frame.bmp 2 # one frame to a file, then exit build/studio --snapshot frame.bmp 2 900 1900 # …at a size of your choosing build/studio --window-id /tmp/id # write its own window number out