xnote

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

xnote.c (11657B)


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