378 lines
9.0 KiB
C
378 lines
9.0 KiB
C
// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett <g@b4.vu>
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "alsa.h"
|
|
#include "routing-lines.h"
|
|
|
|
// dotted dash when a destination is going to be removed by a drag
|
|
static const double dash_dotted[] = { 1, 10 };
|
|
|
|
// dash when dragging and not connected
|
|
static const double dash[] = { 4 };
|
|
|
|
static void choose_line_colour(
|
|
struct routing_src *r_src,
|
|
struct routing_dst *r_dst,
|
|
double *r,
|
|
double *g,
|
|
double *b
|
|
) {
|
|
int odd_in = r_src->lr_num & 1;
|
|
int odd_out = r_dst->elem->lr_num & 1;
|
|
int in2 = ((r_src->lr_num - 1) / 2 & 1);
|
|
int out2 = ((r_dst->elem->lr_num - 1) / 2 & 1);
|
|
|
|
if (odd_in && odd_out) {
|
|
*r = 0;
|
|
*g = 0;
|
|
*b = 0;
|
|
} else if (!odd_in && !odd_out) {
|
|
*r = 1;
|
|
*g = 0;
|
|
*b = 0;
|
|
} else if (odd_in) {
|
|
*r = 0;
|
|
*g = 0.25;
|
|
*b = 0;
|
|
} else {
|
|
*r = 0.25;
|
|
*g = 0.25;
|
|
*b = 0;
|
|
}
|
|
if ((r_src->port_category == PC_MIX) !=
|
|
(r_dst->port_category == PC_MIX)) {
|
|
*b = 0.5;
|
|
}
|
|
if (in2) {
|
|
*r = (*r + 1) / 2;
|
|
*g = (*g + 1) / 2;
|
|
}
|
|
if (out2) {
|
|
*b = (*b + 1) / 2;
|
|
}
|
|
}
|
|
|
|
// draw a bezier curve given the end and control points
|
|
static void curve(
|
|
cairo_t *cr,
|
|
double x1,
|
|
double y1,
|
|
double x2,
|
|
double y2,
|
|
double x3,
|
|
double y3,
|
|
double x4,
|
|
double y4
|
|
) {
|
|
cairo_move_to(cr, x1, y1);
|
|
cairo_curve_to(cr, x2, y2, x3, y3, x4, y4);
|
|
}
|
|
|
|
// given the bezier end & control points and t-value, return the
|
|
// position and tangent angle at that point
|
|
static void point_and_angle_on_bezier(
|
|
double x1,
|
|
double y1,
|
|
double x2,
|
|
double y2,
|
|
double x3,
|
|
double y3,
|
|
double x4,
|
|
double y4,
|
|
double t,
|
|
double *x,
|
|
double *y,
|
|
double *a
|
|
) {
|
|
double t2 = t * t;
|
|
double t3 = t2 * t;
|
|
double ti = 1 - t;
|
|
double ti2 = ti * ti;
|
|
|
|
*x = x1 +
|
|
(-x1 * 3 + t * (3 * x1 - x1 * t)) * t +
|
|
(3 * x2 + t * (-6 * x2 + x2 * 3 * t)) * t +
|
|
(x3 * 3 - x3 * 3 * t) * t2 +
|
|
x4 * t3;
|
|
*y = y1 +
|
|
(-y1 * 3 + t * (3 * y1 - y1 * t)) * t +
|
|
(3 * y2 + t * (-6 * y2 + y2 * 3 * t)) * t +
|
|
(y3 * 3 - y3 * 3 * t) * t2 +
|
|
y4 * t3;
|
|
|
|
double dx = ti2 * (x2 - x1) +
|
|
2 * ti * t * (x3 - x2) +
|
|
t2 * (x4 - x3);
|
|
double dy = ti2 * (y2 - y1) +
|
|
2 * ti * t * (y3 - y2) +
|
|
t2 * (y4 - y3);
|
|
*a = atan2(dy, dx);
|
|
}
|
|
|
|
// draw an arrow in the middle of the line drawn by curve()
|
|
static void arrow(
|
|
cairo_t *cr,
|
|
double x1,
|
|
double y1,
|
|
double x2,
|
|
double y2,
|
|
double x3,
|
|
double y3,
|
|
double x4,
|
|
double y4
|
|
) {
|
|
// get midpoint and angle
|
|
double mx, my, a;
|
|
point_and_angle_on_bezier(x1, y1, x2, y2, x3, y3, x4, y4, 0.5, &mx, &my, &a);
|
|
|
|
// calculate point of arrow
|
|
double px = mx + cos(a) * 12;
|
|
double py = my + sin(a) * 12;
|
|
|
|
// calculate sides of arrow
|
|
double s1x = mx + cos(a - M_PI_2) * 2;
|
|
double s1y = my + sin(a - M_PI_2) * 2;
|
|
double s2x = mx + cos(a + M_PI_2) * 2;
|
|
double s2y = my + sin(a + M_PI_2) * 2;
|
|
|
|
// draw triangle
|
|
cairo_move_to(cr, px, py);
|
|
cairo_line_to(cr, s1x, s1y);
|
|
cairo_line_to(cr, s2x, s2y);
|
|
cairo_close_path(cr);
|
|
}
|
|
|
|
// draw a nice curved line connecting a source at (x1, y1) and a
|
|
// destination at (x2, y2)
|
|
static void draw_connection(
|
|
cairo_t *cr,
|
|
double x1,
|
|
double y1,
|
|
int src_is_mixer,
|
|
double x2,
|
|
double y2,
|
|
int dst_is_mixer,
|
|
double r,
|
|
double g,
|
|
double b,
|
|
double w
|
|
) {
|
|
double x3 = x1, y3 = y1, x4 = x2, y4 = y2;
|
|
|
|
// vertical/horizontal?
|
|
if (src_is_mixer == dst_is_mixer) {
|
|
double f1 = 0.3;
|
|
double f2 = 1 - f1;
|
|
|
|
// vertical
|
|
if (src_is_mixer) {
|
|
y3 = y1 * f2 + y2 * f1;
|
|
y4 = y1 * f1 + y2 * f2;
|
|
|
|
// horizontal
|
|
} else {
|
|
x3 = x1 * f2 + x2 * f1;
|
|
x4 = x1 * f1 + x2 * f2;
|
|
}
|
|
|
|
// diagonal
|
|
} else {
|
|
// calculate a fraction f1 close to 0 when approaching 45°
|
|
// and close to 0.5 when approaching 0°/90°
|
|
double a = fmod((atan2(y1 - y2, x2 - x1) * 180 / M_PI) + 360, 360);
|
|
double f1 = fabs(fmod(a, 90) - 45) / 90;
|
|
double f2 = 1 - f1;
|
|
|
|
// bottom to right
|
|
if (src_is_mixer) {
|
|
y3 = y1 * f2 + y2 * f1;
|
|
x4 = x1 * f1 + x2 * f2;
|
|
|
|
// left to top
|
|
} else {
|
|
x3 = x1 * f2 + x2 * f1;
|
|
y4 = y1 * f1 + y2 * f2;
|
|
}
|
|
}
|
|
|
|
cairo_set_source_rgb(cr, r, g, b);
|
|
cairo_set_line_width(cr, w);
|
|
curve(cr, x1, y1, x3, y3, x4, y4, x2, y2);
|
|
arrow(cr, x1, y1, x3, y3, x4, y4, x2, y2);
|
|
cairo_stroke(cr);
|
|
}
|
|
|
|
// locate the center of a widget in the parent coordinates
|
|
// used for drawing lines to/from the "socket" widget of routing
|
|
// sources and destinations
|
|
static void get_widget_center(
|
|
GtkWidget *w,
|
|
GtkWidget *parent,
|
|
double *x,
|
|
double *y
|
|
) {
|
|
double src_x = gtk_widget_get_allocated_width(w) / 2;
|
|
double src_y = gtk_widget_get_allocated_height(w) / 2;
|
|
gtk_widget_translate_coordinates(w, parent, src_x, src_y, x, y);
|
|
}
|
|
|
|
static void get_src_center(
|
|
struct routing_src *r_src,
|
|
GtkWidget *parent,
|
|
double *x,
|
|
double *y
|
|
) {
|
|
get_widget_center(r_src->widget2, parent, x, y);
|
|
if (r_src->port_category == PC_MIX)
|
|
(*y)++;
|
|
}
|
|
|
|
static void get_dst_center(
|
|
struct routing_dst *r_dst,
|
|
GtkWidget *parent,
|
|
double *x,
|
|
double *y
|
|
) {
|
|
get_widget_center(r_dst->elem->widget2, parent, x, y);
|
|
if (r_dst->port_category == PC_MIX)
|
|
(*y)++;
|
|
}
|
|
|
|
// redraw the overlay lines between the routing sources and
|
|
// destinations
|
|
void draw_routing_lines(
|
|
GtkDrawingArea *drawing_area,
|
|
cairo_t *cr,
|
|
int width,
|
|
int height,
|
|
void *user_data
|
|
) {
|
|
struct alsa_card *card = user_data;
|
|
GtkWidget *parent = card->routing_lines;
|
|
|
|
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
|
|
|
|
int dragging = card->drag_type != DRAG_TYPE_NONE;
|
|
|
|
// go through all the routing destinations
|
|
for (int i = 0; i < card->routing_dsts->len; i++) {
|
|
struct routing_dst *r_dst = &g_array_index(
|
|
card->routing_dsts, struct routing_dst, i
|
|
);
|
|
|
|
// if dragging and a routing destination is being reconnected then
|
|
// draw it with dots
|
|
int dragging_this = dragging && card->dst_drag == r_dst;
|
|
if (dragging_this)
|
|
cairo_set_dash(cr, dash_dotted, 2, 0);
|
|
else
|
|
cairo_set_dash(cr, NULL, 0, 0);
|
|
|
|
// get the destination and skip if it's "Off"
|
|
int r_src_idx = alsa_get_elem_value(r_dst->elem);
|
|
if (!r_src_idx)
|
|
continue;
|
|
|
|
// look up the source
|
|
struct routing_src *r_src = &g_array_index(
|
|
card->routing_srcs, struct routing_src, r_src_idx
|
|
);
|
|
|
|
// locate the source and destination coordinates
|
|
double x1, y1, x2, y2;
|
|
get_src_center(r_src, parent, &x1, &y1);
|
|
get_dst_center(r_dst, parent, &x2, &y2);
|
|
|
|
// pick a colour
|
|
double r, g, b;
|
|
choose_line_colour(r_src, r_dst, &r, &g, &b);
|
|
|
|
// make the colour lighter if it's being shown dotted
|
|
if (dragging_this) {
|
|
r = (r + 1) / 2;
|
|
g = (g + 1) / 2;
|
|
b = (b + 1) / 2;
|
|
}
|
|
|
|
// draw the connection
|
|
draw_connection(
|
|
cr,
|
|
x1, y1, r_src->port_category == PC_MIX,
|
|
x2, y2, r_dst->port_category == PC_MIX,
|
|
r, g, b, 2
|
|
);
|
|
}
|
|
}
|
|
|
|
// draw the overlay dragging line
|
|
void draw_drag_line(
|
|
GtkDrawingArea *drawing_area,
|
|
cairo_t *cr,
|
|
int width,
|
|
int height,
|
|
void *user_data
|
|
) {
|
|
struct alsa_card *card = user_data;
|
|
GtkWidget *parent = card->drag_line;
|
|
|
|
// if not dragging or routing src & dst not specified or drag out of
|
|
// bounds then do nothing
|
|
if (card->drag_type == DRAG_TYPE_NONE ||
|
|
(!card->src_drag && !card->dst_drag) ||
|
|
card->drag_x < 0 ||
|
|
card->drag_y < 0)
|
|
return;
|
|
|
|
// the drag mouse position is relative to card->routing_grid
|
|
// translate it to the overlay card->drag_line
|
|
// (don't need to do this if both src_drag and dst_drag are set)
|
|
double drag_x, drag_y;
|
|
if (!card->src_drag || !card->dst_drag)
|
|
gtk_widget_translate_coordinates(
|
|
card->routing_grid, parent,
|
|
card->drag_x, card->drag_y,
|
|
&drag_x, &drag_y
|
|
);
|
|
|
|
// get the line start position; either a routing source socket
|
|
// widget or the drag mouse position
|
|
double x1, y1;
|
|
if (card->src_drag) {
|
|
get_src_center(card->src_drag, parent, &x1, &y1);
|
|
} else {
|
|
x1 = drag_x;
|
|
y1 = drag_y;
|
|
}
|
|
|
|
// get the line end position; either a routing destination socket
|
|
// widget or the drag mouse position
|
|
double x2, y2;
|
|
if (card->dst_drag) {
|
|
get_dst_center(card->dst_drag, parent, &x2, &y2);
|
|
} else {
|
|
x2 = drag_x;
|
|
y2 = drag_y;
|
|
}
|
|
|
|
// if routing src & dst both specified then draw a curved line as if
|
|
// it was connected (except black)
|
|
if (card->src_drag && card->dst_drag) {
|
|
draw_connection(
|
|
cr,
|
|
x1, y1, card->src_drag->port_category == PC_MIX,
|
|
x2, y2, card->dst_drag->port_category == PC_MIX,
|
|
0, 0, 0, 2
|
|
);
|
|
|
|
// otherwise draw a straight line
|
|
} else {
|
|
cairo_set_dash(cr, dash, 1, 0);
|
|
|
|
cairo_set_source_rgb(cr, 0, 0, 0);
|
|
cairo_set_line_width(cr, 2);
|
|
cairo_move_to(cr, x1, y1);
|
|
cairo_line_to(cr, x2, y2);
|
|
cairo_stroke(cr);
|
|
}
|
|
}
|