diff --git a/src/dulcepan.h b/src/dulcepan.h index e0f4d9c..089083d 100644 --- a/src/dulcepan.h +++ b/src/dulcepan.h @@ -86,13 +86,32 @@ struct dp_seat { struct wl_list link; }; +enum dp_selection_action { + DP_SELECTION_ACTION_NONE, + DP_SELECTION_ACTION_RESIZING, + DP_SELECTION_ACTION_MOVING, +}; + +enum dp_resize_edges { + DP_EDGE_NONE = 0, + + DP_EDGE_TOP = 1 << 0, + DP_EDGE_BOTTOM = 1 << 1, + DP_EDGE_LEFT = 1 << 2, + DP_EDGE_RIGHT = 1 << 3, +}; + struct dp_selection { struct dp_output *output; // May be NULL int x, y, width, height; - struct dp_output *interactive_output; // NULL if not interactively selecting - int interactive_x, interactive_y; - bool interactive_moved; + enum dp_selection_action action; + bool action_active; + // Pointer offset to apply during movement + int ptr_off_x, ptr_off_y; + int resize_edges; + // Resize anchor + int resize_x, resize_y; }; enum dp_file_format { @@ -161,10 +180,13 @@ void dp_output_hide_surface(struct dp_output *output); void dp_seat_create(struct dp_state *state, uint32_t name, struct wl_seat *wl_seat); void dp_seat_destroy(struct dp_seat *seat); -void dp_select_interactive_start( +void dp_select_start_interactive(struct dp_selection *selection, struct dp_output *output, int x, + int y, bool modify_existing); +void dp_select_stop_interactive(struct dp_selection *selection); + +void dp_select_notify_pointer_position( struct dp_selection *selection, struct dp_output *output, int x, int y); -void dp_select_interactive_move(struct dp_selection *selection, int x, int y); -void dp_select_interactive_stop(struct dp_selection *selection); + void dp_select_whole(struct dp_selection *selection, struct dp_output *output); void dp_save(struct dp_state *state); diff --git a/src/seat.c b/src/seat.c index a79ba4e..cdb76f1 100644 --- a/src/seat.c +++ b/src/seat.c @@ -85,12 +85,58 @@ static const struct wl_keyboard_listener keyboard_listener = { .modifiers = keyboard_handle_modifiers, }; -static void save_position(struct dp_seat *seat, wl_fixed_t x, wl_fixed_t y) { +static enum wp_cursor_shape_device_v1_shape get_cursor_shape(struct dp_selection *selection) { + switch (selection->action) { + case DP_SELECTION_ACTION_NONE: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; + case DP_SELECTION_ACTION_RESIZING: + if (selection->width == 0 || selection->height == 0) { + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; + } + switch (selection->resize_edges) { + case DP_EDGE_TOP: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE; + case DP_EDGE_TOP | DP_EDGE_LEFT: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE; + case DP_EDGE_TOP | DP_EDGE_RIGHT: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE; + case DP_EDGE_BOTTOM: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE; + case DP_EDGE_BOTTOM | DP_EDGE_LEFT: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE; + case DP_EDGE_BOTTOM | DP_EDGE_RIGHT: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE; + case DP_EDGE_LEFT: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE; + case DP_EDGE_RIGHT: + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE; + } + break; + case DP_SELECTION_ACTION_MOVING: + if (selection->x == 0 && selection->y == 0 && + selection->width == selection->output->width && + selection->height == selection->output->height) { + // Moving is impossible + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; + } + return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE; + } + abort(); // Unreachable +} + +static void update_cursor(struct dp_seat *seat, uint32_t serial) { + wp_cursor_shape_device_v1_set_shape( + seat->cursor_shape_device, serial, get_cursor_shape(&seat->state->selection)); +} + +static void process_position(struct dp_seat *seat, wl_fixed_t x, wl_fixed_t y, uint32_t serial) { struct dp_output *output = seat->ptr_output; seat->ptr_x = (int)(wl_fixed_to_double(x) * output->transformed_width / output->effective_width); seat->ptr_y = (int)(wl_fixed_to_double(y) * output->transformed_height / output->effective_height); + dp_select_notify_pointer_position(&seat->state->selection, output, seat->ptr_x, seat->ptr_y); + update_cursor(seat, serial); } static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, @@ -98,9 +144,7 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint struct dp_seat *seat = data; seat->ptr_output = wl_surface_get_user_data(surface); assert(seat->ptr_output != NULL); - save_position(seat, sx, sy); - wp_cursor_shape_device_v1_set_shape( - seat->cursor_shape_device, serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + process_position(seat, sx, sy, serial); } static void pointer_handle_leave( @@ -115,8 +159,7 @@ static void pointer_handle_motion( if (seat->ptr_output == NULL) { return; // Shouldn't happen } - save_position(seat, sx, sy); - dp_select_interactive_move(&seat->state->selection, seat->ptr_x, seat->ptr_y); + process_position(seat, sx, sy, serial); } static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, @@ -126,10 +169,10 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uin struct dp_selection *selection = &state->selection; if (button_state != WL_POINTER_BUTTON_STATE_PRESSED) { - if (selection->interactive_moved && state->config.quick_select) { + if (selection->width > 0 && selection->height > 0 && state->config.quick_select) { state->status = DP_STATUS_SAVED; } - dp_select_interactive_stop(selection); + dp_select_stop_interactive(selection); return; } @@ -139,17 +182,22 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uin switch (button) { case BTN_LEFT: - dp_select_interactive_start(selection, seat->ptr_output, seat->ptr_x, seat->ptr_y); - break; case BTN_RIGHT: + dp_select_start_interactive( + selection, seat->ptr_output, seat->ptr_x, seat->ptr_y, button == BTN_LEFT); + update_cursor(seat, serial); + break; + case BTN_MIDDLE: dp_select_whole(selection, seat->ptr_output); + + // dp_select_whole() doesn't invalidate the interactive state, so do it manually + dp_select_notify_pointer_position(selection, seat->ptr_output, seat->ptr_x, seat->ptr_y); + update_cursor(seat, serial); + if (state->config.quick_select) { state->status = DP_STATUS_SAVED; } break; - case BTN_MIDDLE: - state->status = DP_STATUS_SAVED; - return; } } diff --git a/src/select.c b/src/select.c index 8a91ac7..ce0aa2d 100644 --- a/src/select.c +++ b/src/select.c @@ -1,5 +1,14 @@ +#include +#include +#include + #include "dulcepan.h" +#define RESIZE_INNER_SIZE 4 +#define RESIZE_OUTER_SIZE 12 +#define RESIZE_CORNER_SIZE 24 +#define RESIZE_THRESHOLD (RESIZE_OUTER_SIZE + RESIZE_CORNER_SIZE) + static void set_selected_output(struct dp_selection *selection, struct dp_output *output) { if (selection->output == output) { return; @@ -11,44 +20,183 @@ static void set_selected_output(struct dp_selection *selection, struct dp_output } } -void dp_select_interactive_start( - struct dp_selection *selection, struct dp_output *output, int x, int y) { - selection->interactive_output = output; - selection->interactive_x = x; - selection->interactive_y = y; - selection->interactive_moved = false; +static void update_action(struct dp_selection *selection, struct dp_output *output, int x, int y) { + if (output == selection->output) { + int sx = x - selection->x; + int sy = y - selection->y; - dp_output_redraw(output); + if (sx >= RESIZE_INNER_SIZE && sy >= RESIZE_INNER_SIZE && + sx < selection->width - RESIZE_INNER_SIZE && + sy <= selection->height - RESIZE_INNER_SIZE) { + selection->action = DP_SELECTION_ACTION_MOVING; + return; + } else if (sx >= -RESIZE_OUTER_SIZE && sy >= -RESIZE_OUTER_SIZE && + sx < selection->width + RESIZE_OUTER_SIZE && + sy < selection->height + RESIZE_OUTER_SIZE) { + int edges = DP_EDGE_NONE; + + if (sx >= selection->width - RESIZE_THRESHOLD && sx >= selection->width / 2) { + edges |= DP_EDGE_RIGHT; + } else if (sx < RESIZE_THRESHOLD) { + edges |= DP_EDGE_LEFT; + } + if (sy >= selection->height - RESIZE_THRESHOLD && sy >= selection->height / 2) { + edges |= DP_EDGE_BOTTOM; + } else if (sy < RESIZE_THRESHOLD) { + edges |= DP_EDGE_TOP; + } + + if (edges != DP_EDGE_NONE) { + selection->action = DP_SELECTION_ACTION_RESIZING; + selection->resize_edges = edges; + return; + } + } + } + + selection->action = DP_SELECTION_ACTION_NONE; } -void dp_select_interactive_move(struct dp_selection *selection, int x, int y) { - if (selection->interactive_output == NULL) { +static void do_resize(struct dp_selection *selection, int x, int y) { + int ptr_x = x - selection->ptr_off_x, ptr_y = y - selection->ptr_off_y; + int width = selection->width, height = selection->height; + +retry_horiz: + if ((selection->resize_edges & DP_EDGE_LEFT) != 0) { + width = selection->resize_x - ptr_x; + if (width < 0) { + selection->resize_edges = (selection->resize_edges & ~DP_EDGE_LEFT) | DP_EDGE_RIGHT; + goto retry_horiz; + } + selection->x = selection->resize_x - width; + } else if ((selection->resize_edges & DP_EDGE_RIGHT) != 0) { + width = ptr_x - selection->resize_x; + if (width < 0) { + selection->resize_edges = (selection->resize_edges & ~DP_EDGE_RIGHT) | DP_EDGE_LEFT; + goto retry_horiz; + } + selection->x = selection->resize_x; + } + +retry_verti: + if ((selection->resize_edges & DP_EDGE_TOP) != 0) { + height = selection->resize_y - ptr_y; + if (height < 0) { + selection->resize_edges = (selection->resize_edges & ~DP_EDGE_TOP) | DP_EDGE_BOTTOM; + goto retry_verti; + } + selection->y = selection->resize_y - height; + } else if ((selection->resize_edges & DP_EDGE_BOTTOM) != 0) { + height = ptr_y - selection->resize_y; + if (height < 0) { + selection->resize_edges = (selection->resize_edges & ~DP_EDGE_BOTTOM) | DP_EDGE_TOP; + goto retry_verti; + } + selection->y = selection->resize_y; + } + + if (width == selection->width && height == selection->height) { return; } - set_selected_output(selection, selection->interactive_output); - selection->interactive_moved = true; - - if (x < selection->interactive_x) { - selection->x = x; - selection->width = selection->interactive_x - x; - } else { - selection->x = selection->interactive_x; - selection->width = x - selection->interactive_x; - } - if (y < selection->interactive_y) { - selection->y = y; - selection->height = selection->interactive_y - y; - } else { - selection->y = selection->interactive_y; - selection->height = y - selection->interactive_y; - } + selection->width = width; + selection->height = height; dp_output_redraw(selection->output); } -void dp_select_interactive_stop(struct dp_selection *selection) { - selection->interactive_output = NULL; - selection->interactive_moved = false; +static void do_move(struct dp_selection *selection, int x, int y) { + int new_x = x - selection->ptr_off_x, new_y = y - selection->ptr_off_y; + + if (new_x < 0) { + new_x = 0; + } else if (new_x > selection->output->width - selection->width) { + new_x = selection->output->width - selection->width; + } + if (new_y < 0) { + new_y = 0; + } else if (new_y > selection->output->height - selection->height) { + new_y = selection->output->height - selection->height; + } + + if (new_x == selection->x && new_y == selection->y) { + return; + } + + selection->x = new_x; + selection->y = new_y; + dp_output_redraw(selection->output); +} + +static void init_resize(struct dp_selection *selection, int x, int y) { + if ((selection->resize_edges & DP_EDGE_RIGHT) != 0) { + selection->ptr_off_x = x - selection->x - selection->width; + selection->resize_x = selection->x; + } else { + selection->ptr_off_x = x - selection->x; + selection->resize_x = selection->x + selection->width; + } + if ((selection->resize_edges & DP_EDGE_BOTTOM) != 0) { + selection->ptr_off_y = y - selection->y - selection->height; + selection->resize_y = selection->y; + } else { + selection->ptr_off_y = y - selection->y; + selection->resize_y = selection->y + selection->height; + } +} + +void dp_select_start_interactive(struct dp_selection *selection, struct dp_output *output, int x, + int y, bool modify_existing) { + update_action(selection, output, x, y); + selection->action_active = true; + + if (modify_existing) { + switch (selection->action) { + case DP_SELECTION_ACTION_NONE: + break; + case DP_SELECTION_ACTION_RESIZING: + init_resize(selection, x, y); + return; + case DP_SELECTION_ACTION_MOVING: + selection->ptr_off_x = x - selection->x; + selection->ptr_off_y = y - selection->y; + return; + } + } + + // Start a new selection + + set_selected_output(selection, output); + selection->x = x; + selection->y = y; + selection->width = 0; + selection->height = 0; + + selection->action = DP_SELECTION_ACTION_RESIZING; + selection->resize_edges = DP_EDGE_BOTTOM | DP_EDGE_RIGHT; + + init_resize(selection, x, y); +} + +void dp_select_stop_interactive(struct dp_selection *selection) { + selection->action_active = false; +} + +void dp_select_notify_pointer_position( + struct dp_selection *selection, struct dp_output *output, int x, int y) { + if (selection->action_active) { + switch (selection->action) { + case DP_SELECTION_ACTION_NONE: + abort(); // Unreachable + case DP_SELECTION_ACTION_RESIZING: + do_resize(selection, x, y); + break; + case DP_SELECTION_ACTION_MOVING: + do_move(selection, x, y); + break; + } + } else { + update_action(selection, output, x, y); + } } void dp_select_whole(struct dp_selection *selection, struct dp_output *output) {