summaryrefslogtreecommitdiff
path: root/src/modules/mediadisplay/visualiser.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/mediadisplay/visualiser.tsx')
-rw-r--r--src/modules/mediadisplay/visualiser.tsx76
1 files changed, 76 insertions, 0 deletions
diff --git a/src/modules/mediadisplay/visualiser.tsx b/src/modules/mediadisplay/visualiser.tsx
new file mode 100644
index 0000000..fa1adc6
--- /dev/null
+++ b/src/modules/mediadisplay/visualiser.tsx
@@ -0,0 +1,76 @@
+import { Gtk } from "astal/gtk3";
+import cairo from "cairo";
+import AstalCava from "gi://AstalCava";
+import PangoCairo from "gi://PangoCairo";
+
+export default () => (
+ <drawingarea
+ className="visualiser"
+ setup={self => {
+ const cava = AstalCava.get_default();
+
+ if (cava) {
+ cava.set_stereo(true);
+ cava.set_noise_reduction(0.77);
+ cava.set_input(AstalCava.Input.PIPEWIRE);
+
+ self.hook(cava, "notify::values", () => self.queue_draw());
+ self.connect("size-allocate", () => {
+ const width = self.get_allocated_width();
+ const barWidth = self
+ .get_style_context()
+ .get_property("min-width", Gtk.StateFlags.NORMAL) as number;
+ const gaps = self.get_style_context().get_margin(Gtk.StateFlags.NORMAL).right;
+ const bars = Math.floor((width - gaps) / (barWidth + gaps));
+ if (bars > 0) cava.set_bars(bars);
+ });
+ }
+
+ self.connect("draw", (_, cr: cairo.Context) => {
+ const { width, height } = self.get_allocation();
+
+ if (!cava) {
+ // Show error text if cava unavailable
+ const fg = self.get_style_context().get_color(Gtk.StateFlags.NORMAL);
+ cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha);
+ const layout = self.create_pango_layout("Visualiser unavailable");
+ const [w, h] = layout.get_pixel_size();
+ cr.moveTo((width - w) / 2, (height - h) / 2);
+ cr.setAntialias(cairo.Antialias.BEST);
+ PangoCairo.show_layout(cr, layout);
+
+ return;
+ }
+
+ const bg = self.get_style_context().get_background_color(Gtk.StateFlags.NORMAL);
+ cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha);
+ const barWidth = self.get_style_context().get_property("min-width", Gtk.StateFlags.NORMAL) as number;
+ const gaps = self.get_style_context().get_margin(Gtk.StateFlags.NORMAL).right;
+
+ const values = cava.get_values();
+ const len = values.length - 1;
+ const radius = barWidth / 2;
+ const xOff = (width - len * (barWidth + gaps) - gaps) / 2 - radius;
+ const center = height / 2;
+ const half = Math.floor(len / 2);
+
+ // Render channels facing each other
+ for (let i = half - 1; i >= 0; i--) {
+ const x = (half - i) * (barWidth + gaps) + xOff;
+ const value = center * values[i];
+ cr.arc(x, center + value, radius, 0, Math.PI);
+ cr.arc(x, center - value, radius, Math.PI, Math.PI * 2);
+ cr.fill();
+ }
+
+ for (let i = half; i < len; i++) {
+ const x = (i + 1) * (barWidth + gaps) + xOff;
+ const value = center * values[i];
+ cr.arc(x, center + value, radius, 0, Math.PI);
+ cr.arc(x, center - value, radius, Math.PI, Math.PI * 2);
+ cr.fill();
+ }
+ });
+ }}
+ />
+);