diff --git a/src/cursor.c b/src/cursor.c index 2f54997..95d3971 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,13 @@ 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(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. * */ @@ -73,6 +81,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 +99,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 90def02..fa14dbe 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; @@ -17,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); @@ -28,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 a8527e7..083b5a4 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 // /////////////////////// @@ -90,6 +98,92 @@ void cmd_trim_canvas(State *state) { redraw_canvas_win(); } +/* 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 = '+'; + 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) { + 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-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; + y1 = y2; + x2 = sx; + y2 = sy; + dy = -dy; + dx = -dx; + } + + float error = 0.0; + + if (abs(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); + if (abs(error) >= 0.5) { + y = y + dysign; + error = error - dysign; + // logd("r"); + } + // logd(","); + } + // logd("Done\n"); + } else { + // 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 + 1 / 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. @@ -442,6 +536,90 @@ int mode_pan(reason_t reason, State *state) { return 0; } +/* 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; + + // reset state when switched into + if (reason == START) { + mode_cfg->state = SELECT_FIRST; + if (mode_cfg->first_position == NULL) { + mode_cfg->first_position = + cursor_copy(state->cursor); // make sure position is not NULL + } + return 0; + } + + bool should_draw = false; + + 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); + } + } 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 - 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; + } + } + cursor_mouse_to_move(m, state->cursor, state->view); + } + + 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; + } + + return 0; +} + /* 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,