#include "muscles.h"
#include "ui.h"
void *allocate_ui_element(Arena& arena, int i_type) {
auto type = static_cast<Element_Type>(i_type);
// if there's a better way to do this i'm all ears
#define TRY_UI_TYPE(type) \
case Elem_##type:\
return arena.allocate_object<type>();
switch (type) {
TRY_UI_TYPE(Image)
TRY_UI_TYPE(Label)
TRY_UI_TYPE(Button)
TRY_UI_TYPE(Divider)
TRY_UI_TYPE(Data_View)
TRY_UI_TYPE(Scroll)
TRY_UI_TYPE(Edit_Box)
TRY_UI_TYPE(Drop_Down)
TRY_UI_TYPE(Checkbox)
TRY_UI_TYPE(Hex_View)
TRY_UI_TYPE(Text_Editor)
TRY_UI_TYPE(Tabs)
TRY_UI_TYPE(Number_Edit)
}
return nullptr;
}
static inline float clamp_f(float f, float min, float max) {
if (f < min)
return min;
if (f > max && max >= min)
return max;
return f;
}
Rect_Int make_int_ui_box(Rect_Int const& box, Rect const& elem, float scale) {
return {
.x = (int)(0.5 + (float)box.x + elem.x * scale),
.y = (int)(0.5 + (float)box.y + elem.y * scale),
.w = (int)(0.5 + elem.w * scale),
.h = (int)(0.5 + elem.h * scale)
};
}
Rect make_ui_box(Rect_Int const& box, Rect const& elem, float scale) {
return {
.x = (float)box.x + elem.x * scale,
.y = (float)box.y + elem.y * scale,
.w = elem.w * scale,
.h = elem.h * scale
};
}
void reposition_box_buttons(Image& cross, Image& maxm, float box_w, float size) {
cross.pos = {
box_w - size * 1.5f,
size * 0.5f,
size,
size
};
maxm.pos = {
cross.pos.x - size * 1.75f,
size * 0.5f,
size,
size
};
}
void delete_box(UI_Element *elem, Camera& view, bool dbl_click) {
elem->parent->parent->delete_box(elem->parent);
}
void (*get_delete_box(void))(UI_Element*, Camera&, bool) { return delete_box; }
void maximize_box(UI_Element *elem, Camera& view, bool dbl_click) {
Box *box = elem->parent;
float center_x = view.center_x;
float center_y = view.center_y;
center_x *= 0.9;
center_y *= 0.9;
box->box = {
view.x - center_x / view.scale,
view.y - center_y / view.scale,
2 * center_x / view.scale,
2 * center_y / view.scale,
};
box->update_ui(view);
}
void (*get_maximize_box(void))(UI_Element*, Camera&, bool) { return maximize_box; }
void UI_Element::draw(Camera& view, Rect_Int const& rect, bool elem_hovered, bool box_hovered, bool focussed) {
ticks++;
screen = make_int_ui_box(rect, pos, view.scale);
update(view, rect);
if (!use_sf_cache) {
Rect_Int back = screen;
draw_element(nullptr, view, back, elem_hovered, box_hovered, focussed);
return;
}
int w = 0.5 + pos.w * view.scale;
int h = 0.5 + pos.h * view.scale;
Rect_Int back = {0, 0, w, h};
bool draw_existing = false;
Renderer renderer = nullptr;
Renderer hw = sdl_get_hw_renderer();
bool soft_draw = false;
if (needs_redraw || w != old_width || h != old_height) {
reify_timer = 0;
needs_redraw = false;
old_width = w;
old_height = h;
if (tex_cache)
sdl_destroy_texture(&tex_cache);
renderer = hw;
}
else {
reify_timer++;
if (reify_timer < hw_draw_timeout) {
renderer = hw;
}
else if (!tex_cache) {
renderer = sdl_acquire_sw_renderer(w, h);
soft_draw = renderer != nullptr;
}
else {
// this means that we don't need to render a new frame and can simply use the existing surface instead
draw_existing = true;
}
}
if (!draw_existing) {
if (!soft_draw) {
back = screen;
}
draw_element(renderer, view, back, elem_hovered, box_hovered, focussed);
if (soft_draw) {
if (tex_cache)
sdl_destroy_texture(&tex_cache);
tex_cache = sdl_bake_sw_render();
draw_existing = true;
}
if (use_sf_cache)
needs_redraw = false;
}
if (draw_existing)
sdl_apply_texture(tex_cache, screen, nullptr, nullptr);
}
void UI_Element::set_active(bool state) {
needs_redraw = active != state;
active = state;
}
void Button::update_size(float scale) {
Theme *theme = active ? &active_theme : &inactive_theme;
float font_unit = theme->font->render.text_height() / scale;
width = theme->font->render.text_width(text.c_str()) / scale;
if (icon)
width += font_unit * 1.2f;
float hack = 0.15;
width += (hack + 2 * x_offset) * font_unit;
height = 1.2f * font_unit;
}
int Button::get_icon_length(float scale) {
Theme *theme = active ? &active_theme : &inactive_theme;
if (theme->font)
return (int)theme->font->render.text_height();
int w = pos.w * scale + 0.5;
w -= 2 * padding * scale;
return w;
}
void Button::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
bool was_held = held;
held = input.lmouse && pos.contains(cursor);
needs_redraw = was_held != held;
}
void Button::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
float pad = padding * view.scale;
back.x += pad;
back.y += pad;
back.w -= pad * 2;
back.h -= pad * 2;
Theme *theme = active ? &active_theme : &inactive_theme;
RGBA *color = &theme->back;
if (held)
color = &theme->held;
else if (elem_hovered)
color = &theme->hover;
sdl_draw_rect(back, *color, renderer);
int icon_length = get_icon_length(view.scale);
if (!theme->font) {
back.w = icon_length;
back.h = icon_length;
if (icon)
sdl_apply_texture(icon, back, nullptr, renderer);
return;
}
float font_height = theme->font->render.text_height();
float x = x_offset * font_height;
float y = y_offset * font_height;
if (icon) {
Rect box = {(float)back.x, (float)back.y, (float)icon_length, (float)icon_length};
if (icon_right)
box.x += (float)back.w - x - font_height;
else {
box.x += x;
x += font_height * 1.2f;
}
sdl_apply_texture(icon, box, nullptr, renderer);
}
theme->font->render.draw_text_simple(renderer, text.c_str(), back.x + x, back.y + y);
}
void Checkbox::toggle() {
checked = !checked;
needs_redraw = true;
}
void Checkbox::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
if (elem_hovered)
sdl_draw_rect(back, hl_color, renderer);
float font_height = font->render.text_height();
float gap_x = text_off_x * font_height;
float gap_y = text_off_y * font_height;
float text_x = back.x + gap_x;
float text_y = back.y + gap_y;
if (leaning < 0)
text_x += back.h;
font->render.draw_text_simple(renderer, text.c_str(), text_x, text_y);
float frac = clamp_f((leaning + 1) / 2, 0, 1);
float cb_x = frac * (back.w - back.h);
if (cb_x < 0) cb_x = 0;
back.x += cb_x;
back.w = back.h;
sdl_draw_rect(back, default_color, renderer);
if (checked) {
int w = 0.5 + (float)back.w * border_frac;
int h = 0.5 + (float)back.h * border_frac;
Rect_Int check = {
back.x + w,
back.y + h,
back.w - 2*w,
back.h - 2*h
};
sdl_draw_rect(check, sel_color, renderer);
}
}
void Checkbox::base_action(Camera& view, Point& cursor, Input& input) {
checked = !checked;
}
void Data_View::highlight(Camera& view, Point& inside, Input& input) {
if (!font || !data) {
hl_row = hl_col = -1;
return;
}
int top = vscroll ? vscroll->position : 0;
Rect table = pos;
if (show_column_names) {
table.y += header_height;
table.h -= header_height;
}
float font_height = (float)font->render.text_height();
float font_units = font_height / view.scale;
int n_rows = data->tree.size() > 0 ? data->tree.size() : data->row_count();
n_rows = data->filtered >= 0 ? data->filtered : n_rows;
int n_cols = data->column_count();
float space = font->line_spacing * font_units;
float y = 0;
float item_w = table.w - 4;
int old_hl_row = hl_row;
int old_hl_col = hl_col;
hl_row = hl_col = -1;
bool hl = false;
for (int i = top; i < n_rows && y < table.h; i++) {
float h = y > table.h - item_height ? table.h - y : item_height;
if (inside.x >= table.x && inside.x < table.x + table.w && inside.y >= table.y + y && inside.y < table.y + y + h) {
hl_row = i;
float x = table.x;
for (int j = 0; j < n_cols; j++) {
float w = column_width(pos.w, scroll_total, font_height, view.scale, j) / view.scale;
if (inside.x >= x && inside.x < x + w) {
hl_col = j;
/*
if (data->headers[j].type == ColumnElement) {
int row = data->get_table_index(hl_row);
UI_Element *elem = row >= 0 ? (UI_Element*)data->columns[j][row] : nullptr;
if (elem) {
Point yes = {0};
elem->highlight(view, yes); // just hope that elem isn't another Data_View lol
}
}
*/
break;
}
x += w + column_spacing * font_units;
}
hl = true;
break;
}
y += item_height;
}
needs_redraw = needs_redraw || hl_row != old_hl_row || hl_col != old_hl_col;
}
float Data_View::column_width(float total_width, float min_width, float font_height, float scale, int idx) {
float w = data->headers[idx].width;
if (w == 0)
return (total_width - min_width) * scale;
w *= (total_width - total_spacing) * scale;
if (data->headers[idx].max_size > 0) {
float max = data->headers[idx].max_size * font_height;
w = w < max ? w : max;
}
if (data->headers[idx].min_size > 0) {
float min = data->headers[idx].min_size * font_height;
w = w > min ? w : min;
}
return w;
}
void Data_View::draw_item_backing(Renderer renderer, RGBA& color, Rect_Int& back, float scale, int idx) {
int top = vscroll ? vscroll->position : 0;
if (idx < 0 || idx < top)
return;
float y = (idx - top) * item_height * scale;
float h = item_height * scale;
if (y + h > back.h)
h = back.h - y;
if (h > 0) {
Rect_Int r = {
back.x,
(int)((float)back.y + y),
back.w,
(int)h
};
sdl_draw_rect(r, color, renderer);
}
}
void Data_View::draw_cell(Draw_Cell_Info& info, void *cell, Column& header, Rect& r) {
auto type = header.type;
if (type == ColumnString || type == ColumnDec || type == ColumnHex || type == ColumnFile || type == ColumnStdString) {
char *str = nullptr;
char buf[24];
if (type == ColumnString)
str = (char*)cell;
else if (type == ColumnDec) {
write_dec(buf, (s64)cell);
str = buf;
}
else if (type == ColumnHex) {
write_hex(buf, (u64)cell, header.count_per_cell);
str = buf;
}
else if (type == ColumnFile && cell)
str = ((File_Entry*)cell)->name;
else if (type == ColumnStdString && cell)
str = (char*)((std::string*)cell)->c_str();
if (str)
font->render.draw_text(info.renderer, (const char*)str, r.x, r.y - info.line_off, clip);
}
else if (type == ColumnImage && cell) {
sdl_get_texture_size(cell, &info.src.w, &info.src.h);
info.dst.x = r.x;
info.dst.y = r.y + info.sp_half;
if (info.dst.y + info.dst.h > info.y_max) {
int img_h = info.y_max - info.dst.y;
info.src.h = (float)(info.src.h * img_h) / (float)info.dst.h;
info.dst.h = img_h;
}
sdl_apply_texture(cell, info.dst, &info.src, info.renderer);
}
else if (type == ColumnCheckbox) {
float indent = info.font_height * 0.1;
info.dst.x = r.x + 0.5 + indent;
info.dst.y = r.y + info.sp_half + 0.5 + indent;
info.dst.w = 0.5 + info.font_height - 2*indent;
info.dst.h = info.dst.w;
if (info.dst.y + info.dst.h > info.y_max)
info.dst.h = info.y_max - info.dst.y;
sdl_draw_rect(info.dst, parent->parent->colors.table_cb_back, info.renderer);
if (*(u8*)&cell == 2) {
indent = info.font_height * 0.15;
info.src.x = info.dst.x + 0.5 + indent;
info.src.y = info.dst.y + 0.5 + indent;
info.src.w = 0.5 + info.dst.w - 2*indent;
info.src.h = info.src.w;
if (info.src.y + info.src.h > info.y_max)
info.src.h = info.y_max - info.src.y;
sdl_draw_rect(info.src, parent->parent->colors.cb, info.renderer);
info.src.x = info.src.y = 0;
}
info.dst.w = info.dst.h = info.font_height;
}
else if (type == ColumnElement && cell) {
auto elem = (UI_Element*)cell;
elem->font = font;
elem->parent = parent;
elem->table_vis = true;
if (elem->visible) {
Rect_Int back = r.to_rect_int();
back.y += info.elem_pad;
back.h -= 2 * info.elem_pad;
float scale = info.camera->scale;
elem->pos = {0, 0, back.w / scale, back.h / scale};
elem->screen = {
.x = screen.x + back.x - x_origin,
.y = screen.y + back.y - y_origin,
.w = back.w,
.h = back.h
};
elem->update(*info.camera, back);
elem->draw_element(info.renderer, *info.camera, back, false, false, false);
}
}
}
void Data_View::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
x_origin = back.x;
y_origin = back.y;
if (!data) {
sdl_draw_rect(back, default_color, renderer);
return;
}
int n_rows = data->row_count();
int n_cols = data->column_count();
int tree_size = data->tree.size();
float font_height = (float)font->render.text_height();
float font_units = font_height / view.scale;
item_height = (font_height + (font->line_spacing * font_height)) / view.scale;
Rect table = pos;
if (show_column_names) {
table.y += header_height;
table.h -= header_height;
int y = back.y;
back.y += header_height * view.scale + 0.5;
back.h -= back.y - y;
}
int top = 0;
int n_visible = table.h / item_height;
int n_items = data->filtered;
if (n_items < 0)
n_items = tree_size > 0 ? tree_size : n_rows;
if (vscroll) {
top = vscroll->position;
vscroll->set_maximum(n_items, n_visible);
}
float scroll_x = 0;
float table_w = pos.w;
scroll_total = column_spacing;
total_spacing = column_spacing;
for (int i = 0; i < n_cols; i++) {
scroll_total += data->headers[i].min_size + column_spacing;
total_spacing += column_spacing;
}
scroll_total *= font_units;
total_spacing *= font_units;
if (hscroll) {
hscroll->set_maximum(scroll_total, pos.w);
scroll_x = scroll_total - pos.w;
if (scroll_x < 0) scroll_x = 0;
scroll_x = hscroll->position - scroll_x;
table_w = hscroll->maximum;
}
scroll_x *= view.scale;
float col_space = column_spacing * font_height;
float x_start = back.x + col_space - scroll_x;
if (show_column_names) {
float scaled_head_h = header_height * view.scale;
Rect header = {
(float)back.x,
(float)back.y - scaled_head_h,
(float)back.w,
scaled_head_h
};
sdl_draw_rect(header, back_color, renderer);
float x = x_start;
float y = back.y - (header.h + font->line_offset * font_height);
for (int i = 0; i < n_cols; i++) {
font->render.draw_text_simple(renderer, data->headers[i].name, x, y);
float w = column_width(pos.w, scroll_total, font_height, view.scale, i);
x += w + column_spacing * font_height;
}
}
sdl_draw_rect(back, default_color, renderer);
draw_item_backing(renderer, sel_color, back, view.scale, sel_row);
if (hl_row != sel_row && (box_hovered || focussed))
draw_item_backing(renderer, hl_color, back, view.scale, hl_row);
float pad = padding * view.scale;
float x = x_start;
float y = back.y;
float x_max = back.x + back.w - col_space;
float y_max = y + table.h * view.scale;
float line_off = font->line_offset * font_height;
float line_sp = (font->line_spacing + extra_line_spacing) * font_height;
float line_h = font_height + line_sp;
Rect_Int dst = {0, 0, (int)font_height, (int)font_height};
Rect_Int src = {0};
clip.x_lower = x + scroll_x;
clip.y_upper = y_max;
int elem_pad = view.scale + 1.0;
Draw_Cell_Info dci = {
this, &view, renderer, dst, src, y_max, line_off, line_sp / 2, font_height, elem_pad
};
float table_off_y = y;
for (int i = 0; i < n_cols && x < back.x + back.w; i++) {
float w = column_width(pos.w, scroll_total, font_height, view.scale, i);
clip.x_upper = x+w < x_max ? x+w : x_max;
Rect cell_rect = {x, y, w, line_h};
if (data->headers[i].type == ColumnElement) {
for (auto& cell : data->columns[i]) {
auto elem = (UI_Element*)cell;
if (elem)
elem->table_vis = false;
}
}
if (data->filtered >= 0) {
int n = -1;
for (auto& idx : data->list) {
if (y >= y_max)
break;
n++;
if (n < top)
continue;
bool skip_draw = false;
if (condition_col >= 0) {
if (!data->checkbox_checked(condition_col, idx))
continue;
skip_draw = condition_col == i;
}
auto thing = data->columns[i][idx];
if (!skip_draw)
draw_cell(dci, thing, data->headers[i], cell_rect);
y += line_h;
cell_rect.y = y;
}
}
else {
for (int j = top; y < y_max; j++) {
int idx = j;
if (tree_size) {
if (j >= tree_size)
break;
idx = data->tree[j];
}
else if (j >= n_rows)
break;
if (idx < 0 && i == 0) {
int branch = -idx - 1;
Texture icon = data->branches[branch].closed ? icon_plus : icon_minus;
Column temp;
temp.type = ColumnImage;
cell_rect.y += font_height * 0.025;
draw_cell(dci, icon, temp, cell_rect);
int name_idx = data->branches[branch].name_idx;
if (name_idx >= 0)
font->render.draw_text(renderer, data->branch_name_vector.at(name_idx), x + font_height * 1.2, y - line_off, clip);
}
else if (idx >= 0) {
bool skip_draw = false;
if (condition_col >= 0) {
if (!data->checkbox_checked(condition_col, idx))
continue;
skip_draw = condition_col == i;
}
auto thing = data->columns[i][idx];
if (!skip_draw)
draw_cell(dci, thing, data->headers[i], cell_rect);
}
y += line_h;
cell_rect.y = y;
}
}
if (data->headers[i].type == ColumnElement) {
for (auto& cell : data->columns[i]) {
auto elem = (UI_Element*)cell;
if (elem && elem->table_vis && elem->visible && elem->use_post_draw) {
Rect_Int r = view.to_screen_rect(parent->box);
Rect_Int b = make_int_ui_box(r, elem->pos, view.scale);
elem->post_draw(view, b, false, false, false);
}
}
}
x += w + column_spacing * font_height;
y = table_off_y;
}
}
void Data_View::base_action(Camera& view, Point& cursor, Input& input) {
if (hl_row >= 0)
sel_row = hl_row;
if (active_elem) {
active_elem->base_action(view, cursor, input);
if (active_elem->action)
active_elem->action(active_elem, view, input.double_click);
active_elem = nullptr;
}
}
void Data_View::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
if (!data)
return;
if (data->has_ui_elements) {
Rect_Int box = view.to_screen_rect(parent->box);
int n_cols = data->column_count();
for (int i = 0; i < n_cols; i++) {
if (data->headers[i].type != ColumnElement)
continue;
for (int j = 0; j < data->columns[i].size(); j++) {
auto& cell = data->columns[i][j];
if (!cell)
continue;
auto elem = (UI_Element*)cell;
Point inside = {
cursor.x - (elem->screen.x - box.x) / view.scale,
cursor.y - (elem->screen.y - box.y) / view.scale
};
elem->mouse_handler(view, input, inside, hovered);
elem->key_handler(view, input);
if (input.lclick && elem->pos.contains(inside)) {
active_elem = elem;
active_col = i;
active_row = j;
}
}
needs_redraw = true;
}
}
if (!hovered || (!input.lclick && !input.rclick))
return;
if (input.lclick) {
int row = data->get_table_index(hl_row);
if (hl_col >= 0 && row >= 0 && data->headers[hl_col].type == ColumnCheckbox) {
data->toggle_checkbox(hl_col, row);
if (checkbox_toggle_handler)
checkbox_toggle_handler(this, hl_col, row);
}
else if (hl_row >= 0 && row < 0 && data->tree.size() > 0) {
int b = -row - 1;
data->branches[b].closed = !data->branches[b].closed;
data->update_tree();
}
}
if (input.rclick && hl_row >= 0)
sel_row = hl_row;
needs_redraw = true;
}
bool Data_View::scroll_handler(Camera& view, Input& input, Point& inside) {
bool scrolled = false;
if (data && data->has_ui_elements) {
Rect_Int box = view.to_screen_rect(parent->box);
int n_cols = data->column_count();
for (int i = 0; i < n_cols; i++) {
if (data->headers[i].type != ColumnElement)
continue;
for (auto& cell : data->columns[i]) {
if (!cell)
continue;
auto elem = (UI_Element*)cell;
Point inside_elem = {
inside.x - (elem->screen.x - screen.x) / view.scale,
inside.y - (elem->screen.y - screen.y) / view.scale
};
if (elem->pos.contains(inside_elem)) {
scrolled = elem->scroll_handler(view, input, inside_elem);
if (scrolled) {
needs_redraw = true;
return true;
}
}
}
}
}
Scroll *scroll = input.lshift || input.rshift ? hscroll : vscroll;
if (scroll && input.scroll_y > 0) {
scroll->scroll(-1);
scrolled = true;
}
if (scroll && input.scroll_y < 0) {
scroll->scroll(1);
scrolled = true;
}
if (scrolled)
needs_redraw = true;
return scrolled;
}
void Data_View::deselect() {
hl_row = hl_col = -1;
}
void Data_View::release() {
data->release();
}
void Divider::make_icon(float scale) {
bool was_hl = icon == icon_hl;
sdl_destroy_texture(&icon_default);
sdl_destroy_texture(&icon_hl);
RGBA fade_color = default_color;
fade_color.a = 0.5;
float pad = padding * 1.5;
float h = pad * scale;
float w = h * 1.375;
icon_w = w + 0.5;
icon_h = h + 0.5;
double gap = breadth * 2 / pad;
double thickness = 0.15;
double sharpness = 2.0;
icon_default = make_divider_icon(fade_color, icon_w, icon_h, gap, thickness, sharpness, !vertical);
icon_hl = make_divider_icon(default_color, icon_w, icon_h, gap, thickness, sharpness, !vertical);
icon = was_hl ? icon_hl : icon_default;
}
void Divider::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
if (!moveable)
return;
Rect box = pos;
if (vertical) {
box.x -= padding;
box.w += 2 * padding;
}
else {
box.y -= padding;
box.h += 2 * padding;
}
bool use_default = !held && !box.contains(cursor);
Texture old_icon = icon;
icon = use_default ? icon_default : icon_hl;
needs_redraw = icon != old_icon;
if (use_default)
return;
sdl_set_cursor(cursor_type);
parent->parent->cursor_set = true;
if (!input.lmouse) {
held = false;
return;
}
if (input.lclick) {
hold_pos = vertical ? cursor.x - pos.x : cursor.y - pos.y;
held = true;
parent->ui_held = true;
}
if (held) {
float cur = vertical ? cursor.x : cursor.y;
position = clamp_f(cur - hold_pos, minimum, maximum);
}
needs_redraw = true;
}
void Divider::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
sdl_draw_rect(back, default_color, renderer);
if (icon) {
Rect_Int box = {
back.x + (back.w - icon_w) / 2,
back.y + (back.h - icon_h) / 2,
icon_w,
icon_h
};
sdl_apply_texture(icon, box, nullptr, renderer);
}
}
void Drop_Down::update(Camera& view, Rect_Int const& rect) {
auto lines = get_content();
float font_h = font->render.text_height();
float menu_h = (title_off_y + line_height * lines->size()) * font_h;
if (menu_h < 4)
menu_h = 0;
menu_rect = {
.x = (float)screen.x,
.y = (float)(screen.y + screen.h),
.w = width * view.scale,
.h = menu_h
};
}
void Drop_Down::mouse_handler(Camera& view, Input& input, Point& inside, bool hovered) {
if (edit_elem)
return;
if (hovered) {
if (pos.contains(inside)) {
if (input.lclick) {
if (this == parent->current_dd)
parent->set_dropdown(nullptr);
else if (!parent->current_dd)
parent->set_dropdown(this);
}
else if (parent->current_dd)
parent->set_dropdown(this);
}
else if (hl >= 0)
parent->set_dropdown(this);
}
}
void Drop_Down::highlight(Camera& view, Point& inside, Input& input) {
hl = -1;
if (!dropped || input.mouse_x < menu_rect.x || input.mouse_x >= menu_rect.x + menu_rect.w)
return;
float font_h = font->render.text_height();
float y = menu_rect.y;
float h = line_height * font_h;
auto lines = get_content();
for (int i = 0; i < lines->size(); i++) {
if (input.mouse_y >= y && input.mouse_y < y + h) {
hl = i;
break;
}
y += h;
}
}
void Drop_Down::cancel() {
dropped = false;
hl = -1;
}
void Drop_Down::base_action(Camera& view, Point& cursor, Input& input) {
if (hl >= 0) {
if (keep_selected)
sel = hl;
if (edit_elem) {
auto edit = dynamic_cast<Edit_Box*>(edit_elem);
if (edit)
edit->dropdown_item_selected(input);
}
}
}
void Drop_Down::draw_menu(Renderer renderer, Camera& view) {
float font_height = font->render.text_height();
auto lines = get_content();
sdl_draw_rect(menu_rect, hl_color, renderer);
if (hl >= 0) {
Rect hl_rect = menu_rect;
hl_rect.y += line_height * hl * font_height;
hl_rect.h = (title_off_y + line_height) * font_height + 1;
sdl_draw_rect(hl_rect, sel_color, renderer);
}
float hack = 3;
float item_x = item_off_x * font_height;
float item_w = (width - hack) * view.scale - item_x;
item_clip.x_upper = menu_rect.x + item_x + item_w;
float y = font_height * ((line_height - 1) / 2 - font->line_offset);
for (auto& l : *lines) {
if (l)
font->render.draw_text(renderer, l, menu_rect.x + item_x, menu_rect.y + y, item_clip);
y += font_height * line_height;
}
}
void Drop_Down::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
if (edit_elem)
return;
if (elem_hovered || dropped)
sdl_draw_rect(back, hl_color, renderer);
else
sdl_draw_rect(back, default_color, renderer);
auto lines = get_content();
float font_h = font->render.text_height();
const char *text = title;
if (!title && lines && lines->size() > 0 && sel >= 0 && (*lines)[sel])
text = (*lines)[sel];
float x = 0;
float w = pos.w * view.scale;
if (icon) {
Rect_Int r = { back.x, back.y, icon_length, icon_length };
w -= icon_length;
if (!icon_right)
x = icon_length;
else
r.x += back.w - icon_length;
sdl_apply_texture(icon, r, nullptr, renderer);
}
if (text) {
float text_w = font->render.text_width(text);
float pad_x = title_pad_x * font_h;
float gap_y = title_off_y * font_h;
float text_x = x + pad_x + leaning * (w - text_w - 2*pad_x);
font->render.draw_text_simple(renderer, text, back.x + text_x, back.y + gap_y);
}
}
bool Edit_Box::is_above_icon(Point& p) {
const float w = pos.h;
float icon_x = icon_right ? pos.x + pos.w - w : pos.x;
return icon && p.y >= pos.y && p.y < pos.y + pos.h && p.x >= icon_x && p.x - icon_x < w;
}
void Edit_Box::dropdown_item_selected(Input& input) {
editor.set_cursor(editor.primary, 0);
editor.set_cursor(editor.secondary, 0);
editor.text = (*dropdown->get_content())[dropdown->hl];
if (key_action)
key_action(this, input);
}
void Edit_Box::key_handler(Camera& view, Input& input) {
text_changed = false;
if (parent->active_edit != &editor)
return;
int res = editor.handle_input(input);
editor.apply_scroll();
if ((res & 1) && key_action) {
text_changed = true;
key_action(this, input);
}
if (res & 2)
needs_redraw = true;
if (res & 8) {
editor.clear();
//disengage(input, false);
parent->active_edit = nullptr;
}
}
void Edit_Box::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
if (!hovered)
return;
const float w = pos.h;
float text_x = icon_right ? pos.x : pos.x + w;
bool on_icon = is_above_icon(cursor);
use_default_cursor = on_icon;
if (hovered && input.lclick && pos.contains(cursor)) {
float x = (cursor.x - text_x) * view.scale;
float y = (cursor.y - pos.y) * view.scale;
editor.update_cursor(x, y, font, view.scale, ticks, input.lclick);
}
}
void Edit_Box::base_action(Camera& view, Point& cursor, Input& input) {
if (dropdown && is_above_icon(cursor))
parent->set_dropdown(dropdown);
else
parent->active_edit = &editor;
}
void Edit_Box::update_icon(IconType type, float height, float scale) {
pos.h = height;
icon_length = 0.5 + height * scale - 2 * text_off_y * font->render.text_height();
switch (type) {
case IconGoto:
icon = make_goto_icon(icon_color, icon_length);
break;
case IconGlass:
icon = make_glass_icon(icon_color, icon_length);
break;
case IconTriangle:
icon = make_triangle(icon_color, icon_length, icon_length);
break;
}
needs_redraw = true;
}
void Edit_Box::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
if (dropdown) {
dropdown->pos = pos;
dropdown->screen = screen;
dropdown->width = pos.w;
}
sdl_draw_rect(back, default_color, renderer);
const char *str = nullptr;
Font *fnt = font;
if (ph_font && editor.text.size() == 0) {
fnt = ph_font;
str = placeholder.c_str();
}
else
str = editor.text.c_str();
float cur_x;
fnt->render.text_width(str, editor.primary.cursor, &cur_x);
float height = fnt->render.text_height();
float gap_x = text_off_x * height;
float gap_y = text_off_y * height;
float x = gap_x;
float icon_w = 0;
if (icon) {
Rect dst = {back.x + gap_y, back.y + gap_y, (float)icon_length, (float)icon_length};
if (icon_right) {
icon_w = dst.w;
dst.x = back.x + back.w - gap_y - icon_w;
}
else {
x += icon_length;
}
sdl_apply_texture(icon, dst, nullptr, renderer);
}
float window_w = (pos.w * view.scale) - x - icon_w - gap_x;
clip.x_lower = back.x + x;
clip.x_upper = clip.x_lower + window_w;
clip.y_lower = back.y;
clip.y_upper = back.y + back.h;
editor.refresh(clip, font);
float offset = editor.x_offset;
editor.draw_selection_box(renderer, sel_color);
fnt->render.draw_text(renderer, str, back.x + x - offset, back.y + gap_y, clip);
if (parent->active_edit == &editor) {
float caret_w = (float)caret_width * height;
float caret_y = (float)caret_off_y * height;
Rect_Int r = {
(int)((float)back.x + x + cur_x - offset),
(int)((float)back.y + 2.0f*gap_y + caret_y),
(int)caret_w,
(int)height
};
sdl_draw_rect(r, caret, renderer);
}
}
void Hex_View::set_region(u64 address, u64 size) {
region_address = address;
region_size = size;
col_offset = 0;
offset = 0;
sel = -1;
alive = true;
if (scroll)
scroll->maximum = (double)size;
}
void Hex_View::set_offset(u64 off) {
if (scroll)
scroll->position = (double)off;
col_offset = off % columns;
offset = off - col_offset;
}
void Hex_View::update_view(float scale) {
float font_height = font->render.text_height();
vis_rows = pos.h * scale / font_height;
if (scroll) {
u64 size = region_size;
if (size % columns > 0)
size += columns - (size % columns);
scroll->set_maximum(size, vis_rows * columns);
offset = (int)scroll->position;
offset -= offset % columns;
offset += col_offset;
}
addr_digits = count_hex_digits(region_address + region_size - 1);
if (alive) {
if (span_idx < 0)
span_idx = source->request_span();
Span& s = source->spans[span_idx];
s.address = region_address + offset;
s.size = vis_rows * columns;
}
}
float Hex_View::print_address(Renderer renderer, u64 address, float x, float y, float digit_w, Render_Clip& clip, Rect_Int& box, float pad) {
int n_digits = count_hex_digits(address);
float text_x = x + (addr_digits - n_digits) * digit_w;
char buf[20];
write_hex(buf, address, n_digits);
font->render.draw_text(renderer, buf, box.x + text_x, box.y + y, clip);
return x + addr_digits * digit_w + 2*pad;
}
float Hex_View::print_hex_row(Renderer renderer, Span& span, int idx, float x, float y, Rect_Int& box, float pad) {
Rect_Int src, dst;
int n_chars = columns * 2;
char *hex_row = &hex_vec.pool[idx * 2];
for (int i = 0; i < n_chars && x < box.w; i++) {
const Glyph *gl = &font->render.glyphs[hex_row[i]];
src.x = gl->atlas_x;
src.y = gl->atlas_y;
src.w = gl->img_w;
src.h = gl->img_h;
/*
float clip_x = x + pad + gl->left;
if (clip_x + src.w > box.w)
src.w = box.w - clip_x;
*/
dst.x = box.x + x + gl->left;
dst.y = box.y + y - gl->top;
dst.w = src.w;
dst.h = src.h;
sdl_apply_texture(font->render.tex, dst, &src, renderer);
x += gl->box_w;
x += pad * (i % 2 == 1);
}
return x + pad;
}
void Hex_View::print_ascii_row(Renderer renderer, Span& span, int idx, float x, float y, Render_Clip& clip, Rect_Int& box) {
char *ascii = (char*)alloca(columns + 1);
for (int i = 0; i < columns; i++) {
char c = '?';
if (idx+i < span.retrieved) {
c = (char)span.data[idx+i];
if (c < ' ' || c > '~')
c = '.';
}
ascii[i] = c;
}
ascii[columns] = 0;
font->render.draw_text(renderer, ascii, box.x + x, box.y + y, clip);
}
void Hex_View::draw_cursors(Renderer renderer, int idx, Rect_Int& back, float x_start, float pad, float scale) {
int row = idx / columns;
int col = idx % columns;
float cur_w = cursor_width * scale;
float digit_w = font->render.digit_width();
float byte_w = 2 * digit_w + pad;
float hex_w = (float)columns * byte_w;
float x = back.x + x_start;
float hex_x = x + (float)col * byte_w;
float ascii_x = x + (float)col * digit_w;
if (show_hex)
ascii_x += hex_w + pad;
float x_max = back.x + back.w - cur_w;
float y = back.y + (float)row * row_height + (pad / 2);
if (show_hex && hex_x < x_max) {
Rect cursor = {
hex_x,
y,
cur_w,
row_height
};
sdl_draw_rect(cursor, caret, renderer);
}
if (show_ascii && ascii_x < x_max) {
Rect cursor = {
ascii_x,
y,
cur_w,
row_height
};
sdl_draw_rect(cursor, caret, renderer);
}
}
void Hex_View::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
if (!alive || span_idx < 0 || vis_rows <= 0 || columns <= 0) {
sdl_draw_rect(back, default_color, renderer);
return;
}
Rect_Int src, dst;
font_height = font->render.text_height();
float x_start = x_offset * view.scale;
float x = x_start;
float y = font_height;
float pad = padding * font_height;
float digit_w = font->render.digit_width();
int addr_w = addr_digits * digit_w + 2*pad + 0.5;
Rect_Int hex_back = back;
if (show_addrs) {
Rect_Int addr_back = back;
addr_back.w = addr_w;
sdl_draw_rect(addr_back, back_color, renderer);
hex_back.x += addr_w;
hex_back.w -= addr_w;
}
sdl_draw_rect(hex_back, default_color, renderer);
Render_Clip addr_clip = {
CLIP_RIGHT, 0, 0, back.x + addr_w - pad, 0
};
Render_Clip ascii_clip = {
CLIP_RIGHT, 0, 0, back.x + back.w - pad, 0
};
row_height = vis_rows > 0 ? (back.h - pad) / (float)vis_rows : font_height;
auto& span = source->spans[span_idx];
int left = span.size;
int n_chars = span.size * 2;
int end = span.retrieved * 2;
hex_vec.try_expand(n_chars);
for (int i = 0; i < end; i++) {
int digit = span.data[i/2];
digit >>= 4 * (i % 2 == 0);
digit &= 0xf;
hex_vec.pool[i] = (digit <= 9 ? '0' + digit : 'a' - 10 + digit) - MIN_CHAR;
}
if (n_chars > end)
memset(hex_vec.pool + end, '?' - MIN_CHAR, n_chars - end);
while (left > 0) {
int idx = span.size - left;
if (show_addrs)
x = print_address(renderer, region_address + offset + idx, x, y - font_height, digit_w, addr_clip, back, pad);
if (show_hex && x < back.w)
x = print_hex_row(renderer, span, idx, x, y, back, pad);
if (show_ascii && x < back.w)
print_ascii_row(renderer, span, idx, x, y - font_height, ascii_clip, back);
x = x_start;
y += row_height;
left -= columns;
}
if (sel >= 0 && sel >= offset && sel < offset + span.size)
draw_cursors(renderer, sel - offset, hex_back, x_start, pad, view.scale);
}
void Hex_View::key_handler(Camera& view, Input& input) {
bool pg_down = input.strike(input.pgdown);
bool pg_up = input.strike(input.pgup);
if ((pg_up || pg_down) && scroll) {
int delta = pg_up ? -1 : 1;
delta *= columns * vis_rows;
scroll->scroll(delta);
needs_redraw = true;
}
}
bool Hex_View::scroll_handler(Camera& view, Input& input, Point& inside) {
if (!scroll)
return false;
int delta = columns;
if (input.lshift || input.rshift)
delta *= vis_rows;
bool scrolled = false;
if (input.scroll_y > 0) {
scroll->scroll(-delta);
scrolled = true;
}
if (input.scroll_y < 0) {
scroll->scroll(delta);
scrolled = true;
}
if (scrolled)
needs_redraw = true;
return scrolled;
}
void Hex_View::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
if (!hovered || !input.lclick || !pos.contains(cursor))
return;
sel = -1;
float font_h = font_height / view.scale;
float pad = padding * font_h;
float row_height = vis_rows > 0 ? (pos.h - pad) / (float)vis_rows : font_h;
float digit_w = font->render.digit_width() / view.scale;
float byte_w = 2*digit_w + pad;
float hex_w = (float)columns * byte_w;
float x_start = x_offset;
if (show_addrs)
x_start += addr_digits * digit_w + 2*pad;
float x = cursor.x - pos.x - x_start + (pad / 2);
float y = cursor.y - pos.y - (pad / 2);
if (x >= 0 && x < hex_w) {
int row = y / row_height;
int col = x / byte_w;
sel = offset + (row * columns) + col;
}
needs_redraw = true;
}
void Image::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
sdl_apply_texture(img, back, nullptr, renderer);
}
void Label::update(Camera& view, Rect_Int const& rect) {
if (outer_box.w <= 0)
return;
float text_w = font->render.text_width(text.c_str()) / view.scale;
float font_h = font->render.text_height() / view.scale;
pos.x = outer_box.x + (outer_box.w - text_w) / 2;
pos.y = outer_box.y + (outer_box.h - font_h) / 2;
pos.w = text_w;
pos.h = font_h;
}
void Label::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
clip.x_upper = back.x + back.w;
font->render.draw_text(renderer, text.c_str(), back.x, back.y, clip);
}
void Number_Edit::make_icon(float scale) {
sdl_destroy_texture(&icon);
float w = font->render.text_height() * end_width * box_width;
float h = pos.h * scale * box_height;
icon_w = w;
icon_h = h;
double gap = 0.15;
double thickness = 0.15;
double sharpness = 2.0;
icon = make_divider_icon(arrow_color, icon_w, icon_h, gap, thickness, sharpness, true);
}
void Number_Edit::key_handler(Camera& view, Input& input) {
if (parent->active_edit == &editor) {
int res = editor.handle_input(input);
number = strtol(editor.text.c_str(), nullptr, 10);
if ((res & 1) && key_action)
key_action(this, input);
if (res & 2)
needs_redraw = true;
if (res & 8)
parent->active_edit = nullptr;
}
}
void Number_Edit::base_action(Camera& view, Point& cursor, Input& input) {
parent->active_edit = &editor;
}
void Number_Edit::affect_number(int delta) {
number = strtol(editor.text.c_str(), nullptr, 10) + delta;
editor.text = std::to_string(number);
editor.set_cursor(editor.primary, 0);
editor.secondary = editor.primary;
editor.selected = false;
if (key_action) {
Input blank = {};
key_action(this, blank);
}
}
bool Number_Edit::scroll_handler(Camera& view, Input& input, Point& inside) {
int delta = 0;
if (input.scroll_y > 0)
delta = 1;
if (input.scroll_y < 0)
delta = -1;
affect_number(delta);
return delta != 0;
}
void Number_Edit::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
if (!hovered || !input.lclick || !pos.contains(cursor))
return;
float x = (cursor.x - pos.x) * view.scale;
float y = (cursor.y - pos.y) * view.scale;
editor.update_cursor(x, y, font, view.scale, ticks, input.lclick);
if (x >= (pos.w * view.scale) - (end_width * font->render.text_height())) {
int delta = y < pos.h * view.scale / 2 ? 1 : -1;
affect_number(delta);
}
}
void Number_Edit::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
float font_h = font->render.text_height();
float end_w = font_h * end_width;
if (back.w <= end_w)
return;
Rect r = back.to_rect();
r.w -= end_w - 1.0;
sdl_draw_rect(r, default_color, renderer);
float text_x = back.x + font_h * text_off_x;
float text_y = back.y + font_h * text_off_y;
clip.x_upper = back.x + back.w - end_w;
editor.refresh(clip, font, 0, 0);
font->render.draw_text(renderer, editor.text.c_str(), text_x, text_y, clip);
float digit_w = font->render.digit_width();
if (parent->active_edit == &editor) {
Point p = {text_x, text_y};
editor.draw_cursor(renderer, arrow_color, editor.primary, p, clip, view.scale);
}
r.x = clip.x_upper;
r.w = end_w;
sdl_draw_rect(r, sel_color, renderer);
r.x += (r.w - (float)icon_w) / 2.0;
r.y += (r.h - (float)icon_h) / 2.0;
r.w = icon_w;
r.h = icon_h;
sdl_apply_texture(icon, r, nullptr, renderer);
}
void Scroll::set_maximum(double max, double span) {
maximum = max;
show_thumb = span < max;
if (!show_thumb)
span = max;
view_span = span;
}
void Scroll::engage(Point& p) {
held = true;
float thumb_pos = length * (1 - thumb_frac) * position / (maximum - view_span);
if (vertical)
hold_region = p.y - (pos.y + thumb_pos);
else
hold_region = p.x - (pos.x + thumb_pos);
if (hold_region < 0 || hold_region > thumb_frac * length)
hold_region = 0;
parent->ui_held = true;
}
void Scroll::scroll(double delta) {
double old_pos = position;
position = clamp_f(position + delta, 0, maximum - view_span);
double epsilon = 0.01;
needs_redraw = abs(position - old_pos) > epsilon;
if (content)
content->needs_redraw = content->needs_redraw || needs_redraw;
}
void Scroll::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
if (!show_thumb)
return;
if (input.lclick && pos.contains(cursor))
engage(cursor);
if (!input.lmouse)
held = false;
double new_pos = position;
if (held) {
double dist = 0;
if (vertical)
dist = cursor.y - (pos.y + hold_region);
else
dist = cursor.x - (pos.x + hold_region);
double span = (1 - thumb_frac) * length;
new_pos = dist * (maximum - view_span) / span;
}
// apply bounds-checking
scroll(new_pos - position);
}
void Scroll::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
if (vertical)
length = pos.h - 2*padding;
else
length = pos.w - 2*padding;
sdl_draw_rect(back, back_color, renderer);
if (!show_thumb)
return;
thumb_frac = view_span / maximum;
if (thumb_frac < thumb_min) thumb_frac = thumb_min;
float thumb_pos = length * (1 - thumb_frac) * position / (maximum - view_span);
float thumb_len = thumb_frac * length * view.scale;
float gap = 2*padding * view.scale;
float x = padding;
float y = padding;
float w = 0, h = 0;
if (vertical) {
y += thumb_pos;
w = back.w - gap;
h = thumb_len;
}
else {
x += thumb_pos;
w = thumb_len;
h = back.h - gap;
}
Rect_Int scroll_rect = {
back.x + (int)(x * view.scale + 0.5),
back.y + (int)(y * view.scale + 0.5),
(int)(w + 0.5),
(int)(h + 0.5)
};
RGBA *color = &default_color;
if (held)
color = &sel_color;
else if (hl)
color = &hl_color;
sdl_draw_rect(scroll_rect, *color, renderer);
}
void Scroll::highlight(Camera& view, Point& inside, Input& input) {
hl = pos.contains(inside);
}
void Scroll::deselect() {
hl = false;
}
void Tabs::add(const char **names, int count) {
for (int i = 0; i < count; i++) {
Tab t = {(char*)names[i], 0};
tabs.push_back(t);
}
}
void Tabs::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
hl = -1;
if (!hovered || !pos.contains(cursor))
return;
float total = 0;
for (auto& t : tabs)
total += t.width;
float border = tab_border * view.scale;
float spacing = (pos.w * view.scale) / (total + border - 1.0f);
float cur_x = cursor.x - pos.x;
int n_tabs = tabs.size();
float x = 0;
for (int i = 0; i < n_tabs; i++) {
x += tabs[i].width * spacing / view.scale;
if (cur_x < x) {
hl = i;
break;
}
}
if (input.lclick && hl >= 0) {
sel = hl;
event(this);
}
}
void Tabs::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
back.h += 1.0f;
sdl_draw_rect(back, default_color, renderer);
float font_h = font->render.text_height();
float pad = x_offset * font_h;
float border = tab_border * view.scale;
float total = 0;
for (auto& t : tabs) {
t.width = font->render.digit_width() * (float)strlen(t.name);
total += t.width + border;
}
float spacing = back.w / (total + border - 1.0f);
int n_tabs = tabs.size();
float x = back.x + border;
Rect r = {0, back.y + border, 0, back.h - border};
for (int i = 0; i < n_tabs; i++) {
float width = (tabs[i].width + border) * spacing;
if (i == sel || i == hl) {
r.x = x;
r.w = width - border;
RGBA *color = i == hl ? &hl_color : &sel_color;
sdl_draw_rect(r, *color, renderer);
}
font->render.draw_text_simple(renderer, tabs[i].name, x + pad, back.y);
x += width;
}
}
void Text_Editor::key_handler(Camera& view, Input& input) {
if (parent->active_edit == &editor) {
int res = editor.handle_input(input);
editor.apply_scroll(hscroll, vscroll);
if ((res & 1) && key_action)
key_action(this, input);
if (res & 2)
needs_redraw = true;
if (res & 4)
ticks = 0;
if (res & 8)
parent->active_edit = nullptr;
needs_redraw = needs_redraw || res != 0;
}
}
void Text_Editor::mouse_handler(Camera& view, Input& input, Point& cursor, bool hovered) {
if (hovered && input.lclick && pos.contains(cursor)) {
parent->ui_held = true;
parent->active_edit = &editor;
mouse_held = true;
}
else if (!input.lmouse)
mouse_held = false;
if (mouse_held) {
float x = (cursor.x - pos.x) * view.scale;
float y = (cursor.y - pos.y) * view.scale;
editor.update_cursor(x, y, font, view.scale, ticks, input.lclick);
}
needs_redraw = needs_redraw || mouse_held;
}
void Text_Editor::base_action(Camera& view, Point& cursor, Input& input) {
parent->active_edit = &editor;
}
bool Text_Editor::scroll_handler(Camera& view, Input& input, Point& inside) {
Scroll *scroll = input.lshift || input.rshift ? hscroll : vscroll;
bool scrolled = false;
if (scroll && input.scroll_y > 0) {
scroll->scroll(-font->render.text_height());
scrolled = true;
}
if (scroll && input.scroll_y < 0) {
scroll->scroll(font->render.text_height());
scrolled = true;
}
if (scrolled)
needs_redraw = true;
return scrolled;
}
void Text_Editor::update(Camera& view, Rect_Int const& rect) {
show_caret = ticks % (caret_on_time + caret_off_time) < caret_on_time;
}
Render_Clip Text_Editor::make_clip(Rect_Int& r, float edge) {
return {
CLIP_ALL,
r.x + edge,
r.y + edge,
r.x + r.w - edge,
r.y + r.h - edge
};
}
void Text_Editor::draw_element(Renderer renderer, Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
sdl_draw_rect(back, default_color, renderer);
float digit_w = font->render.digit_width();
float font_h = font->render.text_height();
float edge = border * view.scale;
clip = make_clip(back, edge);
float scroll_x = 0, scroll_y = 0;
if (hscroll) {
float view_w = back.w - 2*edge;
hscroll->set_maximum((float)editor.columns * digit_w, view_w);
scroll_x = hscroll->position;
}
if (vscroll) {
float view_h = back.h - 2*edge;
vscroll->set_maximum((float)editor.lines * font_h, view_h);
scroll_y = vscroll->position;
}
editor.refresh(clip, font, scroll_x, scroll_y);
if (editor.secondary.cursor != editor.primary.cursor)
editor.draw_selection_box(renderer, sel_color);
font->render.draw_text(renderer, editor.text.c_str(), back.x + edge - scroll_x, back.y + edge - scroll_y, clip, editor.tab_width, true);
}
void Text_Editor::post_draw(Camera& view, Rect_Int& back, bool elem_hovered, bool box_hovered, bool focussed) {
if (parent->active_edit != &editor)
return;
float edge = border * view.scale;
Render_Clip cur_clip = make_clip(back, edge);
float digit_w = font->render.digit_width();
float font_h = font->render.text_height();
if (editor.secondary.cursor != editor.primary.cursor || show_caret) {
Point origin = {back.x + edge, back.y + edge};
editor.draw_cursor(nullptr, caret_color, editor.primary, origin, cur_clip, view.scale);
if (editor.secondary.cursor != editor.primary.cursor)
editor.draw_cursor(nullptr, caret_color, editor.secondary, origin, cur_clip, view.scale);
}
}