feat: Intel GPU fixes, add tcc compiler, spreadsheet improvements, paint app, file manager improvements

This commit is contained in:
2026-03-21 17:35:16 +01:00
parent 2fd9b7d027
commit 770dde5001
72 changed files with 45499 additions and 305 deletions
+56
View File
@@ -213,6 +213,62 @@ void apply_format(NumFormat f) {
// Scroll clamping
// ============================================================================
// ============================================================================
// Fill down (fill handle)
// ============================================================================
void fill_down(int src_r0, int src_c0, int src_r1, int src_c1, int dst_r1) {
if (dst_r1 <= src_r1) return;
if (dst_r1 >= MAX_ROWS) dst_r1 = MAX_ROWS - 1;
undo_push();
int src_rows = src_r1 - src_r0 + 1;
for (int r = src_r1 + 1; r <= dst_r1; r++) {
int src_r = src_r0 + ((r - src_r0) % src_rows);
int drow = r - src_r;
for (int c = src_c0; c <= src_c1; c++) {
Cell* src = &g_cells[src_r][c];
Cell* dst = &g_cells[r][c];
dst->align = src->align;
dst->fmt = src->fmt;
dst->bold = src->bold;
if (src->input[0] == '=') {
// Formula: adjust cell references
adjust_formula_refs(src->input, dst->input, CELL_TEXT_MAX, 0, drow);
} else {
str_cpy(dst->input, src->input, CELL_TEXT_MAX);
}
}
}
eval_all_cells();
g_modified = true;
}
// ============================================================================
// Auto-fit column width
// ============================================================================
void auto_fit_column(int col) {
if (!g_font) return;
int max_w = MIN_COL_W;
for (int r = 0; r < MAX_ROWS; r++) {
if (g_cells[r][col].display[0]) {
TrueTypeFont* f = (g_cells[r][col].bold && g_font_bold) ? g_font_bold : g_font;
int tw = f->measure_text(g_cells[r][col].display, FONT_SIZE) + 12;
if (tw > max_w) max_w = tw;
}
}
g_col_widths[col] = max_w;
}
// ============================================================================
// Scroll clamping
// ============================================================================
void clamp_scroll() {
int pbh = g_pathbar_open ? PATHBAR_H : 0;
int max_x = content_width() - (g_win_w - ROW_HEADER_W);
+239
View File
@@ -6,6 +6,34 @@
#include "spreadsheet.h"
// ============================================================================
// Formula name list (for autocomplete and parsing)
// ============================================================================
struct FormulaInfo {
const char* name;
const char* hint;
};
static const FormulaInfo g_formula_list[] = {
{"SUM", "SUM(range)"},
{"AVG", "AVG(range)"},
{"MIN", "MIN(range)"},
{"MAX", "MAX(range)"},
{"COUNT", "COUNT(range)"},
{"ABS", "ABS(value)"},
{"SQRT", "SQRT(value)"},
{"ROUND", "ROUND(val,n)"},
{"INT", "INT(value)"},
{"FLOOR", "FLOOR(value)"},
{"CEIL", "CEIL(value)"},
{"POW", "POW(base,exp)"},
{"MOD", "MOD(a,b)"},
{"PI", "PI()"},
{"IF", "IF(cond,t,f)"},
};
static constexpr int FORMULA_COUNT = sizeof(g_formula_list) / sizeof(g_formula_list[0]);
// ============================================================================
// Cell reference parsing
// ============================================================================
@@ -124,6 +152,7 @@ static double eval_primary(const char* s, int* pos, bool* ok) {
}
fname[fi] = '\0';
// Range functions: SUM, AVG, MIN, MAX, COUNT
if (s[*pos] == '(' && (strcmp(fname, "SUM") == 0 ||
strcmp(fname, "AVG") == 0 ||
strcmp(fname, "MIN") == 0 ||
@@ -132,6 +161,77 @@ static double eval_primary(const char* s, int* pos, bool* ok) {
return eval_range_func(fname, s, pos, ok);
}
// No-arg: PI()
if (s[*pos] == '(' && strcmp(fname, "PI") == 0) {
(*pos)++;
skip_spaces(s, pos);
if (s[*pos] == ')') (*pos)++;
return 3.14159265358979;
}
// Single-arg functions
if (s[*pos] == '(' && (strcmp(fname, "ABS") == 0 ||
strcmp(fname, "SQRT") == 0 ||
strcmp(fname, "INT") == 0 ||
strcmp(fname, "FLOOR") == 0 ||
strcmp(fname, "CEIL") == 0)) {
(*pos)++;
double arg = eval_expr(s, pos, ok);
skip_spaces(s, pos);
if (s[*pos] == ')') (*pos)++;
if (!*ok) return 0;
if (strcmp(fname, "ABS") == 0) return stb_fabs(arg);
if (strcmp(fname, "SQRT") == 0) return stb_sqrt(arg);
if (strcmp(fname, "INT") == 0) return (double)(long long)arg;
if (strcmp(fname, "FLOOR") == 0) return stb_floor(arg);
if (strcmp(fname, "CEIL") == 0) return stb_ceil(arg);
return 0;
}
// Two-arg functions: ROUND, POW, MOD
if (s[*pos] == '(' && (strcmp(fname, "ROUND") == 0 ||
strcmp(fname, "POW") == 0 ||
strcmp(fname, "MOD") == 0)) {
(*pos)++;
double a = eval_expr(s, pos, ok);
skip_spaces(s, pos);
if (s[*pos] != ',') { *ok = false; return 0; }
(*pos)++;
double b = eval_expr(s, pos, ok);
skip_spaces(s, pos);
if (s[*pos] == ')') (*pos)++;
if (!*ok) return 0;
if (strcmp(fname, "POW") == 0) return stb_pow(a, b);
if (strcmp(fname, "MOD") == 0) {
if (b == 0) { *ok = false; return 0; }
return stb_fmod(a, b);
}
if (strcmp(fname, "ROUND") == 0) {
double factor = stb_pow(10.0, b);
return stb_floor(a * factor + 0.5) / factor;
}
return 0;
}
// IF(condition, true_value, false_value)
if (s[*pos] == '(' && strcmp(fname, "IF") == 0) {
(*pos)++;
double cond = eval_expr(s, pos, ok);
skip_spaces(s, pos);
if (s[*pos] != ',') { *ok = false; return 0; }
(*pos)++;
double tv = eval_expr(s, pos, ok);
skip_spaces(s, pos);
if (s[*pos] != ',') { *ok = false; return 0; }
(*pos)++;
double fv = eval_expr(s, pos, ok);
skip_spaces(s, pos);
if (s[*pos] == ')') (*pos)++;
if (!*ok) return 0;
return (cond != 0.0) ? tv : fv;
}
// Fall back to cell reference
*pos = save_pos;
int col, row, consumed;
if (parse_cell_ref(s + *pos, &col, &row, &consumed)) {
@@ -306,3 +406,142 @@ void cell_name(char* buf, int col, int row) {
else if (r >= 10) { buf[1] = '0' + r / 10; buf[2] = '0' + r % 10; buf[3] = '\0'; }
else { buf[1] = '0' + r; buf[2] = '\0'; }
}
// ============================================================================
// Formula autocomplete
// ============================================================================
void update_autocomplete() {
g_ac_open = false;
g_ac_count = 0;
if (!g_editing || g_edit_buf[0] != '=') return;
// Find start of current alphabetic word at cursor
int word_start = g_edit_cursor;
while (word_start > 0 && is_alpha(g_edit_buf[word_start - 1]))
word_start--;
int word_len = g_edit_cursor - word_start;
if (word_len == 0 || word_len > 7) return;
// Don't autocomplete if cursor is right before more alpha chars (mid-word)
if (g_edit_cursor < g_edit_len && is_alpha(g_edit_buf[g_edit_cursor])) return;
// Don't show if next char is '(' -- user already completed the name
if (g_edit_cursor < g_edit_len && g_edit_buf[g_edit_cursor] == '(') return;
char partial[8];
for (int i = 0; i < word_len; i++)
partial[i] = to_upper(g_edit_buf[word_start + i]);
partial[word_len] = '\0';
for (int i = 0; i < FORMULA_COUNT && g_ac_count < AC_MAX_MATCHES; i++) {
const char* name = g_formula_list[i].name;
bool match = true;
for (int j = 0; j < word_len; j++) {
if (name[j] == '\0' || to_upper(name[j]) != partial[j]) {
match = false;
break;
}
}
// Don't show exact matches (already fully typed)
if (match && name[word_len] != '\0') {
str_cpy(g_ac_matches[g_ac_count], name, 16);
str_cpy(g_ac_hints[g_ac_count], g_formula_list[i].hint, 32);
g_ac_count++;
}
}
if (g_ac_count > 0) {
g_ac_open = true;
g_ac_sel = 0;
}
}
void accept_autocomplete() {
if (!g_ac_open || g_ac_sel < 0 || g_ac_sel >= g_ac_count) return;
// Find word start
int word_start = g_edit_cursor;
while (word_start > 0 && is_alpha(g_edit_buf[word_start - 1]))
word_start--;
const char* name = g_ac_matches[g_ac_sel];
int name_len = str_len(name);
int old_len = g_edit_cursor - word_start;
int new_len = name_len + 1; // +1 for '('
int delta = new_len - old_len;
// Check buffer space
if (g_edit_len + delta >= CELL_TEXT_MAX - 1) { g_ac_open = false; return; }
// Shift rest of buffer
if (delta > 0) {
for (int i = g_edit_len; i >= g_edit_cursor; i--)
g_edit_buf[i + delta] = g_edit_buf[i];
} else if (delta < 0) {
for (int i = g_edit_cursor; i <= g_edit_len; i++)
g_edit_buf[i + delta] = g_edit_buf[i];
}
// Write function name + opening paren
for (int i = 0; i < name_len; i++)
g_edit_buf[word_start + i] = name[i];
g_edit_buf[word_start + name_len] = '(';
g_edit_len += delta;
g_edit_cursor = word_start + new_len;
g_edit_buf[g_edit_len] = '\0';
g_ac_open = false;
}
// ============================================================================
// Formula reference adjustment (for fill handle)
// ============================================================================
void adjust_formula_refs(const char* src, char* dst, int max, int dcol, int drow) {
int si = 0, di = 0;
while (src[si] && di < max - 1) {
if (is_alpha(src[si])) {
// Try to parse a cell reference
int col, row, consumed;
if (parse_cell_ref(src + si, &col, &row, &consumed)) {
// Check this is actually a cell ref and not part of a function name
// by looking backward: if the previous char is also alpha, it's a
// function name, not a cell ref
bool is_func_part = (si > 0 && is_alpha(src[si - 1]));
if (is_func_part) {
dst[di++] = src[si++];
continue;
}
int nc = col + dcol;
int nr = row + drow;
if (nc < 0) nc = 0;
if (nc >= MAX_COLS) nc = MAX_COLS - 1;
if (nr < 0) nr = 0;
if (nr >= MAX_ROWS) nr = MAX_ROWS - 1;
dst[di++] = 'A' + nc;
int rv = nr + 1;
if (rv >= 100 && di < max - 3) {
dst[di++] = '0' + rv / 100;
dst[di++] = '0' + (rv / 10) % 10;
dst[di++] = '0' + rv % 10;
} else if (rv >= 10 && di < max - 2) {
dst[di++] = '0' + rv / 10;
dst[di++] = '0' + rv % 10;
} else {
dst[di++] = '0' + rv;
}
si += consumed;
} else {
dst[di++] = src[si++];
}
} else {
dst[di++] = src[si++];
}
}
dst[di] = '\0';
}
+206 -10
View File
@@ -60,6 +60,25 @@ UndoEntry* g_undo[UNDO_MAX + 1];
int g_undo_count = 0;
int g_undo_pos = 0;
// Formula autocomplete
bool g_ac_open = false;
int g_ac_sel = 0;
int g_ac_count = 0;
char g_ac_matches[AC_MAX_MATCHES][16];
char g_ac_hints[AC_MAX_MATCHES][32];
// Fill handle
bool g_fill_dragging = false;
int g_fill_target_row = 0;
// Mouse drag selection
bool g_mouse_selecting = false;
// Double-click detection
uint64_t g_last_click_ms = 0;
int g_last_click_col = -1;
int g_last_click_row = -1;
// ============================================================================
// Hit testing
// ============================================================================
@@ -89,6 +108,28 @@ bool hit_cell(int mx, int my, int* out_col, int* out_row) {
return true;
}
bool hit_fill_handle(int mx, int my) {
if (g_editing) return false;
int c0, r0, c1, r1;
sel_range(&c0, &r0, &c1, &r1);
int pbh = g_pathbar_open ? PATHBAR_H : 0;
int grid_y = TOOLBAR_H + pbh + FORMULA_BAR_H + COL_HEADER_H;
int sx = col_x(c0) - g_scroll_x;
int sw = 0;
for (int c = c0; c <= c1; c++) sw += g_col_widths[c];
int sy = grid_y + r0 * ROW_H - g_scroll_y;
int sh = (r1 - r0 + 1) * ROW_H;
int fhx = sx + sw - FILL_HANDLE_SIZE / 2;
int fhy = sy + sh - FILL_HANDLE_SIZE / 2;
return mx >= fhx - 3 && mx <= fhx + FILL_HANDLE_SIZE + 3 &&
my >= fhy - 3 && my <= fhy + FILL_HANDLE_SIZE + 3;
}
bool handle_toolbar_click(int mx, int my) {
if (my >= TOOLBAR_H || my < TB_BTN_Y || my >= TB_BTN_Y + TB_BTN_SIZE) return false;
@@ -202,8 +243,7 @@ extern "C" void _start() {
char args[512] = {};
int arglen = montauk::getargs(args, sizeof(args));
if (arglen > 0 && args[0]) {
str_cpy(g_filepath, args, 256);
load_file(g_filepath);
load_file(args);
}
// Build window title
@@ -295,21 +335,56 @@ extern "C" void _start() {
goto done_keys;
}
// Autocomplete interaction (when editing with autocomplete open)
if (g_ac_open && g_editing) {
if (key.scancode == 0x48) { // Up
g_ac_sel = (g_ac_sel - 1 + g_ac_count) % g_ac_count;
redraw = true;
goto done_keys;
}
if (key.scancode == 0x50) { // Down
g_ac_sel = (g_ac_sel + 1) % g_ac_count;
redraw = true;
goto done_keys;
}
if (key.ascii == '\t') { // Tab: accept autocomplete
accept_autocomplete();
update_autocomplete();
redraw = true;
goto done_keys;
}
if (key.ascii == '\n' || key.ascii == '\r') {
// Enter: accept autocomplete then commit
accept_autocomplete();
g_ac_open = false;
commit_edit();
if (g_sel_row < MAX_ROWS - 1) g_sel_row++;
ensure_sel_visible();
redraw = true;
goto done_keys;
}
if (key.scancode == 0x01) { // Escape: close autocomplete only
g_ac_open = false;
redraw = true;
goto done_keys;
}
}
// Escape: cancel edit or quit
if (key.scancode == 0x01) {
if (g_editing) { cancel_edit(); redraw = true; }
if (g_editing) { cancel_edit(); g_ac_open = false; redraw = true; }
else break;
}
// Enter: commit edit and move down
else if (key.ascii == '\n' || key.ascii == '\r') {
if (g_editing) commit_edit();
if (g_editing) { commit_edit(); g_ac_open = false; }
if (g_sel_row < MAX_ROWS - 1) g_sel_row++;
ensure_sel_visible();
redraw = true;
}
// Tab: commit and move right
// Tab: commit and move right (or accept autocomplete above)
else if (key.ascii == '\t') {
if (g_editing) commit_edit();
if (g_editing) { commit_edit(); g_ac_open = false; }
if (key.shift) {
if (g_sel_col > 0) g_sel_col--;
} else {
@@ -372,6 +447,7 @@ extern "C" void _start() {
g_edit_len--;
g_edit_cursor--;
g_edit_buf[g_edit_len] = '\0';
update_autocomplete();
}
redraw = true;
} else {
@@ -394,6 +470,7 @@ extern "C" void _start() {
g_edit_buf[i] = g_edit_buf[i + 1];
g_edit_len--;
g_edit_buf[g_edit_len] = '\0';
update_autocomplete();
}
redraw = true;
} else {
@@ -411,10 +488,40 @@ extern "C" void _start() {
// Edit mode arrow keys (cursor movement within formula bar)
else if (key.scancode == 0x4B && g_editing) {
if (g_edit_cursor > 0) g_edit_cursor--;
update_autocomplete();
redraw = true;
}
else if (key.scancode == 0x4D && g_editing) {
if (g_edit_cursor < g_edit_len) g_edit_cursor++;
update_autocomplete();
redraw = true;
}
// Home key
else if (key.scancode == 0x47) {
if (g_editing) {
g_edit_cursor = 0;
update_autocomplete();
} else {
g_sel_col = 0;
clear_selection();
ensure_sel_visible();
}
redraw = true;
}
// End key
else if (key.scancode == 0x4F) {
if (g_editing) {
g_edit_cursor = g_edit_len;
update_autocomplete();
} else {
// Jump to last non-empty column in current row
int last = 0;
for (int c = 0; c < MAX_COLS; c++)
if (g_cells[g_sel_row][c].input[0]) last = c;
g_sel_col = last;
clear_selection();
ensure_sel_visible();
}
redraw = true;
}
// Ctrl+B: bold toggle
@@ -487,6 +594,7 @@ extern "C" void _start() {
g_edit_cursor++;
g_edit_buf[g_edit_len] = '\0';
}
update_autocomplete();
redraw = true;
}
// F2: edit current cell content (append mode)
@@ -510,11 +618,15 @@ extern "C" void _start() {
int pbh = g_pathbar_open ? PATHBAR_H : 0;
int header_y = TOOLBAR_H + pbh + FORMULA_BAR_H;
// Update cursor style: resize_h when hovering near column border in header
// Update cursor style
{
int cursor = 0; // arrow
if (g_col_resizing) {
cursor = 1; // resize_h while dragging
} else if (g_fill_dragging) {
cursor = 2; // crosshair during fill
} else if (hit_fill_handle(mx, my)) {
cursor = 2; // crosshair on fill handle
} else if (my >= header_y && my < header_y + COL_HEADER_H) {
for (int c = 0; c < MAX_COLS; c++) {
int cx = col_x(c) - g_scroll_x + g_col_widths[c] - 1;
@@ -527,6 +639,33 @@ extern "C" void _start() {
montauk::win_setcursor(win_id, cursor);
}
// Fill handle: drag in progress
if (g_fill_dragging) {
if (left_held) {
int col, row;
if (hit_cell(mx, my, &col, &row)) {
int c0, r0, c1, r1;
sel_range(&c0, &r0, &c1, &r1);
if (row > r1 && row != g_fill_target_row) {
g_fill_target_row = row;
redraw = true;
}
}
}
if (left_released) {
int c0, r0, c1, r1;
sel_range(&c0, &r0, &c1, &r1);
if (g_fill_target_row > r1) {
fill_down(r0, c0, r1, c1, g_fill_target_row);
g_sel_row = g_fill_target_row;
g_has_selection = true;
}
g_fill_dragging = false;
redraw = true;
}
goto done_mouse;
}
// Column resize: drag in progress
if (g_col_resizing) {
if (left_held) {
@@ -543,11 +682,39 @@ extern "C" void _start() {
goto done_mouse;
}
// Column resize: start drag (click near right edge of column header)
// Mouse drag selection: extend while dragging
if (g_mouse_selecting && left_held && !clicked) {
int col, row;
if (hit_cell(mx, my, &col, &row)) {
if (col != g_sel_col || row != g_sel_row) {
g_sel_col = col;
g_sel_row = row;
g_has_selection = (col != g_anchor_col || row != g_anchor_row);
redraw = true;
}
}
}
if (left_released) {
g_mouse_selecting = false;
}
// Column resize or auto-fit: click/double-click near column header border
if (clicked && my >= header_y && my < header_y + COL_HEADER_H) {
for (int c = 0; c < MAX_COLS; c++) {
int cx = col_x(c) - g_scroll_x + g_col_widths[c] - 1;
if (mx >= cx - COL_RESIZE_GRAB && mx <= cx + COL_RESIZE_GRAB) {
// Double-click: auto-fit column width
uint64_t now = montauk::get_milliseconds();
if (g_last_click_col == c && (now - g_last_click_ms) < DBLCLICK_MS) {
auto_fit_column(c);
clamp_scroll();
g_last_click_ms = 0;
redraw = true;
goto done_mouse;
}
g_last_click_ms = now;
g_last_click_col = c;
g_col_resizing = true;
g_col_resize_idx = c;
g_col_resize_start_x = mx;
@@ -565,12 +732,41 @@ extern "C" void _start() {
redraw = true;
}
else {
// Check fill handle first
if (hit_fill_handle(mx, my)) {
int c0, r0, c1, r1;
sel_range(&c0, &r0, &c1, &r1);
g_fill_dragging = true;
g_fill_target_row = r1;
goto done_mouse;
}
int col, row;
if (hit_cell(mx, my, &col, &row)) {
if (g_editing) commit_edit();
if (g_editing) { commit_edit(); g_ac_open = false; }
// Double-click cell: start editing
uint64_t now = montauk::get_milliseconds();
if (col == g_last_click_col && row == g_last_click_row &&
(now - g_last_click_ms) < DBLCLICK_MS) {
g_sel_col = col;
g_sel_row = row;
clear_selection();
start_editing();
g_last_click_ms = 0;
redraw = true;
goto done_mouse;
}
g_last_click_ms = now;
g_last_click_col = col;
g_last_click_row = row;
g_sel_col = col;
g_sel_row = row;
clear_selection();
g_anchor_col = col;
g_anchor_row = row;
g_has_selection = false;
g_mouse_selecting = true;
g_fmt_dropdown_open = false;
redraw = true;
}
+92 -9
View File
@@ -270,7 +270,7 @@ void render(uint32_t* pixels) {
px_hline(pixels, g_win_w, g_win_h, ROW_HEADER_W, y + ROW_H - 1, g_win_w - ROW_HEADER_W, GRID_COLOR);
}
// ---- Selection border ----
// ---- Selection border + fill handle ----
{
int c0, r0, c1, r1;
sel_range(&c0, &r0, &c1, &r1);
@@ -281,8 +281,27 @@ void render(uint32_t* pixels) {
for (int c = c0; c <= c1; c++) sw += g_col_widths[c];
int sh = (r1 - r0 + 1) * ROW_H;
// Fill drag preview
if (g_fill_dragging && g_fill_target_row > r1) {
int fill_sh = (g_fill_target_row - r0 + 1) * ROW_H;
// Dashed border for fill preview area
for (int dx = 0; dx < sw; dx += 6) {
int seg = dx + 3 < sw ? 3 : sw - dx;
px_hline(pixels, g_win_w, g_win_h, sx + dx, sy_sel + fill_sh - 1, seg, SELECT_BORDER);
}
px_vline(pixels, g_win_w, g_win_h, sx, sy_sel + sh, fill_sh - sh, SELECT_BORDER);
px_vline(pixels, g_win_w, g_win_h, sx + sw - 1, sy_sel + sh, fill_sh - sh, SELECT_BORDER);
}
px_rect(pixels, g_win_w, g_win_h, sx, sy_sel, sw, sh, SELECT_BORDER);
px_rect(pixels, g_win_w, g_win_h, sx + 1, sy_sel + 1, sw - 2, sh - 2, SELECT_BORDER);
// Fill handle (small blue square at bottom-right corner)
if (!g_editing) {
int fhx = sx + sw - FILL_HANDLE_SIZE / 2;
int fhy = sy_sel + sh - FILL_HANDLE_SIZE / 2;
px_fill(pixels, g_win_w, g_win_h, fhx, fhy, FILL_HANDLE_SIZE, FILL_HANDLE_SIZE, SELECT_BORDER);
}
}
// ---- Status bar ----
@@ -302,15 +321,35 @@ void render(uint32_t* pixels) {
int sty = sy + (STATUS_BAR_H - HEADER_FONT) / 2;
g_font->draw_to_buffer(pixels, g_win_w, g_win_h, 6, sty, status, STATUS_TEXT, HEADER_FONT);
char right[64];
char right[128];
if (g_has_selection) {
int c0, r0, c1, r1;
sel_range(&c0, &r0, &c1, &r1);
char n0[8], n1[8];
cell_name(n0, c0, r0);
cell_name(n1, c1, r1);
int ncells = (c1 - c0 + 1) * (r1 - r0 + 1);
snprintf(right, 64, "%s:%s (%d cells) ", n0, n1, ncells);
// Calculate aggregates for numeric cells
double sum = 0;
int num_count = 0;
for (int r = r0; r <= r1; r++) {
for (int c = c0; c <= c1; c++) {
if (g_cells[r][c].type == CT_NUMBER || g_cells[r][c].type == CT_FORMULA) {
sum += g_cells[r][c].value;
num_count++;
}
}
}
if (num_count > 0) {
char sbuf[24], abuf[24];
double_to_str(sbuf, 24, sum);
double_to_str(abuf, 24, sum / num_count);
snprintf(right, 128, "SUM=%s AVG=%s COUNT=%d ", sbuf, abuf, num_count);
} else {
char n0[8], n1[8];
cell_name(n0, c0, r0);
cell_name(n1, c1, r1);
int ncells = (c1 - c0 + 1) * (r1 - r0 + 1);
snprintf(right, 128, "%s:%s (%d cells) ", n0, n1, ncells);
}
} else {
Cell* sel = &g_cells[g_sel_row][g_sel_col];
char cname[8];
@@ -318,9 +357,9 @@ void render(uint32_t* pixels) {
if (sel->type == CT_FORMULA || sel->type == CT_NUMBER) {
char vbuf[32];
double_to_str(vbuf, 32, sel->value);
snprintf(right, 64, "%s = %s ", cname, vbuf);
snprintf(right, 128, "%s = %s ", cname, vbuf);
} else {
snprintf(right, 64, "%s ", cname);
snprintf(right, 128, "%s ", cname);
}
}
int rw = g_font->measure_text(right, HEADER_FONT);
@@ -348,4 +387,48 @@ void render(uint32_t* pixels) {
dx + 8, iy + (item_h - HEADER_FONT) / 2, items[i], CELL_TEXT, HEADER_FONT);
}
}
// ---- Autocomplete dropdown overlay ----
if (g_ac_open && g_editing && g_font) {
int fbar_y_ac = TOOLBAR_H + pathbar_h;
int fx = sep_x + 10;
// Measure text up to the start of the current word
int ws = g_edit_cursor;
while (ws > 0 && is_alpha(g_edit_buf[ws - 1])) ws--;
char word_prefix[CELL_TEXT_MAX];
int wpi = 0;
for (int i = 0; i < ws && wpi < CELL_TEXT_MAX - 1; i++)
word_prefix[wpi++] = g_edit_buf[i];
word_prefix[wpi] = '\0';
int ac_x = fx + g_font->measure_text(word_prefix, FONT_SIZE);
int ac_y = fbar_y_ac + FORMULA_BAR_H;
int ac_item_h = 24;
int ac_w = 160;
int ac_h = g_ac_count * ac_item_h + 4;
// Shadow
px_fill(pixels, g_win_w, g_win_h, ac_x + 2, ac_y + 2, ac_w, ac_h,
Color::from_rgb(0xAA, 0xAA, 0xAA));
// Background
px_fill(pixels, g_win_w, g_win_h, ac_x, ac_y, ac_w, ac_h, BG_COLOR);
px_rect(pixels, g_win_w, g_win_h, ac_x, ac_y, ac_w, ac_h, GRID_COLOR);
for (int i = 0; i < g_ac_count; i++) {
int iy = ac_y + 2 + i * ac_item_h;
if (i == g_ac_sel)
px_fill(pixels, g_win_w, g_win_h, ac_x + 2, iy, ac_w - 4, ac_item_h - 2, SELECT_FILL);
TrueTypeFont* bf = g_font_bold ? g_font_bold : g_font;
bf->draw_to_buffer(pixels, g_win_w, g_win_h,
ac_x + 6, iy + (ac_item_h - HEADER_FONT) / 2,
g_ac_matches[i], CELL_TEXT, HEADER_FONT);
int nw = bf->measure_text(g_ac_matches[i], HEADER_FONT);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
ac_x + 12 + nw, iy + (ac_item_h - HEADER_FONT) / 2,
g_ac_hints[i], HEADER_TEXT, HEADER_FONT);
}
}
}
+29
View File
@@ -11,6 +11,7 @@
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/truetype.hpp>
#include <gui/stb_math.h>
extern "C" {
#include <string.h>
@@ -41,6 +42,9 @@ static constexpr int DEF_COL_W = 100;
static constexpr int MIN_COL_W = 30;
static constexpr int ROW_H = 26;
static constexpr int COL_RESIZE_GRAB = 5; // pixels from border edge for grab zone
static constexpr int FILL_HANDLE_SIZE = 6;
static constexpr int AC_MAX_MATCHES = 8;
static constexpr int DBLCLICK_MS = 400;
static constexpr int CELL_TEXT_MAX = 128;
static constexpr int FONT_SIZE = 18;
@@ -174,6 +178,25 @@ extern UndoEntry* g_undo[UNDO_MAX + 1];
extern int g_undo_count;
extern int g_undo_pos;
// Formula autocomplete
extern bool g_ac_open;
extern int g_ac_sel;
extern int g_ac_count;
extern char g_ac_matches[AC_MAX_MATCHES][16];
extern char g_ac_hints[AC_MAX_MATCHES][32];
// Fill handle
extern bool g_fill_dragging;
extern int g_fill_target_row;
// Mouse drag selection
extern bool g_mouse_selecting;
// Double-click detection
extern uint64_t g_last_click_ms;
extern int g_last_click_col;
extern int g_last_click_row;
// ============================================================================
// Function declarations — helpers.cpp
// ============================================================================
@@ -203,6 +226,9 @@ int content_width();
int content_height();
void cell_name(char* buf, int col, int row);
void format_value(char* buf, int max, double val, NumFormat fmt);
void update_autocomplete();
void accept_autocomplete();
void adjust_formula_refs(const char* src, char* dst, int max, int dcol, int drow);
// ============================================================================
// Function declarations — fileio.cpp
@@ -231,6 +257,8 @@ void apply_align(CellAlign a);
void apply_format(NumFormat f);
void clamp_scroll();
void ensure_sel_visible();
void fill_down(int src_r0, int src_c0, int src_r1, int src_c1, int dst_r1);
void auto_fit_column(int col);
// ============================================================================
// Function declarations — render.cpp
@@ -243,5 +271,6 @@ void render(uint32_t* pixels);
// ============================================================================
bool hit_cell(int mx, int my, int* out_col, int* out_row);
bool hit_fill_handle(int mx, int my);
bool handle_toolbar_click(int mx, int my);
bool handle_fmt_dropdown_click(int mx, int my);