1use alloc::format;
34use alloc::rc::Rc;
35use alloc::string::String;
36use alloc::vec::Vec;
37
38use crate::cmd::{Draw, DrawRef, Gfx};
39use crate::consts::*;
40use crate::keys::*;
41use crate::stock::text_at;
42use crate::Ui;
43
44const LANE_H: i32 = 22; const MIN_VIEW: f64 = 1e-8; const ZOOM: f64 = 1.25; const SCAN: usize = 64; const M_NONE: u8 = 0;
51const M_SCRUB: u8 = 1;
52const M_PAN: u8 = 2;
53const M_SELECT: u8 = 3;
54
55#[derive(Clone, Copy, Debug)]
57pub struct TlTrack<'a> {
58 pub label: &'a str,
59 pub ticks: &'a [(f64, u32)],
61}
62
63#[derive(Clone, Default)]
65pub struct TimelineState {
66 pub t: f64,
68 pub view: (f64, f64),
70 pub sel: Option<(f64, f64)>,
72 pub follow: bool,
74 pub test_mods: u32,
77 mode: u8,
78 part: u8,
79 dragged: bool,
80 click: Option<(u32, i32)>,
81 fitted: bool,
82 seen: (f64, f64),
83 grab_t: f64,
84 grab_dx: i32,
85 grab_view: (f64, f64),
86 cache: Option<(u64, DrawRef)>,
87}
88
89impl TimelineState {
90 pub fn fit(&mut self) {
92 self.view = (0.0, 0.0);
93 }
94}
95
96fn part(id: u32, k: u32) -> u32 {
97 id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
98}
99
100fn floor_f(x: f64) -> f64 {
101 let f = x as i64 as f64;
102 if f > x { f - 1.0 } else { f }
103}
104
105fn ms(x: f64) -> i32 {
107 let v = x * 1000.0;
108 (if v < 0.0 { v - 0.5 } else { v + 0.5 }) as i32
109}
110
111fn nice_step(min: f64) -> (f64, u32) {
114 if !min.is_finite() || min > 1e12 {
115 return (1e12, 5);
116 }
117 let min = if min > 1e-9 { min } else { 1e-9 };
118 let mut p = 1.0f64;
119 while p * 10.0 <= min {
120 p *= 10.0;
121 }
122 while p > min {
123 p /= 10.0;
124 }
125 if p >= min {
126 (p, 5)
127 } else if p * 2.0 >= min {
128 (p * 2.0, 4)
129 } else if p * 5.0 >= min {
130 (p * 5.0, 5)
131 } else {
132 (p * 10.0, 5)
133 }
134}
135
136fn decimals(step: f64) -> u32 {
138 let mut d = 0;
139 let mut s = step;
140 while d < 9 && s < 0.999_999 {
141 s *= 10.0;
142 d += 1;
143 }
144 d
145}
146
147fn snap(t: f64, per_px: f64) -> f64 {
149 if !(per_px > 1e-9) {
150 return t;
151 }
152 let mut q = 1.0f64;
153 while q > per_px {
154 q /= 10.0;
155 }
156 while q * 10.0 <= per_px {
157 q *= 10.0;
158 }
159 floor_f(t / q + 0.5) * q
160}
161
162fn log2f(n: u32) -> f64 {
164 if n <= 1 {
165 return 0.0;
166 }
167 let e = 31 - n.leading_zeros();
168 e as f64 + (n as f64 / (1u64 << e) as f64 - 1.0)
169}
170
171fn zoom_factor(notches: f64) -> f64 {
173 let mut k = if notches < 0.0 { -notches } else { notches };
174 let mut f = 1.0;
175 while k >= 1.0 {
176 f *= ZOOM;
177 k -= 1.0;
178 }
179 f *= 1.0 + (ZOOM - 1.0) * k;
180 if notches < 0.0 { 1.0 / f } else { f }
181}
182
183fn clamp_view(v: (f64, f64), full: (f64, f64)) -> (f64, f64) {
185 let fw = full.1 - full.0;
186 let w = (v.1 - v.0).min(fw).max(MIN_VIEW.min(fw));
187 let mut a = v.0;
188 if a + w > full.1 {
189 a = full.1 - w;
190 }
191 if a < full.0 {
192 a = full.0;
193 }
194 (a, a + w)
195}
196
197fn zoom_view(v: (f64, f64), at: f64, f: f64, full: (f64, f64)) -> (f64, f64) {
199 let w = v.1 - v.0;
200 let fw = full.1 - full.0;
201 let nw = (w / f).min(fw).max(MIN_VIEW.min(fw));
202 let k = if w > 0.0 { (at - v.0) / w } else { 0.5 };
203 let a = at - k * nw;
204 clamp_view((a, a + nw), full)
205}
206
207fn reveal(v: (f64, f64), t: f64, full: (f64, f64)) -> (f64, f64) {
209 let w = v.1 - v.0;
210 if t < v.0 {
211 clamp_view((t - w * 0.1, t + w * 0.9), full)
212 } else if t > v.1 {
213 clamp_view((t - w * 0.9, t + w * 0.1), full)
214 } else {
215 v
216 }
217}
218
219fn tick_before(tracks: &[TlTrack], t: f64) -> Option<f64> {
221 let mut best: Option<f64> = None;
222 for tr in tracks {
223 let i = tr.ticks.partition_point(|k| k.0 < t);
224 if i > 0 {
225 let x = tr.ticks[i - 1].0;
226 if best.is_none_or(|b| x > b) {
227 best = Some(x);
228 }
229 }
230 }
231 best
232}
233
234fn tick_after(tracks: &[TlTrack], t: f64) -> Option<f64> {
235 let mut best: Option<f64> = None;
236 for tr in tracks {
237 let i = tr.ticks.partition_point(|k| k.0 <= t);
238 if let Some(k) = tr.ticks.get(i) {
239 if best.is_none_or(|b| k.0 < b) {
240 best = Some(k.0);
241 }
242 }
243 }
244 best
245}
246
247#[derive(Clone, Copy)]
250struct Col {
251 lane: u32,
252 x: i32,
253 n: u32,
254 c: [(u32, u32); 3],
255}
256
257#[allow(clippy::too_many_arguments)]
259fn bin_lane(ticks: &[(f64, u32)], lane: u32, v0: f64, pps: f64, bw: i32, pw: i32, pal: u32, out: &mut Vec<Col>) {
260 let ncol = ((pw + bw - 1) / bw).max(1);
261 let v1 = v0 + pw as f64 / pps;
262 let a = ticks.partition_point(|k| k.0 < v0);
263 let b = a + ticks[a..].partition_point(|k| k.0 <= v1);
264 let vis = &ticks[a..b];
265 let cps = pps / bw as f64; let mut i = 0;
267 while i < vis.len() {
268 let f = (vis[i].0 - v0) * cps;
269 let c = if f > 0.0 { (f as i64).min(ncol as i64 - 1) as i32 } else { 0 };
270 let j = if c + 1 >= ncol {
271 vis.len()
272 } else {
273 let end = v0 + (c + 1) as f64 / cps;
274 i + 1 + vis[i + 1..].partition_point(|k| k.0 < end)
275 };
276 let mut col = Col { lane, x: c * bw, n: (j - i) as u32, c: [(0, 0); 3] };
277 for k in &vis[i..j.min(i + SCAN)] {
278 let cc = if k.1 == 0 { pal } else { k.1 };
279 if let Some(s) = col.c.iter_mut().find(|s| s.1 != 0 && s.0 == cc) {
280 s.1 += 1;
281 } else if let Some(s) = col.c.iter_mut().find(|s| s.1 == 0) {
282 *s = (cc, 1);
283 } else {
284 col.c[2].1 += 1;
285 }
286 }
287 out.push(col);
288 i = j;
289 }
290}
291
292fn bars(cols: &[Col], lane_h: i32, pad: i32, bw: i32) -> Vec<(i32, i32, i32, i32, u32)> {
296 let maxn = cols.iter().map(|c| c.n).max().unwrap_or(1);
297 let lmax = log2f(maxn);
298 let avail = (lane_h - pad * 2).max(2);
299 let hmin = (avail * 2 / 5).max(2).min(avail);
300 let mut out = Vec::with_capacity(cols.len() + cols.len() / 4);
301 for (k, c) in cols.iter().enumerate() {
302 let h = if lmax > 0.0 { hmin + ((avail - hmin) as f64 * log2f(c.n) / lmax) as i32 } else { hmin };
303 let y = c.lane as i32 * lane_h + (lane_h - h) / 2;
304 let before = k > 0 && cols[k - 1].lane == c.lane && cols[k - 1].x + bw >= c.x;
306 let after = k + 1 < cols.len() && cols[k + 1].lane == c.lane && cols[k + 1].x <= c.x + bw;
307 let (x, w) = if before || after { (c.x, bw) } else { (c.x - bw / 2, bw * 2) };
308 let mut s = c.c;
309 s.sort_by(|a, b| b.1.cmp(&a.1));
310 let total: u32 = s.iter().map(|x| x.1).sum();
311 let used = s.iter().filter(|x| x.1 > 0).count() as i32;
312 let (mut top, mut rest) = (y, h);
313 for (i, &(cc, cn)) in s.iter().filter(|x| x.1 > 0).rev().enumerate() {
314 let left = used - 1 - i as i32; let hs = if left == 0 { rest } else { ((h as i64 * cn as i64 / total.max(1) as i64) as i32).max(1).min(rest - left).max(0) };
316 if hs > 0 {
317 out.push((x, top, w, hs, cc));
318 }
319 top += hs;
320 rest -= hs;
321 }
322 }
323 out
324}
325
326struct Bars(Vec<(i32, i32, i32, i32, u32)>);
328
329impl Draw for Bars {
330 fn draw(&self, g: &mut Gfx, r: [i32; 4]) {
331 for &(x, y, w, h, c) in &self.0 {
332 g.dc.set_color(c);
333 g.dc.fill(g.sf, r[0] + x, r[1] + y, w, h);
334 }
335 }
336}
337
338struct Fnv(u64);
339
340impl Fnv {
341 fn u32(&mut self, v: u32) {
342 for b in v.to_le_bytes() {
343 self.0 ^= b as u64;
344 self.0 = self.0.wrapping_mul(0x0000_0100_0000_01B3);
345 }
346 }
347}
348
349impl Ui {
350 fn tl_text(&self, t: f64, d: u32) -> String {
353 if d <= 6 {
354 return format!("{} s", self.fmt_f64(t, d));
355 }
356 let neg = t < 0.0;
357 let ns = ((if neg { -t } else { t }) * 1e9 + 0.5) as u64;
358 let frac = format!("{:09}", ns % 1_000_000_000);
359 let sep = char::from_u32(self.m.value.decimal_sep).unwrap_or(',');
360 format!("{}{}{}{} s", if neg { "-" } else { "" }, ns / 1_000_000_000, sep, &frac[..(d as usize).min(9)])
361 }
362
363 fn tl_axis(&mut self, v: (f64, f64), pw: i32) -> (f64, u32, u32) {
366 let pps = pw as f64 / (v.1 - v.0);
367 let big = if -v.0 > v.1 { v.0 } else { v.1 };
368 self.ts_font(TS_CAPTION);
369 let mut r = nice_step(self.dp(80) as f64 / pps);
370 for _ in 0..3 {
371 let s = self.tl_text(big, decimals(r.0));
372 let need = (self.text.width(&s) + self.dp(16)) as f64 / pps;
373 if r.0 >= need {
374 break;
375 }
376 r = nice_step(need);
377 }
378 (r.0, r.1, decimals(r.0))
379 }
380
381 fn tl_head(&mut self, s: &str, cx: i32, y: i32, lo: i32, hi: i32) -> [i32; 4] {
383 self.ts_font(TS_CAPTION);
384 let w = self.text.width(s) + self.dp(12);
385 let h = self.ts_line(TS_CAPTION) + self.dp(2);
386 let x = (cx - w / 2).min(hi - w).max(lo);
387 [x, y, w, h]
388 }
389
390 #[cfg_attr(jfgui_dev, track_caller)]
393 pub fn timeline(&mut self, id: u32, st: &mut TimelineState, span: (f64, f64), tracks: &[TlTrack]) -> u32 {
394 let _src = self.src_scope();
395 let n = tracks.len() as i32;
396 let line = self.ts_line(TS_CAPTION);
397 let axis_h = line + self.dp(10);
398 let r = self.place(self.dp(480), axis_h + self.dp(LANE_H) * n.max(1));
399 let lane_h = if n > 0 { ((r[3] - axis_h) / n).clamp(self.dp(12), self.dp(48)) } else { 0 };
400 let hair = self.hair();
401 self.ts_font(TS_CAPTION);
403 let mut lw = 0;
404 for tr in tracks {
405 lw = lw.max(self.text.width(crate::basic::cstr(tr.label)));
406 }
407 let lw = if lw > 0 { (lw + self.dp(16)).min(r[2] * 3 / 10) } else { 0 };
408 let m = self.dp(12);
409 let px = r[0] + lw + m;
410 let pw = (r[2] - lw - m * 2).max(1);
411 let ly = r[1] + axis_h;
412 let lh = (r[3] - axis_h).max(1);
413 let (head_lo, head_hi) = (r[0] + lw + hair, r[0] + r[2] - hair);
414 let head_y = r[1] + self.dp(3);
415 let s0 = if span.0.is_finite() { span.0 } else { 0.0 };
417 let s1 = if span.1.is_finite() && span.1 > s0 { span.1 } else { s0 };
418 let full = (s0, if s1 > s0 { s1 } else { s0 + 1.0 });
419 let sel_in = st.sel;
420 let mut t = if st.t.is_finite() { st.t.clamp(s0, s1) } else { s0 };
421 let mut v = st.view;
423 if (st.fitted && v == st.seen) || !(v.0.is_finite() && v.1.is_finite() && v.1 > v.0) {
424 v = full;
425 }
426 v = clamp_view(v, full);
427 if st.follow {
428 t = s1;
429 v = reveal(v, t, full);
430 }
431 let v_in = v;
433 let pps = pw as f64 / (v.1 - v.0);
434 let per_px = 1.0 / pps;
435 let time_at = |x: i32| v_in.0 + ((x - px) as f64 + 0.5) / pps;
436 let x_at = |v: (f64, f64), t: f64| px + floor_f(((t - v.0) * (pw as f64 / (v.1 - v.0))).clamp(-1e6, 1e6)) as i32;
437 let pid_axis = part(id, 1);
439 let pid_head = part(id, 2);
440 self.ltr_begin(r[0], r[2]);
441 let rtl = self.rtl;
442 let lx = |x: i32, w: i32| if rtl { r[0] + r[0] + r[2] - x - w } else { x }; let mx = self.ltr_x(self.wp.ctx.mx);
444 let pmx = self.ltr_x(self.wp.ctx.press_x);
445 let (_, _, dec) = self.tl_axis(v, pw);
446 let ht = self.tl_text(t, dec.max(3));
447 let hr = self.tl_head(&ht, x_at(v, t), head_y, head_lo, head_hi);
448 let ev_axis = self.hit_rect(pid_axis, r[0], r[1], r[2], axis_h, HB_MOVES);
449 let ev_lane = self.hit_rect(id, r[0], ly, r[2], lh, HB_FOCUS | HB_MOVES);
450 let ev_head = self.hit_rect(pid_head, lx(hr[0], hr[2]), hr[1], hr[2], hr[3], HB_MOVES | CUR_SIZE_WE << HB_CURSOR_SHIFT);
451 let evs = ev_axis | ev_lane | ev_head;
452 let mut out = 0u32;
453 let mut user_t: Option<f64> = None;
454 let mut commit = false;
455 let mut keyed = false;
456 let mut live = false;
457 let (act, pressed) = (self.wp.ctx.active, self.wp.ctx.pressed & (1 << BTN_LEFT) != 0);
459 let ours = act == id || act == pid_axis || act == pid_head;
460 if pressed && ours {
461 let c = &mut self.wp.ctx;
462 c.focus = id;
463 c.focus_visible = false;
464 st.dragged = false;
465 st.part = if act == pid_axis { 1 } else if act == pid_head { 2 } else { 0 };
466 let now = self.now_ms;
469 let again = st.click.is_some_and(|(ms, x)| now.wrapping_sub(ms) <= self.dbl_ms && (mx - x).abs() <= self.dp(4));
470 st.click = None;
471 st.mode = if st.part != 0 && again {
472 v = full;
473 M_NONE
474 } else if st.part == 1 {
475 st.grab_view = v;
476 M_PAN
477 } else if st.part == 0 && self.mods & MOD_SHIFT != 0 {
478 let a = snap(time_at(mx).clamp(v.0, v.1), per_px);
479 st.grab_t = a;
480 st.sel = Some((a, a));
481 M_SELECT
482 } else {
483 st.grab_dx = if st.part == 2 { mx - x_at(v, t).clamp(px, px + pw) } else { 0 };
485 M_SCRUB
486 };
487 }
488 if evs & EV_HELD != 0 {
489 if evs & EV_DRAG != 0 {
490 st.dragged = true;
491 }
492 match st.mode {
493 M_SCRUB if st.part != 2 || st.dragged => {
494 user_t = Some(snap(time_at(mx - st.grab_dx).clamp(v.0, v.1), per_px));
495 }
496 M_PAN if st.dragged => {
497 let g = st.grab_view;
498 let dt = (mx - pmx) as f64 * (g.1 - g.0) / pw as f64;
499 v = clamp_view((g.0 - dt, g.1 - dt), full);
500 }
501 M_SELECT => {
502 let (a, b) = (st.grab_t, snap(time_at(mx).clamp(v.0, v.1), per_px));
503 st.sel = Some(if a <= b { (a, b) } else { (b, a) });
504 }
505 _ => {}
506 }
507 }
508 if evs & EV_COMMIT != 0 {
509 match st.mode {
510 M_SCRUB => commit = true,
511 M_PAN if !st.dragged => {
512 user_t = Some(snap(time_at(pmx).clamp(v_in.0, v_in.1), per_px));
513 commit = true;
514 }
515 M_SELECT if st.sel.is_some_and(|s| s.1 <= s.0) => st.sel = None,
516 _ => {}
517 }
518 if st.mode != M_NONE && st.part != 0 && !st.dragged {
520 st.click = Some((self.now_ms, pmx));
521 }
522 st.mode = M_NONE;
523 } else if !ours {
524 st.mode = M_NONE;
525 }
526 if evs & EV_HOVER != 0 {
528 let mods = self.mods | st.test_mods;
529 let c = &mut self.wp.ctx;
530 let (wx, wy) = (c.wheel_x, c.wheel_y);
531 if mods & MOD_CTRL != 0 && wy != 0 {
532 v = zoom_view(v, time_at(mx).clamp(v.0, v.1), zoom_factor(wy as f64 / 120.0), full);
533 c.wheel_x = 0;
534 c.wheel_y = 0;
535 } else if wx != 0 || (mods & MOD_SHIFT != 0 && wy != 0) {
536 let d = (wx - if mods & MOD_SHIFT != 0 { wy } else { 0 }) as f64 / 120.0;
537 let dt = (v.1 - v.0) * 0.1 * d;
538 let nv = clamp_view((v.0 + dt, v.1 + dt), full);
539 if nv != v {
540 v = nv;
541 c.wheel_x = 0;
542 c.wheel_y = 0;
543 }
544 }
545 }
546 if ev_lane & EV_FOCUS != 0 {
548 let swap = rtl && self.mods & (MOD_CTRL | MOD_ALT) == 0; let (kp, kn) = if swap { (VK_RIGHT, VK_LEFT) } else { (VK_LEFT, VK_RIGHT) };
550 let w = v.1 - v.0;
551 let mut k: Option<Option<f64>> = None;
552 if self.key(kp) {
553 k = Some(tick_before(tracks, t));
554 }
555 if self.key(kn) {
556 k = Some(tick_after(tracks, t));
557 }
558 if self.key(VK_HOME) {
559 k = Some(Some(s0));
560 }
561 if self.key(VK_END) {
562 k = Some(Some(s1));
563 live = true;
564 }
565 if self.key(VK_PRIOR) {
566 k = Some(Some(t - w));
567 }
568 if self.key(VK_NEXT) {
569 k = Some(Some(t + w));
570 }
571 if let Some(k) = k {
572 self.wp.ctx.focus_visible = true;
573 if let Some(x) = k {
574 user_t = Some(x);
575 keyed = true;
576 commit = true;
577 }
578 }
579 let zin = self.key(VK_OEM_PLUS) || self.key(VK_ADD);
580 let zout = self.key(VK_OEM_MINUS) || self.key(VK_SUBTRACT);
581 if zin || zout {
582 let at = if t >= v.0 && t <= v.1 { t } else { (v.0 + v.1) / 2.0 };
583 v = zoom_view(v, at, if zin { ZOOM } else { 1.0 / ZOOM }, full);
584 self.wp.ctx.focus_visible = true;
585 }
586 if st.sel.is_some() && self.key(VK_ESCAPE) {
587 st.sel = None;
588 }
589 }
590 if let Some(x) = self.a11y_value(id) {
592 user_t = Some(x as f64 / 1000.0);
593 keyed = true;
594 commit = true;
595 }
596 if let Some(x) = user_t {
597 let x = x.clamp(s0, s1);
598 st.follow = false;
599 if x != t {
600 t = x;
601 out |= EV_ACT;
602 }
603 if keyed {
604 v = reveal(v, t, full);
605 }
606 }
607 if commit {
608 out |= EV_COMMIT;
609 }
610 if live {
611 st.follow = true;
612 }
613 if st.follow {
614 t = s1;
615 v = reveal(v, t, full);
616 }
617 if t != st.t || v != st.view || st.sel != sel_in {
618 self.repass();
619 }
620 st.t = t;
621 st.view = v;
622 st.seen = v;
623 st.fitted = v.0 <= full.0 && v.1 >= full.1;
624 let mut ss = 0u32;
626 if evs & EV_HOVER != 0 {
627 ss |= SS_HOVER as u32;
628 }
629 if evs & EV_HELD != 0 {
630 ss |= SS_PRESSED as u32;
631 }
632 if ev_lane & EV_FOCUS != 0 {
633 ss |= SS_FOCUSED as u32;
634 }
635 self.wp.ctx.last_rect = r;
636 self.wp.ctx.last_state = ss;
637 let rec = self.sem(K_SLIDER, id, None, ms(t));
638 rec.min = ms(s0);
639 rec.max = ms(s1);
640
641 let pps = pw as f64 / (v.1 - v.0);
643 let (step, minor, dec) = self.tl_axis(v, pw);
644 let (ctrl, stroke, div) = (self.color(T_CTRL), self.color(T_CTRL_STROKE), self.color(T_DIVIDER));
645 let (t2, t3, acc) = (self.color(T_TEXT2), self.color(T_TEXT3), self.color(T_ACCENT));
646 self.cmd_rrect(r[0], r[1], r[2], r[3], self.dp(4), hair, ctrl, stroke, stroke);
647 self.clip_push(r[0] + hair, r[1] + hair, r[2] - hair * 2, r[3] - hair * 2);
648 self.draw_color(div);
650 self.draw_rect(r[0], ly - hair, r[2], hair);
651 if lw > 0 {
652 self.draw_rect(r[0] + lw, ly, hair, lh);
653 }
654 for (k, tr) in tracks.iter().enumerate() {
655 let y = ly + lane_h * k as i32;
656 if k > 0 {
657 self.draw_color(div);
658 self.draw_rect(r[0], y, r[2], hair);
659 }
660 if lw > 0 {
661 let label = crate::basic::cstr(tr.label);
662 let maxw = lw - self.dp(12);
663 self.ts_font(TS_CAPTION);
664 let s = if self.text.width(label) > maxw { self.ellipsize(label.as_bytes(), maxw).0 } else { String::from(label) };
665 let x = r[0] + self.dp(8);
666 text_at(self, TS_CAPTION, x, [x, y, maxw, lane_h], &s, t2, 0);
667 self.wp.ctx.last_rect = [lx(r[0], lw), y, lw, lane_h];
668 self.wp.ctx.last_state = 0;
669 self.sem(K_TEXT, 0, Some(label), tr.ticks.len().min(i32::MAX as usize) as i32);
670 }
671 }
672 let (sub, nsub) = if step / minor as f64 * pps >= self.dp(6) as f64 { (step / minor as f64, minor as i64) } else { (step, 1) };
674 let mut j = floor_f(v.0 / sub) as i64;
675 let (big, small) = (self.dp(5), self.dp(3));
676 for _ in 0..4096 {
677 let tt = j as f64 * sub;
678 let major = j % nsub == 0;
679 j += 1;
680 if tt < v.0 - sub * 1e-6 {
681 continue;
682 }
683 if tt > v.1 + sub * 1e-6 {
684 break;
685 }
686 let x = x_at(v, tt);
687 if !major {
688 self.draw_color(div);
689 self.draw_rect(x, ly - small, hair, small);
690 continue;
691 }
692 self.draw_color(div);
693 self.draw_rect(x, ly, hair, lh);
694 self.draw_color(t3);
695 self.draw_rect(x, ly - big, hair, big);
696 let s = self.tl_text(tt, dec);
697 self.ts_font(TS_CAPTION);
698 let w = self.text.width(&s);
699 let tx = x + self.dp(4);
700 if tx + w <= r[0] + r[2] - self.dp(2) {
701 text_at(self, TS_CAPTION, tx, [tx, head_y, w, line + self.dp(2)], &s, t2, 0);
702 }
703 }
704 let sel_x = st.sel.map(|(a, b)| (x_at(v, a).max(px - hair), x_at(v, b).min(px + pw)));
706 if let Some((xa, xb)) = sel_x {
707 if xb >= xa {
708 self.draw_color((acc & 0x00FF_FFFF) | 0x3000_0000);
709 self.draw_rect(xa, ly, (xb - xa).max(hair), lh);
710 }
711 }
712 let pal = self.style.data().chart_palette(self);
714 let mut cols = Vec::new();
715 if lane_h > 0 {
716 for (k, tr) in tracks.iter().enumerate() {
717 bin_lane(tr.ticks, k as u32, v.0, pps, hair, pw, pal[k % 8], &mut cols);
718 }
719 }
720 if !cols.is_empty() {
721 let bars = bars(&cols, lane_h, self.dp(3), hair);
722 let mut h = Fnv(0xCBF2_9CE4_8422_2325);
723 for &(x, y, w, bh, c) in &bars {
724 for q in [x as u32, y as u32, w as u32, bh as u32, c] {
725 h.u32(q);
726 }
727 }
728 h.u32(bars.len() as u32);
729 let hash = h.0;
730 let d = match &st.cache {
731 Some((k, d)) if *k == hash => d.clone(),
732 _ => {
733 let d: DrawRef = Rc::new(Bars(bars));
734 st.cache = Some((hash, d.clone()));
735 d
736 }
737 };
738 self.cmd_custom(px, ly, pw, lane_h * n, Some(d), 0, hash as u32);
739 } else {
740 st.cache = None;
741 }
742 if let Some((xa, xb)) = sel_x {
743 if xb >= xa {
744 self.draw_color(acc);
745 self.draw_rect(xa, ly, hair, lh);
746 self.draw_rect(xb, ly, hair, lh);
747 }
748 }
749 if evs & EV_HOVER != 0 && st.mode == M_NONE && ev_head & EV_HOVER == 0 && mx >= px && mx < px + pw {
751 self.draw_color(t3);
752 self.draw_rect(mx, r[1] + hair, hair, r[3] - hair * 2);
753 }
754 let ht = self.tl_text(t, dec.max(3));
756 let x = x_at(v, t);
757 let hr = self.tl_head(&ht, x, head_y, head_lo, head_hi);
758 let pwid = (hair * 2).max(2);
759 if x >= px - hair && x <= px + pw {
760 self.draw_color(acc);
761 self.draw_rect(x - pwid / 2, hr[1] + hr[3], pwid, r[1] + r[3] - hr[1] - hr[3]);
762 }
763 self.cmd_rrect(hr[0], hr[1], hr[2], hr[3], hr[3] / 2, 0, acc, 0, 0);
764 let on = self.color(T_ON_ACCENT);
765 text_at(self, TS_CAPTION, hr[0] + self.dp(6), hr, &ht, on, 0);
766 self.wp.ctx.last_rect = [lx(hr[0], hr[2]), hr[1], hr[2], hr[3]];
767 self.wp.ctx.last_state = 0;
768 self.sem(K_TEXT, pid_head, Some(&ht), ms(t));
769 self.clip_pop();
770 self.ltr_end();
771 if self.focus_ring(ev_lane) {
772 let s = self.style;
773 s.focus_ring(self, r, 4);
774 }
775 self.wp.ctx.last_rect = r;
776 self.wp.ctx.last_state = ss;
777 out | (evs & (EV_HOVER | EV_HELD)) | (ev_lane & EV_FOCUS)
778 }
779}