nx_ui_lib.nx source
↩ module page · 122 lines · 5609 B
1// nx_ui_lib.nx -- sovereign INTERACTIVE-FRONTEND render lib (fd-based, no main).
2//
3// The REUSABLE core: writes interactive HTML/SVG to ANY fd (stdout, an HTTP
4// connection, a buffer) so the SAME render serves live, prints, or buffers.
5// Composed by nx_ui (demo), nx_ui_serve (live server), and any future frontend.
6//
7// COMPOSABLE: the page is split into open / svg / controls / close so a server
8// can inject extra content (e.g. a pain tally) between the map and </body>
9// WITHOUT forking the renderer. ui_emit_region_map() composes them for the
10// simple case. Clickable regions + highlight + zoom + query parse. Zero JS.
11// license_tier: ORIGINAL
12
13import "nx_syscalls.nx"
14
15func ui_w(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != 0 as u8 { n = n + 1 } sys_write(fd, s, n); return 0 }
16func ui_wn(fd: i64, n: i64) -> i64 {
17 if n == 0 { sys_write(fd, "0" as *u8, 1); return 0 }
18 var m: i64 = n
19 let d: *u8 = sys_mmap(24); var k: i64 = 0
20 while m > 0 { d[k] = (0x30 + (m % 10)) as u8; m = m / 10; k = k + 1 }
21 var i: i64 = k - 1
22 while i >= 0 { let o: *u8 = sys_mmap(1); o[0] = d[i]; sys_write(fd, o, 1); i = i - 1 }
23 return 0
24}
25
26const UI_NREG: i64 = 5
27
28func ui_rx(i: i64) -> i64 { if i == 0 { return 80 } if i == 1 { return 70 } if i == 2 { return 40 } if i == 3 { return 135 } return 75 }
29func ui_ry(i: i64) -> i64 { if i == 0 { return 10 } if i == 1 { return 55 } if i == 2 { return 55 } if i == 3 { return 55 } return 160 }
30func ui_rw(i: i64) -> i64 { if i == 0 { return 40 } if i == 1 { return 60 } if i == 2 { return 25 } if i == 3 { return 25 } return 50 }
31func ui_rh(i: i64) -> i64 { if i == 0 { return 40 } if i == 1 { return 100 } if i == 2 { return 90 } if i == 3 { return 90 } return 120 }
32func ui_label(i: i64) -> *u8 {
33 if i == 0 { return "head" as *u8 }
34 if i == 1 { return "torso" as *u8 }
35 if i == 2 { return "left-arm" as *u8 }
36 if i == 3 { return "right-arm" as *u8 }
37 return "legs" as *u8
38}
39
40func ui_region(fd: i64, i: i64, selected: i64, zoom: i64) -> i64 {
41 ui_w(fd, "<a href='?region="); ui_wn(fd, i); ui_w(fd, "&zoom="); ui_wn(fd, zoom); ui_w(fd, "'>")
42 ui_w(fd, "<rect x='"); ui_wn(fd, ui_rx(i)); ui_w(fd, "' y='"); ui_wn(fd, ui_ry(i))
43 ui_w(fd, "' width='"); ui_wn(fd, ui_rw(i)); ui_w(fd, "' height='"); ui_wn(fd, ui_rh(i))
44 ui_w(fd, "' fill='")
45 if i == selected { ui_w(fd, "#e33") } else { ui_w(fd, "#9bd") }
46 ui_w(fd, "' stroke='#333'/></a>\n")
47 return 0
48}
49
50// open the page through the map + zoom controls (NO </body></html> -- composable).
51func ui_emit_region_map_open(fd: i64, selected: i64, zoom: i64) -> i64 {
52 var z: i64 = zoom
53 if z < 1 { z = 1 }
54 ui_w(fd, "<!DOCTYPE html><html><head><title>Nishi pain map</title><style>a{cursor:pointer}body{font-family:sans-serif;text-align:center}svg{background:#f6f9fc;border:1px solid #ccc;border-radius:8px}</style></head><body>\n")
55 ui_w(fd, "<h2>Nishi pain map -- region: "); ui_w(fd, ui_label(selected)); ui_w(fd, ", zoom "); ui_wn(fd, z); ui_w(fd, "</h2>\n")
56 ui_w(fd, "<svg width='200' height='400' viewBox='0 0 "); ui_wn(fd, 200 / z); ui_w(fd, " "); ui_wn(fd, 400 / z); ui_w(fd, "'>\n")
57 var i: i64 = 0
58 while i < UI_NREG { ui_region(fd, i, selected, z); i = i + 1 }
59 ui_w(fd, "</svg>\n")
60 ui_w(fd, "<p><a href='?region="); ui_wn(fd, selected); ui_w(fd, "&zoom="); ui_wn(fd, z + 1); ui_w(fd, "'>[zoom in]</a> ")
61 var zo: i64 = z - 1
62 if zo < 1 { zo = 1 }
63 ui_w(fd, "<a href='?region="); ui_wn(fd, selected); ui_w(fd, "&zoom="); ui_wn(fd, zo); ui_w(fd, "'>[zoom out]</a></p>\n")
64 return 0
65}
66
67func ui_emit_page_close(fd: i64) -> i64 { ui_w(fd, "</body></html>\n"); return 0 }
68
69// the simple full-page render (demo / handle_query): open + close, nothing injected.
70func ui_emit_region_map(fd: i64, selected: i64, zoom: i64) -> i64 {
71 ui_emit_region_map_open(fd, selected, zoom)
72 ui_emit_page_close(fd)
73 return 0
74}
75
76// parse the integer value of `key` (e.g. "region=") from a query string; default if absent.
77func ui_qval(qs: *u8, key: *u8, deflt: i64) -> i64 {
78 var ql: i64 = 0; while qs[ql] != 0 as u8 { ql = ql + 1 }
79 var kl: i64 = 0; while key[kl] != 0 as u8 { kl = kl + 1 }
80 if kl == 0 { return deflt }
81 var i: i64 = 0
82 while i <= ql - kl {
83 var j: i64 = 0
84 var matched: i64 = 1
85 while j < kl { if qs[i + j] != key[j] { matched = 0; j = kl } else { j = j + 1 } }
86 if matched == 1 {
87 var p: i64 = i + kl
88 var got: i64 = 0
89 var val: i64 = 0
90 while p < ql {
91 let c: i64 = qs[p] as i64
92 if c < 0x30 { p = ql } else { if c > 0x39 { p = ql } else { val = val * 10 + (c - 0x30); got = 1; p = p + 1 } }
93 }
94 if got == 1 { return val }
95 return deflt
96 }
97 i = i + 1
98 }
99 return deflt
100}
101
102// does the query contain `key`? (1/0) -- lets a server tell a real click from a default load.
103func ui_qhas(qs: *u8, key: *u8) -> i64 {
104 var ql: i64 = 0; while qs[ql] != 0 as u8 { ql = ql + 1 }
105 var kl: i64 = 0; while key[kl] != 0 as u8 { kl = kl + 1 }
106 if kl == 0 { return 0 }
107 var i: i64 = 0
108 while i <= ql - kl {
109 var j: i64 = 0
110 var matched: i64 = 1
111 while j < kl { if qs[i + j] != key[j] { matched = 0; j = kl } else { j = j + 1 } }
112 if matched == 1 { return 1 }
113 i = i + 1
114 }
115 return 0
116}
117
118func ui_handle_query(fd: i64, qs: *u8) -> i64 {
119 let region: i64 = ui_qval(qs, "region=" as *u8, 1)
120 let zoom: i64 = ui_qval(qs, "zoom=" as *u8, 1)
121 return ui_emit_region_map(fd, region, zoom)
122}