html
HolyHTML: an HTML/CSS engine written in HolyPHP, for HolyPHP. It parses a document, computes styles, lays the boxes out, paints them onto a canvas and routes input back to the nodes. That is the whole of what a browser does for a desktop UI — without shipping a browser engine.
One window, one document
The window holds a single canvas. Everything inside it is a box the engine laid out; the script only reacts to the events the view hands back.
import "html";
import "ui";
$win = ui_window("Hello, HolyHTML", -1, -1, 640, 360);
$cv = ui_add($win, 18, "", 0, 0, 640, 360); // 18 = canvas
$view = new HtmlView($cv, 640, 360, html_theme_dark());
$view->load(
"Hello, HolyHTML",
".card { padding: 16px; background-color: #252526; color: #ffffff; }");
$view->paint();
ui_run_main();
The layers
Each layer is a file, each file is testable on its own, and nothing below
view knows that a canvas exists.
| File | Responsibility |
|---|---|
html/dom.hphp | the document tree and a tolerant parser (html_parse, html_set_text) |
html/css.hphp | selectors, specificity, the cascade (css_parse, css_add) |
html/style.hphp | computed style: colours as ints, lengths as unresolved Dim, the default stylesheet |
html/layout.hphp | block, inline and flex layout, text wrapping (HtmlLayout) |
html/paint.hphp | the box tree as canvas draw calls, clipping, scrolling |
html/view.hphp | the facade: mount, hit-test, events, repaint (HtmlView) |
Trees, selectors, and text
import "html";
$view = new HtmlView(0, 640, 360, html_theme_dark()); // 0 = headless
$view->load(
"one" .
"two",
".row { height: 24px; }");
$view->ensure();
$b = $view->find("#b"); // by id
$view->set_text($b, "second"); // text is data, not a callback
$rows = $view->find_all(".row"); // every match, in document order
echo count($rows); // 2
The parser is deliberately forgiving: unclosed tags, stray close tags,
attributes without quotes and void elements all parse. A missing
</div> is not worth failing a window over.
Layout
- Block, inline and flex.
display: flexwithrow/column,flex: 1(basis 0, as in CSS),gap,align-items,justify-content,align-self. box-sizing: border-boxis the default. Every desktop UI writeswidth: 200pxmeaning "200px including the padding"; a window is not a document that has to stay compatible with 1998.- Lengths stay unresolved until layout —
px,%,emandauto— sowidth: 50%resolves against the real containing block. position: absolutefor popups, menus and anything that must sit on top of the flow.
Events are data
mouse() and key() do the bookkeeping — hover,
focus, caret, scroll — and return an HtmlEvent naming the node
under the pointer. The application decides what that means, which is what
keeps the whole input path testable without a window.
import "html";
$view = new HtmlView(0, 400, 200, html_theme_dark());
$view->load("open me", ".row { height: 22px; }");
$view->ensure();
$ev = $view->mouse("click", 10, 10, 1, 0);
if ($ev->node != null) {
$view->add_class($ev->node, "active");
echo "clicked " . $ev->node->id; // a
}
Painting, and testing the paint
Painter has a dry run: with $dry set it records
every draw call as a string instead of ink. A layout can be wrong and every
state test still pass; a draw list (or a pixel read back from the canvas, as
examples/holyhtml_demo.hphp does) cannot.
import "html";
$view = new HtmlView(0, 320, 160, html_theme_dark());
$view->load("x", ".box { width: 100px; height: 40px; background-color: #007acc; }");
$view->ensure();
$paint = new Painter(0, true);
$paint->paint($view->root, $view->th->bg); // second argument: the clear colour
echo count($paint->log) . " draw calls";
Theming
An HtmlTheme carries the colours, the font families and the
base metrics the default stylesheet uses. html_theme_dark() and
html_theme_light() are the two starting points; copy one and
change the fields you care about.
import "html";
$th = html_theme_light();
$th->accent = 0xd13438;
$th->fontPx = 14;
$view = new HtmlView(0, 400, 200, $th);
$view->load("red accent", ".card { padding: 8px; }");
Naming: the layout engine is HtmlLayout, not
Layout — the ui library has a
Layout of its own (the box a widget stacks in), and a HolyPHP
program has one namespace. An HTML view inside a desktop window imports both.