Terminal styling and layout widgets
3.8 kB
92 lines
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6open Console
7
8(* Count non-overlapping occurrences of [sub] in [s]. ANSI escapes never contain
9 the block glyphs, so counting them measures the visible fill directly. *)
10let count sub s =
11 let n = String.length sub and m = String.length s in
12 let c = ref 0 and i = ref 0 in
13 while !i + n <= m do
14 if String.sub s !i n = sub then begin
15 incr c;
16 i := !i + n
17 end
18 else incr i
19 done;
20 !c
21
22let full = count "█"
23let track = count "░"
24
25(* Blocky fills [pct]% of the [width] cells with full blocks and the rest with a
26 shaded track. *)
27let test_blocky_fill () =
28 let s = Bar.render ~style:`Blocky ~width:10 ~pct:30 () in
29 Alcotest.(check int) "three full blocks" 3 (full s);
30 Alcotest.(check int) "seven track cells" 7 (track s)
31
32let test_blocky_extremes () =
33 let e = Bar.render ~style:`Blocky ~width:10 ~pct:0 () in
34 Alcotest.(check int) "empty has no fill" 0 (full e);
35 Alcotest.(check int) "empty is all track" 10 (track e);
36 let f = Bar.render ~style:`Blocky ~width:10 ~pct:100 () in
37 Alcotest.(check int) "full is all fill" 10 (full f);
38 Alcotest.(check int) "full has no track" 0 (track f)
39
40(* [pct] is clamped to 0..100: out-of-range values render as the endpoints. *)
41let test_pct_clamped () =
42 Alcotest.(check string)
43 "negative clamps to 0"
44 (Bar.render ~style:`Blocky ~width:8 ~pct:0 ())
45 (Bar.render ~style:`Blocky ~width:8 ~pct:(-50) ());
46 Alcotest.(check string)
47 "over 100 clamps to 100"
48 (Bar.render ~style:`Blocky ~width:8 ~pct:100 ())
49 (Bar.render ~style:`Blocky ~width:8 ~pct:200 ())
50
51(* Smooth uses whole eighth-cell fills: 50% of 10 cells is exactly 5 full cells
52 (80 eighths * 50% = 40 = 5 cells, no partial). *)
53let test_smooth_fill () =
54 let s = Bar.render ~style:`Smooth ~width:10 ~pct:50 () in
55 Alcotest.(check int) "five full cells" 5 (full s)
56
57(* A single cell at 50% is a half-cell glyph (8 eighths * 50% = 4 = the "▌"). *)
58let test_smooth_partial () =
59 let s = Bar.render ~style:`Smooth ~width:1 ~pct:50 () in
60 Alcotest.(check int) "one half-cell glyph" 1 (count "▌" s)
61
62(* Style and colour resolve from the theme when not given, and an explicit style
63 overrides the theme's. *)
64let test_style_resolution () =
65 Alcotest.(check int)
66 "dos theme gives a blocky bar" 5
67 (full (Bar.render ~theme:Theme.dos ~width:10 ~pct:50 ()));
68 Alcotest.(check int)
69 "explicit smooth overrides the theme" 5
70 (full (Bar.render ~theme:Theme.dos ~style:`Smooth ~width:10 ~pct:50 ()))
71
72(* The indeterminate bar sweeps a triangle wave up and back over two seconds. At
73 width 100 the blocky fill count equals the percentage. *)
74let test_at_triangle () =
75 let pct elapsed = full (Bar.at ~style:`Blocky ~width:100 ~elapsed ()) in
76 Alcotest.(check int) "t=0 empty" 0 (pct 0.);
77 Alcotest.(check int) "t=0.5 half, rising" 50 (pct 0.5);
78 Alcotest.(check int) "t=1 full at the peak" 100 (pct 1.0);
79 Alcotest.(check int) "t=1.5 half, falling" 50 (pct 1.5);
80 Alcotest.(check int) "t=2 back to empty" 0 (pct 2.0)
81
82let suite =
83 ( "bar",
84 [
85 Alcotest.test_case "blocky fill" `Quick test_blocky_fill;
86 Alcotest.test_case "blocky extremes" `Quick test_blocky_extremes;
87 Alcotest.test_case "pct clamped" `Quick test_pct_clamped;
88 Alcotest.test_case "smooth fill" `Quick test_smooth_fill;
89 Alcotest.test_case "smooth partial" `Quick test_smooth_partial;
90 Alcotest.test_case "style resolution" `Quick test_style_resolution;
91 Alcotest.test_case "at triangle wave" `Quick test_at_triangle;
92 ] )