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>
This commit is contained in:
75
hosts/native/test/test_render.ml
Normal file
75
hosts/native/test/test_render.ml
Normal file
@@ -0,0 +1,75 @@
|
||||
(** 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"
|
||||
Reference in New Issue
Block a user