code wiki / (root) / nx_ui_lib.nx

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> &nbsp; ") 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}