~sircmpwn/wio

Resize views accurately v1 PROPOSED

Leonid Bobrov
Leonid Bobrov: 1
 Resize views accurately

 5 files changed, 352 insertions(+), 178 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/wio/patches/11214/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] Resize views accurately Export this patch

Leonid Bobrov
As promised, I did:
* avoid some, but not all, duplicate code;
* resize views when dragging corner borders;
* width and height never become negative
  to avoid rendering bugs while reflecting;
* reflection behaves like in Rio;
* minimal size is enforced on red box like in Rio.

TODO: input.c needs int scale
---
 include/server.h |   6 +-
 include/view.h   |  18 +--
 input.c          | 281 ++++++++++++++++++++++++++++-------------------
 output.c         | 179 ++++++++++++++++++++++++------
 view.c           |  46 ++++----
 5 files changed, 352 insertions(+), 178 deletions(-)

diff --git a/include/server.h b/include/server.h
index 60842e1..f49b2dd 100644
--- a/include/server.h
+++ b/include/server.h
@@ -26,9 +26,13 @@ enum wio_input_state {
	INPUT_STATE_RESIZE_SELECT,
	INPUT_STATE_RESIZE_START,
	INPUT_STATE_RESIZE_END,
	INPUT_STATE_BORDER_DRAG_TOP_RIGHT,
	INPUT_STATE_BORDER_DRAG_TOP_LEFT,
	INPUT_STATE_BORDER_DRAG_TOP,
	INPUT_STATE_BORDER_DRAG_RIGHT,
	INPUT_STATE_BORDER_DRAG_BOTTOM_RIGHT,
	INPUT_STATE_BORDER_DRAG_BOTTOM_LEFT,
	INPUT_STATE_BORDER_DRAG_BOTTOM,
	INPUT_STATE_BORDER_DRAG_RIGHT,
	INPUT_STATE_BORDER_DRAG_LEFT,
	INPUT_STATE_DELETE_SELECT,
	INPUT_STATE_HIDE_SELECT,
diff --git a/include/view.h b/include/view.h
index 36303fa..e7631de 100644
--- a/include/view.h
+++ b/include/view.h
@@ -3,10 +3,14 @@
#include <wlr/types/wlr_xdg_shell.h>
#include <wayland-server.h>

#define MINWIDTH 100
#define MINHEIGHT 100

struct wio_server;

struct wio_view {
	int x, y;
	int area;
	struct wlr_xdg_surface *xdg_surface;
	struct wio_server *server;
	struct wl_list link;
@@ -15,19 +19,19 @@ struct wio_view {
};

enum wio_view_area {
	VIEW_AREA_SURFACE = 0,
	VIEW_AREA_BORDER_TOP,
	VIEW_AREA_BORDER_RIGHT,
	VIEW_AREA_BORDER_BOTTOM,
	VIEW_AREA_BORDER_LEFT,
	VIEW_AREA_NONE = 0,
	VIEW_AREA_SURFACE = 1,
	VIEW_AREA_BORDER_TOP = 2,
	VIEW_AREA_BORDER_BOTTOM = 4,
	VIEW_AREA_BORDER_RIGHT = 8,
	VIEW_AREA_BORDER_LEFT = 16,
};

void server_new_xdg_surface(struct wl_listener *listener, void *data);

void wio_view_focus(struct wio_view *view, struct wlr_surface *surface);
struct wio_view *wio_view_at(struct wio_server *server, double lx, double ly,
		struct wlr_surface **surface, double *sx, double *sy,
		int *view_area);
		struct wlr_surface **surface, double *sx, double *sy);
void wio_view_move(struct wio_view *view, int x, int y);

#endif
diff --git a/input.c b/input.c
index e4e4fbf..84c16b0 100644
--- a/input.c
+++ b/input.c
@@ -14,6 +14,10 @@
#include "server.h"
#include "view.h"

// TODO: scale
#define less_swap1(A, B) { if (A < B) { int C = A; A = B + window_border * 2; B = C + window_border * 2; } }
#define less_swap2(A, B) { if (A < B) { int C = A; A = B - window_border * 2; B = C - window_border * 2; } }

static void keyboard_handle_modifiers(
		struct wl_listener *listener, void *data) {
	struct wio_keyboard *keyboard =
@@ -112,12 +116,10 @@ void server_new_input(struct wl_listener *listener, void *data) {

static void process_cursor_motion(struct wio_server *server, uint32_t time) {
	double sx, sy;
	int view_area;
	struct wlr_seat *seat = server->seat;
	struct wlr_surface *surface = NULL;
	struct wio_view *view = wio_view_at(
			server, server->cursor->x, server->cursor->y, &surface, &sx, &sy,
			&view_area);
			server, server->cursor->x, server->cursor->y, &surface, &sx, &sy);
	if (!view) {
		switch (server->input_state) {
		case INPUT_STATE_MOVE_SELECT:
@@ -131,8 +133,13 @@ static void process_cursor_motion(struct wio_server *server, uint32_t time) {
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"grabbing", server->cursor);
			break;
		case INPUT_STATE_BORDER_DRAG_TOP_RIGHT:
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"top_right_corner", server->cursor);
			break;
		case INPUT_STATE_RESIZE_START:
		case INPUT_STATE_NEW_START:
		case INPUT_STATE_BORDER_DRAG_TOP_LEFT:
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"top_left_corner", server->cursor);
			break;
@@ -140,22 +147,27 @@ static void process_cursor_motion(struct wio_server *server, uint32_t time) {
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"top_side", server->cursor);
			break;
		case INPUT_STATE_BORDER_DRAG_RIGHT:
		case INPUT_STATE_RESIZE_END:
		case INPUT_STATE_NEW_END:
		case INPUT_STATE_BORDER_DRAG_BOTTOM_RIGHT:
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"right_side", server->cursor);
					"bottom_right_corner", server->cursor);
			break;
		case INPUT_STATE_BORDER_DRAG_BOTTOM_LEFT:
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"bottom_left_corner", server->cursor);
			break;
		case INPUT_STATE_BORDER_DRAG_BOTTOM:
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"bottom_side", server->cursor);
			break;
		case INPUT_STATE_BORDER_DRAG_LEFT:
		case INPUT_STATE_BORDER_DRAG_RIGHT:
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"left_side", server->cursor);
					"right_side", server->cursor);
			break;
		case INPUT_STATE_RESIZE_END:
		case INPUT_STATE_NEW_END:
		case INPUT_STATE_BORDER_DRAG_LEFT:
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
					"bottom_right_corner", server->cursor);
					"left_side", server->cursor);
			break;
		default:
			wlr_xcursor_manager_set_cursor_image(server->cursor_mgr,
@@ -245,27 +257,26 @@ static void view_end_interactive(struct wio_server *server) {
static void new_view(struct wio_server *server) {
	int x1 = server->interactive.sx, x2 = server->cursor->x;
	int y1 = server->interactive.sy, y2 = server->cursor->y;
	if (x2 < x1) {
		int _ = x1;
		x1 = x2;
		x2 = _;
	less_swap2(x2, x1);
	less_swap2(y2, y1);
	int width = x2 - x1, height = y2 - y1;
	struct wio_new_view *view = calloc(1, sizeof(struct wio_new_view));
	if (width < MINWIDTH && (x1 - server->interactive.sx) < 0) {
		x1 -= MINWIDTH - width;
	}
	if (y2 < y1) {
		int _ = y1;
		y1 = y2;
		y2 = _;
	if (height < MINHEIGHT && (y1 - server->interactive.sy) < 0) {
		y1 -= MINHEIGHT - height;
	}
	struct wio_new_view *view = calloc(1, sizeof(struct wio_new_view));
	view->box.x = x1;
	view->box.y = y1;
	view->box.width = x2 - x1;
	view->box.height = y2 - y1;
	if (view->box.width < 100){
		view->box.width = 100;
	if (width < MINWIDTH) {
		width = MINWIDTH;
	}
	if (view->box.height < 100){
		view->box.height = 100;
	if (height < MINHEIGHT) {
		height = MINHEIGHT;
	}
	view->box.x = x1;
	view->box.y = y1;
	view->box.width = width;
	view->box.height = height;
	int fd[2];
	if (pipe(fd) != 0) {
		wlr_log(WLR_ERROR, "Unable to create pipe for fork");
@@ -358,11 +369,9 @@ static void handle_button_internal(
	case INPUT_STATE_RESIZE_SELECT:
		if (event->state == WLR_BUTTON_PRESSED) {
			double sx, sy;
			int view_area;
			struct wlr_surface *surface = NULL;
			struct wio_view *view = wio_view_at(server,
					server->cursor->x, server->cursor->y, &surface, &sx, &sy,
					&view_area);
					server->cursor->x, server->cursor->y, &surface, &sx, &sy);
			if (view != NULL) {
				view_begin_interactive(view, surface, sx, sy,
						"bottom_right_corner", INPUT_STATE_RESIZE_START);
@@ -378,105 +387,141 @@ static void handle_button_internal(
			server->input_state = INPUT_STATE_RESIZE_END;
		}
		break;
	case INPUT_STATE_BORDER_DRAG_TOP:
		y1 = server->interactive.view->y + server->interactive.view->xdg_surface->surface->current.height;
		y2 = server->cursor->y;
	case INPUT_STATE_BORDER_DRAG_TOP_RIGHT:
		y1 = server->cursor->y;
		y2 = server->interactive.view->y + server->interactive.view->xdg_surface->surface->current.height;
		x1 = server->interactive.view->x;
		if (y2 < y1) {
			int _ = y1;
			y1 = y2;
			y2 = _;
		x2 = server->cursor->x;
		less_swap1(y2, y1);
		less_swap2(x2, x1);
		width = x2 - x1;
		if (width < MINWIDTH && (x1 - server->interactive.view->x) < 0) {
			x1 -= MINWIDTH - width;
		}
		wio_view_move(server->interactive.view,
				x1, y1);
		height = y2 - y1;
		if (height < MINHEIGHT
			&& (y1 - server->interactive.view->y) < (server->interactive.view->xdg_surface->surface->current.height)) {
			y1 -= MINHEIGHT - height;
		}
		goto Done;
	case INPUT_STATE_BORDER_DRAG_TOP_LEFT:
		y1 = server->cursor->y;
		y2 = server->interactive.view->y + server->interactive.view->xdg_surface->surface->current.height;
		x1 = server->cursor->x;
		x2 = server->interactive.view->x + server->interactive.view->xdg_surface->surface->current.width;
		less_swap1(y2, y1);
		less_swap1(x2, x1);
		width = x2 - x1;
		if (width < MINWIDTH
			&& (x1 - server->interactive.view->x) < (server->interactive.view->xdg_surface->surface->current.width)) {
			x1 -= MINWIDTH - width;
		}
		height = y2 - y1;
		if (height < MINHEIGHT
			&& (y1 - server->interactive.view->y) < (server->interactive.view->xdg_surface->surface->current.height)) {
			y1 -= MINHEIGHT - height;
		}
		goto Done;
	case INPUT_STATE_BORDER_DRAG_TOP:
		y1 = server->cursor->y;
		y2 = server->interactive.view->y + server->interactive.view->xdg_surface->surface->current.height;
		x1 = server->interactive.view->x;
		less_swap1(y2, y1);
		width = server->interactive.view->xdg_surface->surface->current.width;
		height = y2 - y1;
		if (height < 100) {
			height = 100;
		if (height < MINHEIGHT
			&& (y1 - server->interactive.view->y) < (server->interactive.view->xdg_surface->surface->current.height)) {
			y1 -= MINHEIGHT - height;
		}
		wlr_xdg_toplevel_set_size(
				server->interactive.view->xdg_surface, width, height);
		view_end_interactive(server);
		break;
	case INPUT_STATE_BORDER_DRAG_LEFT:
		x1 = server->interactive.view->x + server->interactive.view->xdg_surface->surface->current.width;
		goto Done;
	case INPUT_STATE_BORDER_DRAG_BOTTOM_RIGHT:
		x1 = server->interactive.view->x;
		x2 = server->cursor->x;
		y1 = server->interactive.view->y;
		if (x2 < x1) {
			int _ = x1;
			x1 = x2;
			x2 = _;
		y2 = server->cursor->y;
		less_swap2(x2, x1);
		less_swap2(y2, y1);
		width = x2 - x1;
		if (width < MINWIDTH && (x1 - server->interactive.view->x) < 0) {
			x1 -= MINWIDTH - width;
		}
		wio_view_move(server->interactive.view,
				x1, y1);
		height = y2 - y1;
		if (height < MINHEIGHT && (y1 - server->interactive.view->y) < 0) {
			y1 -= MINHEIGHT - height;
		}
		goto Done;
	case INPUT_STATE_BORDER_DRAG_BOTTOM_LEFT:
		x1 = server->cursor->x;
		x2 = server->interactive.view->x + server->interactive.view->xdg_surface->surface->current.width;
		y1 = server->interactive.view->y;
		y2 = server->cursor->y;
		less_swap1(x2, x1);
		less_swap2(y2, y1);
		width = x2 - x1;
		height = server->interactive.view->xdg_surface->surface->current.height;
		if (width < 100) {
			width = 100;
		if (width < MINWIDTH
			&& (x1 - server->interactive.view->x) < (server->interactive.view->xdg_surface->surface->current.width)) {
			x1 -= MINWIDTH - width;
		}
		wlr_xdg_toplevel_set_size(
				server->interactive.view->xdg_surface, width, height);
		view_end_interactive(server);
		break;
		height = y2 - y1;
		if (height < MINHEIGHT && (y1 - server->interactive.view->y) < 0) {
			y1 -= MINHEIGHT - height;
		}
		goto Done;
	case INPUT_STATE_BORDER_DRAG_BOTTOM:
		x1 = server->interactive.view->x;
		y1 = server->interactive.view->y, y2 = server->cursor->y;
		if (y2 < y1) {
			int _ = y1;
			y1 = y2;
			y2 = _;
		}
		wio_view_move(server->interactive.view,
				x1, y1);
		y1 = server->interactive.view->y;
		y2 = server->cursor->y;
		less_swap2(y2, y1);
		width = server->interactive.view->xdg_surface->surface->current.width;
		height = y2 - y1;
		if (width < 100) {
			width = 100;
		if (height < MINHEIGHT && (y1 - server->interactive.view->y) < 0) {
			y1 -= MINHEIGHT - height;
		}
		wlr_xdg_toplevel_set_size(
				server->interactive.view->xdg_surface, width, height);
		view_end_interactive(server);
		break;
		goto Done;
	case INPUT_STATE_BORDER_DRAG_RIGHT:
		x1 = server->interactive.view->x, x2 = server->cursor->x;
		x1 = server->interactive.view->x;
		x2 = server->cursor->x;
		y1 = server->interactive.view->y;
		if (x2 < x1) {
			int _ = x1;
			x1 = x2;
			x2 = _;
		}
		wio_view_move(server->interactive.view,
				x1, y1);
		less_swap2(x2, x1);
		width = x2 - x1;
		if (width < MINWIDTH && (x1 - server->interactive.view->x) < 0) {
			x1 -= MINWIDTH - width;
		}
		height = server->interactive.view->xdg_surface->surface->current.height;
		if (width < 100) {
			width = 100;
		goto Done;
	case INPUT_STATE_BORDER_DRAG_LEFT:
		x1 = server->cursor->x;
		x2 = server->interactive.view->x + server->interactive.view->xdg_surface->surface->current.width;
		y1 = server->interactive.view->y;
		less_swap1(x2, x1);
		width = x2 - x1;
		if (width < MINWIDTH
			&& (x1 - server->interactive.view->x) < (server->interactive.view->xdg_surface->surface->current.width)) {
			x1 -= MINWIDTH - width;
		}
		wlr_xdg_toplevel_set_size(
				server->interactive.view->xdg_surface, width, height);
		view_end_interactive(server);
		break;
		height = server->interactive.view->xdg_surface->surface->current.height;
		goto Done;
	case INPUT_STATE_RESIZE_END:
		x1 = server->interactive.sx, x2 = server->cursor->x;
		y1 = server->interactive.sy, y2 = server->cursor->y;
		if (x2 < x1) {
			int _ = x1;
			x1 = x2;
			x2 = _;
		less_swap2(x2, x1);
		less_swap2(y2, y1);
		width = x2 - x1;
		if (width < MINWIDTH && (x1 - server->interactive.sx) < 0) {
			x1 -= MINWIDTH - width;
		}
		if (y2 < y1) {
			int _ = y1;
			y1 = y2;
			y2 = _;
		height = y2 - y1;
		if (height < MINHEIGHT && (y1 - server->interactive.sy) < 0) {
			y1 -= MINHEIGHT - height;
		}
	Done:
		wio_view_move(server->interactive.view,
				x1, y1);
		width = x2 - x1, height = y2 - y1;
		if (width < 100) {
			width = 100;
		if (width < MINWIDTH) {
			width = MINWIDTH;
		}
		if (height < 100) {
			height = 100;
		if (height < MINHEIGHT) {
			height = MINHEIGHT;
		}
		wlr_xdg_toplevel_set_size(
				server->interactive.view->xdg_surface, width, height);
@@ -485,11 +530,9 @@ static void handle_button_internal(
	case INPUT_STATE_MOVE_SELECT:
		if (event->state == WLR_BUTTON_PRESSED) {
			double sx, sy;
			int view_area;
			struct wlr_surface *surface = NULL;
			struct wio_view *view = wio_view_at(server,
					server->cursor->x, server->cursor->y, &surface, &sx, &sy,
					&view_area);
					server->cursor->x, server->cursor->y, &surface, &sx, &sy);
			if (view != NULL) {
				view_begin_interactive(view, surface, sx, sy,
						"grabbing", INPUT_STATE_MOVE);
@@ -507,11 +550,9 @@ static void handle_button_internal(
	case INPUT_STATE_DELETE_SELECT:
		if (event->state == WLR_BUTTON_PRESSED) {
			double sx, sy;
			int view_area;
			struct wlr_surface *surface = NULL;
			struct wio_view *view = wio_view_at(server,
					server->cursor->x, server->cursor->y, &surface, &sx, &sy,
					&view_area);
					server->cursor->x, server->cursor->y, &surface, &sx, &sy);
			if (view != NULL) {
				wlr_xdg_toplevel_send_close(view->xdg_surface);
			}
@@ -530,29 +571,43 @@ void server_cursor_button(struct wl_listener *listener, void *data) {
	struct wlr_event_pointer_button *event = data;
	double sx, sy;
	struct wlr_surface *surface = NULL;
	int view_area;
	struct wio_view *view = wio_view_at(
			server, server->cursor->x, server->cursor->y, &surface, &sx, &sy,
			&view_area);
			server, server->cursor->x, server->cursor->y, &surface, &sx, &sy);
	if (server->input_state == INPUT_STATE_NONE && view) {
		wio_view_focus(view, surface);
		switch (view_area) {
		switch (view->area) {
		case VIEW_AREA_SURFACE:
			wlr_seat_pointer_notify_button(server->seat,
					event->time_msec, event->button, event->state);
			break;
		case VIEW_AREA_BORDER_TOP|VIEW_AREA_BORDER_RIGHT:
			view_begin_interactive(view, surface, view->x, view->y,
					"top_right_corner", INPUT_STATE_BORDER_DRAG_TOP_RIGHT);
			break;
		case VIEW_AREA_BORDER_TOP|VIEW_AREA_BORDER_LEFT:
			view_begin_interactive(view, surface, view->x, view->y,
					"top_left_corner", INPUT_STATE_BORDER_DRAG_TOP_LEFT);
			break;
		case VIEW_AREA_BORDER_TOP:
			view_begin_interactive(view, surface, view->x, view->y,
					"top_side", INPUT_STATE_BORDER_DRAG_TOP);
			break;
		case VIEW_AREA_BORDER_RIGHT:
		case VIEW_AREA_BORDER_BOTTOM|VIEW_AREA_BORDER_RIGHT:
			view_begin_interactive(view, surface, view->x, view->y,
					"right_side", INPUT_STATE_BORDER_DRAG_RIGHT);
					"bottom_right_corner", INPUT_STATE_BORDER_DRAG_BOTTOM_RIGHT);
			break;
		case VIEW_AREA_BORDER_BOTTOM|VIEW_AREA_BORDER_LEFT:
			view_begin_interactive(view, surface, view->x, view->y,
					"bottom_left_corner", INPUT_STATE_BORDER_DRAG_BOTTOM_LEFT);
			break;
		case VIEW_AREA_BORDER_BOTTOM:
			view_begin_interactive(view, surface, view->x, view->y,
					"bottom_side", INPUT_STATE_BORDER_DRAG_BOTTOM);
			break;
		case VIEW_AREA_BORDER_RIGHT:
			view_begin_interactive(view, surface, view->x, view->y,
					"right_side", INPUT_STATE_BORDER_DRAG_RIGHT);
			break;
		case VIEW_AREA_BORDER_LEFT:
			view_begin_interactive(view, surface, view->x, view->y,
					"left_side", INPUT_STATE_BORDER_DRAG_LEFT);
diff --git a/output.c b/output.c
index 6a059f5..88f827b 100644
--- a/output.c
+++ b/output.c
@@ -272,6 +272,7 @@ static void output_frame(struct wl_listener *listener, void *data) {
	struct wio_output *output = wl_container_of(listener, output, frame);
	struct wio_server *server = output->server;
	struct wlr_renderer *renderer = server->renderer;
	int x, y, width, height, scale;

	struct timespec now;
	clock_gettime(CLOCK_MONOTONIC, &now);
@@ -307,39 +308,134 @@ static void output_frame(struct wl_listener *listener, void *data) {
				render_surface, &rdata);
	}
	view = server->interactive.view;
	scale = output->wlr_output->scale;
	switch (server->input_state) {
	case INPUT_STATE_BORDER_DRAG_TOP_RIGHT:
		x = view->x;
		y = server->cursor->y;
		width = server->cursor->x - server->interactive.sx;
		height = view->xdg_surface->surface->current.height - (server->cursor->y - server->interactive.sy);
		if (height < MINHEIGHT && height > -MINHEIGHT) {
			if (height < 0) {
				height *= -1;
				y -= height - window_border * 2 * scale;
			} else {
				y -= MINHEIGHT - height;
			}
			height = MINHEIGHT;
		}
		if (height < 0) {
			height *= -1;
			y -= height - window_border * 2 * scale;
		}
		goto Done;
	case INPUT_STATE_BORDER_DRAG_TOP_LEFT:
		x = server->cursor->x;
		y = server->cursor->y;
		width = view->xdg_surface->surface->current.width - (server->cursor->x - server->interactive.sx);
		height = view->xdg_surface->surface->current.height - (server->cursor->y - server->interactive.sy);
		if (width < MINWIDTH && width > -MINWIDTH) {
			if (width < 0) {
				width *= -1;
				x -= width - window_border * 2 * scale;
			} else {
				x -= MINWIDTH - width;
			}
			width = MINWIDTH;
		}
		if (height < MINHEIGHT && height > -MINHEIGHT) {
			if (height < 0) {
				height *= -1;
				y -= height - window_border * 2 * scale;
			} else {
				y -= MINHEIGHT - height;
			}
			height = MINHEIGHT;
		}
		if (width < 0) {
			width *= -1;
			x -= width - window_border * 2 * scale;
		}
		if (height < 0) {
			height *= -1;
			y -= height - window_border * 2 * scale;
		}
		goto Done;
	case INPUT_STATE_BORDER_DRAG_TOP:
		render_view_border(renderer, output, view,
			view->x,
			server->cursor->y,
			view->xdg_surface->surface->current.width,
			view->xdg_surface->surface->current.height - (server->cursor->y - server->interactive.sy),
			1);
		break;
	case INPUT_STATE_BORDER_DRAG_LEFT:
		render_view_border(renderer, output, view,
			server->cursor->x,
			view->y,
			view->xdg_surface->surface->current.width - (server->cursor->x - server->interactive.sx),
			view->xdg_surface->surface->current.height,
			1);
		break;
		x = view->x;
		y = server->cursor->y;
		width = view->xdg_surface->surface->current.width;
		height = view->xdg_surface->surface->current.height - (server->cursor->y - server->interactive.sy);
		if (height < MINHEIGHT && height > -MINHEIGHT) {
			if (height < 0) {
				height *= -1;
				y -= height - window_border * 2 * scale;
			} else {
				y -= MINHEIGHT - height;
			}
			height = MINHEIGHT;
		}
		if (height < 0) {
			height *= -1;
			y -= height - window_border * 2 * scale;
		}
		goto Done;
	case INPUT_STATE_BORDER_DRAG_BOTTOM_RIGHT:
		x = view->x;
		y = view->y;
		width = server->cursor->x - server->interactive.sx;
		height = server->cursor->y - server->interactive.sy;
		goto Done;
	case INPUT_STATE_BORDER_DRAG_BOTTOM_LEFT:
		x = server->cursor->x;
		y = view->y;
		width = view->xdg_surface->surface->current.width - (server->cursor->x - server->interactive.sx);
		height = server->cursor->y - server->interactive.sy;
		if (width < MINWIDTH && width > -MINWIDTH) {
			if (width < 0) {
				width *= -1;
				x -= width - window_border * 2 * scale;
			} else {
				x -= MINWIDTH - width;
			}
			width = MINWIDTH;
		}
		if (width < 0) {
			width *= -1;
			x -= width - window_border * 2 * scale;
		}
		goto Done;
	case INPUT_STATE_BORDER_DRAG_BOTTOM:
		render_view_border(renderer, output, view,
			view->x,
			view->y,
			view->xdg_surface->surface->current.width,
			server->cursor->y - server->interactive.sy,
			1);
		break;
		x = view->x;
		y = view->y;
		width = view->xdg_surface->surface->current.width;
		height = server->cursor->y - server->interactive.sy;
		goto Done;
	case INPUT_STATE_BORDER_DRAG_RIGHT:
		render_view_border(renderer, output, view,
			view->x,
			view->y,
			server->cursor->x - server->interactive.sx,
			view->xdg_surface->surface->current.height,
			1);
		break;
		x = view->x;
		y = view->y;
		width = server->cursor->x - server->interactive.sx;
		height = view->xdg_surface->surface->current.height;
		goto Done;
	case INPUT_STATE_BORDER_DRAG_LEFT:
		x = server->cursor->x;
		y = view->y;
		width = view->xdg_surface->surface->current.width - (server->cursor->x - server->interactive.sx);
		height = view->xdg_surface->surface->current.height;
		if (width < MINWIDTH && width > -MINWIDTH) {
			if (width < 0) {
				width *= -1;
				x -= width - window_border * 2 * scale;
			} else {
				x -= MINWIDTH - width;
			}
			width = MINWIDTH;
		}
		if (width < 0) {
			width *= -1;
			x -= width - window_border * 2 * scale;
		}
		goto Done;
	case INPUT_STATE_MOVE:
		render_view_border(renderer, output, view,
			server->cursor->x - server->interactive.sx,
@@ -350,11 +446,26 @@ static void output_frame(struct wl_listener *listener, void *data) {
		break;
	case INPUT_STATE_NEW_END:
	case INPUT_STATE_RESIZE_END:
		render_view_border(renderer, output, NULL,
			server->interactive.sx, server->interactive.sy,
			server->cursor->x - server->interactive.sx,
			server->cursor->y - server->interactive.sy,
			1);
		x = server->interactive.sx;
		y = server->interactive.sy;
		width = server->cursor->x - server->interactive.sx;
		height = server->cursor->y - server->interactive.sy;
	Done:
		if (width < 0) {
			width *= -1;
			x -= ((width < MINWIDTH) ? MINWIDTH : width) + window_border * 2 * scale;
		}
		if (height < 0) {
			height *= -1;
			y -= ((height < MINHEIGHT) ? MINHEIGHT : height) + window_border * 2 * scale;
		}
		if (width < MINWIDTH) {
			width = MINWIDTH;
		}
		if (height < MINHEIGHT) {
			height = MINHEIGHT;
		}
		render_view_border(renderer, output, NULL, x, y, width, height, 1);
		break;
	default:
		break;
diff --git a/view.c b/view.c
index 37ad734..259a7b1 100644
--- a/view.c
+++ b/view.c
@@ -123,56 +123,56 @@ static bool view_at(struct wio_view *view,
}

struct wio_view *wio_view_at(struct wio_server *server, double lx, double ly,
		struct wlr_surface **surface, double *sx, double *sy,
		int *view_area) {
		struct wlr_surface **surface, double *sx, double *sy) {
	struct wlr_box border_box = {
		.x = 0, .y = 0,
		.width = 0, .height = 0,
	};
	struct wio_view *view;
	wl_list_for_each(view, &server->views, link) {
		view->area = VIEW_AREA_NONE;

		// Surface
		if (view_at(view, lx, ly, surface, sx, sy)) {
			*view_area = VIEW_AREA_SURFACE;
			view->area = VIEW_AREA_SURFACE;
			return view;
		}

		// Top border
		border_box.height = window_border;
		border_box.width = view->xdg_surface->surface->current.width;
		border_box.x = view->x;
		border_box.width = view->xdg_surface->surface->current.width + window_border * 2;
		border_box.x = view->x - window_border;
		border_box.y = view->y - window_border;
		if (wlr_box_contains_point(&border_box, server->cursor->x, server->cursor->y)) {
			*view_area = VIEW_AREA_BORDER_TOP;
			return view;
			view->area = VIEW_AREA_BORDER_TOP;
			goto SideBorder;
		}

		// Bottom border
		border_box.y = view->y + view->xdg_surface->surface->current.height;
		if (wlr_box_contains_point(&border_box, server->cursor->x, server->cursor->y)) {
			view->area = VIEW_AREA_BORDER_BOTTOM;
		}

	SideBorder:
		// Right border
		border_box.height = view->xdg_surface->surface->current.height;
		border_box.height = view->xdg_surface->surface->current.height + window_border * 2;
		border_box.width = window_border;
		border_box.x = view->x + view->xdg_surface->surface->current.width;
		border_box.y = view->y;
		border_box.y = view->y - window_border;
		if (wlr_box_contains_point(&border_box, server->cursor->x, server->cursor->y)) {
			*view_area = VIEW_AREA_BORDER_RIGHT;
			view->area |= VIEW_AREA_BORDER_RIGHT;
			return view;
		}

		// Bottom border
		border_box.height = window_border;
		border_box.width = view->xdg_surface->surface->current.width;
		border_box.x = view->x;
		border_box.y = view->y + view->xdg_surface->surface->current.height;
		// Left border
		border_box.x = view->x - window_border;
		if (wlr_box_contains_point(&border_box, server->cursor->x, server->cursor->y)) {
			*view_area = VIEW_AREA_BORDER_BOTTOM;
			view->area |= VIEW_AREA_BORDER_LEFT;
			return view;
		}

		// Left border
		border_box.height = view->xdg_surface->surface->current.height;
		border_box.width = window_border;
		border_box.x = view->x - window_border;
		border_box.y = view->y;
		if (wlr_box_contains_point(&border_box, server->cursor->x, server->cursor->y)) {
			*view_area = VIEW_AREA_BORDER_LEFT;
		if (view->area != VIEW_AREA_NONE) {
			return view;
		}
	}
-- 
2.27.0
It still looks terrible:
* input.c and output.c have their own logics for calculating x and y,
  width and height, there should be a way to place it in one function;
* VIEW_AREA_* can be calculated more efficiently;
* hovering mouse over border doesn't change its cursor
  to appropriate arrow;
* forcing MINWIDTH and MIDHEIGHT in output.c to New and Resize
  commands is my mistake;
* maybe it's better to calculate coordinates from borders?

Rio has nicer code to handle all of these and more in 1 file:
http://9p.io/sources/plan9/sys/src/cmd/rio/rio.c