Skip to content
This repository was archived by the owner on Mar 11, 2024. It is now read-only.

use Snapshot API instead of cairo #64

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 40 additions & 51 deletions src/waveform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,19 @@

import Adw from "gi://Adw";
import GObject from "gi://GObject";
import Gdk from "gi://Gdk?version=4.0";
import Gtk from "gi://Gtk?version=4.0";
import Gst from "gi://Gst";

// @ts-expect-error This module doesn't import nicely
import Cairo from "cairo";
import Graphene from "gi://Graphene";

export enum WaveType {
Recorder,
Player,
}

const GUTTER = 4;
const LINE_WIDTH = 1;

export class APWaveForm extends Gtk.DrawingArea {
export class APWaveForm extends Gtk.Widget {
private _position: number;
private dragGesture?: Gtk.GestureDrag;
private hcId: number;
Expand Down Expand Up @@ -93,8 +91,6 @@ export class APWaveForm extends Gtk.DrawingArea {
this.queue_draw();
},
);

this.set_draw_func(this.drawFunc.bind(this));
}

get peaks(): number[] {
Expand Down Expand Up @@ -122,12 +118,10 @@ export class APWaveForm extends Gtk.DrawingArea {
this.emit("position-changed", this.position);
}

private drawFunc(
_: Gtk.DrawingArea,
ctx: Cairo.Context,
width: number,
height: number,
) {
vfunc_snapshot(snapshot: Gtk.Snapshot): void {
const height = this.get_height();
const width = this.get_width();

const peaks = this.peaks;
const vertiCenter = height / 2;
const horizCenter = width / 2;
Expand All @@ -140,23 +134,17 @@ export class APWaveForm extends Gtk.DrawingArea {

const leftColor = this.safeLookupColor("accent_color");

// Because the cairo module isn't real, we have to use these to ignore `any`.
// We keep them to the minimum possible scope to catch real errors.
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
ctx.setLineCap(Cairo.LineCap.SQUARE);
ctx.setAntialias(Cairo.Antialias.NONE);
ctx.setLineWidth(2);

this.setSourceRGBA(ctx, leftColor);

ctx.moveTo(horizCenter, vertiCenter - height);
ctx.lineTo(horizCenter, vertiCenter + height);
ctx.stroke();
// Clip the snapshot to the widget area.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general the more sensible approach is to draw inside this rectangle, this code implies that things will be drawn outside and can appear half-way offscreen.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct. The reason is that the peaks are drawn "scaled up" by another 70%

Usually the waveform peaks are short (50-70%) of the widget height. I make them a bit larger so that there's not much empty space.

Should I stop clipping the peaks or should I instead clip the waveform lines before they are given their size is larger than widget.height?

Copy link
Contributor

@A6GibKm A6GibKm Feb 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not familiar with the code, ideally one does the math and draws them at a size based on height in such a way that even after adding the position it does not go beyond the given size.

Without thinking too much, I would set the rectangle with height factor * height (factor goes from 0 to 1) and a y position given by (height - factor * height) / 2.0 so that it looks centered.

Note that you have not ensure everything passed to the snapshot API (e.g. values for a rectangle) is an integer if you want to avoid blurryness. (just use a floor() fn whenever you do a division or multiple by a random float like factor).

I will try to test or review this with more time in the future.

// Turns out the DrawingArea was automatically doing that for us
snapshot.push_clip(
new Graphene.Rect({ size: new Graphene.Size({ width, height }) }),
);

ctx.setLineWidth(2);
/* eslint-enable @typescript-eslint/no-unsafe-call */
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
const indicator = new Graphene.Rect({
origin: new Graphene.Point({ x: horizCenter, y: 0 }),
size: new Graphene.Size({ width: LINE_WIDTH, height }),
});
snapshot.append_color(leftColor, indicator);

// only draw the waveform for peaks inside the view
let invisible_peaks = 0;
Expand All @@ -166,7 +154,9 @@ export class APWaveForm extends Gtk.DrawingArea {
pointer = pointer + invisible_peaks;
}

for (const peak of peaks.slice(invisible_peaks / GUTTER)) {
// eslint-disable-next-line @typescript-eslint/no-for-in-array
for (const id in peaks.slice(invisible_peaks / GUTTER)) {
const peak = peaks.slice(invisible_peaks / GUTTER)[id];
// this shouldn't happen, but just in case
if (pointer < 0) {
pointer += GUTTER;
Expand All @@ -177,22 +167,29 @@ export class APWaveForm extends Gtk.DrawingArea {
break;
}

if (pointer > horizCenter) {
this.setSourceRGBA(ctx, rightColor);
} else {
this.setSourceRGBA(ctx, leftColor);
}

/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
ctx.moveTo(pointer, vertiCenter + peak * height);
ctx.lineTo(pointer, vertiCenter - peak * height);
ctx.stroke();
/* eslint-enable @typescript-eslint/no-unsafe-call */
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
// only show 70% of the peaks. there are usually few peaks that are
// over 70% high, and those get clipped so that not much space is empty
const line_height = Math.max(peak * height * 0.7, 1);

const line = new Graphene.Rect({
origin: new Graphene.Point({
x: pointer,
y: vertiCenter - line_height,
}),
size: new Graphene.Size({
width: LINE_WIDTH,
height: line_height * 2,
}),
});
snapshot.append_color(
pointer > horizCenter ? rightColor : leftColor,
line,
);

pointer += GUTTER;
}

snapshot.pop();
}

set position(pos: number) {
Expand All @@ -207,14 +204,6 @@ export class APWaveForm extends Gtk.DrawingArea {
return this._position;
}

private setSourceRGBA(cr: Cairo.Context, rgba: Gdk.RGBA): void {
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
cr.setSourceRGBA(rgba.red, rgba.green, rgba.blue, rgba.alpha);
/* eslint-enable @typescript-eslint/no-unsafe-call */
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
}

public destroy(): void {
Adw.StyleManager.get_default().disconnect(this.hcId);
this.peaks.length = 0;
Expand Down