feat(ui): min(), max() and clamp(); insets as percentages of the containing block
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
355201f094
commit
12f4ba8431
13 changed files with 150 additions and 21 deletions
47
packages/ludic.ui/attr_inset.ludic
Normal file
47
packages/ludic.ui/attr_inset.ludic
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# attr_inset.ludic - top, right, bottom and left as lengths, percentages or calc() of both: the
|
||||
# percentage is kept apart and taken of the containing block's width (left, right) or height (top,
|
||||
# bottom) when the element is placed
|
||||
function el_inset(n: UiNode, side: int, v: Val) -> float {
|
||||
let s = vl_text(v)
|
||||
var pct = 0.0
|
||||
var px = UI_AUTO
|
||||
if s != "auto" {
|
||||
if ca_is(s) {
|
||||
px = 0.0
|
||||
if ca_eval(s) {
|
||||
pct = ca_pct
|
||||
px = ca_px
|
||||
}
|
||||
} else if len(s) > 1 and s[len(s) - 1] == 37 {
|
||||
pct = el_num(s)
|
||||
px = 0.0
|
||||
} else { px = el_len(v) }
|
||||
}
|
||||
if pct != 0.0 or n.ex != null {
|
||||
let x = nx(n)
|
||||
if side == 0 { x.ip_t = pct }
|
||||
if side == 1 { x.ip_r = pct }
|
||||
if side == 2 { x.ip_b = pct }
|
||||
if side == 3 { x.ip_l = pct }
|
||||
}
|
||||
return px
|
||||
}
|
||||
# an inset in px against a containing block `base` wide (or high), or UI_AUTO
|
||||
function ly_ins(c: UiNode, side: int, base: float) -> float {
|
||||
var v = c.top
|
||||
if side == 1 { v = c.right }
|
||||
if side == 2 { v = c.bottom }
|
||||
if side == 3 { v = c.left }
|
||||
if v == UI_AUTO or c.ex == null { return v }
|
||||
var p = c.ex.ip_t
|
||||
if side == 1 { p = c.ex.ip_r }
|
||||
if side == 2 { p = c.ex.ip_b }
|
||||
if side == 3 { p = c.ex.ip_l }
|
||||
return v + base * p / 100.0
|
||||
}
|
||||
# position: relative moves an element by its insets, a percentage taken of its parent's content box
|
||||
function ly_nudge(n: UiNode, c: UiNode) -> void {
|
||||
let bw = n.cw - ly_hin(n)
|
||||
let bh = n.ch - ly_vin(n)
|
||||
ly_offset(c, ly_or0(ly_ins(c, 3, bw)) - ly_or0(ly_ins(c, 1, bw)), ly_or0(ly_ins(c, 0, bh)) - ly_or0(ly_ins(c, 2, bh)))
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ function el_place_prop(n: UiNode, key: string, v: Val) -> bool {
|
|||
if s == "relative" { n.pos = 1 }
|
||||
if s == "absolute" { n.pos = 2 }
|
||||
if s == "fixed" { n.pos = 3 }
|
||||
} else if key == "top" { n.top = el_inset(v) } else if key == "right" { n.right = el_inset(v) } else if key == "bottom" { n.bottom = el_inset(v) } else if key == "left" { n.left = el_inset(v) } else if key == "inset" {
|
||||
} else if key == "top" { n.top = el_inset(n, 0, v) } else if key == "right" { n.right = el_inset(n, 1, v) } else if key == "bottom" { n.bottom = el_inset(n, 2, v) } else if key == "left" { n.left = el_inset(n, 3, v) } else if key == "inset" {
|
||||
el_sides(v)
|
||||
n.top = el_s0
|
||||
n.right = el_s1
|
||||
|
|
@ -17,7 +17,3 @@ function el_place_prop(n: UiNode, key: string, v: Val) -> bool {
|
|||
} else if key == "white-space" { n.nowrap = s == "nowrap" or s == "pre" } else if key == "text-overflow" { n.ellipsis = s == "ellipsis" } else { return false }
|
||||
return true
|
||||
}
|
||||
function el_inset(v: Val) -> float {
|
||||
if vl_text(v) == "auto" { return UI_AUTO }
|
||||
return el_len(v)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
# calc.ludic - CSS's calc(): + - * / and brackets over lengths (px, em, rem, vw, vh, or a bare
|
||||
# number, which is px as everywhere here), percentages and plain numbers; var() is already read by
|
||||
# then. A width or height keeps its percentage apart (w_pct plus an offset) to be taken of the room
|
||||
# the element is given; anywhere else a percentage in calc() has nothing to be a share of.
|
||||
# calc.ludic - CSS's calc(), min(), max() and clamp(): + - * / and brackets over lengths (px, em, rem,
|
||||
# vw, vh, a bare number is px), percentages and plain numbers. A width, height or inset keeps its
|
||||
# percentage apart, to be taken of the room it is given; elsewhere a percentage has nothing to share.
|
||||
var ca_s: string = ""
|
||||
var ca_i: int = 0
|
||||
var ca_bad: string = ""
|
||||
var ca_px: float = 0.0 # the result: its lengths, its percentage, and whether it had units
|
||||
var ca_pct: float = 0.0
|
||||
function ca_is(s: string) -> bool { return len(s) > 5 and s[0..5] == "calc(" }
|
||||
function ca_is(s: string) -> bool { return ca_fn_at(s, 0) != "" }
|
||||
# calc(...) worked out into ca_px and ca_pct; false (with the reason said) when it cannot be
|
||||
function ca_eval(s: string) -> bool {
|
||||
ca_s = s[5..len(s)]
|
||||
ca_s = s
|
||||
ca_i = 0
|
||||
ca_bad = ""
|
||||
let v = ca_sum()
|
||||
let v = ca_factor()
|
||||
ca_ws()
|
||||
if ca_bad == "" and (ca_i >= len(ca_s) or ca_s[ca_i] != 41) { ca_bad = "a closing )" }
|
||||
if ca_bad == "" and ca_i < len(ca_s) { ca_bad = "nothing after its closing )" }
|
||||
if ca_bad != "" {
|
||||
ui_err(`{s}: calc() expected {ca_bad}`)
|
||||
return false
|
||||
|
|
|
|||
48
packages/ludic.ui/calc_fns.ludic
Normal file
48
packages/ludic.ui/calc_fns.ludic
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# calc_fns.ludic - min(a, b, ...), max(a, b, ...) and clamp(least, want, most) over lengths or plain
|
||||
# numbers, inside calc() or on their own. Comparing needs a length: a percentage there has no room yet.
|
||||
# the function a value starts with at `at` (calc, min, max or clamp), or ""
|
||||
function ca_fn_at(s: string, at: int) -> string {
|
||||
if ca_word(s, at, "calc(") { return "calc" }
|
||||
if ca_word(s, at, "min(") { return "min" }
|
||||
if ca_word(s, at, "max(") { return "max" }
|
||||
if ca_word(s, at, "clamp(") { return "clamp" }
|
||||
return ""
|
||||
}
|
||||
function ca_word(s: string, at: int, w: string) -> bool { return at + len(w) <= len(s) and s[at..at + len(w)] == w }
|
||||
function ca_fn(f: string) -> CaVal {
|
||||
ca_i = ca_i + len(f) + 1
|
||||
let args = new []CaVal
|
||||
while ca_bad == "" {
|
||||
push(args, ca_sum())
|
||||
ca_ws()
|
||||
if ca_i < len(ca_s) and ca_s[ca_i] == 44 { ca_i += 1 } else { break }
|
||||
}
|
||||
if ca_bad == "" and (ca_i >= len(ca_s) or ca_s[ca_i] != 41) { ca_bad = "a closing )" }
|
||||
ca_i += 1
|
||||
if ca_bad != "" { return new CaVal }
|
||||
if f == "clamp" and len(args) != 3 {
|
||||
ca_bad = "clamp(least, want, most)"
|
||||
return new CaVal
|
||||
}
|
||||
for i in 0 .. len(args) {
|
||||
if args[i].pct != 0.0 {
|
||||
ca_bad = `lengths in {f}(), not a percentage`
|
||||
return new CaVal
|
||||
}
|
||||
}
|
||||
if f == "clamp" { return ca_pick(ca_pick(args[1], args[0], false), args[2], true) }
|
||||
var r = args[0]
|
||||
for i in 1 .. len(args) { r = ca_pick(r, args[i], f == "min") }
|
||||
return r
|
||||
}
|
||||
# the smaller (or the larger) of two, a bare number beside a length being px
|
||||
function ca_pick(a: CaVal, b: CaVal, smaller: bool) -> CaVal {
|
||||
if a.bare and b.bare {
|
||||
if (b.n < a.n) == smaller and b.n != a.n { return b }
|
||||
return a
|
||||
}
|
||||
let la = ca_len(a)
|
||||
let lb = ca_len(b)
|
||||
if (lb.px < la.px) == smaller and lb.px != la.px { return lb }
|
||||
return la
|
||||
}
|
||||
|
|
@ -43,7 +43,9 @@ function ca_factor() -> CaVal {
|
|||
ca_i += 1
|
||||
return ca_scale(ca_factor(), -1.0)
|
||||
}
|
||||
if c == 40 or (ca_i + 5 <= len(ca_s) and ca_s[ca_i..ca_i + 5] == "calc(") {
|
||||
let f = ca_fn_at(ca_s, ca_i)
|
||||
if f == "min" or f == "max" or f == "clamp" { return ca_fn(f) }
|
||||
if c == 40 or f == "calc" {
|
||||
while ca_s[ca_i] != 40 { ca_i += 1 }
|
||||
ca_i += 1
|
||||
let v = ca_sum()
|
||||
|
|
|
|||
|
|
@ -49,10 +49,12 @@ import "attr.ludic"
|
|||
import "attr_box.ludic"
|
||||
import "attr_border.ludic"
|
||||
import "attr_place.ludic"
|
||||
import "attr_inset.ludic"
|
||||
import "attr_edge.ludic"
|
||||
import "attr_more.ludic"
|
||||
import "calc.ludic"
|
||||
import "calc_terms.ludic"
|
||||
import "calc_fns.ludic"
|
||||
import "ua.ludic"
|
||||
import "anim.ludic"
|
||||
import "transition.ludic"
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function ly_place_line(n: UiNode, from: int, to: int, top: float, main_room: flo
|
|||
var m = sizes[i - from]
|
||||
if grows > 0.0 and free > 0.0 { m = m + free * ly_grow(c, n.dir) / grows }
|
||||
ly_place_child(n, c, at, top, m, cross_room)
|
||||
if c.pos == 1 { ly_offset(c, ly_or0(c.left) - ly_or0(c.right), ly_or0(c.top) - ly_or0(c.bottom)) }
|
||||
if c.pos == 1 { ly_nudge(n, c) }
|
||||
at = at + m + n.gap + extra
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,12 +38,16 @@ function ly_place_abs(n: UiNode, c: UiNode) -> void {
|
|||
var h = c.mh
|
||||
if c.w_pct > 0.0 { w = ly_pw(c, bw) }
|
||||
if c.h_pct > 0.0 { h = ly_ph(c, bh) }
|
||||
if c.w == UI_FILL or (c.left != UI_AUTO and c.right != UI_AUTO and c.w == UI_AUTO) { w = bw - ly_or0(c.left) - ly_or0(c.right) - c.ml - c.mr }
|
||||
if c.h == UI_FILL or (c.top != UI_AUTO and c.bottom != UI_AUTO and c.h == UI_AUTO) { h = bh - ly_or0(c.top) - ly_or0(c.bottom) - c.mt - c.mb }
|
||||
let l = ly_ins(c, 3, bw)
|
||||
let r = ly_ins(c, 1, bw)
|
||||
let t = ly_ins(c, 0, bh)
|
||||
let b = ly_ins(c, 2, bh)
|
||||
if c.w == UI_FILL or (l != UI_AUTO and r != UI_AUTO and c.w == UI_AUTO) { w = bw - ly_or0(l) - ly_or0(r) - c.ml - c.mr }
|
||||
if c.h == UI_FILL or (t != UI_AUTO and b != UI_AUTO and c.h == UI_AUTO) { h = bh - ly_or0(t) - ly_or0(b) - c.mt - c.mb }
|
||||
var x = bx + n.pl + c.ml
|
||||
if c.left != UI_AUTO { x = bx + c.left + c.ml } else if c.right != UI_AUTO { x = bx + bw - c.right - c.mr - w }
|
||||
if l != UI_AUTO { x = bx + l + c.ml } else if r != UI_AUTO { x = bx + bw - r - c.mr - w }
|
||||
var y = by + n.pt + c.mt
|
||||
if c.top != UI_AUTO { y = by + c.top + c.mt } else if c.bottom != UI_AUTO { y = by + bh - c.bottom - c.mb - h }
|
||||
if t != UI_AUTO { y = by + t + c.mt } else if b != UI_AUTO { y = by + bh - b - c.mb - h }
|
||||
ly_place(c, x, y, w, h)
|
||||
}
|
||||
function ly_or0(v: float) -> float {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ export property UiNodeX {
|
|||
fit_min: float = 0.0 # text-fit: shrink MIN - the smallest a line is shrunk to fit its box
|
||||
lh_kind: int = 0 # line-height: 0 normal, 1 a multiple of the font size, 2 px
|
||||
lh_v: float = 0.0
|
||||
ip_t: float = 0.0 # an inset's percentage of its containing block (top right bottom left)
|
||||
ip_r: float = 0.0
|
||||
ip_b: float = 0.0
|
||||
ip_l: float = 0.0
|
||||
}
|
||||
function nx(n: UiNode) -> UiNodeX {
|
||||
if n.ex == null { n.ex = new UiNodeX }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue