Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Line mode #92

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/cursor.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <ncurses.h>
#include <stdlib.h>
#include "frontend.h"
#include "util.h"

/* The Cursor struct helps with controls.
* It also maps the drawing area to the canvas nicely.
Expand All @@ -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.
*
*/
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions src/cursor.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
#include <ncurses.h>
#include <stdlib.h>

// A coordinate pair
typedef struct CURSOR {
int x;
int y;
} 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);

Expand All @@ -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
178 changes: 178 additions & 0 deletions src/fe_modes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand All @@ -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 //
///////////////////////
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/fe_modes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions src/mode_id.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ typedef enum {
MODE_PICKER,
MODE_INSERT,
MODE_PAN,
MODE_LINE,
MODE_FREE_LINE,
MODE_BRUSH,

Expand Down