96 lines
3.8 KiB
Text
96 lines
3.8 KiB
Text
# 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 sat(s, 0, "linear-gradient(") }
|
|
# a gradient is parsed once per text and shared by every element that wears it - never changed after
|
|
function gr_parse(ui_st: mut UiState, s0: string) -> UiGradient {
|
|
let m = mm_of(ui_st)
|
|
let i = mm_find(m, MM_GRAD, s0)
|
|
if i >= 0 { return m.grads[i] }
|
|
let g = gr_parse_new(ui_st, s0)
|
|
let j = mm_add(m, MM_GRAD, s0)
|
|
if j >= 0 { m.grads[j] = g }
|
|
return g
|
|
}
|
|
@alloc_ok("a memo miss (gr_parse): once per gradient")
|
|
function gr_parse_new(ui_st: mut UiState, s0: string) -> UiGradient {
|
|
let s = tw(ui_st, 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, tw(ui_st, parts[0])) { from = 1 }
|
|
for i in from .. len(parts) {
|
|
let words = mm_words(ui_st, tw(ui_st, parts[i]))
|
|
var col = tw(ui_st, 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 = tw(ui_st, col[0..len(col) - len(last)])
|
|
}
|
|
push(g.cols, ui_color(ui_st, col))
|
|
push(g.alphas, ui_st.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 sat(w, len(w) - 3, "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(ui_st: mut UiState, g: UiGradient, x: float, y: float, w: float, h: float, a: float) -> void {
|
|
if ui_st.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(ui_st, g, t)
|
|
if across { ui_st.ui_be.rect(x + e0, y, e1 - e0, h, c, ui_st.gr_a * a) } else { ui_st.ui_be.rect(x, y + e0, w, e1 - e0, c, ui_st.gr_a * a) }
|
|
}
|
|
}
|
|
# the colour at `t` between the stops around it, its alpha in gr_a
|
|
function gr_at(ui_st: mut UiState, 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 }
|
|
ui_st.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
|
|
}
|