Files
rose-ash/hosts/native/test/test_render.ml
giles f0d8db9b68 Add native SX desktop browser — renders s-expressions to pixels
A 5.9MB OCaml binary that renders SX pages directly using SDL2 + Cairo,
bypassing HTML/CSS/JS entirely. Can fetch live pages from sx.rose-ash.com
or render local .sx files.

Architecture (1,387 lines of new code):
  sx_native_types.ml   — render nodes, styles, layout boxes, color palette
  sx_native_style.ml   — ~40 Tailwind classes → native style records
  sx_native_layout.ml  — pure OCaml flexbox (measure + position)
  sx_native_render.ml  — SX value tree → native render nodes
  sx_native_paint.ml   — render nodes → Cairo draw commands
  sx_native_fetch.ml   — HTTP fetch via curl with SX-Request headers
  sx_native_app.ml     — SDL2 window, event loop, navigation, scrolling

Usage:
  dune build                           # from hosts/native/
  ./sx_native_app.exe /sx/             # browse sx.rose-ash.com home
  ./sx_native_app.exe /sx/(applications.(native-browser))
  ./sx_native_app.exe demo/counter.sx  # render local file

Features:
  - Flexbox layout (row/column, gap, padding, alignment, grow)
  - Tailwind color palette (stone, violet, white)
  - Rounded corners, borders, shadows
  - Text rendering with font size/weight
  - Click navigation (links trigger refetch)
  - Scroll with mouse wheel
  - Window resize → re-layout
  - URL bar showing current path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:01:22 +00:00

76 lines
3.0 KiB
OCaml

(** Smoke test: parse SX, render to node tree, measure, layout, paint to PNG. *)
open Sx_native.Sx_native_types
let demo_sx = {|
(div :class "flex flex-col items-center gap-6 p-8 bg-stone-50"
(h1 :class "text-3xl font-bold text-stone-800" "SX Native Browser")
(p :class "text-stone-500" "Rendering s-expressions directly to pixels")
(div :class "flex gap-4 items-center"
(div :class "p-4 rounded-lg bg-white border border-stone-200 shadow"
(h3 :class "font-bold text-stone-700" "No HTML")
(p :class "text-sm text-stone-500" "This is not a web page"))
(div :class "p-4 rounded-lg bg-white border border-stone-200 shadow"
(h3 :class "font-bold text-stone-700" "No CSS")
(p :class "text-sm text-stone-500" "Tailwind classes parsed to native styles"))
(div :class "p-4 rounded-lg bg-white border border-stone-200 shadow"
(h3 :class "font-bold text-stone-700" "No JavaScript")
(p :class "text-sm text-stone-500" "The SX evaluator does everything")))
(div :class "p-6 rounded-lg bg-violet-600"
(p :class "text-white text-lg font-bold" "5000 lines of OCaml instead of 35 million lines of browser engine")))
|}
let rec count_nodes (node : node) : int =
1 + List.fold_left (fun acc c -> acc + count_nodes c) 0 node.children
let rec print_tree indent (node : node) =
let prefix = String.make (indent * 2) ' ' in
let text_info = match node.text with
| Some t -> Printf.sprintf " \"%s\"" (if String.length t > 30 then String.sub t 0 30 ^ "..." else t)
| None -> ""
in
let size_info = Printf.sprintf " [%.0fx%.0f @ (%.0f,%.0f)]" node.box.w node.box.h node.box.x node.box.y in
Printf.printf "%s<%s>%s%s\n" prefix node.tag text_info size_info;
List.iter (print_tree (indent + 1)) node.children
let () =
Printf.printf "=== SX Native Browser Smoke Test ===\n\n";
(* 1. Parse *)
let values = Sx_parser.parse_all demo_sx in
Printf.printf "1. Parsed %d top-level form(s)\n" (List.length values);
(* 2. Render to node tree *)
let root = Sx_native.Sx_native_render.render_page values in
let n = count_nodes root in
Printf.printf "2. Render tree: %d nodes, root tag=%s\n" n root.tag;
(* 3. Create Cairo surface for measurement *)
let surface = Cairo.Image.create Cairo.Image.ARGB32 ~w:1024 ~h:768 in
let cr = Cairo.create surface in
(* 4. Measure *)
Sx_native.Sx_native_layout.measure cr root;
Printf.printf "3. Measured intrinsic size: %.0f x %.0f\n" root.box.w root.box.h;
(* 5. Layout *)
Sx_native.Sx_native_layout.layout root 0. 0. 1024. 732.;
Printf.printf "4. Layout complete, root positioned at (%.0f, %.0f) size %.0f x %.0f\n"
root.box.x root.box.y root.box.w root.box.h;
(* 6. Paint *)
Sx_native.Sx_native_paint.paint_scene cr root "sx://demo" 1024. 768.;
Cairo.Surface.flush surface;
(* 7. Write PNG *)
let png_path = "/tmp/sx_browser_test.png" in
Cairo.PNG.write surface png_path;
Printf.printf "5. Rendered to %s\n\n" png_path;
(* Print tree *)
Printf.printf "=== Render Tree ===\n";
print_tree 0 root;
Cairo.Surface.finish surface;
Printf.printf "\n=== All OK! ===\n"