1
0
mirror of https://codeberg.org/vyivel/dulcepan/ synced 2025-12-17 15:45:12 +02:00

Add fancy borders

This commit is contained in:
Kirill Primak
2024-06-24 07:40:17 +03:00
parent f590bd2b73
commit 8794cbdeb2
6 changed files with 188 additions and 4 deletions

View File

@@ -4,10 +4,31 @@
unselected-color = ffffff40
selected-color = 00000000
border-color = ffffff
border-secondary-color = 000000
# 0 to disable borders
border-size = 2
# Border gradient type:
# - none: only the primary border color is used
# - linear: uses a linear gradient relative to the selection
# - loop: uses a repeated linear gradient
border-gradient = none
# For "linear" gradient mode
# Counterclockwise, in degrees
gradient-angle = 45
# For "loop" gradient mode
# The distance between gradient stops
loop-step = 100
# For "linear" and "loop" gradient modes
# In milliseconds, 0 to disable animation
# With "linear" mode: the time it takes for the pattern to make one full turn
# With "loop" mode: the time it takes for the pattern to move by double the loop step
animation-duration = 0
# If true, dulcepan will save immediately when interactive selection is stopped
# or when a whole output is selected with a mouse button.
quick-select = false

View File

@@ -92,12 +92,17 @@ void dp_config_load(struct dp_state *state, const char *user_path) {
.quit_key = XKB_KEY_Escape,
.save_key = XKB_KEY_space,
.border_size = 2,
.border_gradient = DP_BORDER_GRADIENT_NONE,
.gradient_angle = 45,
.loop_step = 100,
.animation_duration = 0,
.png_compression = 6,
.quick_select = false,
};
bytes_to_color((uint8_t[]){0xff, 0xff, 0xff, 0x40}, config->unselected_color);
bytes_to_color((uint8_t[]){0x00, 0x00, 0x00, 0x00}, config->selected_color);
bytes_to_color((uint8_t[]){0xff, 0xff, 0xff, 0xff}, config->border_color);
bytes_to_color((uint8_t[]){0x00, 0x00, 0x00, 0xff}, config->border_secondary_color);
FILE *fp = NULL;
if (user_path != NULL) {
@@ -180,8 +185,28 @@ void dp_config_load(struct dp_state *state, const char *user_path) {
load_color(value, line_idx, config->selected_color);
} else if (strcmp(key, "border-color") == 0) {
load_color(value, line_idx, config->border_color);
} else if (strcmp(key, "border-secondary-color") == 0) {
load_color(value, line_idx, config->border_secondary_color);
} else if (strcmp(key, "border-size") == 0) {
load_int(value, line_idx, 0, INT_MAX, &config->border_size);
} else if (strcmp(key, "border-gradient") == 0) {
if (strcmp(value, "none") == 0) {
config->border_gradient = DP_BORDER_GRADIENT_NONE;
} else if (strcmp(value, "linear") == 0) {
config->border_gradient = DP_BORDER_GRADIENT_LINEAR;
} else if (strcmp(value, "loop") == 0) {
config->border_gradient = DP_BORDER_GRADIENT_LOOP;
} else {
dp_log_fatal("Config: invalid border-gradient %s on line %d", value, line_idx);
}
} else if (strcmp(key, "gradient-angle") == 0) {
int deg;
load_int(value, line_idx, INT_MIN, INT_MAX, &deg);
config->gradient_angle = (double)deg * DP_PI / 180;
} else if (strcmp(key, "loop-step") == 0) {
load_int(value, line_idx, 1, INT_MAX, &config->loop_step);
} else if (strcmp(key, "animation-duration") == 0) {
load_int(value, line_idx, 0, INT_MAX, &config->animation_duration);
} else if (strcmp(key, "png-compression") == 0) {
load_int(value, line_idx, 0, 9, &config->png_compression);
} else if (strcmp(key, "quick-select") == 0) {

View File

@@ -8,6 +8,10 @@
#include <wayland-util.h>
#include <xkbcommon/xkbcommon.h>
#define DP_PI 3.14159265358979323846
// sqrt(2) / 2
#define DP_SQRT2_2 0.7071067811865476
// Per-output
#define DP_SWAPCHAIN_LEN 2
@@ -128,14 +132,28 @@ enum dp_file_format {
DP_FILE_PPM,
};
enum dp_border_gradient {
DP_BORDER_GRADIENT_NONE,
DP_BORDER_GRADIENT_LINEAR,
DP_BORDER_GRADIENT_LOOP,
};
struct dp_config {
// RGBA, not premultiplied
float unselected_color[4];
float selected_color[4];
float border_color[4];
float border_secondary_color[4];
xkb_keysym_t quit_key;
xkb_keysym_t save_key;
int border_size; // 0 if disabled
enum dp_border_gradient border_gradient;
double gradient_angle; // In radians
int loop_step;
int animation_duration; // In milliseconds
int png_compression;
bool quick_select;
};
@@ -172,6 +190,11 @@ struct dp_state {
enum dp_file_format output_format;
bool show_cursors;
cairo_pattern_t *border_pattern;
cairo_matrix_t precomputed_linear_matrix;
double loop_step_scale;
double time_multiplier;
};
void dp_config_load(struct dp_state *state, const char *user_path);
@@ -207,6 +230,8 @@ void dp_log_fatal(const char *fmt, ...);
void *dp_zalloc(size_t size);
char *dp_strdup(const char *str);
void dp_matrix_compute_linear(cairo_matrix_t *matrix, double angle);
const char *dp_ext_from_path(const char *path);
enum dp_file_format dp_ext_to_format(const char *ext);

View File

@@ -222,15 +222,46 @@ int main(int argc, char **argv) {
wl_list_init(&state.outputs);
wl_list_init(&state.seats);
state.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
state.display = wl_display_connect(NULL);
if (state.display == NULL) {
dp_log_fatal("Failed to connect to a Wayland compositor");
}
state.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
struct dp_config *config = &state.config;
if (config->border_gradient != DP_BORDER_GRADIENT_NONE) {
state.border_pattern = cairo_pattern_create_linear(0, 0, 1, 0);
cairo_pattern_add_color_stop_rgba(state.border_pattern, 0, config->border_color[0],
config->border_color[1], config->border_color[2], config->border_color[3]);
cairo_pattern_add_color_stop_rgba(state.border_pattern, 1,
config->border_secondary_color[0], config->border_secondary_color[1],
config->border_secondary_color[2], config->border_secondary_color[3]);
switch (config->border_gradient) {
case DP_BORDER_GRADIENT_NONE:
abort(); // Unreachable
case DP_BORDER_GRADIENT_LINEAR:
if (config->animation_duration != 0) {
state.time_multiplier = DP_PI * 2.0 / config->animation_duration;
} else {
dp_matrix_compute_linear(&state.precomputed_linear_matrix, config->gradient_angle);
}
break;
case DP_BORDER_GRADIENT_LOOP:
cairo_pattern_set_extend(state.border_pattern, CAIRO_EXTEND_REFLECT);
state.loop_step_scale = 1.0 / config->loop_step;
if (config->animation_duration != 0) {
state.time_multiplier = 2.0 / config->animation_duration;
}
break;
}
}
run(&state);
cairo_pattern_destroy(state.border_pattern);
xkb_context_unref(state.xkb_context);
wl_display_disconnect(state.display);

View File

@@ -1,6 +1,7 @@
#include <assert.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <time.h>
#include <wayland-client-protocol.h>
#include "dulcepan.h"
@@ -265,6 +266,56 @@ static inline void set_cairo_color(cairo_t *cairo, float color[static 4]) {
cairo_set_source_rgba(cairo, color[0], color[1], color[2], color[3]);
}
static inline uint32_t get_time_msec(void) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (uint32_t)(now.tv_sec * 1000 + now.tv_nsec / 1000000);
}
static void setup_linear_gradient(cairo_t *cairo, struct dp_state *state) {
struct dp_selection *selection = &state->selection;
struct dp_config *config = &state->config;
cairo_matrix_t matrix;
if (config->animation_duration != 0) {
double angle = (double)get_time_msec() * state->time_multiplier;
dp_matrix_compute_linear(&matrix, angle);
} else {
matrix = state->precomputed_linear_matrix;
}
cairo_matrix_scale(&matrix, 1.0 / selection->width, 1.0 / selection->height);
cairo_matrix_translate(&matrix, -selection->x, -selection->y);
cairo_pattern_set_matrix(state->border_pattern, &matrix);
cairo_set_source(cairo, state->border_pattern);
}
static void setup_loop_gradient(cairo_t *cairo, struct dp_state *state) {
struct dp_selection *selection = &state->selection;
struct dp_config *config = &state->config;
double scale = state->loop_step_scale / selection->output->scale;
double offset =
(selection->x + selection->y + (selection->width + selection->height) / 2.0) * scale;
if (config->animation_duration != 0) {
offset += (double)get_time_msec() * state->time_multiplier / selection->output->scale;
}
cairo_matrix_t matrix;
cairo_matrix_init(&matrix, scale, 0, 0, scale, fmod(offset, 2.0), 0);
// Rotate by 45°
cairo_matrix_t rotation;
cairo_matrix_init(&rotation, -DP_SQRT2_2, DP_SQRT2_2, -DP_SQRT2_2, -DP_SQRT2_2, 0, 0);
cairo_matrix_multiply(&matrix, &rotation, &matrix);
cairo_pattern_set_matrix(state->border_pattern, &matrix);
cairo_set_source(cairo, state->border_pattern);
}
static void redraw(struct dp_output *output) {
assert(output->redraw_callback == NULL);
@@ -282,6 +333,8 @@ static void redraw(struct dp_output *output) {
}
buffer->used = true;
output->needs_redraw = false;
struct dp_state *state = output->state;
struct dp_selection *selection = &state->selection;
struct dp_config *config = &state->config;
@@ -299,7 +352,19 @@ static void redraw(struct dp_output *output) {
double off = scaled_size / 2.0;
cairo_rectangle(buffer->cairo, selection->x - off, selection->y - off,
selection->width + scaled_size, selection->height + scaled_size);
set_cairo_color(buffer->cairo, config->border_color);
switch (config->border_gradient) {
case DP_BORDER_GRADIENT_NONE:
set_cairo_color(buffer->cairo, config->border_color);
break;
case DP_BORDER_GRADIENT_LINEAR:
setup_linear_gradient(buffer->cairo, state);
break;
case DP_BORDER_GRADIENT_LOOP:
setup_loop_gradient(buffer->cairo, state);
break;
}
cairo_stroke(buffer->cairo);
}
@@ -307,6 +372,10 @@ static void redraw(struct dp_output *output) {
buffer->cairo, selection->x, selection->y, selection->width, selection->height);
set_cairo_color(buffer->cairo, config->selected_color);
cairo_fill(buffer->cairo);
if (config->border_gradient != DP_BORDER_GRADIENT_NONE && config->animation_duration != 0) {
output->needs_redraw = true;
}
}
wl_surface_attach(output->select_surface, buffer->wl_buffer, 0, 0);
@@ -315,7 +384,6 @@ static void redraw(struct dp_output *output) {
output->redraw_callback = wl_surface_frame(output->select_surface);
wl_callback_add_listener(output->redraw_callback, &redraw_callback_listener, output);
output->needs_redraw = false;
wl_surface_commit(output->select_surface);
}

View File

@@ -63,3 +63,17 @@ enum dp_file_format dp_ext_to_format(const char *ext) {
}
return DP_FILE_UNKNOWN;
}
void dp_matrix_compute_linear(cairo_matrix_t *matrix, double angle) {
double c = cos(angle), s = sin(angle);
double f = 1.0 / (fabs(c) + fabs(s));
cairo_matrix_init_translate(matrix, 0.5, 0.5);
cairo_matrix_scale(matrix, f, 1);
cairo_matrix_t tmp;
cairo_matrix_init(&tmp, c, s, -s, c, 0, 0);
cairo_matrix_multiply(matrix, &tmp, matrix);
cairo_matrix_translate(matrix, -0.5, -0.5);
}