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:
1 parent
07191b1286
commit
d3e49a925d
66 files changed
+6855
-905
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user