feat: move Files to its own process; add window server messaging (multi-format clipboard, drag and drop), undo/redo, sidebar, breadcrumbs, thumbnails, file type icons, New menu and proper text fields

This commit is contained in:
daniel committed 2026-10-24 12:44:20 +02:00
1 parent 07191b1286
commit d3e49a925d
66 files changed
+6855 -905

No files matched your search

+171
View File
@@ -0,0 +1,171 @@
/*
* textfield.cpp
* Single-line text field for Files (the rename box and the path bar):
* editing comes from mtk::TextInputState (selection, clipboard, word
* jumps); this adds horizontal scrolling, clipped drawing and
* double/triple-click selection
* Copyright (c) 2026 Daniel Hammer
*/
#include "filemanager_internal.hpp"
namespace filemanager {
static constexpr uint64_t MULTI_CLICK_MS = 400;
int filemanager_field_len(const FileManagerTextField& f) {
return mtk::text_input_len(f.buf, f.cap);
}
void filemanager_field_set(FileManagerTextField& f, const char* text, int cap, int pad,
int sel_start, int sel_end, mtk::TextInputCharFilter filter) {
f.cap = cap > (int)sizeof(f.buf) ? (int)sizeof(f.buf) : cap;
montauk::strncpy(f.buf, text ? text : "", f.cap);
f.pad = pad;
f.filter = filter;
f.scroll = 0;
f.clicks = 0;
f.last_click = 0;
int len = filemanager_field_len(f);
mtk::text_input_reset(f.in);
f.in.selection_anchor = mtk::text_input_clamp_cursor(sel_start, len);
f.in.cursor = mtk::text_input_clamp_cursor(sel_end, len);
}
void filemanager_field_select(FileManagerTextField& f, int anchor, int cursor) {
int len = filemanager_field_len(f);
f.in.selection_anchor = mtk::text_input_clamp_cursor(anchor, len);
f.in.cursor = mtk::text_input_clamp_cursor(cursor, len);
}
bool filemanager_field_key(FileManagerTextField& f, const montauk::abi::KeyEvent& key) {
int r = mtk::text_input_key(f.in, f.buf, f.cap, key, f.filter, nullptr);
return r != mtk::TEXT_INPUT_NONE;
}
// Byte index under window x, given where the text starts.
static int index_at(const FileManagerTextField& f, const Rect& box, int mx) {
// text_input_index_from_x measures from bounds.x + 8.
Rect origin = {box.x + f.pad - f.scroll - 8, box.y, box.w, box.h};
return mtk::text_input_index_from_x(origin, f.buf, mx);
}
// The word (or run of separators) around byte i.
static void word_at(const FileManagerTextField& f, int i, int* start, int* end) {
int len = filemanager_field_len(f);
if (i >= len && i > 0) i = len - 1;
bool word = len > 0 && mtk::text_input_word_char(f.buf[i]);
int s = i, e = i;
while (s > 0 && mtk::text_input_word_char(f.buf[s - 1]) == word) s--;
while (e < len && mtk::text_input_word_char(f.buf[e]) == word) e++;
*start = s;
*end = e;
}
bool filemanager_field_mouse(FileManagerTextField& f, const Rect& box, int mx, int my,
const MouseEvent& ev, int view_w, int view_h, bool shift) {
// Its Cut/Copy/Paste menu takes every event while open.
if (f.in.context.open) {
mtk::text_input_handle_mouse(f.in, box, f.buf, f.cap, mx, my, ev.buttons,
ev.prev_buttons, view_w, view_h, true, false,
f.filter, nullptr);
return true;
}
bool inside = box.contains(mx, my);
int len = filemanager_field_len(f);
if (ev.left_pressed() && inside) {
int i = index_at(f, box, mx);
uint64_t now = montauk::get_milliseconds();
f.clicks = (now - f.last_click < MULTI_CLICK_MS && f.clicks < 3) ? f.clicks + 1 : 1;
f.last_click = now;
if (f.clicks == 2) {
int s, e;
word_at(f, i, &s, &e);
filemanager_field_select(f, s, e);
} else if (f.clicks == 3) {
filemanager_field_select(f, 0, len);
} else {
mtk::text_input_set_cursor(f.in, len, i, shift);
f.in.dragging = true;
}
return true;
}
if (f.in.dragging) {
if (ev.left_held()) f.in.cursor = index_at(f, box, mx);
else f.in.dragging = false;
return true;
}
if (ev.right_pressed() && inside) {
// Right-click outside the selection moves the caret there first.
int i = index_at(f, box, mx);
int s = mtk::text_input_selection_start(f.in, len);
int e = mtk::text_input_selection_end(f.in, len);
if (s == e || i < s || i > e) mtk::text_input_set_cursor(f.in, len, i, false);
mtk::context_menu_open(f.in.context, mx, my);
return true;
}
return false;
}
static void text_clipped(Canvas& c, int x, int y, const char* s, Color color, const Rect& clip) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_to_buffer_clipped(c.pixels, c.w, c.h, x, y, s, color,
fonts::UI_SIZE, clip.x, clip.y,
clip.w, clip.h);
return;
}
c.text(x, y, s, color);
}
void filemanager_field_draw(Canvas& c, FileManagerTextField& f, const Rect& box, Color accent) {
int len = filemanager_field_len(f);
int fh = system_font_height();
Rect inner = {box.x + 1, box.y + 1, box.w - 2, box.h - 2};
int view_w = box.w - f.pad * 2;
if (view_w < 1) view_w = 1;
// Keep the caret in view, and no blank space after the text's end.
int caret = mtk::text_input_prefix_width(f.buf, f.in.cursor, false);
int total = text_width_n(f.buf, len);
if (caret - f.scroll > view_w - 1) f.scroll = caret - view_w + 1;
if (caret < f.scroll) f.scroll = caret;
if (f.scroll > total - view_w + 1) f.scroll = total - view_w + 1;
if (f.scroll < 0) f.scroll = 0;
c.fill_rect(box.x, box.y, box.w, box.h, colors::WHITE);
c.rect(box.x, box.y, box.w, box.h, accent);
int tx = box.x + f.pad - f.scroll;
int ty = box.y + (box.h - fh) / 2;
int s = mtk::text_input_selection_start(f.in, len);
int e = mtk::text_input_selection_end(f.in, len);
if (s != e) {
int sx = gui_max(tx + mtk::text_input_prefix_width(f.buf, s, false), inner.x);
int ex = gui_min(tx + mtk::text_input_prefix_width(f.buf, e, false), inner.x + inner.w);
if (ex > sx) c.fill_rect(sx, ty - 1, ex - sx, fh + 2, mtk::make_theme(accent).text_selection);
}
text_clipped(c, tx, ty, f.buf, colors::TEXT_COLOR, inner);
if (s == e) {
int cx = tx + caret;
if (cx >= inner.x && cx < inner.x + inner.w) c.vline(cx, ty - 1, fh + 2, colors::TEXT_COLOR);
}
}
void filemanager_field_draw_menu(Canvas& c, const FileManagerTextField& f, Color accent) {
if (!f.in.context.open) return;
mtk::Theme theme = mtk::make_theme(accent);
theme.border = colors::BORDER;
theme.text = colors::TEXT_COLOR;
int len = filemanager_field_len(f);
mtk::draw_text_input_context_menu(c, f.in, theme,
mtk::text_input_selection_start(f.in, len) !=
mtk::text_input_selection_end(f.in, len));
}
} // namespace filemanager