(ctx, w, h)
| 89 | } |
| 90 | |
| 91 | function paint(ctx, w, h) { |
| 92 | // Cream paper background. |
| 93 | ctx.fillStyle = theme.widget_base; |
| 94 | ctx.fillRect(0, 0, w, h); |
| 95 | |
| 96 | if (datasets.length === 0) return; |
| 97 | |
| 98 | // Card background. |
| 99 | const pad = 14; |
| 100 | ctx.fillStyle = theme.alternate_base; |
| 101 | ctx.fillRect(pad, pad, w - pad * 2, h - pad * 2); |
| 102 | ctx.strokeStyle = theme.widget_border; |
| 103 | ctx.lineWidth = 1; |
| 104 | ctx.strokeRect(pad + 0.5, pad + 0.5, w - pad * 2 - 1, h - pad * 2 - 1); |
| 105 | |
| 106 | // Header strip. |
| 107 | const headerY = pad + 10; |
| 108 | ctx.fillStyle = theme.widget_text; |
| 109 | ctx.font = "bold 11px sans-serif"; |
| 110 | ctx.textAlign = "start"; |
| 111 | ctx.fillText("LEVEL METER", pad + 16, headerY + 4); |
| 112 | ctx.fillStyle = theme.placeholder_text; |
| 113 | ctx.font = "9px sans-serif"; |
| 114 | ctx.textAlign = "end"; |
| 115 | ctx.fillText(datasets.length === 1 ? datasets[0].title : "MULTI", |
| 116 | w - pad - 16, headerY + 4); |
| 117 | ctx.fillStyle = theme.widget_border; |
| 118 | ctx.fillRect(pad + 12, headerY + 12, w - (pad + 12) * 2, 1); |
| 119 | |
| 120 | // Bars geometry. |
| 121 | const labelW = 28; |
| 122 | const valueW = 64; |
| 123 | const barX = pad + 12 + labelW; |
| 124 | const barW = w - pad * 2 - 24 - labelW - valueW; |
| 125 | const barH = 16; |
| 126 | |
| 127 | // Vertically distribute bars. |
| 128 | const top = headerY + 24; |
| 129 | const bot = h - pad - 16; |
| 130 | const gap = 6; |
| 131 | const total = datasets.length * barH + (datasets.length - 1) * gap; |
| 132 | let y = top + Math.max(0, ((bot - top) - total) * 0.5); |
| 133 | |
| 134 | for (let i = 0; i < datasets.length; ++i) { |
| 135 | const ds = datasets[i]; |
| 136 | const span = (ds.max - ds.min) || 1; |
| 137 | const norm = Number.isFinite(ds.value) |
| 138 | ? clamp01((ds.value - ds.min) / span) |
| 139 | : 0; |
| 140 | |
| 141 | // Channel label on the left. |
| 142 | ctx.fillStyle = theme.widget_text; |
| 143 | ctx.font = "bold 12px sans-serif"; |
| 144 | ctx.textAlign = "center"; |
| 145 | const tag = ds.title ? ds.title.substring(0, 2).toUpperCase() : "" + (i + 1); |
| 146 | ctx.fillText(tag, pad + 12 + labelW * 0.5, y + barH * 0.5 + 4); |
| 147 | |
| 148 | draw_bar(ctx, barX, y, barW, barH, norm, peaks[i] || 0); |
nothing calls this directly
no test coverage detected