feat(ui): popovers, tooltips, progress; the render3d backend installs itself; duplicate names caught first
- `popover`: a top layer that keeps the pointer and the keyboard, closed by a press outside or Esc. - `title` tooltips after half a second's rest, styled by .ui-tooltip. - `<progress>` and `<meter>`. - Atlases take rows and number cells, and importing ludic.ui/render3d.ludic is enough to draw with render3d. - The compiler reports two declarations of one name before it type-checks, so a package global clashing with a program's reads as that, not as 29 type errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1dad96102a
commit
0a7525b125
24 changed files with 243 additions and 21 deletions
12
LANGUAGE.md
12
LANGUAGE.md
|
|
@ -446,14 +446,20 @@ applies to both:
|
|||
|
||||
Each reports with `on-change` and `event.value`, and plays `sound="..."` (or "click") through
|
||||
the program's `ui_sounds`.
|
||||
- **Popovers, tooltips and bars.**
|
||||
- `<div popover on-close="...">` sits absolutely in its parent and draws on the top layer. While
|
||||
it is up, the pointer and Tab stay inside it, and a press outside it or Esc closes it.
|
||||
- `title="..."` shows a tooltip (`.ui-tooltip`) after the pointer rests for half a second.
|
||||
- `<progress value max>` and `<meter value min max>` fill (`.ui-fill`) as far as their value.
|
||||
- **Natives** are for what markup cannot say. `ui_native("minimap", measure, draw)` makes
|
||||
`<minimap>` an element the program draws itself, laid out and styled like any other. It reports
|
||||
with `ui_fire(n, "change", value)` and reads its attributes with `ui_attr` / `ui_attr_on`.
|
||||
- **Renderers.** `ui_backend(b)` takes any renderer with `rect`, `text` and `measure`, and uses
|
||||
`round`, `ring`, `image`, `nine`, `clip`, `scale` and `now` when it has them.
|
||||
`import "ludic.ui/render3d.ludic"` and `ui_render3d()` draw with render3d's overlay:
|
||||
- images by path, cells of an atlas named with `ui_atlas(prefix, texture, cols, names)` as
|
||||
`<img src="prefix:name">`, and nine-slices for `border-image`;
|
||||
Importing `ludic.ui/render3d.ludic` makes render3d's overlay the renderer:
|
||||
- images by path, and cells of an atlas named with `ui_atlas(prefix, texture, cols, rows,
|
||||
names)` as `<img src="prefix:name">` (or `prefix:12`);
|
||||
- nine-slices for `border-image`;
|
||||
- a scale from the screen's height (`ui_render3d_scale` for the player's interface size).
|
||||
|
||||
`ui_translator(fn)`, `ui_sounds(fn)` and `ui_clock(fn)` hand the runtime the program's
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ The runtime is a UI framework, not only a template engine:
|
|||
- more CSS: `rgba()`/`#rrggbbaa`, `border-radius`, `outline`, `box-shadow`, `background-image`,
|
||||
`border-image`, group `opacity`, `@keyframes` / `animation` / `transition`;
|
||||
- HTML mixed content, and boolean attributes;
|
||||
- `popover` (a top layer that keeps the pointer and keys, with light dismissal), `title` tooltips,
|
||||
and `<progress>` / `<meter>`;
|
||||
- importing `ludic.ui/render3d.ludic` installs the backend, and atlases take rows;
|
||||
- hooks for the program's language, sounds and clock.
|
||||
|
||||
render3d gains `tex_width` / `tex_height`, and the XML reader keeps text runs among elements in
|
||||
|
|
@ -63,3 +66,6 @@ Also:
|
|||
- An index is evaluated before the slice's elements are read. A `xs[f()]` whose `f` grew `xs`
|
||||
read stale memory.
|
||||
- A runtime error names the file its expression is in, not the program's.
|
||||
|
||||
Two declarations with one name (a package's private global and a program's, say) are reported as
|
||||
such before type checking. They used to surface as a page of type errors about the wrong type.
|
||||
|
|
|
|||
59
examples/library/ui_popover.ludic
Normal file
59
examples/library/ui_popover.ludic
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# ui_popover.ludic — HTML's popover and progress in ludic.ui: a button opens a popover of verbs, the
|
||||
# keyboard stays inside it, a press outside or Esc closes it, and a progress bar fills to its value.
|
||||
import "ludic.ui"
|
||||
import "ui_popover_parts/Verbs.ludic"
|
||||
program UiPopover {
|
||||
numbers float
|
||||
function frame(i: UiInput) -> UiNode {
|
||||
ui_input(i)
|
||||
ui_show("Verbs", null, 0.0, 0.0, 400.0, 400.0)
|
||||
let root: UiNode = ui_nodes("Verbs", null)
|
||||
ui_place(root, 0.0, 0.0, 400.0, 400.0)
|
||||
return root
|
||||
}
|
||||
function click(x: float, y: float) -> void {
|
||||
let d = new UiInput
|
||||
d.x = x
|
||||
d.y = y
|
||||
d.down = true
|
||||
frame(d)
|
||||
let u = new UiInput
|
||||
u.x = x
|
||||
u.y = y
|
||||
frame(u)
|
||||
}
|
||||
function said(root: UiNode) -> string {
|
||||
let page = root.children[0]
|
||||
return page.children[len(page.children) - 1].text
|
||||
}
|
||||
entry {
|
||||
ui_backend(new UiBackend)
|
||||
var root = frame(new UiInput)
|
||||
let cell = root.children[0].children[0]
|
||||
click(cell.x + 2.0, cell.y + 2.0)
|
||||
root = frame(new UiInput)
|
||||
let tab = new UiInput
|
||||
tab.tab = true
|
||||
frame(tab)
|
||||
frame(tab)
|
||||
root = frame(tab)
|
||||
let enter = new UiInput
|
||||
enter.enter = true
|
||||
root = frame(enter)
|
||||
root = frame(new UiInput)
|
||||
let first = said(root)
|
||||
click(cell.x + 2.0, cell.y + 2.0)
|
||||
root = frame(new UiInput)
|
||||
let opened = said(root)
|
||||
click(390.0, 390.0)
|
||||
root = frame(new UiInput)
|
||||
let outside = said(root)
|
||||
click(cell.x + 2.0, cell.y + 2.0)
|
||||
let esc = new UiInput
|
||||
esc.escape = true
|
||||
frame(esc)
|
||||
root = frame(new UiInput)
|
||||
let bar = root.children[0].children[1]
|
||||
print(`{first} | {opened} | {outside} | {said(root)} | fill {int(bar.children[0].cw)} of {int(bar.cw)}`)
|
||||
}
|
||||
}
|
||||
3
examples/library/ui_popover_parts/Verbs.lss
Normal file
3
examples/library/ui_popover_parts/Verbs.lss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.page { width: 300px; height: 200px; gap: 4px }
|
||||
.verbs { top: 40px; left: 10px; width: 120px }
|
||||
progress { width: 200px }
|
||||
6
examples/library/ui_popover_parts/Verbs.ludic
Normal file
6
examples/library/ui_popover_parts/Verbs.ludic
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Verbs.ludic - a grid cell that opens a popover of verbs
|
||||
component Verbs {
|
||||
state open: bool = false
|
||||
state used: string = "none"
|
||||
state health: int = 30
|
||||
}
|
||||
11
examples/library/ui_popover_parts/Verbs.xml
Normal file
11
examples/library/ui_popover_parts/Verbs.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<div class="page">
|
||||
<button id="cell" on-click="set open = true">Axe</button>
|
||||
<if test="{open}">
|
||||
<div popover class="verbs" on-close="set open = false">
|
||||
<button on-click="set used = 'drop'; set open = false">Drop</button>
|
||||
<button on-click="set used = 'give'; set open = false">Give</button>
|
||||
</div>
|
||||
</if>
|
||||
<progress value="{health}" max="{100}"/>
|
||||
<p title="how much is left">{used} {open}</p>
|
||||
</div>
|
||||
|
|
@ -21,7 +21,21 @@ function ct_part(n: UiNode, cls: string, text: string) -> UiNode {
|
|||
}
|
||||
return p
|
||||
}
|
||||
# <progress value max> and <meter value min max>: a track with a fill as far as the value
|
||||
function ct_build_bar(n: UiNode) -> void {
|
||||
n.dir = UI_ROW
|
||||
let lo = Value.as_float(ui_attr(n, "min"))
|
||||
var hi = Value.as_float(ui_attr(n, "max"))
|
||||
if hi <= lo { hi = lo + 1.0 }
|
||||
let fill = ct_part(n, "ui-fill", "")
|
||||
fill.w_pct = Math.clamp((Value.as_float(ui_attr(n, "value")) - lo) / (hi - lo), 0.0, 1.0) * 100.0
|
||||
push(n.children, fill)
|
||||
}
|
||||
function ct_build(n: UiNode) -> void {
|
||||
if n.tag == "progress" or n.tag == "meter" {
|
||||
ct_build_bar(n)
|
||||
return
|
||||
}
|
||||
n.dir = UI_ROW
|
||||
n.focusable = true
|
||||
let t = ui_attr_text(n, "type")
|
||||
|
|
|
|||
|
|
@ -29,20 +29,20 @@ function fr_clip_pop() -> void {
|
|||
function fr_children(n: UiNode) -> void {
|
||||
var positioned = 0
|
||||
for i in 0 .. len(n.children) {
|
||||
if n.children[i].pos == 0 { fr_draw(n.children[i]) } else { positioned += 1 }
|
||||
if n.children[i].popover { push(pop_list, n.children[i]) } else if n.children[i].pos == 0 { fr_draw(n.children[i]) } else { positioned += 1 }
|
||||
}
|
||||
if positioned == 0 { return }
|
||||
var lo = 1000000
|
||||
var hi = -1000000
|
||||
for i in 0 .. len(n.children) {
|
||||
if n.children[i].pos != 0 {
|
||||
if n.children[i].pos != 0 and not n.children[i].popover {
|
||||
lo = Math.min(lo, n.children[i].z)
|
||||
hi = Math.max(hi, n.children[i].z)
|
||||
}
|
||||
}
|
||||
for z in lo .. hi + 1 {
|
||||
for i in 0 .. len(n.children) {
|
||||
if n.children[i].pos != 0 and n.children[i].z == z { fr_draw(n.children[i]) }
|
||||
if n.children[i].pos != 0 and not n.children[i].popover and n.children[i].z == z { fr_draw(n.children[i]) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# elem.ludic - an element made from its template: what kind of node its tag is, its id, classes
|
||||
# and attributes (kept for selectors, applied after every rule), its text, and its children
|
||||
const EL_BOXES: string = " col div section header footer nav main article aside ul ol li form box "
|
||||
const EL_BOXES: string = " col div progress meter section header footer nav main article aside ul ol li form box "
|
||||
const EL_TEXTS: string = " text span p label h1 h2 h3 h4 h5 h6 strong em small b i a option "
|
||||
function bd_elem(k: UiTpl, e: UiEnv, key: string) -> UiNode {
|
||||
ui_at = k.where
|
||||
|
|
@ -27,7 +27,7 @@ function bd_elem(k: UiTpl, e: UiEnv, key: string) -> UiNode {
|
|||
bd_kids(k.kids, e, key, n.children)
|
||||
if k.text != null { el_text(n, ui_tr(ui_eval_text(k.text, e))) }
|
||||
el_inline(n)
|
||||
if k.tag == "input" or k.tag == "select" { ct_build(n) }
|
||||
if k.tag == "input" or k.tag == "select" or k.tag == "progress" or k.tag == "meter" { ct_build(n) }
|
||||
return n
|
||||
}
|
||||
function el_kind(n: UiNode, tag: string) -> void {
|
||||
|
|
@ -61,6 +61,12 @@ function el_keep(n: UiNode, key: string, v: Val) -> void {
|
|||
for i in 0 .. len(parts) {
|
||||
if len(parts[i]) > 0 { push(n.classes, parts[i]) }
|
||||
}
|
||||
} else if key == "popover" {
|
||||
n.popover = true
|
||||
n.pos = 2
|
||||
push(n.akeys, key)
|
||||
push(n.avals, s)
|
||||
push(n.vals, v)
|
||||
} else if key == "tabindex" { n.focusable = s != "-1" } else if key == "hidden" { n.hidden = el_flag(v) } else if key == "disabled" { n.enabled = not el_flag(v) } else if key == "src" { n.src = s } else {
|
||||
if key == "enabled" { n.enabled = el_flag(v) }
|
||||
push(n.akeys, key)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ function fc_move(step: int) -> void {
|
|||
# the keys, after the tree is laid out: moving, then acting on what has the focus
|
||||
function fc_keys(root: UiNode) -> void {
|
||||
fc_list = new []UiNode
|
||||
fc_collect(root)
|
||||
let top = pp_top(root)
|
||||
if top != null and in_now.escape { ui_fire(top, "close", Value.null()) }
|
||||
if top != null { fc_collect(top) } else { fc_collect(root) }
|
||||
let i = in_now
|
||||
if fc_find() < 0 { fc_autofocus() }
|
||||
let at = fc_find()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ export function ui_show(screen: string, view: UiView, x: float, y: float, w: flo
|
|||
sc_update(root)
|
||||
fr_pointer(root)
|
||||
fc_keys(root)
|
||||
if ui_be != null and ui_be.text != null { fr_draw(root) }
|
||||
if ui_be != null and ui_be.text != null {
|
||||
fr_draw(root)
|
||||
pp_draw_top()
|
||||
tt_draw(root)
|
||||
}
|
||||
dv_after(screen, root)
|
||||
lc_run(screen)
|
||||
nt_fired_run()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import "controls_build.ludic"
|
|||
import "input.ludic"
|
||||
import "focus.ludic"
|
||||
import "scroll.ludic"
|
||||
import "popover.ludic"
|
||||
import "tooltip.ludic"
|
||||
import "life.ludic"
|
||||
import "tpl.ludic"
|
||||
import "tpl_lines.ludic"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# link.ludic - each tag in a file that is not an element names a component: the file's own, an
|
||||
# unnamed import's exported one, or `as:Name` from the import called `as`
|
||||
const LK_OWN: string = " row button input select textarea spacer scroll img hr if else each slot state style component screen import let fragment template provide #text "
|
||||
const LK_OWN: string = " row button input select textarea progress meter spacer scroll img hr if else each slot state style component screen import let fragment template provide #text "
|
||||
function lib_link(f: UiFile, t: UiTpl) -> void {
|
||||
t.sheet = f.sheet
|
||||
if not lk_element(t.tag) {
|
||||
|
|
|
|||
|
|
@ -103,5 +103,6 @@ export property UiNode {
|
|||
anim_loop: bool = false
|
||||
anim_alt: bool = false
|
||||
tr_dur: float = 0.0 # transition: how long a change of opacity takes
|
||||
popover: bool = false # drawn on the top layer; a press outside it or Esc closes it
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,14 @@ function fr_pointer(root: UiNode) -> void {
|
|||
let i = in_now
|
||||
fr_down_now = i.down
|
||||
let keys = new []string
|
||||
fr_under(root, i.x, i.y, keys)
|
||||
let top = pp_top(root)
|
||||
var scope = root
|
||||
if top != null { scope = top }
|
||||
fr_under(scope, i.x, i.y, keys)
|
||||
fr_hover = keys
|
||||
let hit = fr_hit(root, i.x, i.y)
|
||||
tt_track(scope, i.x, i.y)
|
||||
if top != null and pp_pointer(top) { return }
|
||||
let hit = fr_hit(scope, i.x, i.y)
|
||||
if i.down and not in_was_down {
|
||||
fr_press_key = ""
|
||||
if hit != null {
|
||||
|
|
|
|||
32
packages/ludic.ui/popover.ludic
Normal file
32
packages/ludic.ui/popover.ludic
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# popover.ludic - HTML's popover: `<div popover on-close="...">` sits where its position puts it
|
||||
# (absolutely, in its parent) and is drawn on the top layer, over everything. While one is up the
|
||||
# pointer and the keyboard are its: a press outside it, or Esc, closes it (on-close), and Tab
|
||||
# walks only its own controls.
|
||||
var pop_list: []UiNode = new []UiNode
|
||||
function pp_find(n: UiNode, out: []UiNode) -> void {
|
||||
if n.popover {
|
||||
push(out, n)
|
||||
return
|
||||
}
|
||||
for i in 0 .. len(n.children) { pp_find(n.children[i], out) }
|
||||
}
|
||||
# the topmost popover open in this tree, or null
|
||||
function pp_top(root: UiNode) -> UiNode {
|
||||
let all = new []UiNode
|
||||
pp_find(root, all)
|
||||
if len(all) == 0 { return null }
|
||||
return all[len(all) - 1]
|
||||
}
|
||||
# the pointer against the popover: inside it is as usual; a press outside closes it and is spent
|
||||
function pp_pointer(top: UiNode) -> bool {
|
||||
let i = in_now
|
||||
if not (i.down and not in_was_down) { return false }
|
||||
if i.x >= top.x and i.y >= top.y and i.x < top.x + top.cw and i.y < top.y + top.ch { return false }
|
||||
ui_fire(top, "close", Value.null())
|
||||
return true
|
||||
}
|
||||
function pp_draw_top() -> void {
|
||||
let list = pop_list
|
||||
pop_list = new []UiNode
|
||||
for i in 0 .. len(list) { fr_draw(list[i]) }
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# render3d.ludic - ludic.ui drawn with ludic.render3d's overlay. A program that draws with render3d
|
||||
# imports this and calls ui_render3d() once; its templates then draw in the overlay pass, sized in
|
||||
# imports this, and its templates draw in the overlay pass (ui_render3d() installs it again), sized in
|
||||
# design pixels of a 1080-line screen (ui_render3d_scale sets the player's own interface size).
|
||||
# <img src="path/to/picture.png"> a texture, loaded once
|
||||
# <img src="icon:tent"> a cell of an atlas the program named with ui_atlas
|
||||
|
|
@ -12,7 +12,14 @@ var r3u_tex_ids: []int = new []int
|
|||
var r3u_atlas: []string = new []string
|
||||
var r3u_atlas_tex: []int = new []int
|
||||
var r3u_atlas_cols: []int = new []int
|
||||
var r3u_atlas_rows: []int = new []int
|
||||
var r3u_atlas_names: [][]string = new [][]string
|
||||
var r3u_installed: bool = r3u_install()
|
||||
# importing this file is enough: it is the renderer from the program's first frame
|
||||
function r3u_install() -> bool {
|
||||
ui_render3d()
|
||||
return true
|
||||
}
|
||||
export function ui_render3d() -> void {
|
||||
let b = new UiBackend
|
||||
b.rect = fn r3u_rect
|
||||
|
|
@ -30,11 +37,13 @@ export function ui_render3d() -> void {
|
|||
}
|
||||
# the player's interface size, 1.0 as designed
|
||||
export function ui_render3d_scale(s: float) -> void { r3u_user = s }
|
||||
# a grid atlas: `names` are its cells, row by row, `cols` to a row; <img src="prefix:name"> draws one
|
||||
export function ui_atlas(prefix: string, tex: int, cols: int, names: []string) -> void {
|
||||
# a grid atlas: `names` are its cells, row by row, `cols` across and `rows` down; <img
|
||||
# src="prefix:name"> draws one (by its name, or by its number: "item:12")
|
||||
export function ui_atlas(prefix: string, tex: int, cols: int, rows: int, names: []string) -> void {
|
||||
push(r3u_atlas, prefix)
|
||||
push(r3u_atlas_tex, tex)
|
||||
push(r3u_atlas_cols, cols)
|
||||
push(r3u_atlas_rows, Math.max(rows, 1))
|
||||
push(r3u_atlas_names, names)
|
||||
}
|
||||
function r3u_now() -> float { return float(Time.now_us() / 1000) / 1000.0 }
|
||||
|
|
|
|||
|
|
@ -37,17 +37,19 @@ function r3u_cell(k: int, name: string, x: float, y: float, w: float, h: float,
|
|||
for i in 0 .. len(names) {
|
||||
if names[i] == name { idx = i }
|
||||
}
|
||||
if idx < 0 and len(name) > 0 and name[0] >= 48 and name[0] <= 57 { idx = int(el_num(name)) }
|
||||
if idx < 0 {
|
||||
ui_err(`{r3u_atlas[k]}:{name}: the atlas has no such picture`)
|
||||
return
|
||||
}
|
||||
let cols = r3u_atlas_cols[k]
|
||||
let rows = r3u_atlas_rows[k]
|
||||
let cx = idx % cols
|
||||
let cy = idx / cols
|
||||
let u0 = float(cx) / float(cols)
|
||||
let v0 = float(cy) / float(cols)
|
||||
let v0 = float(cy) / float(rows)
|
||||
let s = Math.min(w, h)
|
||||
ov_sub(r3u_atlas_tex[k], int(x + (w - s) / 2.0), int(y + (h - s) / 2.0), int(s), int(s), u0, v0, u0 + 1.0 / float(cols), v0 + 1.0 / float(cols), r3u_r(c), r3u_g(c), r3u_b(c), a)
|
||||
ov_sub(r3u_atlas_tex[k], int(x + (w - s) / 2.0), int(y + (h - s) / 2.0), int(s), int(s), u0, v0, u0 + 1.0 / float(cols), v0 + 1.0 / float(rows), r3u_r(c), r3u_g(c), r3u_b(c), a)
|
||||
}
|
||||
# border-image: the texture's `slice`-pixel corners kept, the rest stretched, drawn `dst` wide
|
||||
function r3u_nine(src: string, slice: float, x: float, y: float, w: float, h: float, dst: float, c: int, a: float) -> void {
|
||||
|
|
|
|||
47
packages/ludic.ui/tooltip.ludic
Normal file
47
packages/ludic.ui/tooltip.ludic
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# tooltip.ludic - an element's title="..." shown beside the pointer once it has rested on the
|
||||
# element for half a second, in a box styled by .ui-tooltip (the default sheet's, or a theme's)
|
||||
var tt_key: string = ""
|
||||
var tt_since: float = 0.0
|
||||
var tt_text: string = ""
|
||||
var tt_x: float = 0.0
|
||||
var tt_y: float = 0.0
|
||||
function tt_track(scope: UiNode, x: float, y: float) -> void {
|
||||
let n = tt_under(scope, x, y)
|
||||
if n == null {
|
||||
tt_key = ""
|
||||
return
|
||||
}
|
||||
if n.key != tt_key {
|
||||
tt_key = n.key
|
||||
tt_since = an_now()
|
||||
}
|
||||
tt_text = ui_tr(ui_attr_text(n, "title"))
|
||||
tt_x = x
|
||||
tt_y = y
|
||||
}
|
||||
# the deepest element under the point that has a title
|
||||
function tt_under(n: UiNode, x: float, y: float) -> UiNode {
|
||||
if x < n.x or y < n.y or x >= n.x + n.cw or y >= n.y + n.ch { return null }
|
||||
var i = len(n.children) - 1
|
||||
while i >= 0 {
|
||||
let t = tt_under(n.children[i], x, y)
|
||||
if t != null { return t }
|
||||
i -= 1
|
||||
}
|
||||
if n.akeys != null and ui_attr_text(n, "title") != "" { return n }
|
||||
return null
|
||||
}
|
||||
function tt_draw(root: UiNode) -> void {
|
||||
if tt_key == "" or tt_text == "" or an_now() - tt_since < 0.5 { return }
|
||||
let n = ct_part(root, "ui-tooltip", "")
|
||||
n.tag = "div"
|
||||
n.parent = root
|
||||
n.children = new []UiNode
|
||||
push(n.children, ct_part(root, "ui-tooltip-text", tt_text))
|
||||
cs_tree(n)
|
||||
ly_measure(n, ly_vp_w * 0.4, ui_px(16.0), root.rfg)
|
||||
let x = Math.min(tt_x + ui_px(16.0), ly_vp_x + ly_vp_w - n.mw)
|
||||
let y = Math.min(tt_y + ui_px(20.0), ly_vp_y + ly_vp_h - n.mh)
|
||||
ly_place(n, x, y, n.mw, n.mh)
|
||||
fr_draw(n)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# ua.ludic - what every element looks like before a program styles it, as a browser's own sheet
|
||||
# does: headings sized, buttons and controls usable and plain, a focus ring. A theme overrides any
|
||||
# of it, with the same selectors or with the variables it reads.
|
||||
const UA_LSS: string = "h1 { font-size: 32 } h2 { font-size: 26 } h3 { font-size: 22 } h4 { font-size: 19 } h5 { font-size: 16 } h6 { font-size: 14 } small { font-size: 13 } hr { height: 1; width: fill; background: #808080; margin: 4 0 } scroll { width: fill; height: fill } img { width: 24; height: 24 } button { align-items: center; justify-content: center; gap: 8; padding: 6 14; background: #3a3f46; border-radius: 4 } button:hover { background: #4a5058 } button:disabled { opacity: 0.4 } :focus-visible { outline: 2px solid #ffcc55; outline-offset: 2 } input, select { align-items: center; gap: 10; padding: 6 10 } .ui-label { flex-grow: 1 } .ui-track { width: 44; height: 22; padding: 3; background: #555a60; border-radius: 11 } .ui-knob { width: 16; height: 16; background: #eeeeee; border-radius: 8 } input:checked .ui-track { justify-content: end; background: #d9a54a } input[type=range] .ui-track { width: 200; height: 8; padding: 0; border-radius: 4 } .ui-fill { height: fill; background: #d9a54a; justify-content: end; align-items: center; border-radius: 4 } .ui-thumb { width: 14; height: 14; background: #eeeeee; border-radius: 7; margin-right: -7 } .ui-value { min-width: 40 } .ui-field { width: 200; padding: 4 8; background: #202428; border: 1px solid #555a60 } .ui-caret { width: 2; height: 1.2em; background: #eeeeee } .ui-prev, .ui-next { color: #aaaaaa }"
|
||||
const UA_LSS: string = "h1 { font-size: 32 } h2 { font-size: 26 } h3 { font-size: 22 } h4 { font-size: 19 } h5 { font-size: 16 } h6 { font-size: 14 } small { font-size: 13 } hr { height: 1; width: fill; background: #808080; margin: 4 0 } scroll { width: fill; height: fill } img { width: 24; height: 24 } button { align-items: center; justify-content: center; gap: 8; padding: 6 14; background: #3a3f46; border-radius: 4 } button:hover { background: #4a5058 } button:disabled { opacity: 0.4 } :focus-visible { outline: 2px solid #ffcc55; outline-offset: 2 } input, select { align-items: center; gap: 10; padding: 6 10 } .ui-label { flex-grow: 1 } .ui-track { width: 44; height: 22; padding: 3; background: #555a60; border-radius: 11 } .ui-knob { width: 16; height: 16; background: #eeeeee; border-radius: 8 } input:checked .ui-track { justify-content: end; background: #d9a54a } input[type=range] .ui-track { width: 200; height: 8; padding: 0; border-radius: 4 } .ui-fill { height: fill; background: #d9a54a; justify-content: end; align-items: center; border-radius: 4 } .ui-thumb { width: 14; height: 14; background: #eeeeee; border-radius: 7; margin-right: -7 } .ui-value { min-width: 40 } .ui-field { width: 200; padding: 4 8; background: #202428; border: 1px solid #555a60 } .ui-caret { width: 2; height: 1.2em; background: #eeeeee } .ui-prev, .ui-next { color: #aaaaaa } progress, meter { width: 160; height: 10; background: #202428; border-radius: 5 } progress .ui-fill, meter .ui-fill { justify-content: start; border-radius: 5 } [popover] { position: absolute; z-index: 900 } .ui-tooltip { position: fixed; padding: 6 10; background: #202428; border: 1px solid #555a60; font-size: 15; z-index: 1000 }"
|
||||
|
|
|
|||
|
|
@ -12771,7 +12771,7 @@ declare void @hs_free(i32)
|
|||
@.str12493 = private unnamed_addr constant [2 x i8] c" \00"
|
||||
@.str12494 = private unnamed_addr constant [50 x i8] c" -Wl,/subsystem:windows -Wl,/entry:mainCRTStartup\00"
|
||||
@.str12495 = private unnamed_addr constant [3 x i8] c" \22\00"
|
||||
@.str12496 = private unnamed_addr constant [55 x i8] c"build/cfree/selfhost.ludic:17806: index out of range: \00"
|
||||
@.str12496 = private unnamed_addr constant [55 x i8] c"build/cfree/selfhost.ludic:17808: index out of range: \00"
|
||||
@.str12497 = private unnamed_addr constant [2 x i8] c"\22\00"
|
||||
@.str12498 = private unnamed_addr constant [5 x i8] c" -o \00"
|
||||
@.str12499 = private unnamed_addr constant [2 x i8] c"\22\00"
|
||||
|
|
@ -134235,6 +134235,8 @@ ifend89:
|
|||
br label %ifend86
|
||||
ifend86:
|
||||
store i32 0, ptr @g_g_parsing
|
||||
call void @fn_check_duplicate_fns()
|
||||
call void @fn_check_duplicate_decls()
|
||||
call void @fn_check_program()
|
||||
call void @fn_emit_program()
|
||||
%t230 = load i32, ptr %t3
|
||||
|
|
|
|||
|
|
@ -13088,7 +13088,7 @@ entry:
|
|||
@.str12493 = private unnamed_addr constant [2 x i8] c" \00"
|
||||
@.str12494 = private unnamed_addr constant [50 x i8] c" -Wl,/subsystem:windows -Wl,/entry:mainCRTStartup\00"
|
||||
@.str12495 = private unnamed_addr constant [3 x i8] c" \22\00"
|
||||
@.str12496 = private unnamed_addr constant [55 x i8] c"build/cfree/selfhost.ludic:17806: index out of range: \00"
|
||||
@.str12496 = private unnamed_addr constant [55 x i8] c"build/cfree/selfhost.ludic:17808: index out of range: \00"
|
||||
@.str12497 = private unnamed_addr constant [2 x i8] c"\22\00"
|
||||
@.str12498 = private unnamed_addr constant [5 x i8] c" -o \00"
|
||||
@.str12499 = private unnamed_addr constant [2 x i8] c"\22\00"
|
||||
|
|
@ -134538,6 +134538,8 @@ ifend89:
|
|||
br label %ifend86
|
||||
ifend86:
|
||||
store i32 0, ptr @g_g_parsing
|
||||
call void @fn_check_duplicate_fns()
|
||||
call void @fn_check_duplicate_decls()
|
||||
call void @fn_check_program()
|
||||
call void @fn_emit_program()
|
||||
%t230 = load i32, ptr %t3
|
||||
|
|
|
|||
|
|
@ -203,6 +203,8 @@ entry {
|
|||
else { g_windowed = has_target and has_systems() }
|
||||
|
||||
g_parsing = false # lowering errors now locate by statement, not token
|
||||
check_duplicate_fns() # two declarations of one name: the cause, before its symptoms
|
||||
check_duplicate_decls()
|
||||
check_program() # L4: the types agree before anything is emitted
|
||||
emit_program()
|
||||
|
||||
|
|
|
|||
|
|
@ -759,6 +759,7 @@ function cmd_dev_test() -> int {
|
|||
reject_case("rejected/component_no_template", "there is no Lonely.xml beside it", "a component needs its template beside it")
|
||||
controller_case("library/ui_controls", "", "1 false 7 2 Adalove 70 1 false 8 2 Adalove 70; scrolled 40", "ui_controls.ludic (L11: focus and keys, the built-in controls, a range dragged, a box scrolled)")
|
||||
controller_case("library/ui_anim", "", "0 100 100 | 49 50 100 | 100 100 61", "ui_anim.ludic (L11: @keyframes and animation, looping and alternating, and a transition)")
|
||||
controller_case("library/ui_popover", "", "drop false | drop true | drop false | drop false | fill 60 of 200", "ui_popover.ludic (L11: a popover keeps the keys and closes on a press outside or Esc; progress fills)")
|
||||
reject_case("rejected/view_untyped", "view Sums: say what type total is", "a view field that is not a bare global says its type")
|
||||
reject_case("rejected/view_without_ui", "a view is shown by ludic.ui", "a view needs ludic.ui to show it")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue