1173 lines
38 KiB
C++
1173 lines
38 KiB
C++
/*
|
|
* main.cpp
|
|
* MontaukOS Text Editor - standalone Window Server app
|
|
* Single-buffer text editor with line numbers, cursor, scrolling, file I/O,
|
|
* syntax highlighting for C and Lua files
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include <montauk/syscall.h>
|
|
#include <montauk/string.h>
|
|
#include <montauk/heap.h>
|
|
#include <gui/gui.hpp>
|
|
#include <gui/truetype.hpp>
|
|
#include <gui/svg.hpp>
|
|
#include <gui/stb_math.h>
|
|
|
|
extern "C" {
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
}
|
|
|
|
using namespace gui;
|
|
|
|
#include "syntax_highlight.hpp"
|
|
|
|
// ============================================================================
|
|
// Constants
|
|
// ============================================================================
|
|
|
|
static constexpr int INIT_W = 600;
|
|
static constexpr int INIT_H = 450;
|
|
static constexpr int TOOLBAR_H = 36;
|
|
static constexpr int PATHBAR_H = 32;
|
|
static constexpr int STATUS_H = 24;
|
|
static constexpr int LINE_NUM_W = 48;
|
|
static constexpr int SCROLLBAR_W = 12;
|
|
static constexpr int INIT_CAP = 4096;
|
|
static constexpr int MAX_CAP = 262144; // 256KB
|
|
static constexpr int MAX_LINES = 16384;
|
|
static constexpr int TAB_WIDTH = 4;
|
|
static constexpr int FONT_SIZE = 16;
|
|
static constexpr int SYN_LINE_BUF_CAP = 1024;
|
|
|
|
// Colors
|
|
static constexpr Color BG_COLOR = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
|
static constexpr Color TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
|
|
static constexpr Color BTN_BG = Color::from_rgb(0xE8, 0xE8, 0xE8);
|
|
static constexpr Color BORDER_COLOR = Color::from_rgb(0xD0, 0xD0, 0xD0);
|
|
static constexpr Color LINENUM_COLOR = Color::from_rgb(0x99, 0x99, 0x99);
|
|
static constexpr Color LINENUM_BG = Color::from_rgb(0xF0, 0xF0, 0xF0);
|
|
static constexpr Color CURSOR_LINE_BG = Color::from_rgb(0xFF, 0xFD, 0xE8);
|
|
static constexpr Color TEXT_COLOR = Color::from_rgb(0x1E, 0x1E, 0x1E);
|
|
static constexpr Color SEL_BG = Color::from_rgb(0xB0, 0xD0, 0xF0);
|
|
static constexpr Color ACCENT = Color::from_rgb(0x36, 0x7B, 0xF0);
|
|
static constexpr Color STATUS_BG = Color::from_rgb(0x2B, 0x3E, 0x50);
|
|
static constexpr Color STATUS_TEXT = Color::from_rgb(0xE0, 0xE0, 0xE0);
|
|
static constexpr Color PATHBAR_BG = Color::from_rgb(0xF0, 0xF0, 0xF0);
|
|
static constexpr Color WHITE = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
|
|
|
// ============================================================================
|
|
// Global state
|
|
// ============================================================================
|
|
|
|
int g_win_w = INIT_W;
|
|
int g_win_h = INIT_H;
|
|
|
|
TrueTypeFont* g_font = nullptr; // UI font (Roboto)
|
|
TrueTypeFont* g_font_mono = nullptr; // Editor font (JetBrains Mono)
|
|
|
|
SvgIcon g_icon_folder = {};
|
|
SvgIcon g_icon_save = {};
|
|
|
|
// Buffer
|
|
char* g_buffer = nullptr;
|
|
int g_buf_len = 0;
|
|
int g_buf_cap = 0;
|
|
|
|
// Line index
|
|
int* g_line_offsets = nullptr;
|
|
int g_line_count = 0;
|
|
|
|
// Cursor
|
|
int g_cursor_pos = 0; // byte position
|
|
int g_cursor_line = 0;
|
|
int g_cursor_col = 0;
|
|
int g_scroll_y = 0; // first visible line
|
|
int g_scroll_x = 0; // horizontal scroll in pixels
|
|
bool g_modified = false;
|
|
bool g_cursor_moved = true;
|
|
|
|
// File
|
|
char g_filepath[256] = {};
|
|
char g_filename[64] = {};
|
|
|
|
// Selection
|
|
int g_sel_anchor = 0;
|
|
int g_sel_end = 0;
|
|
bool g_has_selection = false;
|
|
bool g_mouse_selecting = false;
|
|
|
|
// Pathbar
|
|
bool g_pathbar_open = false;
|
|
bool g_pathbar_save = false;
|
|
char g_pathbar_text[256] = {};
|
|
int g_pathbar_len = 0;
|
|
int g_pathbar_cursor = 0;
|
|
|
|
// Syntax highlighting
|
|
SynLanguage g_syntax_language = SYN_LANG_NONE;
|
|
|
|
// ============================================================================
|
|
// Pixel drawing helpers
|
|
// ============================================================================
|
|
|
|
static void px_fill(uint32_t* px, int bw, int bh,
|
|
int x, int y, int w, int h, Color c) {
|
|
uint32_t v = c.to_pixel();
|
|
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
|
|
int x1 = x + w > bw ? bw : x + w;
|
|
int y1 = y + h > bh ? bh : y + h;
|
|
for (int row = y0; row < y1; row++)
|
|
for (int col = x0; col < x1; col++)
|
|
px[row * bw + col] = v;
|
|
}
|
|
|
|
static void px_hline(uint32_t* px, int bw, int bh,
|
|
int x, int y, int w, Color c) {
|
|
if (y < 0 || y >= bh) return;
|
|
uint32_t v = c.to_pixel();
|
|
int x0 = x < 0 ? 0 : x;
|
|
int x1 = x + w > bw ? bw : x + w;
|
|
for (int col = x0; col < x1; col++)
|
|
px[y * bw + col] = v;
|
|
}
|
|
|
|
static void px_vline(uint32_t* px, int bw, int bh,
|
|
int x, int y, int h, Color c) {
|
|
if (x < 0 || x >= bw) return;
|
|
uint32_t v = c.to_pixel();
|
|
int y0 = y < 0 ? 0 : y;
|
|
int y1 = y + h > bh ? bh : y + h;
|
|
for (int row = y0; row < y1; row++)
|
|
px[row * bw + x] = v;
|
|
}
|
|
|
|
static void px_fill_rounded(uint32_t* px, int bw, int bh,
|
|
int x, int y, int w, int h, int r, Color c) {
|
|
uint32_t v = c.to_pixel();
|
|
for (int row = 0; row < h; row++) {
|
|
int py = y + row;
|
|
if (py < 0 || py >= bh) continue;
|
|
int inset = 0;
|
|
if (row < r) {
|
|
int dy = r - 1 - row;
|
|
if (r == 3) {
|
|
if (dy >= 2) inset = 2;
|
|
else if (dy >= 1) inset = 1;
|
|
}
|
|
} else if (row >= h - r) {
|
|
int dy = row - (h - r);
|
|
if (r == 3) {
|
|
if (dy >= 2) inset = 2;
|
|
else if (dy >= 1) inset = 1;
|
|
}
|
|
}
|
|
int x0 = x + inset;
|
|
int x1 = x + w - inset;
|
|
if (x0 < 0) x0 = 0;
|
|
if (x1 > bw) x1 = bw;
|
|
for (int col = x0; col < x1; col++)
|
|
px[py * bw + col] = v;
|
|
}
|
|
}
|
|
|
|
static void px_icon(uint32_t* px, int bw, int bh,
|
|
int x, int y, const SvgIcon& ic) {
|
|
if (!ic.pixels) return;
|
|
for (int row = 0; row < ic.height; row++) {
|
|
int dy = y + row;
|
|
if (dy < 0 || dy >= bh) continue;
|
|
for (int col = 0; col < ic.width; col++) {
|
|
int dx = x + col;
|
|
if (dx < 0 || dx >= bw) continue;
|
|
uint32_t src = ic.pixels[row * ic.width + col];
|
|
uint8_t sa = (src >> 24) & 0xFF;
|
|
if (sa == 0) continue;
|
|
if (sa == 255) {
|
|
px[dy * bw + dx] = src;
|
|
} else {
|
|
uint32_t dst = px[dy * bw + dx];
|
|
uint8_t sr = (src >> 16) & 0xFF, sg = (src >> 8) & 0xFF, sb = src & 0xFF;
|
|
uint8_t dr = (dst >> 16) & 0xFF, dg = (dst >> 8) & 0xFF, db = dst & 0xFF;
|
|
uint32_t a = sa, inv = 255 - sa;
|
|
uint32_t rr = (a * sr + inv * dr + 128) / 255;
|
|
uint32_t gg = (a * sg + inv * dg + 128) / 255;
|
|
uint32_t bb = (a * sb + inv * db + 128) / 255;
|
|
px[dy * bw + dx] = 0xFF000000 | (rr << 16) | (gg << 8) | bb;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Font metrics
|
|
// ============================================================================
|
|
|
|
static constexpr int MONO_SIZE = 18;
|
|
|
|
static int font_cell_w() {
|
|
if (g_font_mono && g_font_mono->valid) {
|
|
GlyphCache* gc = g_font_mono->get_cache(MONO_SIZE);
|
|
CachedGlyph* g = g_font_mono->get_glyph(gc, 'M');
|
|
if (g) return g->advance;
|
|
}
|
|
return 8;
|
|
}
|
|
|
|
static int font_cell_h() {
|
|
if (g_font_mono && g_font_mono->valid)
|
|
return g_font_mono->get_line_height(MONO_SIZE);
|
|
return 16;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Line index management
|
|
// ============================================================================
|
|
|
|
static void recompute_lines() {
|
|
if (!g_line_offsets)
|
|
g_line_offsets = (int*)montauk::malloc(MAX_LINES * sizeof(int));
|
|
|
|
g_line_count = 0;
|
|
g_line_offsets[g_line_count++] = 0;
|
|
for (int i = 0; i < g_buf_len; i++) {
|
|
if (g_buffer[i] == '\n' && g_line_count < MAX_LINES)
|
|
g_line_offsets[g_line_count++] = i + 1;
|
|
}
|
|
}
|
|
|
|
static void update_cursor_pos() {
|
|
g_cursor_line = 0;
|
|
for (int i = g_line_count - 1; i >= 0; i--) {
|
|
if (g_cursor_pos >= g_line_offsets[i]) {
|
|
g_cursor_line = i;
|
|
break;
|
|
}
|
|
}
|
|
g_cursor_col = g_cursor_pos - g_line_offsets[g_cursor_line];
|
|
g_cursor_moved = true;
|
|
}
|
|
|
|
static int line_length(int line) {
|
|
if (line < 0 || line >= g_line_count) return 0;
|
|
int start = g_line_offsets[line];
|
|
int end = (line + 1 < g_line_count) ? g_line_offsets[line + 1] - 1 : g_buf_len;
|
|
return end - start;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Buffer operations
|
|
// ============================================================================
|
|
|
|
static void ensure_capacity(int needed) {
|
|
if (g_buf_len + needed <= g_buf_cap) return;
|
|
int new_cap = g_buf_cap * 2;
|
|
if (new_cap > MAX_CAP) new_cap = MAX_CAP;
|
|
if (new_cap < g_buf_len + needed) new_cap = g_buf_len + needed;
|
|
g_buffer = (char*)montauk::realloc(g_buffer, new_cap);
|
|
g_buf_cap = new_cap;
|
|
}
|
|
|
|
static void delete_range(int start, int count) {
|
|
for (int i = start; i < g_buf_len - count; i++)
|
|
g_buffer[i] = g_buffer[i + count];
|
|
g_buf_len -= count;
|
|
}
|
|
|
|
static void insert_char(char c) {
|
|
if (g_buf_len >= MAX_CAP - 1) return;
|
|
ensure_capacity(1);
|
|
for (int i = g_buf_len; i > g_cursor_pos; i--)
|
|
g_buffer[i] = g_buffer[i - 1];
|
|
g_buffer[g_cursor_pos] = c;
|
|
g_buf_len++;
|
|
g_cursor_pos++;
|
|
g_modified = true;
|
|
recompute_lines();
|
|
update_cursor_pos();
|
|
}
|
|
|
|
static void insert_string(const char* s, int len) {
|
|
if (g_buf_len + len >= MAX_CAP) return;
|
|
ensure_capacity(len);
|
|
for (int i = g_buf_len - 1; i >= g_cursor_pos; i--)
|
|
g_buffer[i + len] = g_buffer[i];
|
|
for (int i = 0; i < len; i++)
|
|
g_buffer[g_cursor_pos + i] = s[i];
|
|
g_buf_len += len;
|
|
g_cursor_pos += len;
|
|
g_modified = true;
|
|
recompute_lines();
|
|
update_cursor_pos();
|
|
}
|
|
|
|
static void backspace() {
|
|
if (g_cursor_pos <= 0) return;
|
|
g_cursor_pos--;
|
|
delete_range(g_cursor_pos, 1);
|
|
g_modified = true;
|
|
recompute_lines();
|
|
update_cursor_pos();
|
|
}
|
|
|
|
static void delete_char() {
|
|
if (g_cursor_pos >= g_buf_len) return;
|
|
delete_range(g_cursor_pos, 1);
|
|
g_modified = true;
|
|
recompute_lines();
|
|
update_cursor_pos();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Cursor movement
|
|
// ============================================================================
|
|
|
|
static void move_up() {
|
|
if (g_cursor_line <= 0) return;
|
|
int col = g_cursor_col;
|
|
int prev = g_cursor_line - 1;
|
|
int len = line_length(prev);
|
|
if (col > len) col = len;
|
|
g_cursor_pos = g_line_offsets[prev] + col;
|
|
update_cursor_pos();
|
|
}
|
|
|
|
static void move_down() {
|
|
if (g_cursor_line >= g_line_count - 1) return;
|
|
int col = g_cursor_col;
|
|
int next = g_cursor_line + 1;
|
|
int len = line_length(next);
|
|
if (col > len) col = len;
|
|
g_cursor_pos = g_line_offsets[next] + col;
|
|
update_cursor_pos();
|
|
}
|
|
|
|
static void move_left() {
|
|
if (g_cursor_pos > 0) { g_cursor_pos--; update_cursor_pos(); }
|
|
}
|
|
|
|
static void move_right() {
|
|
if (g_cursor_pos < g_buf_len) { g_cursor_pos++; update_cursor_pos(); }
|
|
}
|
|
|
|
static void move_home() {
|
|
g_cursor_pos = g_line_offsets[g_cursor_line];
|
|
update_cursor_pos();
|
|
}
|
|
|
|
static void move_end() {
|
|
g_cursor_pos = g_line_offsets[g_cursor_line] + line_length(g_cursor_line);
|
|
update_cursor_pos();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Selection
|
|
// ============================================================================
|
|
|
|
static void clear_selection() {
|
|
g_has_selection = false;
|
|
g_sel_anchor = 0;
|
|
g_sel_end = 0;
|
|
}
|
|
|
|
static void sel_range(int* out_s, int* out_e) {
|
|
if (g_sel_anchor < g_sel_end) { *out_s = g_sel_anchor; *out_e = g_sel_end; }
|
|
else { *out_s = g_sel_end; *out_e = g_sel_anchor; }
|
|
}
|
|
|
|
static void start_selection() {
|
|
if (!g_has_selection) {
|
|
g_sel_anchor = g_cursor_pos;
|
|
g_sel_end = g_cursor_pos;
|
|
g_has_selection = true;
|
|
}
|
|
}
|
|
|
|
static void update_selection() {
|
|
g_sel_end = g_cursor_pos;
|
|
if (g_sel_anchor == g_sel_end)
|
|
g_has_selection = false;
|
|
}
|
|
|
|
static void delete_selection() {
|
|
if (!g_has_selection) return;
|
|
int ss, se;
|
|
sel_range(&ss, &se);
|
|
int count = se - ss;
|
|
if (count <= 0) { clear_selection(); return; }
|
|
delete_range(ss, count);
|
|
g_cursor_pos = ss;
|
|
g_modified = true;
|
|
clear_selection();
|
|
recompute_lines();
|
|
update_cursor_pos();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Scrolling
|
|
// ============================================================================
|
|
|
|
static void ensure_cursor_visible(int visible_lines, int text_area_w) {
|
|
if (g_cursor_line < g_scroll_y)
|
|
g_scroll_y = g_cursor_line;
|
|
if (g_cursor_line >= g_scroll_y + visible_lines)
|
|
g_scroll_y = g_cursor_line - visible_lines + 1;
|
|
|
|
int max_scroll_y = g_line_count - visible_lines;
|
|
if (max_scroll_y < 0) max_scroll_y = 0;
|
|
if (g_scroll_y > max_scroll_y) g_scroll_y = max_scroll_y;
|
|
if (g_scroll_y < 0) g_scroll_y = 0;
|
|
|
|
int cell_w = font_cell_w();
|
|
int cursor_px = g_cursor_col * cell_w;
|
|
int view_w = text_area_w - LINE_NUM_W - SCROLLBAR_W;
|
|
if (cursor_px - g_scroll_x > view_w - cell_w * 2)
|
|
g_scroll_x = cursor_px - view_w + cell_w * 4;
|
|
if (cursor_px < g_scroll_x) {
|
|
g_scroll_x = cursor_px - cell_w * 2;
|
|
if (g_scroll_x < 0) g_scroll_x = 0;
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// File I/O
|
|
// ============================================================================
|
|
|
|
static void load_file(const char* path) {
|
|
int fd = montauk::open(path);
|
|
if (fd < 0) return;
|
|
|
|
uint64_t size = montauk::getsize(fd);
|
|
if (size > (uint64_t)MAX_CAP) size = MAX_CAP;
|
|
|
|
if ((int)size >= g_buf_cap) {
|
|
int new_cap = (int)size + 1024;
|
|
if (new_cap > MAX_CAP) new_cap = MAX_CAP;
|
|
g_buffer = (char*)montauk::realloc(g_buffer, new_cap);
|
|
g_buf_cap = new_cap;
|
|
}
|
|
|
|
montauk::read(fd, (uint8_t*)g_buffer, 0, size);
|
|
montauk::close(fd);
|
|
|
|
g_buf_len = (int)size;
|
|
g_cursor_pos = 0;
|
|
g_scroll_y = 0;
|
|
g_scroll_x = 0;
|
|
g_modified = false;
|
|
|
|
montauk::strncpy(g_filepath, path, 255);
|
|
|
|
int last_slash = -1;
|
|
for (int i = 0; path[i]; i++)
|
|
if (path[i] == '/') last_slash = i;
|
|
if (last_slash >= 0)
|
|
montauk::strncpy(g_filename, path + last_slash + 1, 63);
|
|
else
|
|
montauk::strncpy(g_filename, path, 63);
|
|
|
|
g_syntax_language = syn_detect_language(g_filepath);
|
|
|
|
recompute_lines();
|
|
update_cursor_pos();
|
|
}
|
|
|
|
static void save_file() {
|
|
if (g_filepath[0] == '\0') return;
|
|
|
|
int fd = montauk::fcreate(g_filepath);
|
|
if (fd < 0) return;
|
|
|
|
montauk::fwrite(fd, (const uint8_t*)g_buffer, 0, g_buf_len);
|
|
montauk::close(fd);
|
|
|
|
g_modified = false;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Hit testing
|
|
// ============================================================================
|
|
|
|
static int hit_test(int mx, int my, int editor_y) {
|
|
int cell_w = font_cell_w();
|
|
int cell_h = font_cell_h();
|
|
|
|
int clicked_line = g_scroll_y + (my - editor_y) / cell_h;
|
|
if (clicked_line >= g_line_count) clicked_line = g_line_count - 1;
|
|
if (clicked_line < 0) clicked_line = 0;
|
|
|
|
int clicked_col = (mx - LINE_NUM_W - 4 + g_scroll_x + cell_w / 2) / cell_w;
|
|
if (clicked_col < 0) clicked_col = 0;
|
|
int ll = line_length(clicked_line);
|
|
if (clicked_col > ll) clicked_col = ll;
|
|
|
|
return g_line_offsets[clicked_line] + clicked_col;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Rendering
|
|
// ============================================================================
|
|
|
|
static void render(uint32_t* pixels) {
|
|
int W = g_win_w, H = g_win_h;
|
|
int cell_w = font_cell_w();
|
|
int cell_h = font_cell_h();
|
|
|
|
// Clear
|
|
px_fill(pixels, W, H, 0, 0, W, H, BG_COLOR);
|
|
|
|
// ---- Toolbar ----
|
|
px_fill(pixels, W, H, 0, 0, W, TOOLBAR_H, TOOLBAR_BG);
|
|
|
|
// Open button
|
|
px_fill_rounded(pixels, W, H, 4, 6, 24, 24, 3, BTN_BG);
|
|
px_icon(pixels, W, H, 8, 10, g_icon_folder);
|
|
|
|
// Save button
|
|
px_fill_rounded(pixels, W, H, 32, 6, 24, 24, 3, BTN_BG);
|
|
px_icon(pixels, W, H, 36, 10, g_icon_save);
|
|
|
|
// Separator
|
|
px_vline(pixels, W, H, 60, 4, 28, BORDER_COLOR);
|
|
|
|
// Filename + modified
|
|
char label[128];
|
|
if (g_filename[0])
|
|
snprintf(label, 128, "%s%s", g_filename, g_modified ? " [modified]" : "");
|
|
else
|
|
snprintf(label, 128, "Untitled%s", g_modified ? " [modified]" : "");
|
|
if (g_font)
|
|
g_font->draw_to_buffer(pixels, W, H, 68, (TOOLBAR_H - FONT_SIZE) / 2, label, TEXT_COLOR, FONT_SIZE);
|
|
|
|
// Toolbar bottom line
|
|
px_hline(pixels, W, H, 0, TOOLBAR_H - 1, W, BORDER_COLOR);
|
|
|
|
// ---- Pathbar (conditional) ----
|
|
int editor_y = TOOLBAR_H;
|
|
|
|
if (g_pathbar_open) {
|
|
int pb_y = TOOLBAR_H;
|
|
px_fill(pixels, W, H, 0, pb_y, W, PATHBAR_H, PATHBAR_BG);
|
|
|
|
int btn_w = 56;
|
|
int inp_x = 8;
|
|
int inp_y = pb_y + 4;
|
|
int inp_w = W - inp_x - btn_w - 12;
|
|
int inp_h = 24;
|
|
|
|
// Input box
|
|
px_fill(pixels, W, H, inp_x, inp_y, inp_w, inp_h, WHITE);
|
|
// Input border (top, bottom, left, right)
|
|
px_hline(pixels, W, H, inp_x, inp_y, inp_w, ACCENT);
|
|
px_hline(pixels, W, H, inp_x, inp_y + inp_h - 1, inp_w, ACCENT);
|
|
px_vline(pixels, W, H, inp_x, inp_y, inp_h, ACCENT);
|
|
px_vline(pixels, W, H, inp_x + inp_w - 1, inp_y, inp_h, ACCENT);
|
|
|
|
// Path text
|
|
if (g_font && g_pathbar_text[0])
|
|
g_font->draw_to_buffer(pixels, W, H, inp_x + 4, inp_y + (inp_h - FONT_SIZE) / 2,
|
|
g_pathbar_text, TEXT_COLOR, FONT_SIZE);
|
|
|
|
// Cursor in pathbar
|
|
char prefix[256];
|
|
int plen = g_pathbar_cursor > 255 ? 255 : g_pathbar_cursor;
|
|
for (int i = 0; i < plen; i++) prefix[i] = g_pathbar_text[i];
|
|
prefix[plen] = '\0';
|
|
int cx = inp_x + 4 + (g_font ? g_font->measure_text(prefix, FONT_SIZE) : 0);
|
|
px_fill(pixels, W, H, cx, inp_y + 3, 2, inp_h - 6, ACCENT);
|
|
|
|
// Button
|
|
int ob_x = inp_x + inp_w + 6;
|
|
px_fill_rounded(pixels, W, H, ob_x, inp_y, btn_w, inp_h, 3, ACCENT);
|
|
const char* btn_label = g_pathbar_save ? "Save" : "Open";
|
|
if (g_font) {
|
|
int tw = g_font->measure_text(btn_label, FONT_SIZE);
|
|
g_font->draw_to_buffer(pixels, W, H, ob_x + (btn_w - tw) / 2,
|
|
inp_y + (inp_h - FONT_SIZE) / 2, btn_label, WHITE, FONT_SIZE);
|
|
}
|
|
|
|
// Pathbar bottom line
|
|
px_hline(pixels, W, H, 0, pb_y + PATHBAR_H - 1, W, BORDER_COLOR);
|
|
|
|
editor_y = TOOLBAR_H + PATHBAR_H;
|
|
}
|
|
|
|
// ---- Editor area ----
|
|
int text_area_h = H - editor_y - STATUS_H;
|
|
int visible_lines = text_area_h / cell_h;
|
|
if (visible_lines < 1) visible_lines = 1;
|
|
int text_area_w = W - SCROLLBAR_W;
|
|
|
|
if (g_cursor_moved) {
|
|
ensure_cursor_visible(visible_lines, W);
|
|
g_cursor_moved = false;
|
|
}
|
|
|
|
// Line number gutter
|
|
px_fill(pixels, W, H, 0, editor_y, LINE_NUM_W, text_area_h, LINENUM_BG);
|
|
px_vline(pixels, W, H, LINE_NUM_W, editor_y, text_area_h, BORDER_COLOR);
|
|
|
|
// Selection range
|
|
int sel_s = 0, sel_e = 0;
|
|
if (g_has_selection) sel_range(&sel_s, &sel_e);
|
|
|
|
int text_start_x = LINE_NUM_W + 4;
|
|
|
|
// Syntax highlighting: compute multi-line syntax state up to the first visible line
|
|
SynState syn_state = syn_make_state();
|
|
SynToken syn_line_buf[SYN_LINE_BUF_CAP];
|
|
if (g_syntax_language != SYN_LANG_NONE) {
|
|
for (int i = 0; i < g_scroll_y && i < g_line_count; i++) {
|
|
int ls = g_line_offsets[i];
|
|
int ll = line_length(i);
|
|
syn_highlight_line(g_buffer + ls, ll, nullptr, 0, g_syntax_language, syn_state);
|
|
}
|
|
}
|
|
|
|
GlyphCache* gc = (g_font_mono && g_font_mono->valid) ? g_font_mono->get_cache(MONO_SIZE) : nullptr;
|
|
|
|
for (int vis = 0; vis < visible_lines + 1; vis++) {
|
|
int line = g_scroll_y + vis;
|
|
if (line >= g_line_count) break;
|
|
|
|
int py = editor_y + vis * cell_h;
|
|
if (py >= editor_y + text_area_h) break;
|
|
|
|
// Cursor line highlight
|
|
if (line == g_cursor_line && !g_has_selection) {
|
|
int hl_h = cell_h;
|
|
if (py + hl_h > editor_y + text_area_h) hl_h = editor_y + text_area_h - py;
|
|
if (hl_h > 0)
|
|
px_fill(pixels, W, H, LINE_NUM_W + 1, py, text_area_w - LINE_NUM_W - 1, hl_h, CURSOR_LINE_BG);
|
|
}
|
|
|
|
// Line number
|
|
char num_str[8];
|
|
snprintf(num_str, 8, "%4d", line + 1);
|
|
if (g_font_mono)
|
|
g_font_mono->draw_to_buffer(pixels, W, H, 4, py, num_str, LINENUM_COLOR, MONO_SIZE);
|
|
|
|
// Line text
|
|
int line_start = g_line_offsets[line];
|
|
int line_len = line_length(line);
|
|
|
|
// Syntax highlight this line
|
|
int syn_len = line_len > SYN_LINE_BUF_CAP ? SYN_LINE_BUF_CAP : line_len;
|
|
if (g_syntax_language != SYN_LANG_NONE)
|
|
syn_highlight_line(g_buffer + line_start, line_len, syn_line_buf,
|
|
SYN_LINE_BUF_CAP, g_syntax_language, syn_state);
|
|
|
|
for (int ci = 0; ci < line_len; ci++) {
|
|
int px_x = text_start_x + ci * cell_w - g_scroll_x;
|
|
if (px_x + cell_w <= LINE_NUM_W + 1) continue;
|
|
if (px_x >= text_area_w) break;
|
|
|
|
int byte_pos = line_start + ci;
|
|
char ch = g_buffer[byte_pos];
|
|
|
|
// Selection highlight
|
|
if (g_has_selection && byte_pos >= sel_s && byte_pos < sel_e) {
|
|
int hl_h = cell_h;
|
|
if (py + hl_h > editor_y + text_area_h) hl_h = editor_y + text_area_h - py;
|
|
if (hl_h > 0)
|
|
px_fill(pixels, W, H, px_x, py, cell_w, hl_h, SEL_BG);
|
|
}
|
|
|
|
Color char_color = TEXT_COLOR;
|
|
if (g_syntax_language != SYN_LANG_NONE && ci < syn_len)
|
|
char_color = syn_color(syn_line_buf[ci]);
|
|
|
|
if (ch >= 32 || ch < 0) {
|
|
if (gc) {
|
|
g_font_mono->draw_char_to_buffer(pixels, W, H, px_x, py + gc->ascent,
|
|
ch, char_color, gc);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Selection highlight for newline at end of line
|
|
if (g_has_selection && line + 1 < g_line_count) {
|
|
int nl_pos = line_start + line_len;
|
|
if (nl_pos >= sel_s && nl_pos < sel_e) {
|
|
int px_x = text_start_x + line_len * cell_w - g_scroll_x;
|
|
if (px_x > LINE_NUM_W && px_x < text_area_w) {
|
|
int hl_h = cell_h;
|
|
if (py + hl_h > editor_y + text_area_h) hl_h = editor_y + text_area_h - py;
|
|
if (hl_h > 0)
|
|
px_fill(pixels, W, H, px_x, py, cell_w / 2, hl_h, SEL_BG);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cursor
|
|
if (line == g_cursor_line) {
|
|
int cx = text_start_x + g_cursor_col * cell_w - g_scroll_x;
|
|
if (cx > LINE_NUM_W && cx + 2 <= text_area_w) {
|
|
int cur_h = cell_h;
|
|
if (py + cur_h > editor_y + text_area_h) cur_h = editor_y + text_area_h - py;
|
|
if (cur_h > 0)
|
|
px_fill(pixels, W, H, cx, py, 2, cur_h, ACCENT);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- Scrollbar ----
|
|
int content_h = g_line_count * cell_h;
|
|
if (content_h > text_area_h) {
|
|
int sb_x = W - SCROLLBAR_W;
|
|
px_fill(pixels, W, H, sb_x, editor_y, SCROLLBAR_W, text_area_h, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
|
int max_scr = g_line_count - visible_lines;
|
|
if (max_scr < 1) max_scr = 1;
|
|
int thumb_h = (text_area_h * text_area_h) / content_h;
|
|
if (thumb_h < 20) thumb_h = 20;
|
|
int range = text_area_h - thumb_h;
|
|
int thumb_y = editor_y + (g_scroll_y * range) / max_scr;
|
|
px_fill(pixels, W, H, sb_x + 1, thumb_y, SCROLLBAR_W - 2, thumb_h, Color::from_rgb(0xAA, 0xAA, 0xAA));
|
|
}
|
|
|
|
// ---- Status bar ----
|
|
int status_y = H - STATUS_H;
|
|
px_fill(pixels, W, H, 0, status_y, W, STATUS_H, STATUS_BG);
|
|
|
|
int sty = status_y + (STATUS_H - FONT_SIZE) / 2;
|
|
|
|
// Left: filename
|
|
char status_left[128];
|
|
if (g_filename[0])
|
|
snprintf(status_left, 128, " %s%s", g_filename, g_modified ? " [modified]" : "");
|
|
else
|
|
snprintf(status_left, 128, " Untitled%s", g_modified ? " [modified]" : "");
|
|
if (g_font)
|
|
g_font->draw_to_buffer(pixels, W, H, 4, sty, status_left, STATUS_TEXT, FONT_SIZE);
|
|
|
|
// Right: cursor position
|
|
char status_right[48];
|
|
if (g_has_selection) {
|
|
int ss, se;
|
|
sel_range(&ss, &se);
|
|
snprintf(status_right, 48, "%d selected Ln %d, Col %d ", se - ss, g_cursor_line + 1, g_cursor_col + 1);
|
|
} else {
|
|
snprintf(status_right, 48, "Ln %d, Col %d ", g_cursor_line + 1, g_cursor_col + 1);
|
|
}
|
|
if (g_font) {
|
|
int rw = g_font->measure_text(status_right, FONT_SIZE);
|
|
g_font->draw_to_buffer(pixels, W, H, W - rw - 4, sty, status_right, STATUS_TEXT, FONT_SIZE);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Pathbar actions
|
|
// ============================================================================
|
|
|
|
static void pathbar_confirm(int win_id) {
|
|
if (g_pathbar_text[0] == '\0') return;
|
|
|
|
if (g_pathbar_save) {
|
|
montauk::strncpy(g_filepath, g_pathbar_text, 255);
|
|
const char* name = g_pathbar_text;
|
|
for (int i = 0; g_pathbar_text[i]; i++)
|
|
if (g_pathbar_text[i] == '/') name = g_pathbar_text + i + 1;
|
|
montauk::strncpy(g_filename, name, 63);
|
|
g_syntax_language = syn_detect_language(g_filepath);
|
|
save_file();
|
|
} else {
|
|
load_file(g_pathbar_text);
|
|
}
|
|
|
|
g_pathbar_open = false;
|
|
}
|
|
|
|
static void open_pathbar_open() {
|
|
g_pathbar_open = true;
|
|
g_pathbar_save = false;
|
|
montauk::strncpy(g_pathbar_text, g_filepath, 255);
|
|
g_pathbar_len = montauk::slen(g_pathbar_text);
|
|
g_pathbar_cursor = g_pathbar_len;
|
|
}
|
|
|
|
static void open_pathbar_save() {
|
|
g_pathbar_open = true;
|
|
g_pathbar_save = true;
|
|
g_pathbar_text[0] = '\0';
|
|
g_pathbar_len = 0;
|
|
g_pathbar_cursor = 0;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Keyboard handler
|
|
// ============================================================================
|
|
|
|
static bool handle_key(Montauk::KeyEvent& key, int win_id) {
|
|
if (!key.pressed) return false;
|
|
|
|
// ---- Pathbar mode ----
|
|
if (g_pathbar_open) {
|
|
if (key.ascii == '\n' || key.ascii == '\r') {
|
|
pathbar_confirm(win_id);
|
|
return true;
|
|
}
|
|
if (key.scancode == 0x01) { // Escape
|
|
g_pathbar_open = false;
|
|
return true;
|
|
}
|
|
if (key.ascii == '\b' || key.scancode == 0x0E) {
|
|
if (g_pathbar_cursor > 0) {
|
|
for (int i = g_pathbar_cursor - 1; i < g_pathbar_len - 1; i++)
|
|
g_pathbar_text[i] = g_pathbar_text[i + 1];
|
|
g_pathbar_len--;
|
|
g_pathbar_cursor--;
|
|
g_pathbar_text[g_pathbar_len] = '\0';
|
|
}
|
|
return true;
|
|
}
|
|
if (key.scancode == 0x4B) { if (g_pathbar_cursor > 0) g_pathbar_cursor--; return true; }
|
|
if (key.scancode == 0x4D) { if (g_pathbar_cursor < g_pathbar_len) g_pathbar_cursor++; return true; }
|
|
if (key.ascii >= 32 && key.ascii < 127 && g_pathbar_len < 254) {
|
|
for (int i = g_pathbar_len; i > g_pathbar_cursor; i--)
|
|
g_pathbar_text[i] = g_pathbar_text[i - 1];
|
|
g_pathbar_text[g_pathbar_cursor] = key.ascii;
|
|
g_pathbar_cursor++;
|
|
g_pathbar_len++;
|
|
g_pathbar_text[g_pathbar_len] = '\0';
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ---- Editor mode ----
|
|
|
|
// Ctrl+S: save
|
|
if (key.ctrl && (key.ascii == 's' || key.ascii == 'S')) {
|
|
if (g_filepath[0] == '\0')
|
|
open_pathbar_save();
|
|
else
|
|
save_file();
|
|
return true;
|
|
}
|
|
|
|
// Ctrl+O: open
|
|
if (key.ctrl && (key.ascii == 'o' || key.ascii == 'O')) {
|
|
if (g_pathbar_open) g_pathbar_open = false;
|
|
else open_pathbar_open();
|
|
return true;
|
|
}
|
|
|
|
// Ctrl+A: select all
|
|
if (key.ctrl && (key.ascii == 'a' || key.ascii == 'A')) {
|
|
g_sel_anchor = 0;
|
|
g_sel_end = g_buf_len;
|
|
g_has_selection = (g_buf_len > 0);
|
|
g_cursor_pos = g_buf_len;
|
|
update_cursor_pos();
|
|
return true;
|
|
}
|
|
|
|
// Arrow keys
|
|
if (key.scancode == 0x48) { // Up
|
|
if (key.shift) start_selection(); else if (g_has_selection) clear_selection();
|
|
move_up();
|
|
if (key.shift) update_selection();
|
|
return true;
|
|
}
|
|
if (key.scancode == 0x50) { // Down
|
|
if (key.shift) start_selection(); else if (g_has_selection) clear_selection();
|
|
move_down();
|
|
if (key.shift) update_selection();
|
|
return true;
|
|
}
|
|
if (key.scancode == 0x4B) { // Left
|
|
if (key.shift) {
|
|
start_selection(); move_left(); update_selection();
|
|
} else if (g_has_selection) {
|
|
int s, e; sel_range(&s, &e);
|
|
g_cursor_pos = s; update_cursor_pos(); clear_selection();
|
|
} else move_left();
|
|
return true;
|
|
}
|
|
if (key.scancode == 0x4D) { // Right
|
|
if (key.shift) {
|
|
start_selection(); move_right(); update_selection();
|
|
} else if (g_has_selection) {
|
|
int s, e; sel_range(&s, &e);
|
|
g_cursor_pos = e; update_cursor_pos(); clear_selection();
|
|
} else move_right();
|
|
return true;
|
|
}
|
|
|
|
// Home
|
|
if (key.scancode == 0x47) {
|
|
if (key.shift) start_selection(); else if (g_has_selection) clear_selection();
|
|
move_home();
|
|
if (key.shift) update_selection();
|
|
return true;
|
|
}
|
|
// End
|
|
if (key.scancode == 0x4F) {
|
|
if (key.shift) start_selection(); else if (g_has_selection) clear_selection();
|
|
move_end();
|
|
if (key.shift) update_selection();
|
|
return true;
|
|
}
|
|
|
|
// Page Up
|
|
if (key.scancode == 0x49) {
|
|
if (key.shift) start_selection(); else if (g_has_selection) clear_selection();
|
|
int cell_h = font_cell_h();
|
|
int vis = cell_h > 0 ? (g_win_h - TOOLBAR_H - STATUS_H) / cell_h : 20;
|
|
for (int i = 0; i < vis; i++) move_up();
|
|
if (key.shift) update_selection();
|
|
return true;
|
|
}
|
|
// Page Down
|
|
if (key.scancode == 0x51) {
|
|
if (key.shift) start_selection(); else if (g_has_selection) clear_selection();
|
|
int cell_h = font_cell_h();
|
|
int vis = cell_h > 0 ? (g_win_h - TOOLBAR_H - STATUS_H) / cell_h : 20;
|
|
for (int i = 0; i < vis; i++) move_down();
|
|
if (key.shift) update_selection();
|
|
return true;
|
|
}
|
|
|
|
// Delete
|
|
if (key.scancode == 0x53) {
|
|
if (g_has_selection) delete_selection(); else delete_char();
|
|
return true;
|
|
}
|
|
|
|
// Backspace
|
|
if (key.ascii == '\b' || key.scancode == 0x0E) {
|
|
if (g_has_selection) delete_selection(); else backspace();
|
|
return true;
|
|
}
|
|
|
|
// Enter
|
|
if (key.ascii == '\n' || key.ascii == '\r') {
|
|
if (g_has_selection) delete_selection();
|
|
insert_char('\n');
|
|
return true;
|
|
}
|
|
|
|
// Tab
|
|
if (key.ascii == '\t') {
|
|
if (g_has_selection) delete_selection();
|
|
for (int i = 0; i < TAB_WIDTH; i++) insert_char(' ');
|
|
return true;
|
|
}
|
|
|
|
// Printable
|
|
if (key.ascii >= 32 && key.ascii < 127) {
|
|
if (g_has_selection) delete_selection();
|
|
insert_char(key.ascii);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Mouse handler
|
|
// ============================================================================
|
|
|
|
static bool handle_mouse(Montauk::WinEvent& ev, int win_id) {
|
|
int mx = ev.mouse.x;
|
|
int my = ev.mouse.y;
|
|
uint8_t btns = ev.mouse.buttons;
|
|
uint8_t prev = ev.mouse.prev_buttons;
|
|
bool clicked = (btns & 1) && !(prev & 1);
|
|
bool held = (btns & 1) != 0;
|
|
bool released = !(btns & 1) && (prev & 1);
|
|
int scroll = ev.mouse.scroll;
|
|
int cell_h = font_cell_h();
|
|
|
|
int editor_y = TOOLBAR_H + (g_pathbar_open ? PATHBAR_H : 0);
|
|
int text_area_h = g_win_h - editor_y - STATUS_H;
|
|
|
|
// ---- Toolbar clicks ----
|
|
if (clicked && my < TOOLBAR_H) {
|
|
// Open button
|
|
if (mx >= 4 && mx < 28 && my >= 6 && my < 30) {
|
|
if (g_pathbar_open) g_pathbar_open = false;
|
|
else open_pathbar_open();
|
|
return true;
|
|
}
|
|
// Save button
|
|
if (mx >= 32 && mx < 56 && my >= 6 && my < 30) {
|
|
if (g_filepath[0] == '\0')
|
|
open_pathbar_save();
|
|
else
|
|
save_file();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ---- Pathbar clicks ----
|
|
if (g_pathbar_open && my >= TOOLBAR_H && my < TOOLBAR_H + PATHBAR_H) {
|
|
if (clicked) {
|
|
int btn_w = 56;
|
|
int inp_w = g_win_w - 8 - btn_w - 12;
|
|
int ob_x = 8 + inp_w + 6;
|
|
if (mx >= ob_x && mx < ob_x + btn_w)
|
|
pathbar_confirm(win_id);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ---- Editor click ----
|
|
if (clicked && my >= editor_y && my < editor_y + text_area_h
|
|
&& mx > LINE_NUM_W && mx < g_win_w - SCROLLBAR_W) {
|
|
int pos = hit_test(mx, my, editor_y);
|
|
g_cursor_pos = pos;
|
|
update_cursor_pos();
|
|
g_sel_anchor = pos;
|
|
g_sel_end = pos;
|
|
g_has_selection = false;
|
|
g_mouse_selecting = true;
|
|
return true;
|
|
}
|
|
|
|
// ---- Editor drag ----
|
|
if (g_mouse_selecting && held && my >= editor_y - 20) {
|
|
int pos = hit_test(mx, my, editor_y);
|
|
g_sel_end = pos;
|
|
g_cursor_pos = pos;
|
|
update_cursor_pos();
|
|
g_has_selection = (g_sel_anchor != g_sel_end);
|
|
return true;
|
|
}
|
|
|
|
// ---- Mouse release ----
|
|
if (g_mouse_selecting && released) {
|
|
g_mouse_selecting = false;
|
|
return true;
|
|
}
|
|
|
|
// ---- Scroll ----
|
|
if (scroll != 0 && my >= editor_y && my < editor_y + text_area_h) {
|
|
int old_scroll = g_scroll_y;
|
|
g_scroll_y -= scroll * 3;
|
|
if (g_scroll_y < 0) g_scroll_y = 0;
|
|
int visible_lines = text_area_h / cell_h;
|
|
int max_scroll = g_line_count - visible_lines;
|
|
if (max_scroll < 0) max_scroll = 0;
|
|
if (g_scroll_y > max_scroll) g_scroll_y = max_scroll;
|
|
|
|
// Move cursor with scroll to keep it visible
|
|
int delta = g_scroll_y - old_scroll;
|
|
if (delta != 0) {
|
|
int new_line = g_cursor_line + delta;
|
|
if (new_line < g_scroll_y) new_line = g_scroll_y;
|
|
if (new_line >= g_scroll_y + visible_lines) new_line = g_scroll_y + visible_lines - 1;
|
|
if (new_line < 0) new_line = 0;
|
|
if (new_line >= g_line_count) new_line = g_line_count - 1;
|
|
int col = g_cursor_col;
|
|
int ll = line_length(new_line);
|
|
if (col > ll) col = ll;
|
|
g_cursor_pos = g_line_offsets[new_line] + col;
|
|
g_cursor_line = new_line;
|
|
g_cursor_col = col;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Entry point
|
|
// ============================================================================
|
|
|
|
extern "C" void _start() {
|
|
// Initialize buffer
|
|
g_buffer = (char*)montauk::malloc(INIT_CAP);
|
|
g_buf_cap = INIT_CAP;
|
|
g_buf_len = 0;
|
|
|
|
recompute_lines();
|
|
update_cursor_pos();
|
|
|
|
// Load fonts
|
|
auto load_font = [](const char* path) -> TrueTypeFont* {
|
|
TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
|
|
if (!f) return nullptr;
|
|
montauk::memset(f, 0, sizeof(TrueTypeFont));
|
|
if (!f->init(path)) { montauk::mfree(f); return nullptr; }
|
|
return f;
|
|
};
|
|
g_font = load_font("0:/fonts/Roboto-Medium.ttf");
|
|
g_font_mono = load_font("0:/fonts/JetBrainsMono-Regular.ttf");
|
|
|
|
// Load toolbar icons
|
|
Color icon_color = Color::from_rgb(0x44, 0x44, 0x44);
|
|
g_icon_folder = svg_load("0:/icons/folder.svg", 16, 16, icon_color);
|
|
g_icon_save = svg_load("0:/icons/document-save-symbolic.svg", 16, 16, icon_color);
|
|
|
|
// Check for file argument
|
|
char args[512] = {};
|
|
int arglen = montauk::getargs(args, sizeof(args));
|
|
if (arglen > 0 && args[0])
|
|
load_file(args);
|
|
|
|
// Build window title
|
|
char title[64] = "Text Editor";
|
|
if (g_filename[0]) {
|
|
snprintf(title, 64, "%s - Editor", g_filename);
|
|
}
|
|
|
|
// Create window
|
|
Montauk::WinCreateResult wres;
|
|
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
|
montauk::exit(1);
|
|
|
|
int win_id = wres.id;
|
|
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
|
|
|
render(pixels);
|
|
montauk::win_present(win_id);
|
|
|
|
while (true) {
|
|
Montauk::WinEvent ev;
|
|
int r = montauk::win_poll(win_id, &ev);
|
|
|
|
if (r < 0) break;
|
|
if (r == 0) { montauk::sleep_ms(16); continue; }
|
|
|
|
// Close
|
|
if (ev.type == 3) break;
|
|
|
|
// Resize
|
|
if (ev.type == 2) {
|
|
g_win_w = ev.resize.w;
|
|
g_win_h = ev.resize.h;
|
|
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
|
|
render(pixels);
|
|
montauk::win_present(win_id);
|
|
continue;
|
|
}
|
|
|
|
bool redraw = false;
|
|
|
|
// Keyboard
|
|
if (ev.type == 0)
|
|
redraw = handle_key(ev.key, win_id);
|
|
|
|
// Mouse
|
|
if (ev.type == 1)
|
|
redraw = handle_mouse(ev, win_id);
|
|
|
|
if (redraw) {
|
|
render(pixels);
|
|
montauk::win_present(win_id);
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
if (g_buffer) montauk::mfree(g_buffer);
|
|
if (g_line_offsets) montauk::mfree(g_line_offsets);
|
|
if (g_font) montauk::mfree(g_font);
|
|
if (g_font_mono) montauk::mfree(g_font_mono);
|
|
montauk::win_destroy(win_id);
|
|
montauk::exit(0);
|
|
}
|