# 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 }