feat(ui): linear-gradient backgrounds, object-fit and aspect-ratio
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
abf45ef9db
commit
7605a0253c
18 changed files with 248 additions and 4 deletions
|
|
@ -532,6 +532,13 @@ applies to both:
|
|||
- **The look is CSS.**
|
||||
- Colours: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()`, `rgba()` and the basic names,
|
||||
usually through `var(--...)` from a theme.
|
||||
- `background` (or `background-image`) may be `linear-gradient(to right, #000, rgba(0,0,0,0) 80%)`:
|
||||
`to top|bottom|left|right` or an angle taken to the nearest side, and stops with or without a
|
||||
place. It is drawn as whole-pixel bands with square corners.
|
||||
- `aspect-ratio: 16 / 9` (or `1.5`) makes an element's height from its width (fixed, a share or
|
||||
`fill`), or its width from a fixed height. `object-fit: fill|contain|cover|none|scale-down`
|
||||
places a picture in its box by the picture's own size (a renderer's `image_w` / `image_h`;
|
||||
`cover` is clipped to the box); a native places its own content with `ui_object_fit(n, w, h)`.
|
||||
- Boxes: `border-radius`, `box-shadow` (sharp, offset), `background-image: url(...)`, and
|
||||
`border-image: url(...) slice / width` as a nine-slice. With no background colour a nine-slice
|
||||
is drawn as painted, as CSS draws one; with one it is tinted by it, so one rounded texture serves
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ What a game's screens found missing, now in `ludic.ui`:
|
|||
pixels (`ui_nine_cuts`), so a small key cap has no seam;
|
||||
- `scroll-top="{px}"` holds a scroll box at an offset, with `on-scroll` when the player moves it;
|
||||
`ui_scroll_set(id, px)` moves one once;
|
||||
- `linear-gradient(...)` backgrounds; `aspect-ratio`; `object-fit` for pictures (the renderer's
|
||||
`image_w` / `image_h`) and `ui_object_fit` for natives;
|
||||
- a transition lands exactly on its end value (it had stopped a rounding error short of it, at every
|
||||
frame rate).
|
||||
|
||||
|
|
|
|||
39
examples/library/ui_fill.ludic
Normal file
39
examples/library/ui_fill.ludic
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# ui_fill.ludic — linear-gradient backgrounds (a direction or an angle, stops placed or spread) drawn
|
||||
# as whole-pixel bands; object-fit contain and cover for a picture of its own size; aspect-ratio
|
||||
# making a height from a width, and a native's width from its height.
|
||||
import "ludic.ui"
|
||||
program UiFill {
|
||||
numbers float
|
||||
var rects: []string = new []string
|
||||
var pics: []string = new []string
|
||||
var clips: int = 0
|
||||
const STYLE: string = "<style>.g { width: 100px; height: 20px; background: linear-gradient(to right, #000000, #ffffff) }\n.h { width: 50px; height: 40px; background-image: linear-gradient(180deg, #ff0000 0%, #0000ff 50%, #0000ff) }\nimg { width: 100px; height: 100px }\n.contain { object-fit: contain }\n.cover { object-fit: cover }\n.r { width: 160px; aspect-ratio: 16 / 9 }\nknob { height: 30px; aspect-ratio: 2 }</style>"
|
||||
const BODY: string = "<screen name='f'><div class='g'/><div class='h'/><img class='contain' src='wide.png'/><img class='cover' src='wide.png'/><div class='r'/><row><knob/></row></screen>"
|
||||
function rec_rect(x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(rects, `{int(x)},{int(y)} {int(w)}x{int(h)} {ui_hex(c)}`) }
|
||||
function rec_image(src: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void { push(pics, `{int(x)},{int(y)} {int(w)}x{int(h)}`) }
|
||||
function rec_clip(x: float, y: float, w: float, h: float) -> void { clips += 1 }
|
||||
function no_clip() -> void { }
|
||||
function no_text(x: float, y: float, s: string, size: float, c: int, a: float) -> void { }
|
||||
function nat_w(src: string) -> float { return 200.0 }
|
||||
function nat_h(src: string) -> float { return 100.0 }
|
||||
function knob_measure(n: UiNode, avail: float) -> void { }
|
||||
function knob_draw(n: UiNode) -> void { }
|
||||
entry {
|
||||
let b = new UiBackend
|
||||
b.rect = fn rec_rect
|
||||
b.text = fn no_text
|
||||
b.image = fn rec_image
|
||||
b.image_w = fn nat_w
|
||||
b.image_h = fn nat_h
|
||||
b.clip = fn rec_clip
|
||||
b.unclip = fn no_clip
|
||||
ui_backend(b)
|
||||
ui_native("knob", fn knob_measure, fn knob_draw)
|
||||
ui_load_text("<ui>" + STYLE + BODY + "</ui>", "f.xml")
|
||||
ui_show("f", null, 0.0, 0.0, 400.0, 600.0)
|
||||
let r: UiNode = ui_nodes("f", null)
|
||||
ui_place(r, 0.0, 0.0, 400.0, 600.0)
|
||||
let n = len(rects)
|
||||
print(`{rects[0]} .. {rects[49]} | {rects[50]} .. {rects[59]} .. {rects[69]} of {n} | {pics[0]} | {pics[1]} clipped {clips} | ratio {int(r.children[4].ch)} knob {int(r.children[5].children[0].cw)}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +49,7 @@ function el_look(n: UiNode, key: string, v: Val) -> bool {
|
|||
n.fg = ui_color(s)
|
||||
n.fg_a = cl_alpha
|
||||
} else if key == "bg" {
|
||||
if len(s) > 4 and s[0..4] == "url(" { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" {
|
||||
if gr_is(tpl_words(s)) { nx(n).grad = gr_parse(s) } else if len(s) > 4 and s[0..4] == "url(" { n.bg_img = el_url(s) } else if tpl_words(s) == "transparent" {
|
||||
n.bg = 0
|
||||
n.bg_a = 0.0
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ function el_edge(n: UiNode, key: string, v: Val) -> bool {
|
|||
let parts = lss_split(s, 32)
|
||||
if len(parts) > 0 { n.bar_c = ui_color(parts[0]) }
|
||||
if len(parts) > 1 { n.bar_track = ui_color(parts[1]) }
|
||||
} else if key == "background-image" { n.bg_img = el_url(s) } else if key == "border-image" { el_border_image(n, s) } else if key == "box-shadow" { el_shadow(n, s) } else if key == "animation" { el_animation(n, s) } else if key == "transition" { n.tr_dur = el_secs(s) } else { return false }
|
||||
} else if key == "background-image" and gr_is(tpl_words(s)) { nx(n).grad = gr_parse(s) } else if key == "background-image" { n.bg_img = el_url(s) } else if key == "border-image" { el_border_image(n, s) } else if key == "box-shadow" { el_shadow(n, s) } else if key == "animation" { el_animation(n, s) } else if key == "transition" { n.tr_dur = el_secs(s) } else { return false }
|
||||
return true
|
||||
}
|
||||
# url(path) or url("path") -> path
|
||||
|
|
|
|||
33
packages/ludic.ui/attr_fit.ludic
Normal file
33
packages/ludic.ui/attr_fit.ludic
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# attr_fit.ludic - aspect-ratio (16 / 9, or 1.5) and object-fit (fill, contain, cover, none,
|
||||
# scale-down) for pictures and natives, and a list split at its top-level commas
|
||||
function el_fit_props(n: UiNode, key: string, s: string) -> bool {
|
||||
if key == "aspect-ratio" {
|
||||
let parts = lss_split(s, 47)
|
||||
var r = el_num(tpl_words(parts[0]))
|
||||
if len(parts) > 1 and el_num(tpl_words(parts[1])) > 0.0 { r = r / el_num(tpl_words(parts[1])) }
|
||||
if tpl_words(s) == "auto" { r = 0.0 }
|
||||
nx(n).ratio = Math.max(r, 0.0)
|
||||
} else if key == "object-fit" {
|
||||
let w = tpl_words(s)
|
||||
var k = 0
|
||||
if w == "contain" { k = 1 } else if w == "cover" { k = 2 } else if w == "none" { k = 3 } else if w == "scale-down" { k = 4 }
|
||||
nx(n).fit_obj = k
|
||||
} else { return false }
|
||||
return true
|
||||
}
|
||||
# "a, rgba(1, 2, 3, 0.5) 40%, b" -> its three parts
|
||||
function el_commas(s: string) -> []string {
|
||||
let out = new []string
|
||||
var at = 0
|
||||
var depth = 0
|
||||
for i in 0 .. len(s) {
|
||||
if s[i] == 40 { depth += 1 }
|
||||
if s[i] == 41 { depth -= 1 }
|
||||
if s[i] == 44 and depth == 0 {
|
||||
push(out, s[at..i])
|
||||
at = i + 1
|
||||
}
|
||||
}
|
||||
push(out, s[at..len(s)])
|
||||
return out
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# shorthand, flex-shrink, order, text-shadow, pointer-events, text-fit and line-height (text_fit.ludic)
|
||||
function el_more(n: UiNode, key: string, v: Val) -> bool {
|
||||
let s = vl_text(v)
|
||||
if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(n, s) } else if key == "pointer-events" { nx(n).no_pointer = tpl_words(s) == "none" } else if key == "text-fit" { el_text_fit(n, s) } else if key == "line-height" { el_line_h(n, s) } else { return false }
|
||||
if key == "shrink" { nx(n).shrink = Math.max(el_num(s), 0.0) } else if key == "order" { nx(n).order = int(el_num(s)) } else if key == "text-shadow" { el_text_shadow(n, s) } else if key == "pointer-events" { nx(n).no_pointer = tpl_words(s) == "none" } else if key == "text-fit" { el_text_fit(n, s) } else if key == "line-height" { el_line_h(n, s) } else { return el_fit_props(n, key, s) }
|
||||
return true
|
||||
}
|
||||
# width or height: a length (0 included), fill, a percentage, calc(), or auto/fit (none)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ export property UiBackend {
|
|||
text: fn(float, float, string, float, int, float) -> void = null # x y text size rgb alpha
|
||||
measure: fn(string, float) -> float = null
|
||||
image: fn(string, float, float, float, float, int, float) -> void = null # src x y w h tint alpha
|
||||
image_w: fn(string) -> float = null # a picture's own size, for object-fit
|
||||
image_h: fn(string) -> float = null
|
||||
nine: fn(string, float, float, float, float, float, float, int, float) -> void = null # src slice x y w h width tint alpha (ui_nine_cuts)
|
||||
clip: fn(float, float, float, float) -> void = null
|
||||
unclip: fn() -> void = null
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ function fr_background(n: UiNode) -> void {
|
|||
}
|
||||
if a > 0.0 { fr_fill(n, n.x, n.y, c, a) }
|
||||
}
|
||||
if n.ex != null and n.ex.grad != null { gr_draw(n.ex.grad, n.x, n.y, n.cw, n.ch, fr_ga) }
|
||||
if n.bg_img != "" and ui_be.image != null { ui_be.image(n.bg_img, n.x, n.y, n.cw, n.ch, 16777215, fr_ga) }
|
||||
}
|
||||
function fr_fill(n: UiNode, x: float, y: float, c: int, a: float) -> void {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,17 @@ function fr_image(n: UiNode) -> void {
|
|||
c = n.rfg
|
||||
a = fr_ga * n.rfg_a
|
||||
}
|
||||
ui_be.image(n.src, n.x + n.pl + n.bl, n.y + n.pt + n.bt, n.cw - ly_hin(n), n.ch - ly_vin(n), c, a)
|
||||
var nat_w = 0.0
|
||||
var nat_h = 0.0
|
||||
if ui_be.image_w != null and ui_be.image_h != null {
|
||||
nat_w = ui_be.image_w(n.src)
|
||||
nat_h = ui_be.image_h(n.src)
|
||||
}
|
||||
let r = ui_object_fit(n, nat_w, nat_h)
|
||||
let clipped = ui_be.clip != null and (r[2] > n.cw - ly_hin(n) or r[3] > n.ch - ly_vin(n))
|
||||
if clipped { fr_clip_push(n.x + n.pl + n.bl, n.y + n.pt + n.bt, n.cw - ly_hin(n), n.ch - ly_vin(n)) }
|
||||
ui_be.image(n.src, r[0], r[1], r[2], r[3], c, a)
|
||||
if clipped { fr_clip_pop() }
|
||||
}
|
||||
# prefix:name, and not a path (C:/... is a drive, not an atlas)
|
||||
function fr_is_cell(src: string) -> bool {
|
||||
|
|
|
|||
86
packages/ludic.ui/draw_gradient.ludic
Normal file
86
packages/ludic.ui/draw_gradient.ludic
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# draw_gradient.ludic - background: linear-gradient(to bottom, #102030, rgba(0,0,0,0) 80%): colour
|
||||
# stops (a stop without a place is spread evenly), along `to top|bottom|left|right` or an angle
|
||||
# (0deg is up, 90deg right, taken to the nearest side); drawn as whole-pixel bands, square-cornered
|
||||
export property UiGradient {
|
||||
dir: int = 0 # 0 down, 1 up, 2 right, 3 left
|
||||
cols: []int = null
|
||||
alphas: []float = null
|
||||
at: []float = null # each stop's place, 0 to 1
|
||||
}
|
||||
function gr_is(s: string) -> bool { return len(s) > 16 and s[0..16] == "linear-gradient(" }
|
||||
function gr_parse(s0: string) -> UiGradient {
|
||||
let s = tpl_words(s0)
|
||||
let parts = el_commas(s[16..len(s) - 1])
|
||||
let g = new UiGradient
|
||||
g.cols = new []int
|
||||
g.alphas = new []float
|
||||
g.at = new []float
|
||||
var from = 0
|
||||
if len(parts) > 0 and gr_dir(g, tpl_words(parts[0])) { from = 1 }
|
||||
for i in from .. len(parts) {
|
||||
let words = el_words(tpl_words(parts[i]))
|
||||
var col = tpl_words(parts[i])
|
||||
var at = -1.0
|
||||
let last = words[len(words) - 1]
|
||||
if len(words) > 1 and last[len(last) - 1] == 37 {
|
||||
at = el_num(last) / 100.0
|
||||
col = tpl_words(col[0..len(col) - len(last)])
|
||||
}
|
||||
push(g.cols, ui_color(col))
|
||||
push(g.alphas, cl_alpha)
|
||||
push(g.at, at)
|
||||
}
|
||||
for i in 0 .. len(g.at) {
|
||||
if g.at[i] < 0.0 { g.at[i] = float(i) / float(Math.max(len(g.at) - 1, 1)) }
|
||||
}
|
||||
return g
|
||||
}
|
||||
function gr_dir(g: UiGradient, w: string) -> bool {
|
||||
if w == "to bottom" { g.dir = 0 } else if w == "to top" { g.dir = 1 } else if w == "to right" { g.dir = 2 } else if w == "to left" { g.dir = 3 } else if len(w) > 3 and w[len(w) - 3..len(w)] == "deg" {
|
||||
let d = int(Math.round(el_num(w) / 90.0)) % 4
|
||||
g.dir = 1
|
||||
if d == 1 or d == -3 { g.dir = 2 }
|
||||
if d == 2 or d == -2 { g.dir = 0 }
|
||||
if d == 3 or d == -1 { g.dir = 3 }
|
||||
} else { return false }
|
||||
return true
|
||||
}
|
||||
# the gradient over the box, a band every two pixels at most
|
||||
function gr_draw(g: UiGradient, x: float, y: float, w: float, h: float, a: float) -> void {
|
||||
if ui_be.rect == null or len(g.cols) == 0 { return }
|
||||
let across = g.dir >= 2
|
||||
var span = h
|
||||
if across { span = w }
|
||||
let bands = Math.clamp(int(span / 2.0), 1, 64)
|
||||
for i in 0 .. bands {
|
||||
let e0 = Math.floor(span * float(i) / float(bands) + 0.5)
|
||||
let e1 = Math.floor(span * float(i + 1) / float(bands) + 0.5)
|
||||
var t = (e0 + e1) / 2.0 / span
|
||||
if g.dir == 1 or g.dir == 3 { t = 1.0 - t }
|
||||
let c = gr_at(g, t)
|
||||
if across { ui_be.rect(x + e0, y, e1 - e0, h, c, gr_a * a) } else { ui_be.rect(x, y + e0, w, e1 - e0, c, gr_a * a) }
|
||||
}
|
||||
}
|
||||
var gr_a: float = 1.0
|
||||
# the colour at `t` between the stops around it, its alpha in gr_a
|
||||
function gr_at(g: UiGradient, t: float) -> int {
|
||||
var k = 0
|
||||
while k + 1 < len(g.at) and g.at[k + 1] < t { k += 1 }
|
||||
let j = Math.min(k + 1, len(g.at) - 1)
|
||||
var f = 0.0
|
||||
if g.at[j] > g.at[k] { f = Math.clamp((t - g.at[k]) / (g.at[j] - g.at[k]), 0.0, 1.0) }
|
||||
if t <= g.at[0] { f = 0.0 }
|
||||
gr_a = g.alphas[k] + (g.alphas[j] - g.alphas[k]) * f
|
||||
return cl_mix(g.cols[k], g.cols[j], f)
|
||||
}
|
||||
function cl_mix(a: int, b: int, f: float) -> int {
|
||||
var out = 0
|
||||
var shift = 65536
|
||||
for i in 0 .. 3 {
|
||||
let ca = a / shift % 256
|
||||
let cb = b / shift % 256
|
||||
out = out + int(Math.round(float(ca) + float(cb - ca) * f)) * shift
|
||||
shift = shift / 256
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ import "attr_box.ludic"
|
|||
import "attr_border.ludic"
|
||||
import "attr_place.ludic"
|
||||
import "attr_inset.ludic"
|
||||
import "attr_fit.ludic"
|
||||
import "attr_edge.ludic"
|
||||
import "attr_more.ludic"
|
||||
import "calc.ludic"
|
||||
|
|
@ -74,6 +75,7 @@ import "layout_box.ludic"
|
|||
import "layout_shrink.ludic"
|
||||
import "layout_text.ludic"
|
||||
import "text_fit.ludic"
|
||||
import "layout_fit.ludic"
|
||||
import "layout_run.ludic"
|
||||
import "layout_wrap.ludic"
|
||||
import "frame.ludic"
|
||||
|
|
@ -85,4 +87,5 @@ import "draw.ludic"
|
|||
import "draw_content.ludic"
|
||||
import "draw_order.ludic"
|
||||
import "draw_nine.ludic"
|
||||
import "draw_gradient.ludic"
|
||||
import "dump.ludic"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ function ly_measure(n: UiNode, avail: float, size: float, fg: int) -> void {
|
|||
n.content_h = n.mh
|
||||
if fixed_w >= 0.0 { n.mw = fixed_w }
|
||||
if n.h >= 0.0 { n.mh = n.h }
|
||||
ly_ratio(n, fixed_w, avail)
|
||||
n.mw = ly_clamp(n.mw, n.min_w, n.max_w)
|
||||
n.mh = ly_clamp(n.mh, n.min_h, n.max_h)
|
||||
ly_min_note(n)
|
||||
|
|
|
|||
38
packages/ludic.ui/layout_fit.ludic
Normal file
38
packages/ludic.ui/layout_fit.ludic
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# layout_fit.ludic - aspect-ratio in layout: a width (fixed, a share, or fill) makes the height, a
|
||||
# height the width, and neither the height from the content's width; object-fit in drawing: where a
|
||||
# picture (or a native's content) of a natural size goes in the element's content box
|
||||
function ly_ratio(n: UiNode, fixed_w: float, avail: float) -> void {
|
||||
if n.ex == null or n.ex.ratio <= 0.0 { return }
|
||||
let r = n.ex.ratio
|
||||
if n.h < 0.0 and n.h_pct <= 0.0 {
|
||||
var w = n.mw
|
||||
if fixed_w >= 0.0 { w = fixed_w } else if n.w == UI_FILL { w = avail - n.ml - n.mr }
|
||||
n.mw = w
|
||||
n.mh = w / r
|
||||
} else if fixed_w < 0.0 and n.w != UI_FILL and n.h >= 0.0 { n.mw = n.h * r }
|
||||
}
|
||||
# x, y, w, h: a `nat_w` x `nat_h` picture in the node's content box by its object-fit
|
||||
export function ui_object_fit(n: UiNode, nat_w: float, nat_h: float) -> []float {
|
||||
let bx = n.x + n.pl + n.bl
|
||||
let by = n.y + n.pt + n.bt
|
||||
let bw = n.cw - ly_hin(n)
|
||||
let bh = n.ch - ly_vin(n)
|
||||
var k = 0
|
||||
if n.ex != null { k = n.ex.fit_obj }
|
||||
var w = bw
|
||||
var h = bh
|
||||
if k != 0 and nat_w > 0.0 and nat_h > 0.0 {
|
||||
var s = Math.min(bw / nat_w, bh / nat_h)
|
||||
if k == 2 { s = Math.max(bw / nat_w, bh / nat_h) }
|
||||
if k == 3 { s = 1.0 }
|
||||
if k == 4 { s = Math.min(s, 1.0) }
|
||||
w = nat_w * s
|
||||
h = nat_h * s
|
||||
}
|
||||
let out = new []float
|
||||
push(out, bx + (bw - w) / 2.0)
|
||||
push(out, by + (bh - h) / 2.0)
|
||||
push(out, w)
|
||||
push(out, h)
|
||||
return out
|
||||
}
|
||||
|
|
@ -26,6 +26,9 @@ export property UiNodeX {
|
|||
ip_r: float = 0.0
|
||||
ip_b: float = 0.0
|
||||
ip_l: float = 0.0
|
||||
grad: UiGradient = null # background: linear-gradient(...)
|
||||
ratio: float = 0.0 # aspect-ratio, width over height; 0 none
|
||||
fit_obj: int = 0 # object-fit: 0 fill, 1 contain, 2 cover, 3 none, 4 scale-down
|
||||
}
|
||||
function nx(n: UiNode) -> UiNodeX {
|
||||
if n.ex == null { n.ex = new UiNodeX }
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ export function ui_render3d() -> void {
|
|||
b.text = fn r3u_text
|
||||
b.measure = fn r3u_measure
|
||||
b.image = fn r3u_image
|
||||
b.image_w = fn r3u_image_w
|
||||
b.image_h = fn r3u_image_h
|
||||
b.nine = fn r3u_nine
|
||||
b.clip = fn r3u_clip
|
||||
b.unclip = fn r3u_unclip
|
||||
|
|
|
|||
|
|
@ -61,6 +61,22 @@ function r3u_image(src: string, x: float, y: float, w: float, h: float, c: int,
|
|||
let t = r3u_tex(src)
|
||||
if t != 0 { ov_sub(t, int(x), int(y), int(w), int(h), 0.0, 0.0, 1.0, 1.0, r3u_r(c), r3u_g(c), r3u_b(c), a) }
|
||||
}
|
||||
# a picture's own size, for object-fit; an atlas cell is drawn square
|
||||
function r3u_image_w(src: string) -> float {
|
||||
if r3u_is_cell(src) { return 1.0 }
|
||||
return float(tex_width(r3u_tex(src)))
|
||||
}
|
||||
function r3u_image_h(src: string) -> float {
|
||||
if r3u_is_cell(src) { return 1.0 }
|
||||
return float(tex_height(r3u_tex(src)))
|
||||
}
|
||||
function r3u_is_cell(src: string) -> bool {
|
||||
for k in 0 .. len(r3u_atlas) {
|
||||
let p = r3u_atlas[k]
|
||||
if len(src) > len(p) and src[0..len(p)] == p and src[len(p)] == 58 { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
function r3u_cell(k: int, name: string, x: float, y: float, w: float, h: float, c: int, a: float) -> void {
|
||||
let names = r3u_atlas_names[k]
|
||||
var idx = -1
|
||||
|
|
|
|||
|
|
@ -770,6 +770,7 @@ function cmd_dev_test() -> int {
|
|||
controller_case("library/ui_calc", "", "min 200 max 82 clamp 50 21 | p 150,60 q 255,80 r 30", "ui_calc.ludic (L11: min(), max() and clamp(), and insets as percentages and calc() of the containing block)")
|
||||
controller_case("library/ui_nine", "", "cap 0 12 18 30/ 0 8 8 16 | wide 0 12 88 100/ 0 12 28 40 | odd 10 21 21 31/ 4 8 8 13", "ui_nine.ludic (L11: a nine-slice's corners clamped to half the box each way, on whole pixels)")
|
||||
controller_case("library/ui_scroll_hold", "", "120 50 0 top 120 | 80 50 0 top 80 | 80 50 0 top 80 | 80 50 70 top 80 | 80 50 30 top 80", "ui_scroll_hold.ludic (L11: scroll-top holds a box at an offset, on-scroll follows the wheel, ui_scroll_set moves one once)")
|
||||
controller_case("library/ui_fill", "", "0,0 2x20 #030303 .. 98,0 2x20 #fcfcfc | 0,20 50x2 #f2000d .. 0,38 50x2 #0d00f2 .. 0,58 50x2 #0000ff of 70 | 0,85 100x50 | -50,160 200x100 clipped 1 | ratio 90 knob 60", "ui_fill.ludic (L11: linear-gradient backgrounds, object-fit contain and cover, aspect-ratio for boxes and natives)")
|
||||
controller_case("library/ui_inputs", "", "count 12 12 32 0.5 0 | 20 32 0.5 0 | 19 32 0.5 0 | How many to carry | Space bar listening 1 19 9 0.5 0 | 19 9 0.5 0 | 19 0 0.5 0 | 60% 0.50 s | 19 0 0.6 9 | 7!", "ui_inputs.ludic (L11: number fields, key capture with Esc and Backspace, notes, range formats, an event named set, a string prop given a number)")
|
||||
controller_case("library/ui_boxes", "", "list 120 of 200 | text at 32 152 wide, 3 lines, row 60 | zero 0x0 | half 140x36 pad 4 2 | order abc at 16 | fill 98x8 of 200x12", "ui_boxes.ludic (L11: flex-shrink to the leftover room, text wrapping beside its siblings, zero sizes, calc(), order, the range track as a row)")
|
||||
controller_case("library/ui_look", "", "root div.in.i.m pad 5 margin 9 color #ff0000 text Hi 2,53 #102030 | text Hi 0,50 #eeeeee | opacity 0.25 | nine 40 #ffffff nine 40 #ff0000 of 2 image photo.png #ffffff | image icon:tent #00ff00 | text first line 29,205 #eeeeee | text second line 29,223 #eeeeee by id at 84,50 | flipped at 246,268 100x32", "ui_look.ludic (L11: nested components styled by specificity, text-shadow, ui_opacity, border-image tints, pictures, tooltip lines, anchored popovers)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue