xnote

X11 note-taking app
git clone https://git.sinitax.com/sinitax/xnote
Log | Files | Refs | LICENSE | sfeed.txt

xnote.c (12774B)


      1#include <GL/gl.h>
      2#include <GLFW/glfw3.h>
      3#include <fontconfig/fontconfig.h>
      4#include <ft2build.h>
      5#include FT_FREETYPE_H
      6
      7#include <assert.h>
      8#include <string.h>
      9#include <stdlib.h>
     10#include <stdio.h>
     11#include <stdarg.h>
     12#include <stdbool.h>
     13
     14enum align {
     15	CENTER,
     16	LEFT,
     17	RIGHT
     18};
     19
     20struct point {
     21	double x, y;
     22};
     23
     24struct line {
     25	struct point *points;
     26	size_t count, cap;
     27};
     28
     29struct note {
     30	double x, y;
     31	char *text;
     32	enum align align;
     33	size_t len, cap;
     34};
     35
     36enum event {
     37	EVENT_START,
     38	EVENT_START_LINE,
     39	EVENT_START_RECT,
     40	EVENT_END_SHAPE,
     41	EVENT_START_NOTE,
     42	EVENT_MOUSE_MOVE,
     43	EVENT_MOUSE_DRAG,
     44};
     45
     46static struct line *lines = NULL;
     47static size_t line_count = 0;
     48static size_t line_cap = 0;
     49
     50static struct note *notes = NULL;
     51static size_t note_count = 0;
     52static size_t note_cap = 0;
     53
     54static enum event *events = NULL;
     55static size_t event_count = 0;
     56static size_t event_cap = 0;
     57
     58static double canvas_x = 0;
     59static double canvas_y = 0;
     60
     61static double cursor_x = 0;
     62static double cursor_y = 0;
     63
     64static double drag_canvas_x = 0;
     65static double drag_canvas_y = 0;
     66static double drag_cursor_x = 0;
     67static double drag_cursor_y = 0;
     68
     69static int window_width = 0;
     70static int window_height = 0;
     71
     72static GLuint glyphs[128];
     73static FT_Vector glyph_advance[128];
     74static FT_Vector glyph_offset[128];
     75static FT_Vector glyph_size[128];
     76
     77static void
     78die(const char *fmt, ...)
     79{
     80	va_list ap;
     81
     82	va_start(ap, fmt);
     83	fputs("xnote: ", stderr);
     84	vfprintf(stderr, fmt, ap);
     85	if (*fmt && fmt[strlen(fmt)] == ':')
     86		perror(NULL);
     87	else
     88		putc('\n', stderr);
     89	va_end(ap);
     90
     91	exit(1);
     92}
     93
     94static void *
     95append(void *_array, size_t *count, size_t *cap, size_t size)
     96{
     97	uint8_t **array = _array;
     98	if (*count == *cap) {
     99		if (*cap == 0) *cap = 1;
    100		*cap *= 2;
    101		*array = realloc(*array, *cap * size);
    102		if (!*array) die("realloc:");
    103	}
    104	return (void *) (*array + (*count)++ * size);
    105}
    106
    107static void
    108shrink(void *_array, size_t count, size_t *cap, size_t size)
    109{
    110	uint8_t **array = _array;
    111	if (count == 0) {
    112		*array = NULL;
    113		*cap = 0;
    114	} else {
    115		*cap = count;
    116		*array = realloc(*array, *cap * size);
    117		if (!*array) die("realloc:");
    118	}
    119}
    120
    121static void
    122glfw_error(int rc, const char *error)
    123{
    124	die("glfw error (%i): %s", rc, error);
    125}
    126
    127static void
    128push_event(enum event event)
    129{
    130	enum event *slot = append(&events,
    131		&event_count, &event_cap, sizeof(enum event));
    132	*slot = event;
    133}
    134
    135static enum event
    136pop_event(void)
    137{
    138	event_count -= 1;
    139	enum event event = events[event_count];
    140	switch (event) {
    141	case EVENT_START_LINE:
    142	case EVENT_START_RECT:
    143		line_count -= 1;
    144		free(lines[line_count].points);
    145		break;
    146	case EVENT_START_NOTE:
    147		note_count -= 1;
    148		free(notes[note_count].text);
    149		break;
    150	case EVENT_START:
    151		event_count += 1;
    152		break;
    153	default:
    154		break;
    155	}
    156	return event;
    157}
    158
    159static void
    160undo(void)
    161{
    162	while (1) {
    163		switch (pop_event()) {
    164		case EVENT_START_LINE:
    165		case EVENT_START_NOTE:
    166		case EVENT_START_RECT:
    167		case EVENT_START:
    168			break;
    169		default:
    170			continue;
    171		}
    172		break;
    173	}
    174	shrink(&events, event_count, &event_cap, sizeof(enum event));
    175}
    176
    177static enum event
    178last_event(void)
    179{
    180	return events[event_count - 1];
    181}
    182
    183static void
    184window_resize_cb(GLFWwindow *window, int width, int height)
    185{
    186	window_width = width;
    187	window_height = height;
    188	glViewport(0, 0, width, height);
    189	glClear(GL_COLOR_BUFFER_BIT);
    190	glLoadIdentity();
    191	glOrtho(0, width, height, 0, -1, 1);
    192}
    193
    194static void
    195mouse_move_cb(GLFWwindow *window, double x, double y)
    196{
    197	cursor_x = x;
    198	cursor_y = y;
    199	if (last_event() == EVENT_START_LINE) {
    200		struct line *line = &lines[line_count-1];
    201		if (line->count > 0) {
    202			struct point *last_point = &line->points[line->count-1];
    203			if (cursor_x == last_point->x && cursor_y == last_point->y)
    204				return;
    205		}
    206		struct point *point = append(&line->points,
    207			&line->count, &line->cap, sizeof(struct point));
    208		point->x = canvas_x + cursor_x;
    209		point->y = canvas_y + cursor_y;
    210	} else if (last_event() == EVENT_START_RECT) {
    211		struct line *line = &lines[line_count-1];
    212		line->points[1].x = canvas_x + cursor_x;
    213		line->points[2].x = canvas_x + cursor_x;
    214		line->points[2].y = canvas_y + cursor_y;
    215		line->points[3].y = canvas_y + cursor_y;
    216	} else if (last_event() == EVENT_MOUSE_DRAG) {
    217		canvas_x = drag_canvas_x - (cursor_x - drag_cursor_x);
    218		canvas_y = drag_canvas_y - (cursor_y - drag_cursor_y);
    219	} else if (last_event() != EVENT_MOUSE_MOVE) {
    220		push_event(EVENT_MOUSE_MOVE);
    221	}
    222}
    223
    224static void
    225mouse_press_cb(GLFWwindow *window, int button, int action, int mods)
    226{
    227	if (button == GLFW_MOUSE_BUTTON_LEFT) {
    228		if (action == GLFW_PRESS) {
    229			struct line *line = append(&lines,
    230				&line_count, &line_cap, sizeof(struct line));
    231			line->count = line->cap = 0;
    232			line->points = NULL;
    233			struct point *point = append(&line->points,
    234				&line->count, &line->cap, sizeof(struct point));
    235			point->x = canvas_x + cursor_x;
    236			point->y = canvas_y + cursor_y;
    237			push_event(EVENT_START_LINE);
    238		} else if (action == GLFW_RELEASE) {
    239			if (last_event() == EVENT_START_LINE
    240					|| last_event() == EVENT_START_RECT) {
    241				struct line *line = &lines[line_count-1];
    242				shrink(&line->points, line->count,
    243					&line->cap, sizeof(struct point));
    244				push_event(EVENT_END_SHAPE);
    245			}
    246		}
    247	} else if (button == GLFW_MOUSE_BUTTON_RIGHT
    248			|| button == GLFW_MOUSE_BUTTON_MIDDLE) {
    249		if (action == GLFW_PRESS) {
    250			drag_canvas_x = canvas_x;
    251			drag_canvas_y = canvas_y;
    252			drag_cursor_x = cursor_x;
    253			drag_cursor_y = cursor_y;
    254			push_event(EVENT_MOUSE_DRAG);
    255		} else if (action == GLFW_RELEASE && last_event() == EVENT_MOUSE_DRAG) {
    256			pop_event();
    257		}
    258	}
    259}
    260
    261static void
    262key_char_cb(GLFWwindow *window, unsigned int codepoint)
    263{
    264	if (codepoint < 10 || codepoint >= 128)
    265		return;
    266
    267	if (last_event() != EVENT_START_NOTE) {
    268		struct note *note = append(&notes, &note_count, &note_cap, sizeof(struct note));
    269		note->text = NULL;
    270		note->align = CENTER;
    271		note->len = note->cap = 0;
    272		note->x = canvas_x + cursor_x;
    273		note->y = canvas_y + cursor_y;
    274		push_event(EVENT_START_NOTE);
    275	}
    276
    277	struct note *note = &notes[note_count - 1];
    278	*(char *)append(&note->text, &note->len, &note->cap, 1) = codepoint;
    279}
    280
    281static void
    282key_press_cb(GLFWwindow *window, int key, int scancode, int action, int mods)
    283{
    284	if (action == GLFW_PRESS || action == GLFW_REPEAT) {
    285		const char *keysym = glfwGetKeyName(key, scancode);
    286		if (key == GLFW_KEY_ENTER) {
    287			key_char_cb(window, '\n');
    288			return;
    289		}
    290		if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "z")) {
    291			if (event_count) undo();
    292		} else if ((mods & GLFW_MOD_CONTROL)
    293				&& keysym && !strcmp(keysym, "l")
    294				&& last_event() == EVENT_START_NOTE) {
    295			notes[note_count-1].align = LEFT;
    296		} else if ((mods & GLFW_MOD_CONTROL)
    297				&& keysym && !strcmp(keysym, "r")
    298				&& last_event() == EVENT_START_NOTE) {
    299			notes[note_count-1].align = RIGHT;
    300		} else if ((mods & GLFW_MOD_CONTROL)
    301				&& keysym && !strcmp(keysym, "c")
    302				&& last_event() == EVENT_START_NOTE) {
    303			notes[note_count-1].align = CENTER;
    304		} else if (action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)
    305				&& keysym && !strcmp(keysym, "r")) {
    306			struct line *line = append(&lines,
    307				&line_count, &line_cap, sizeof(struct line));
    308			line->count = line->cap = 5;
    309			line->points = malloc(line->count * sizeof(struct point));
    310			for (size_t i = 0; i < line->count; i++) {
    311				line->points[i].x = canvas_x + cursor_x;
    312				line->points[i].y = canvas_y + cursor_y;
    313			}
    314			push_event(EVENT_START_RECT);
    315		} else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "c")) {
    316			for (size_t i = 0; i < line_count; i++)
    317				free(lines[i].points);
    318			line_count = 0;
    319			for (size_t i = 0; i < note_count; i++)
    320				free(notes[i].text);
    321			note_count = 0;
    322			event_count = 0;
    323		} else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "q")) {
    324			exit(0);
    325		} else if (key == GLFW_KEY_BACKSPACE && last_event() == EVENT_START_NOTE) {
    326			if (note_count) {
    327				struct note *note = &notes[note_count - 1];
    328				if (note->len > 1) {
    329					note->len -= 1;
    330					shrink(&note->text, note->len, &note->cap, 1);
    331				} else if (note->len == 1) {
    332					pop_event();
    333				}
    334			}
    335		}
    336	}
    337}
    338
    339static void
    340draw_texture(int texture, double x, double y)
    341{
    342	glBindTexture(GL_TEXTURE_2D, texture);
    343	glBegin(GL_QUADS);
    344}
    345
    346static void
    347draw_text(const char *text, enum align align, size_t len, GLint x, GLint y)
    348{
    349	if (len == 0) return;
    350
    351	GLint sx = x;
    352	size_t i = 0;
    353	while (i < len) {
    354		size_t k = i;
    355
    356		double width = glyph_offset[text[0]].x;
    357		for (; k < len; k++) {
    358			if (text[k] == '\n') break;
    359			width += glyph_advance[text[k]].x;
    360		}
    361
    362		if (k > i) {
    363			width -= glyph_advance[text[k-1]].x;
    364			width += glyph_size[text[k-1]].x;
    365
    366			x = sx;
    367			if (align == CENTER) {
    368				x -= width / 2;
    369			} else if (align == RIGHT) {
    370				x -= width;
    371			}
    372
    373			glEnable(GL_TEXTURE_2D);
    374			for (k = i; k < len; k++) {
    375				if (text[k] == '\n') break;
    376				GLint gx = x + glyph_offset[text[k]].x;
    377				GLint gy = y - glyph_offset[text[k]].y;
    378				GLint sx = glyph_size[text[k]].x;
    379				GLint sy = glyph_size[text[k]].y;
    380				glBindTexture(GL_TEXTURE_2D, glyphs[text[k]]);
    381				glBegin(GL_QUADS);
    382				glTexCoord2d(0, 1); glVertex2i(gx, gy + sy);
    383				glTexCoord2d(1, 1); glVertex2i(gx + sx, gy + sy);
    384				glTexCoord2d(1, 0); glVertex2i(gx + sx, gy);
    385				glTexCoord2d(0, 0); glVertex2i(gx, gy);
    386				glEnd();
    387				x += glyph_advance[text[k]].x;
    388			}
    389			glDisable(GL_TEXTURE_2D);
    390		}
    391
    392		y += glyph_size['0'].y * 1.4;
    393		i = k + 1;
    394	}
    395}
    396
    397int
    398main(void)
    399{
    400	glfwSetErrorCallback(glfw_error);
    401
    402	if (!glfwInit()) die("glfwInit");
    403
    404	GLFWwindow *window = glfwCreateWindow(640, 480, "xnote", NULL, NULL);
    405	if (!window) die("glfwCreateWindow");
    406
    407	glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
    408	glfwSetWindowSizeCallback(window, window_resize_cb);
    409	glfwSetCursorPosCallback(window, mouse_move_cb);
    410	glfwSetMouseButtonCallback(window, mouse_press_cb);
    411	glfwSetKeyCallback(window, key_press_cb);
    412	glfwSetCharCallback(window, key_char_cb);
    413	glfwMakeContextCurrent(window);
    414
    415	glClearColor(0, 0, 0, 0);
    416	glColor3f(1, 1, 1);
    417	glLineWidth(2);
    418	glPointSize(2);
    419
    420	glfwGetCursorPos(window, &cursor_x, &cursor_y);
    421	glfwGetWindowSize(window, &window_width, &window_height);
    422
    423	window_resize_cb(window, window_width, window_height);
    424
    425	push_event(EVENT_START);
    426
    427	const char *fontname = getenv("XNOTE_FONT") ?: "Arial";
    428	FcConfig *config = FcInitLoadConfigAndFonts();
    429	FcPattern *pattern = FcNameParse((const FcChar8 *)fontname);
    430	FcConfigSubstitute(config, pattern, FcMatchPattern);
    431	FcDefaultSubstitute(pattern);
    432	FcResult result;
    433	FcPattern *font = FcFontMatch(config, pattern, &result);
    434	if (!font) die("Failed to load font: %s", fontname);
    435	FcChar8 *filepath = NULL;
    436	assert(FcPatternGetString(font, FC_FILE, 0, &filepath) == FcResultMatch);
    437	int fontsize = 0;
    438	assert(FcPatternGetInteger(font, FC_SIZE, 0, &fontsize) == FcResultMatch);
    439
    440	FT_Library freetype;
    441	FT_Face face;
    442	assert(!FT_Init_FreeType(&freetype));
    443	assert(!FT_New_Face(freetype, (const char *)filepath, 0, &face));
    444	assert(!FT_Set_Char_Size(face, 0, fontsize * 64, 300, 300));
    445
    446	FcPatternDestroy(pattern);
    447	FcConfigDestroy(config);
    448
    449	glGenTextures(128, glyphs);
    450	for (size_t c = 0; c < 128; c++) {
    451		FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_UInt32)c);
    452		assert(!FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER));
    453		glyph_advance[c].x = face->glyph->advance.x >> 6;
    454		glyph_advance[c].y = face->glyph->advance.y >> 6;
    455		glyph_offset[c].x = face->glyph->bitmap_left;
    456		glyph_offset[c].y = face->glyph->bitmap_top;
    457		glyph_size[c].x = face->glyph->bitmap.width;
    458		glyph_size[c].y = face->glyph->bitmap.rows;
    459		glBindTexture(GL_TEXTURE_2D, glyphs[c]);
    460		glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    461		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    462		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    463		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    464		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    465		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    466		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
    467			face->glyph->bitmap.width, face->glyph->bitmap.rows,
    468			0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
    469			face->glyph->bitmap.buffer);
    470	}
    471
    472	while (!glfwWindowShouldClose(window)) {
    473		glClear(GL_COLOR_BUFFER_BIT);
    474
    475		for (size_t i = 0; i < note_count; i++) {
    476			struct note *note = &notes[i];
    477			draw_text(note->text, note->align, note->len,
    478				note->x - canvas_x, note->y - canvas_y);
    479		}
    480
    481		for (size_t i = 0; i < line_count; i++) {
    482			struct line *line = &lines[i];
    483			glBegin(GL_LINE_STRIP);
    484			for (size_t k = 0; k < line->count; k++)
    485				glVertex2f(line->points[k].x - canvas_x,
    486					line->points[k].y - canvas_y);
    487			glEnd();
    488		}
    489
    490		glBegin(GL_POINTS);
    491		glVertex2f(cursor_x, cursor_y);
    492		glEnd();
    493
    494		glfwSwapBuffers(window);
    495
    496		glfwPollEvents();
    497	}
    498
    499	glfwTerminate();
    500	FT_Done_Face(face);
    501	FT_Done_FreeType(freetype);
    502}