/* * table.hpp * Montauk Toolkit table/list helpers * Copyright (c) 2026 Daniel Hammer */ #pragma once #include "gui/canvas.hpp" #include "gui/mtk/theme.hpp" namespace gui::mtk { struct TableColumn { const char* label; int x; int w; }; struct TableLayout { Rect header; Rect body; int row_h; }; struct TableStyle { Color header_bg; Color header_border; Color header_text; Color row_selected_bg; Color row_hover_bg; }; inline TableStyle make_table_style(const Theme& theme) { return { theme.surface_alt, theme.border, theme.text_subtle, theme.accent_soft, colors::TRANSPARENT, }; } inline int table_visible_rows(const TableLayout& layout) { if (layout.row_h <= 0 || layout.body.h <= 0) return 0; return layout.body.h / layout.row_h; } inline int table_max_scroll(const TableLayout& layout, int row_count) { int visible = table_visible_rows(layout); if (row_count > visible && visible > 0) return row_count - visible; return 0; } inline void clamp_table_scroll(int* scroll, const TableLayout& layout, int row_count) { if (!scroll) return; *scroll = gui_clamp(*scroll, 0, table_max_scroll(layout, row_count)); } inline void ensure_table_row_visible(int* scroll, const TableLayout& layout, int row_count, int selected) { if (!scroll || selected < 0) return; int visible = table_visible_rows(layout); if (visible <= 0) return; if (selected < *scroll) { *scroll = selected; } else if (selected >= *scroll + visible) { *scroll = selected - visible + 1; } clamp_table_scroll(scroll, layout, row_count); } inline Rect table_row_rect(const TableLayout& layout, int scroll, int row_index) { return { layout.body.x, layout.body.y + (row_index - scroll) * layout.row_h, layout.body.w, layout.row_h }; } inline Rect table_cell_rect(const Rect& row_rect, const TableColumn& col) { return {row_rect.x + col.x, row_rect.y, col.w, row_rect.h}; } inline int table_hit_row(const TableLayout& layout, int scroll, int row_count, int mx, int my) { if (!layout.body.contains(mx, my) || layout.row_h <= 0) return -1; int row = (my - layout.body.y) / layout.row_h; int index = scroll + row; if (index < 0 || index >= row_count) return -1; return index; } using TableRowRenderer = void (*)(Canvas& c, void* userdata, int row_index, const Rect& row_rect, int text_y, const TableColumn* cols, int col_count, const Theme& theme, const TableStyle& style); inline void draw_table(Canvas& c, const TableLayout& layout, const TableColumn* cols, int col_count, int row_count, int scroll, int selected, int hovered, const Theme& theme, const TableStyle& style, TableRowRenderer draw_row, void* userdata) { c.fill_rect(layout.header.x, layout.header.y, layout.header.w, layout.header.h, style.header_bg); c.hline(layout.header.x, layout.header.y + layout.header.h - 1, layout.header.w, style.header_border); int fh = system_font_height(); int header_text_y = layout.header.y + (layout.header.h - fh) / 2; for (int i = 0; i < col_count; i++) { c.text(layout.header.x + cols[i].x, header_text_y, cols[i].label, style.header_text); } int visible = table_visible_rows(layout); for (int row = 0; row < visible; row++) { int index = scroll + row; if (index >= row_count) break; Rect row_rect = table_row_rect(layout, scroll, index); if (index == selected) { c.fill_rect(row_rect.x, row_rect.y, row_rect.w, row_rect.h, style.row_selected_bg); } else if (index == hovered && style.row_hover_bg.a > 0) { c.fill_rect(row_rect.x, row_rect.y, row_rect.w, row_rect.h, style.row_hover_bg); } if (draw_row) { int text_y = row_rect.y + (layout.row_h - fh) / 2; draw_row(c, userdata, index, row_rect, text_y, cols, col_count, theme, style); } } } } // namespace gui::mtk