From 80aa6a208f24227f639b5962c099f0e23fb734f3 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Tue, 26 Nov 2019 01:02:06 -0500 Subject: [PATCH 1/4] Working line drawing --- src/cursor.h | 1 + src/fe_modes.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++++ src/fe_modes.h | 1 + src/mode_id.h | 1 + 4 files changed, 139 insertions(+) diff --git a/src/cursor.h b/src/cursor.h index 90def02..ab5daef 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -10,6 +10,7 @@ #include #include +// A coordinate pair typedef struct CURSOR { int x; int y; diff --git a/src/fe_modes.c b/src/fe_modes.c index a8527e7..4bd2797 100644 --- a/src/fe_modes.c +++ b/src/fe_modes.c @@ -37,6 +37,7 @@ editor_mode_t modes[] = { {"Switcher", "Switch to another mode", mode_picker}, {"Insert", "Insert characters", mode_insert}, {"Pan", "Pan around the canvas", mode_pan}, + {"Line", "Draw straight lines", mode_line}, {"Free-Line", "Draw a line with your arrow keys", mode_free_line}, {"Brush", "Paint with arrow keys and mouse", mode_brush}, }; @@ -57,6 +58,13 @@ typedef struct { mode_insert_config_t mode_insert_config = {NULL}; +typedef struct { + Cursor *first_position; + enum { SELECT_FIRST, SELECT_SECOND } state; +} mode_line_config_t; + +mode_line_config_t mode_line_config = {NULL, SELECT_FIRST}; + /////////////////////// // GENERAL FUNCTIONS // /////////////////////// @@ -442,6 +450,134 @@ int mode_pan(reason_t reason, State *state) { return 0; } +/* mode_line + * + * Draw straight lines between two points. + */ +int mode_line(reason_t reason, State *state) { + mode_line_config_t *mode_cfg = &mode_line_config; + + // reset state when switched into + if (reason == START) { + mode_cfg->state = SELECT_FIRST; + return 0; + } + + if (reason != NEW_KEY) { + return 0; + } + + if (state->ch_in == KEY_ENTER) { + switch (mode_cfg->state) { + case SELECT_FIRST: + // set first position + mode_cfg->first_position = cursor_copy(state->cursor); + mode_cfg->state = SELECT_SECOND; + return 0; + break; + case SELECT_SECOND: { + // draw line from previous point to current + const int x1 = mode_cfg->first_position->x; + const int y1 = mode_cfg->first_position->y; + const int x2 = state->cursor->x; + const int y2 = state->cursor->y; + draw_line(state->view->canvas, x1, y1, x2, y2); + // TODO: update view only at positions drawn + redraw_canvas_win(); + mode_cfg->state = SELECT_FIRST; + } + default: + break; + } + return 0; + } + + // free line behavior + if ((state->ch_in == KEY_LEFT) || (state->ch_in == KEY_RIGHT) || + (state->ch_in == KEY_UP) || (state->ch_in == KEY_DOWN)) { + int current_arrow = state->ch_in; + cursor_key_to_move(current_arrow, state->cursor, state->view); + } + return 0; +} + +// Draws a straight line between two points +// TODO: fix broken lines on reverse angles (posibly the switching logic?) +void draw_line(Canvas *c, int x1, int y1, int x2, int y2) { + const char stroke = '+'; + // interpolate between points, with Bresenham's line algorithm + int dx = x2 - x1; + int dy = y2 - y1; + + // short-circuit if horizontal or vertical + if (dy == 0) { + const int y = y1; + for (int x = min(x1, x2); x <= max(x1, x2); x++) { + canvas_scharyx(c, y, x, stroke); + } + return; + } else if (dx == 0) { + const int x = x1; + for (int y = min(y1, y2); y <= max(y1, y2); y++) { + canvas_scharyx(c, y, x, stroke); + } + return; + } + + // swap points if necessary to keep line left-to-right/bottom-to-top + if ((dy < 0 && dx < 0)) { + logd("Swapping points\n"); + int sx = x1; + int sy = y1; + x1 = x2; + y1 = y2; + x2 = sx; + y2 = sy; + dy = -dy; + dx = -dx; + } + + const float m = (float)dy / dx; + + float error = 0.0; + + if (abs(m) < 1) { + logd("Drawing m < 1:"); + // for |m| < 1, f(x) = y + const int dysign = (int)(abs(dy) / dy); + int y = y1; + for (int x = x1; x <= x2; x++) { + canvas_scharyx(c, y, x, stroke); + error = error + m; + logd("%f", error); + if (abs(error) >= 0.5) { + y = y + dysign; + error = error - dysign; + logd("r"); + } + logd("."); + } + logd("Done\n"); + } else { + logd("Drawing m > 1:"); + // for |m| > 1, f(y) = x + const int dxsign = (int)(abs(dx) / dx); + int x = x1; + for (int y = y1; y <= y2; y++) { + canvas_scharyx(c, y, x, stroke); + error = error + m; + logd("%f", error); + if (abs(error) >= 0.5) { + x = x + dxsign; + error = error - dxsign; + logd("r"); + } + logd("."); + } + logd("Done\n"); + } +} + /* mode_free_line * * Move with arrows and insert character with keyboard. diff --git a/src/fe_modes.h b/src/fe_modes.h index 39f0b0e..3bac929 100644 --- a/src/fe_modes.h +++ b/src/fe_modes.h @@ -15,6 +15,7 @@ typedef int mode_function_t(reason_t, State *); mode_function_t mode_picker; mode_function_t mode_insert; mode_function_t mode_pan; +mode_function_t mode_line; mode_function_t mode_free_line; mode_function_t mode_brush; diff --git a/src/mode_id.h b/src/mode_id.h index 98d806c..5d75b4d 100644 --- a/src/mode_id.h +++ b/src/mode_id.h @@ -10,6 +10,7 @@ typedef enum { MODE_PICKER, MODE_INSERT, MODE_PAN, + MODE_LINE, MODE_FREE_LINE, MODE_BRUSH, From cbba4c7da6a2af448017781f224ff6a275f19f15 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Tue, 26 Nov 2019 14:48:49 -0500 Subject: [PATCH 2/4] Add mouse support --- src/cursor.c | 12 +++ src/cursor.h | 2 + src/fe_modes.c | 243 ++++++++++++++++++++++++++++--------------------- 3 files changed, 152 insertions(+), 105 deletions(-) diff --git a/src/cursor.c b/src/cursor.c index 2f54997..4982643 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -2,6 +2,7 @@ #include #include #include "frontend.h" +#include "util.h" /* The Cursor struct helps with controls. * It also maps the drawing area to the canvas nicely. @@ -22,6 +23,10 @@ Cursor *cursor_newyx(int y, int x) { return cursor; } +/* Make a new cursor at the position of an ncurses mouse event. + */ +Cursor *cursor_newmouse(MEVENT *m) { return cursor_newyx(m->y - 1, m->x - 1); } + /* Make a copy of an existing cursor. * */ @@ -73,6 +78,7 @@ int cursor_x_to_canvas(Cursor *cursor) { return cursor->x + 1; } int cursor_y_to_canvas(Cursor *cursor) { return cursor->y + 1; } +// Move cursor based on arrow keys void cursor_key_to_move(int arrow, Cursor *cursor, View *view) { switch (arrow) { case KEY_LEFT: @@ -90,6 +96,12 @@ void cursor_key_to_move(int arrow, Cursor *cursor, View *view) { } } +// move cursor to mouse event, bounded to view +void cursor_mouse_to_move(MEVENT *mevent, Cursor *cursor, View *view) { + cursor->x = min(view_max_x, max(0, mevent->x - 1)); + cursor->y = min(view_max_y, max(0, mevent->y - 1)); +} + int cursor_opposite_dir(int arrow) { switch (arrow) { case KEY_LEFT: diff --git a/src/cursor.h b/src/cursor.h index ab5daef..fa14dbe 100644 --- a/src/cursor.h +++ b/src/cursor.h @@ -18,6 +18,7 @@ typedef struct CURSOR { Cursor *cursor_new(); Cursor *cursor_newyx(int y, int x); +Cursor *cursor_newmouse(MEVENT *m); Cursor *cursor_copy(Cursor *original); void cursor_free(Cursor *cursor); @@ -29,6 +30,7 @@ void cursor_move_right(Cursor *cursor, View *view); int cursor_x_to_canvas(Cursor *cursor); int cursor_y_to_canvas(Cursor *cursor); void cursor_key_to_move(int arrow, Cursor *cursor, View *view); +void cursor_mouse_to_move(MEVENT *mevent, Cursor *cursor, View *view); int cursor_opposite_dir(int arrow); #endif diff --git a/src/fe_modes.c b/src/fe_modes.c index 4bd2797..af436a1 100644 --- a/src/fe_modes.c +++ b/src/fe_modes.c @@ -98,6 +98,83 @@ void cmd_trim_canvas(State *state) { redraw_canvas_win(); } +// Draws a straight line between two points +// TODO: fix broken lines on reverse angles (possibly the switching logic?) +void draw_line(Canvas *c, int x1, int y1, int x2, int y2) { + const char stroke = '+'; + // interpolate between points, with Bresenham's line algorithm + int dx = x2 - x1; + int dy = y2 - y1; + + // short-circuit if horizontal or vertical + if (dy == 0) { + const int y = y1; + for (int x = min(x1, x2); x <= max(x1, x2); x++) { + canvas_scharyx(c, y, x, stroke); + } + return; + } else if (dx == 0) { + const int x = x1; + for (int y = min(y1, y2); y <= max(y1, y2); y++) { + canvas_scharyx(c, y, x, stroke); + } + return; + } + + // swap points if necessary to keep line left-to-right/bottom-to-top + if ((dy < 0 && dx < 0)) { + logd("Swapping points\n"); + int sx = x1; + int sy = y1; + x1 = x2; + y1 = y2; + x2 = sx; + y2 = sy; + dy = -dy; + dx = -dx; + } + + const float m = (float)dy / dx; + + float error = 0.0; + + if (abs(m) < 1) { + logd("Drawing m < 1:"); + // for |m| < 1, f(x) = y + const int dysign = (int)(abs(dy) / dy); + int y = y1; + for (int x = x1; x <= x2; x++) { + canvas_scharyx(c, y, x, stroke); + error = error + m; + logd("%f", error); + if (abs(error) >= 0.5) { + y = y + dysign; + error = error - dysign; + logd("r"); + } + logd("."); + } + logd("Done\n"); + } else { + logd("Drawing m > 1:"); + // for |m| > 1, f(y) = x + const int dxsign = (int)(abs(dx) / dx); + int x = x1; + for (int y = y1; y <= y2; y++) { + canvas_scharyx(c, y, x, stroke); + error = error + m; + logd("%f", error); + if (abs(error) >= 0.5) { + x = x + dxsign; + error = error - dxsign; + logd("r"); + } + logd("."); + } + logd("Done\n"); + } +} + /* Call a mode given its Mode_ID. * * This makes sure info_win is always updated. @@ -453,6 +530,8 @@ int mode_pan(reason_t reason, State *state) { /* mode_line * * Draw straight lines between two points. + * + * TODO: fix drawing view position != canvas position */ int mode_line(reason_t reason, State *state) { mode_line_config_t *mode_cfg = &mode_line_config; @@ -460,122 +539,76 @@ int mode_line(reason_t reason, State *state) { // reset state when switched into if (reason == START) { mode_cfg->state = SELECT_FIRST; - return 0; - } - - if (reason != NEW_KEY) { - return 0; - } - - if (state->ch_in == KEY_ENTER) { - switch (mode_cfg->state) { - case SELECT_FIRST: - // set first position - mode_cfg->first_position = cursor_copy(state->cursor); - mode_cfg->state = SELECT_SECOND; - return 0; - break; - case SELECT_SECOND: { - // draw line from previous point to current - const int x1 = mode_cfg->first_position->x; - const int y1 = mode_cfg->first_position->y; - const int x2 = state->cursor->x; - const int y2 = state->cursor->y; - draw_line(state->view->canvas, x1, y1, x2, y2); - // TODO: update view only at positions drawn - redraw_canvas_win(); - mode_cfg->state = SELECT_FIRST; - } - default: - break; + if (mode_cfg->first_position == NULL) { + mode_cfg->first_position = + cursor_copy(state->cursor); // make sure position is not NULL } return 0; } - // free line behavior - if ((state->ch_in == KEY_LEFT) || (state->ch_in == KEY_RIGHT) || - (state->ch_in == KEY_UP) || (state->ch_in == KEY_DOWN)) { - int current_arrow = state->ch_in; - cursor_key_to_move(current_arrow, state->cursor, state->view); - } - return 0; -} + bool should_draw = false; -// Draws a straight line between two points -// TODO: fix broken lines on reverse angles (posibly the switching logic?) -void draw_line(Canvas *c, int x1, int y1, int x2, int y2) { - const char stroke = '+'; - // interpolate between points, with Bresenham's line algorithm - int dx = x2 - x1; - int dy = y2 - y1; - - // short-circuit if horizontal or vertical - if (dy == 0) { - const int y = y1; - for (int x = min(x1, x2); x <= max(x1, x2); x++) { - canvas_scharyx(c, y, x, stroke); + if (reason == NEW_KEY) { + // KEYBOARD BEHAVIOR + // press ENTER to set start and end points, move with keys + if (state->ch_in == KEY_ENTER) { + switch (mode_cfg->state) { + case SELECT_FIRST: { + // set first position + Cursor *old_cursor = mode_cfg->first_position; + mode_cfg->first_position = cursor_copy(state->cursor); + cursor_free(old_cursor); + mode_cfg->state = SELECT_SECOND; + return 0; + break; + } + case SELECT_SECOND: { + // draw line from previous point to current + should_draw = true; + } + default: + break; + } + } else if ((state->ch_in == KEY_LEFT) || (state->ch_in == KEY_RIGHT) || + (state->ch_in == KEY_UP) || (state->ch_in == KEY_DOWN)) { + // move cursor with keys + int current_arrow = state->ch_in; + cursor_key_to_move(current_arrow, state->cursor, state->view); } - return; - } else if (dx == 0) { - const int x = x1; - for (int y = min(y1, y2); y <= max(y1, y2); y++) { - canvas_scharyx(c, y, x, stroke); + } else if (reason == NEW_MOUSE) { + // MOUSE BEHAVIOR + // click and drag to draw a line, or click two points + MEVENT *m = state->mevent_in; + if (m->bstate & BUTTON1_PRESSED && mode_cfg->state == SELECT_FIRST) { + // set position 1 on mouse down + mode_cfg->state = SELECT_SECOND; + Cursor *old_cursor = mode_cfg->first_position; + mode_cfg->first_position = cursor_newmouse(m); + cursor_free(old_cursor); + } else if (m->bstate & BUTTON1_RELEASED) { + if (mode_cfg->state == SELECT_SECOND && + (m->y != mode_cfg->first_position->y || + m->x != mode_cfg->first_position->x)) { + // only draw new line if mouse has moved + should_draw = true; + } } - return; + cursor_mouse_to_move(m, state->cursor, state->view); } - // swap points if necessary to keep line left-to-right/bottom-to-top - if ((dy < 0 && dx < 0)) { - logd("Swapping points\n"); - int sx = x1; - int sy = y1; - x1 = x2; - y1 = y2; - x2 = sx; - y2 = sy; - dy = -dy; - dx = -dx; + if (should_draw) { + // draws from mode_cfg->first_position to state->cursor + const int x1 = mode_cfg->first_position->x; + const int y1 = mode_cfg->first_position->y; + const int x2 = state->cursor->x; + const int y2 = state->cursor->y; + draw_line(state->view->canvas, x1, y1, x2, y2); + // TODO: update view only at positions drawn + redraw_canvas_win(); + mode_cfg->state = SELECT_FIRST; } - const float m = (float)dy / dx; - - float error = 0.0; - - if (abs(m) < 1) { - logd("Drawing m < 1:"); - // for |m| < 1, f(x) = y - const int dysign = (int)(abs(dy) / dy); - int y = y1; - for (int x = x1; x <= x2; x++) { - canvas_scharyx(c, y, x, stroke); - error = error + m; - logd("%f", error); - if (abs(error) >= 0.5) { - y = y + dysign; - error = error - dysign; - logd("r"); - } - logd("."); - } - logd("Done\n"); - } else { - logd("Drawing m > 1:"); - // for |m| > 1, f(y) = x - const int dxsign = (int)(abs(dx) / dx); - int x = x1; - for (int y = y1; y <= y2; y++) { - canvas_scharyx(c, y, x, stroke); - error = error + m; - logd("%f", error); - if (abs(error) >= 0.5) { - x = x + dxsign; - error = error - dxsign; - logd("r"); - } - logd("."); - } - logd("Done\n"); - } + return 0; } /* mode_free_line From a258e7710a95278e1b3795c9af2566b18c69f152 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Tue, 26 Nov 2019 15:10:00 -0500 Subject: [PATCH 3/4] Fix mouse coordinates off by one error --- src/fe_modes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fe_modes.c b/src/fe_modes.c index af436a1..fcc515b 100644 --- a/src/fe_modes.c +++ b/src/fe_modes.c @@ -587,8 +587,8 @@ int mode_line(reason_t reason, State *state) { cursor_free(old_cursor); } else if (m->bstate & BUTTON1_RELEASED) { if (mode_cfg->state == SELECT_SECOND && - (m->y != mode_cfg->first_position->y || - m->x != mode_cfg->first_position->x)) { + ((m->y - 1) != mode_cfg->first_position->y || + (m->x - 1) != mode_cfg->first_position->x)) { // only draw new line if mouse has moved should_draw = true; } From ebf129191d085cf88e56bd349aa2b05d608e301f Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Tue, 26 Nov 2019 21:25:27 -0500 Subject: [PATCH 4/4] Fix line-drawing bug Lines with abs(slope) > 1 weren't drawing correctly, the error value should be modified with 1/m instead of m --- src/cursor.c | 5 ++++- src/fe_modes.c | 47 ++++++++++++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/cursor.c b/src/cursor.c index 4982643..95d3971 100644 --- a/src/cursor.c +++ b/src/cursor.c @@ -25,7 +25,10 @@ Cursor *cursor_newyx(int y, int x) { /* Make a new cursor at the position of an ncurses mouse event. */ -Cursor *cursor_newmouse(MEVENT *m) { return cursor_newyx(m->y - 1, m->x - 1); } +Cursor *cursor_newmouse(MEVENT *m) { + return cursor_newyx(min(view_max_y, max(0, m->y - 1)), + min(view_max_x, max(0, m->x - 1))); +} /* Make a copy of an existing cursor. * diff --git a/src/fe_modes.c b/src/fe_modes.c index fcc515b..083b5a4 100644 --- a/src/fe_modes.c +++ b/src/fe_modes.c @@ -98,13 +98,19 @@ void cmd_trim_canvas(State *state) { redraw_canvas_win(); } -// Draws a straight line between two points -// TODO: fix broken lines on reverse angles (possibly the switching logic?) +/* Draws a straight line between two points with Bresenham's line algorithm. + * + * The relative position of the points does not matter, but they should be valid + * coordinates for the canvas. + */ void draw_line(Canvas *c, int x1, int y1, int x2, int y2) { + // logd("Drawing (%d,%d) to (%d,%d)\n", x1, y1, x2, y2); const char stroke = '+'; - // interpolate between points, with Bresenham's line algorithm int dx = x2 - x1; int dy = y2 - y1; + const float m = (float)dy / dx; // line slope + + // logd("dx: %d\tdy: %d\tm: %f\n", dx, dy, m); // short-circuit if horizontal or vertical if (dy == 0) { @@ -121,9 +127,12 @@ void draw_line(Canvas *c, int x1, int y1, int x2, int y2) { return; } - // swap points if necessary to keep line left-to-right/bottom-to-top - if ((dy < 0 && dx < 0)) { - logd("Swapping points\n"); + // swap points if necessary to keep line-drawing left-to-right/bottom-to-top + // if we're drawing y based on x and dx < 0, swap points + // if we're drawing x based on y and dy < 0, swap points + + if ((abs(m) < 1 && dx < 0) || (abs(m) >= 1 && dy < 0)) { + // logd("Swapping points\n"); int sx = x1; int sy = y1; x1 = x2; @@ -134,44 +143,44 @@ void draw_line(Canvas *c, int x1, int y1, int x2, int y2) { dx = -dx; } - const float m = (float)dy / dx; - float error = 0.0; if (abs(m) < 1) { - logd("Drawing m < 1:"); + // logd("m < 1:"); // for |m| < 1, f(x) = y const int dysign = (int)(abs(dy) / dy); int y = y1; for (int x = x1; x <= x2; x++) { + // logd("(%d,%d)", x, y); canvas_scharyx(c, y, x, stroke); error = error + m; - logd("%f", error); + // logd("%f", error); if (abs(error) >= 0.5) { y = y + dysign; error = error - dysign; - logd("r"); + // logd("r"); } - logd("."); + // logd(","); } - logd("Done\n"); + // logd("Done\n"); } else { - logd("Drawing m > 1:"); + // logd("m > 1:"); // for |m| > 1, f(y) = x const int dxsign = (int)(abs(dx) / dx); int x = x1; for (int y = y1; y <= y2; y++) { + // logd("(%d,%d)", x, y); canvas_scharyx(c, y, x, stroke); - error = error + m; - logd("%f", error); + error = error + 1 / m; + // logd("%f", error); if (abs(error) >= 0.5) { x = x + dxsign; error = error - dxsign; - logd("r"); + // logd("r"); } - logd("."); + // logd(","); } - logd("Done\n"); + // logd("Done\n"); } }