Files
raddebugger/src/dbg_frontend/dbg_frontend_views.c
T

9214 lines
356 KiB
C

// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ rjf: Code Views
internal void
df_code_view_init(DF_CodeViewState *cv, DF_View *view)
{
if(cv->initialized == 0)
{
cv->initialized = 1;
cv->preferred_column = 1;
cv->find_text_arena = df_view_push_arena_ext(view);
cv->center_cursor = 1;
}
df_view_equip_loading_info(view, 1, 0, 0);
view->loading_t = view->loading_t_target = 1.f;
}
internal void
df_code_view_cmds(DF_View *view, DF_CodeViewState *cv, String8 text_data, TXT_TextInfo *text_info, DASM_LineArray *dasm_lines, Rng1U64 dasm_vaddr_range, DI_Key dasm_dbgi_key)
{
for(DF_Cmd *cmd = 0; df_next_cmd(&cmd);)
{
// rjf: mismatched window/panel => skip
if(!d_handle_match(df_regs()->window, cmd->regs->window) ||
!d_handle_match(df_regs()->panel, cmd->regs->panel))
{
continue;
}
// rjf: process
DF_CmdKind kind = df_cmd_kind_from_string(cmd->name);
switch(kind)
{
default: break;
case DF_CmdKind_GoToLine:
{
cv->goto_line_num = cmd->regs->cursor.line;
}break;
case DF_CmdKind_CenterCursor:
{
cv->center_cursor = 1;
}break;
case DF_CmdKind_ContainCursor:
{
cv->contain_cursor = 1;
}break;
case DF_CmdKind_FindTextForward:
{
arena_clear(cv->find_text_arena);
cv->find_text_fwd = push_str8_copy(cv->find_text_arena, cmd->regs->string);
}break;
case DF_CmdKind_FindTextBackward:
{
arena_clear(cv->find_text_arena);
cv->find_text_bwd = push_str8_copy(cv->find_text_arena, cmd->regs->string);
}break;
case DF_CmdKind_ToggleWatchExpressionAtMouse:
{
cv->watch_expr_at_mouse = 1;
}break;
}
}
}
internal DF_CodeViewBuildResult
df_code_view_build(Arena *arena, DF_View *view, DF_CodeViewState *cv, DF_CodeViewBuildFlags flags, Rng2F32 rect, String8 text_data, TXT_TextInfo *text_info, DASM_LineArray *dasm_lines, Rng1U64 dasm_vaddr_range, DI_Key dasm_dbgi_key)
{
ProfBeginFunction();
Temp scratch = scratch_begin(&arena, 1);
HS_Scope *hs_scope = hs_scope_open();
TXT_Scope *txt_scope = txt_scope_open();
//////////////////////////////
//- rjf: extract invariants
//
FNT_Tag code_font = df_font_from_slot(DF_FontSlot_Code);
F32 code_font_size = df_font_size_from_slot(DF_FontSlot_Code);
F32 code_tab_size = fnt_column_size_from_tag_size(code_font, code_font_size)*df_setting_val_from_code(DF_SettingCode_TabWidth).s32;
FNT_Metrics code_font_metrics = fnt_metrics_from_tag_size(code_font, code_font_size);
F32 code_line_height = ceil_f32(fnt_line_height_from_metrics(&code_font_metrics) * 1.5f);
F32 big_glyph_advance = fnt_dim_from_tag_size_string(code_font, code_font_size, 0, 0, str8_lit("H")).x;
Vec2F32 panel_box_dim = dim_2f32(rect);
F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f);
Vec2F32 code_area_dim = v2f32(panel_box_dim.x - scroll_bar_dim, panel_box_dim.y - scroll_bar_dim);
S64 num_possible_visible_lines = (S64)(code_area_dim.y/code_line_height)+1;
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->thread);
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process);
//////////////////////////////
//- rjf: determine visible line range / count
//
Rng1S64 visible_line_num_range = r1s64(view->scroll_pos.y.idx + (S64)(view->scroll_pos.y.off) + 1 - !!(view->scroll_pos.y.off < 0),
view->scroll_pos.y.idx + (S64)(view->scroll_pos.y.off) + 1 + num_possible_visible_lines);
Rng1S64 target_visible_line_num_range = r1s64(view->scroll_pos.y.idx + 1,
view->scroll_pos.y.idx + 1 + num_possible_visible_lines);
U64 visible_line_count = 0;
{
visible_line_num_range.min = Clamp(1, visible_line_num_range.min, (S64)text_info->lines_count);
visible_line_num_range.max = Clamp(1, visible_line_num_range.max, (S64)text_info->lines_count);
visible_line_num_range.min = Max(1, visible_line_num_range.min);
visible_line_num_range.max = Max(1, visible_line_num_range.max);
target_visible_line_num_range.min = Clamp(1, target_visible_line_num_range.min, (S64)text_info->lines_count);
target_visible_line_num_range.max = Clamp(1, target_visible_line_num_range.max, (S64)text_info->lines_count);
target_visible_line_num_range.min = Max(1, target_visible_line_num_range.min);
target_visible_line_num_range.max = Max(1, target_visible_line_num_range.max);
visible_line_count = (U64)dim_1s64(visible_line_num_range)+1;
}
//////////////////////////////
//- rjf: calculate scroll bounds
//
S64 line_size_x = 0;
Rng1S64 scroll_idx_rng[Axis2_COUNT] = {0};
{
line_size_x = (text_info->lines_max_size*big_glyph_advance*3)/2;
line_size_x = ClampBot(line_size_x, (S64)big_glyph_advance*120);
line_size_x = ClampBot(line_size_x, (S64)code_area_dim.x);
scroll_idx_rng[Axis2_X] = r1s64(0, line_size_x-(S64)code_area_dim.x);
scroll_idx_rng[Axis2_Y] = r1s64(0, (S64)text_info->lines_count-1);
}
//////////////////////////////
//- rjf: calculate line-range-dependent info
//
F32 line_num_width_px = big_glyph_advance * (log10(visible_line_num_range.max) + 3);
F32 priority_margin_width_px = 0;
F32 catchall_margin_width_px = 0;
if(flags & DF_CodeViewBuildFlag_Margins)
{
priority_margin_width_px = big_glyph_advance*3.5f;
catchall_margin_width_px = big_glyph_advance*3.5f;
}
TXT_LineTokensSlice slice = txt_line_tokens_slice_from_info_data_line_range(scratch.arena, text_info, text_data, visible_line_num_range);
//////////////////////////////
//- rjf: get active search query
//
String8 search_query = {0};
Side search_query_side = Side_Invalid;
B32 search_query_is_active = 0;
{
DF_Window *window = df_window_from_handle(df_regs()->window);
DF_CmdKind query_cmd_kind = df_cmd_kind_from_string(window->query_cmd_name);
if(query_cmd_kind == DF_CmdKind_FindTextForward ||
query_cmd_kind == DF_CmdKind_FindTextBackward)
{
search_query = str8(window->query_view_stack_top->query_buffer, window->query_view_stack_top->query_string_size);
search_query_is_active = 1;
search_query_side = (query_cmd_kind == DF_CmdKind_FindTextForward) ? Side_Max : Side_Min;
}
}
//////////////////////////////
//- rjf: prepare code slice info bundle, for the viewable region of text
//
DF_CodeSliceParams code_slice_params = {0};
{
// rjf: fill basics
code_slice_params.flags = DF_CodeSliceFlag_LineNums|DF_CodeSliceFlag_Clickable;
if(flags & DF_CodeViewBuildFlag_Margins)
{
code_slice_params.flags |= DF_CodeSliceFlag_PriorityMargin|DF_CodeSliceFlag_CatchallMargin;
}
code_slice_params.line_num_range = visible_line_num_range;
code_slice_params.line_text = push_array(scratch.arena, String8, visible_line_count);
code_slice_params.line_ranges = push_array(scratch.arena, Rng1U64, visible_line_count);
code_slice_params.line_tokens = push_array(scratch.arena, TXT_TokenArray, visible_line_count);
code_slice_params.line_bps = push_array(scratch.arena, D_EntityList, visible_line_count);
code_slice_params.line_ips = push_array(scratch.arena, CTRL_EntityList, visible_line_count);
code_slice_params.line_pins = push_array(scratch.arena, D_EntityList, visible_line_count);
code_slice_params.line_vaddrs = push_array(scratch.arena, U64, visible_line_count);
code_slice_params.line_infos = push_array(scratch.arena, D_LineList, visible_line_count);
code_slice_params.font = code_font;
code_slice_params.font_size = code_font_size;
code_slice_params.tab_size = code_tab_size;
code_slice_params.line_height_px = code_line_height;
code_slice_params.search_query = search_query;
code_slice_params.priority_margin_width_px = priority_margin_width_px;
code_slice_params.catchall_margin_width_px = catchall_margin_width_px;
code_slice_params.line_num_width_px = line_num_width_px;
code_slice_params.line_text_max_width_px = (F32)line_size_x;
code_slice_params.margin_float_off_px = view->scroll_pos.x.idx + view->scroll_pos.x.off;
// rjf: fill text info
{
S64 line_num = visible_line_num_range.min;
U64 line_idx = visible_line_num_range.min-1;
for(U64 visible_line_idx = 0; visible_line_idx < visible_line_count; visible_line_idx += 1, line_idx += 1, line_num += 1)
{
code_slice_params.line_text[visible_line_idx] = str8_substr(text_data, text_info->lines_ranges[line_idx]);
code_slice_params.line_ranges[visible_line_idx] = text_info->lines_ranges[line_idx];
code_slice_params.line_tokens[visible_line_idx] = slice.line_tokens[visible_line_idx];
}
}
// rjf: find visible breakpoints for source code
ProfScope("find visible breakpoints")
{
D_EntityList bps = d_query_cached_entity_list_with_kind(D_EntityKind_Breakpoint);
for(D_EntityNode *n = bps.first; n != 0; n = n->next)
{
D_Entity *bp = n->entity;
D_Entity *loc = d_entity_child_from_kind(bp, D_EntityKind_Location);
if(path_match_normalized(loc->string, df_regs()->file_path) &&
visible_line_num_range.min <= loc->text_point.line && loc->text_point.line <= visible_line_num_range.max)
{
U64 slice_line_idx = (loc->text_point.line-visible_line_num_range.min);
d_entity_list_push(scratch.arena, &code_slice_params.line_bps[slice_line_idx], bp);
}
}
}
// rjf: find live threads mapping to source code
ProfScope("find live threads mapping to this file")
{
String8 file_path = df_regs()->file_path;
CTRL_Entity *selected_thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->thread);
CTRL_EntityList threads = ctrl_entity_list_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Thread);
for(CTRL_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next)
{
CTRL_Entity *thread = thread_n->v;
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process);
U64 unwind_count = (thread == selected_thread) ? df_regs()->unwind_count : 0;
U64 inline_depth = (thread == selected_thread) ? df_regs()->inline_depth : 0;
U64 rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, unwind_count);
U64 last_inst_on_unwound_rip_vaddr = rip_vaddr - !!unwind_count;
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, last_inst_on_unwound_rip_vaddr);
U64 rip_voff = ctrl_voff_from_vaddr(module, last_inst_on_unwound_rip_vaddr);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff);
for(D_LineNode *n = lines.first; n != 0; n = n->next)
{
if(path_match_normalized(n->v.file_path, file_path) && visible_line_num_range.min <= n->v.pt.line && n->v.pt.line <= visible_line_num_range.max)
{
U64 slice_line_idx = n->v.pt.line-visible_line_num_range.min;
ctrl_entity_list_push(scratch.arena, &code_slice_params.line_ips[slice_line_idx], thread);
}
}
}
}
// rjf: find visible watch pins for source code
ProfScope("find visible watch pins")
{
D_EntityList wps = d_query_cached_entity_list_with_kind(D_EntityKind_WatchPin);
for(D_EntityNode *n = wps.first; n != 0; n = n->next)
{
D_Entity *wp = n->entity;
D_Entity *loc = d_entity_child_from_kind(wp, D_EntityKind_Location);
if(path_match_normalized(loc->string, df_regs()->file_path) &&
visible_line_num_range.min <= loc->text_point.line && loc->text_point.line <= visible_line_num_range.max)
{
U64 slice_line_idx = (loc->text_point.line-visible_line_num_range.min);
d_entity_list_push(scratch.arena, &code_slice_params.line_pins[slice_line_idx], wp);
}
}
}
// rjf: find all src -> dasm info
ProfScope("find all src -> dasm info")
{
String8 file_path = df_regs()->file_path;
D_LineListArray lines_array = d_lines_array_from_file_path_line_range(scratch.arena, file_path, visible_line_num_range);
if(lines_array.count != 0)
{
MemoryCopy(code_slice_params.line_infos, lines_array.v, sizeof(D_LineList)*lines_array.count);
}
code_slice_params.relevant_dbgi_keys = lines_array.dbgi_keys;
}
// rjf: find live threads mapping to disasm
if(dasm_lines) ProfScope("find live threads mapping to this disassembly")
{
CTRL_Entity *selected_thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->thread);
CTRL_EntityList threads = ctrl_entity_list_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Thread);
for(CTRL_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next)
{
CTRL_Entity *thread = thread_n->v;
U64 unwind_count = (thread == selected_thread) ? df_regs()->unwind_count : 0;
U64 rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, unwind_count);
if(ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process) == process && contains_1u64(dasm_vaddr_range, rip_vaddr))
{
U64 rip_off = rip_vaddr - dasm_vaddr_range.min;
S64 line_num = dasm_line_array_idx_from_code_off__linear_scan(dasm_lines, rip_off)+1;
if(contains_1s64(visible_line_num_range, line_num))
{
U64 slice_line_idx = (line_num-visible_line_num_range.min);
ctrl_entity_list_push(scratch.arena, &code_slice_params.line_ips[slice_line_idx], thread);
}
}
}
}
// rjf: find breakpoints mapping to this disasm
if(dasm_lines) ProfScope("find breakpoints mapping to this disassembly")
{
D_EntityList bps = d_query_cached_entity_list_with_kind(D_EntityKind_Breakpoint);
for(D_EntityNode *n = bps.first; n != 0; n = n->next)
{
D_Entity *bp = n->entity;
D_Entity *loc = d_entity_child_from_kind(bp, D_EntityKind_Location);
if(loc->flags & D_EntityFlag_HasVAddr && contains_1u64(dasm_vaddr_range, loc->vaddr))
{
U64 off = loc->vaddr-dasm_vaddr_range.min;
U64 idx = dasm_line_array_idx_from_code_off__linear_scan(dasm_lines, off);
S64 line_num = (S64)(idx+1);
if(contains_1s64(visible_line_num_range, line_num))
{
U64 slice_line_idx = (line_num-visible_line_num_range.min);
d_entity_list_push(scratch.arena, &code_slice_params.line_bps[slice_line_idx], bp);
}
}
}
}
// rjf: find watch pins mapping to this disasm
if(dasm_lines) ProfScope("find watch pins mapping to this disassembly")
{
D_EntityList pins = d_query_cached_entity_list_with_kind(D_EntityKind_WatchPin);
for(D_EntityNode *n = pins.first; n != 0; n = n->next)
{
D_Entity *pin = n->entity;
D_Entity *loc = d_entity_child_from_kind(pin, D_EntityKind_Location);
if(loc->flags & D_EntityFlag_HasVAddr && contains_1u64(dasm_vaddr_range, loc->vaddr))
{
U64 off = loc->vaddr-dasm_vaddr_range.min;
U64 idx = dasm_line_array_idx_from_code_off__linear_scan(dasm_lines, off);
S64 line_num = (S64)(idx+1);
if(contains_1s64(visible_line_num_range, line_num))
{
U64 slice_line_idx = (line_num-visible_line_num_range.min);
d_entity_list_push(scratch.arena, &code_slice_params.line_pins[slice_line_idx], pin);
}
}
}
}
// rjf: fill dasm -> src info
if(dasm_lines)
{
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, dasm_vaddr_range.min);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
for(S64 line_num = visible_line_num_range.min; line_num < visible_line_num_range.max; line_num += 1)
{
U64 vaddr = dasm_vaddr_range.min + dasm_line_array_code_off_from_idx(dasm_lines, line_num-1);
U64 voff = ctrl_voff_from_vaddr(module, vaddr);
U64 slice_idx = line_num-visible_line_num_range.min;
code_slice_params.line_vaddrs[slice_idx] = vaddr;
code_slice_params.line_infos[slice_idx] = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff);
}
}
// rjf: add dasm dbgi key to relevant dbgis
if(dasm_lines != 0)
{
di_key_list_push(scratch.arena, &code_slice_params.relevant_dbgi_keys, &dasm_dbgi_key);
}
}
//////////////////////////////
//- rjf: build container
//
UI_Box *container_box = &ui_g_nil_box;
{
ui_set_next_pref_width(ui_px(code_area_dim.x, 1));
ui_set_next_pref_height(ui_px(code_area_dim.y, 1));
ui_set_next_child_layout_axis(Axis2_Y);
container_box = ui_build_box_from_stringf(UI_BoxFlag_Clip|
UI_BoxFlag_Scroll|
UI_BoxFlag_AllowOverflowX|
UI_BoxFlag_AllowOverflowY,
"###code_area_%p", view);
}
//////////////////////////////
//- rjf: cancelled search query -> center cursor
//
if(!search_query_is_active && cv->drifted_for_search)
{
cv->drifted_for_search = 0;
cv->center_cursor = 1;
}
//////////////////////////////
//- rjf: do searching operations
//
{
//- rjf: find text (forward)
if(cv->find_text_fwd.size != 0)
{
Temp scratch = scratch_begin(0, 0);
B32 found = 0;
B32 first = 1;
S64 line_num_start = df_regs()->cursor.line;
S64 line_num_last = (S64)text_info->lines_count;
for(S64 line_num = line_num_start;; first = 0)
{
// rjf: pop scratch
temp_end(scratch);
// rjf: gather line info
String8 line_string = str8_substr(text_data, text_info->lines_ranges[line_num-1]);
U64 search_start = 0;
if(df_regs()->cursor.line == line_num && first)
{
search_start = df_regs()->cursor.column;
}
// rjf: search string
U64 needle_pos = str8_find_needle(line_string, search_start, cv->find_text_fwd, StringMatchFlag_CaseInsensitive);
if(needle_pos < line_string.size)
{
df_regs()->cursor.line = line_num;
df_regs()->cursor.column = needle_pos+1;
df_regs()->mark = df_regs()->cursor;
found = 1;
break;
}
// rjf: break if circled back around to cursor
else if(line_num == line_num_start && !first)
{
break;
}
// rjf: increment
line_num += 1;
if(line_num > line_num_last)
{
line_num = 1;
}
}
cv->center_cursor = found;
if(found == 0)
{
log_user_errorf("Could not find \"%S\"", cv->find_text_fwd);
}
scratch_end(scratch);
}
//- rjf: find text (backward)
if(cv->find_text_bwd.size != 0)
{
Temp scratch = scratch_begin(0, 0);
B32 found = 0;
B32 first = 1;
S64 line_num_start = df_regs()->cursor.line;
S64 line_num_last = (S64)text_info->lines_count;
for(S64 line_num = line_num_start;; first = 0)
{
// rjf: pop scratch
temp_end(scratch);
// rjf: gather line info
String8 line_string = str8_substr(text_data, text_info->lines_ranges[line_num-1]);
if(df_regs()->cursor.line == line_num && first)
{
line_string = str8_prefix(line_string, df_regs()->cursor.column-1);
}
// rjf: search string
U64 next_needle_pos = line_string.size;
for(U64 needle_pos = 0; needle_pos < line_string.size;)
{
needle_pos = str8_find_needle(line_string, needle_pos, cv->find_text_bwd, StringMatchFlag_CaseInsensitive);
if(needle_pos < line_string.size)
{
next_needle_pos = needle_pos;
needle_pos += 1;
}
}
if(next_needle_pos < line_string.size)
{
df_regs()->cursor.line = line_num;
df_regs()->cursor.column = next_needle_pos+1;
df_regs()->mark = df_regs()->cursor;
found = 1;
break;
}
// rjf: break if circled back around to cursor line
else if(line_num == line_num_start && !first)
{
break;
}
// rjf: increment
line_num -= 1;
if(line_num == 0)
{
line_num = line_num_last;
}
}
cv->center_cursor = found;
if(found == 0)
{
log_user_errorf("Could not find \"%S\"", cv->find_text_bwd);
}
scratch_end(scratch);
}
MemoryZeroStruct(&cv->find_text_fwd);
MemoryZeroStruct(&cv->find_text_bwd);
arena_clear(cv->find_text_arena);
}
//////////////////////////////
//- rjf: do goto line
//
if(cv->goto_line_num != 0)
{
S64 line_num = cv->goto_line_num;
cv->goto_line_num = 0;
line_num = Clamp(1, line_num, text_info->lines_count);
df_regs()->cursor = df_regs()->mark = txt_pt(line_num, 1);
cv->center_cursor = !cv->contain_cursor || (line_num < target_visible_line_num_range.min+4 || target_visible_line_num_range.max-4 < line_num);
}
//////////////////////////////
//- rjf: do keyboard interaction
//
B32 snap[Axis2_COUNT] = {0};
UI_Focus(UI_FocusKind_On)
{
if(ui_is_focus_active() && visible_line_num_range.max >= visible_line_num_range.min)
{
snap[Axis2_X] = snap[Axis2_Y] = df_do_txt_controls(text_info, text_data, ClampBot(num_possible_visible_lines, 10) - 10, &df_regs()->cursor, &df_regs()->mark, &cv->preferred_column);
}
}
//////////////////////////////
//- rjf: build container contents
//
UI_Parent(container_box)
{
//- rjf: build fractional space
container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
container_box->view_off.y = container_box->view_off_target.y = code_line_height*mod_f32(view->scroll_pos.y.off, 1.f) + code_line_height*(view->scroll_pos.y.off < 0) - code_line_height*(view->scroll_pos.y.off == -1.f && view->scroll_pos.y.idx == 1);
//- rjf: build code slice
DF_CodeSliceSignal sig = {0};
UI_Focus(UI_FocusKind_On)
{
sig = df_code_slicef(&code_slice_params, &df_regs()->cursor, &df_regs()->mark, &cv->preferred_column, "txt_view_%p", view);
}
//- rjf: press code slice? -> focus panel
if(ui_pressed(sig.base))
{
df_cmd(DF_CmdKind_FocusPanel);
}
//- rjf: dragging & outside region? -> contain cursor
if(ui_dragging(sig.base) && sig.base.event_flags == 0)
{
if(!contains_2f32(sig.base.box->rect, ui_mouse()))
{
cv->contain_cursor = 1;
}
else
{
snap[Axis2_X] = 1;
}
}
//- rjf: ctrl+pressed? -> go to name
if(ui_pressed(sig.base) && sig.base.event_flags & OS_EventFlag_Ctrl)
{
ui_kill_action();
df_cmd(DF_CmdKind_GoToName, .string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng));
}
//- rjf: watch expr at mouse
if(cv->watch_expr_at_mouse)
{
cv->watch_expr_at_mouse = 0;
df_cmd(DF_CmdKind_ToggleWatchExpression, .string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng));
}
//- rjf: selected text on single line, no query? -> set search text
if(!txt_pt_match(df_regs()->cursor, df_regs()->mark) && df_regs()->cursor.line == df_regs()->mark.line && search_query.size == 0)
{
String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, txt_rng(df_regs()->cursor, df_regs()->mark));
df_set_search_string(text);
}
}
//////////////////////////////
//- rjf: apply post-build view snapping rules
//
{
// rjf: contain => snap
if(cv->contain_cursor)
{
cv->contain_cursor = 0;
snap[Axis2_X] = 1;
snap[Axis2_Y] = 1;
}
// rjf: center cursor
if(cv->center_cursor)
{
cv->center_cursor = 0;
String8 cursor_line = str8_substr(text_data, text_info->lines_ranges[df_regs()->cursor.line-1]);
F32 cursor_advance = fnt_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, df_regs()->cursor.column-1)).x;
// rjf: scroll x
{
S64 new_idx = (S64)(cursor_advance - code_area_dim.x/2);
new_idx = Clamp(scroll_idx_rng[Axis2_X].min, new_idx, scroll_idx_rng[Axis2_X].max);
ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx);
snap[Axis2_X] = 0;
}
// rjf: scroll y
{
S64 new_idx = (df_regs()->cursor.line-1) - num_possible_visible_lines/2 + 2;
new_idx = Clamp(scroll_idx_rng[Axis2_Y].min, new_idx, scroll_idx_rng[Axis2_Y].max);
ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx);
snap[Axis2_Y] = 0;
}
}
// rjf: snap in X
if(snap[Axis2_X])
{
String8 cursor_line = str8_substr(text_data, text_info->lines_ranges[df_regs()->cursor.line-1]);
S64 cursor_off = (S64)(fnt_dim_from_tag_size_string(code_font, code_font_size, 0, code_tab_size, str8_prefix(cursor_line, df_regs()->cursor.column-1)).x + priority_margin_width_px + catchall_margin_width_px + line_num_width_px);
Rng1S64 visible_pixel_range =
{
view->scroll_pos.x.idx,
view->scroll_pos.x.idx + (S64)code_area_dim.x,
};
Rng1S64 cursor_pixel_range =
{
cursor_off - (S64)(big_glyph_advance*4) - (S64)(priority_margin_width_px + catchall_margin_width_px + line_num_width_px),
cursor_off + (S64)(big_glyph_advance*4),
};
S64 min_delta = Min(0, cursor_pixel_range.min - visible_pixel_range.min);
S64 max_delta = Max(0, cursor_pixel_range.max - visible_pixel_range.max);
S64 new_idx = view->scroll_pos.x.idx+min_delta+max_delta;
new_idx = Clamp(scroll_idx_rng[Axis2_X].min, new_idx, scroll_idx_rng[Axis2_X].max);
ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx);
}
// rjf: snap in Y
if(snap[Axis2_Y])
{
Rng1S64 cursor_visibility_range = r1s64(df_regs()->cursor.line-4, df_regs()->cursor.line+4);
cursor_visibility_range.min = ClampBot(0, cursor_visibility_range.min);
cursor_visibility_range.max = ClampBot(0, cursor_visibility_range.max);
S64 min_delta = Min(0, cursor_visibility_range.min-(target_visible_line_num_range.min));
S64 max_delta = Max(0, cursor_visibility_range.max-(target_visible_line_num_range.min+num_possible_visible_lines));
S64 new_idx = view->scroll_pos.y.idx+min_delta+max_delta;
new_idx = Clamp(0, new_idx, (S64)text_info->lines_count-1);
ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx);
}
}
//////////////////////////////
//- rjf: build horizontal scroll bar
//
{
ui_set_next_fixed_x(0);
ui_set_next_fixed_y(code_area_dim.y);
ui_set_next_fixed_width(panel_box_dim.x - scroll_bar_dim);
ui_set_next_fixed_height(scroll_bar_dim);
{
view->scroll_pos.x = ui_scroll_bar(Axis2_X,
ui_px(scroll_bar_dim, 1.f),
view->scroll_pos.x,
scroll_idx_rng[Axis2_X],
(S64)code_area_dim.x);
}
}
//////////////////////////////
//- rjf: build vertical scroll bar
//
{
ui_set_next_fixed_x(code_area_dim.x);
ui_set_next_fixed_y(0);
ui_set_next_fixed_width(scroll_bar_dim);
ui_set_next_fixed_height(panel_box_dim.y - scroll_bar_dim);
{
view->scroll_pos.y = ui_scroll_bar(Axis2_Y,
ui_px(scroll_bar_dim, 1.f),
view->scroll_pos.y,
scroll_idx_rng[Axis2_Y],
num_possible_visible_lines);
}
}
//////////////////////////////
//- rjf: top-level container interaction (scrolling)
//
{
UI_Signal sig = ui_signal_from_box(container_box);
if(sig.scroll.x != 0)
{
S64 new_idx = view->scroll_pos.x.idx+sig.scroll.x*big_glyph_advance;
new_idx = clamp_1s64(scroll_idx_rng[Axis2_X], new_idx);
ui_scroll_pt_target_idx(&view->scroll_pos.x, new_idx);
}
if(sig.scroll.y != 0)
{
S64 new_idx = view->scroll_pos.y.idx + sig.scroll.y;
new_idx = clamp_1s64(scroll_idx_rng[Axis2_Y], new_idx);
ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx);
}
ui_scroll_pt_clamp_idx(&view->scroll_pos.x, scroll_idx_rng[Axis2_X]);
ui_scroll_pt_clamp_idx(&view->scroll_pos.y, scroll_idx_rng[Axis2_Y]);
if(ui_mouse_over(sig))
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->kind == UI_EventKind_Scroll && evt->modifiers & OS_EventFlag_Ctrl)
{
ui_eat_event(evt);
if(evt->delta_2f32.y < 0)
{
df_cmd(DF_CmdKind_IncCodeFontScale);
}
else if(evt->delta_2f32.y > 0)
{
df_cmd(DF_CmdKind_DecCodeFontScale);
}
}
}
}
}
//////////////////////////////
//- rjf: build result
//
DF_CodeViewBuildResult result = {0};
{
for(DI_KeyNode *n = code_slice_params.relevant_dbgi_keys.first; n != 0; n = n->next)
{
DI_Key copy = di_key_copy(arena, &n->v);
di_key_list_push(arena, &result.dbgi_keys, &copy);
}
}
txt_scope_close(txt_scope);
hs_scope_close(hs_scope);
scratch_end(scratch);
ProfEnd();
return result;
}
////////////////////////////////
//~ rjf: Watch Views
//- rjf: index -> column
internal DF_WatchViewColumn *
df_watch_view_column_from_x(DF_WatchViewState *wv, S64 index)
{
DF_WatchViewColumn *result = wv->first_column;
S64 idx = 0;
for(DF_WatchViewColumn *c = wv->first_column; c != 0; c = c->next, idx += 1)
{
result = c;
if(idx == index)
{
break;
}
}
return result;
}
//- rjf: watch view points <-> table coordinates
internal B32
df_watch_view_point_match(DF_WatchViewPoint a, DF_WatchViewPoint b)
{
return (a.x == b.x &&
ev_key_match(a.parent_key, b.parent_key) &&
ev_key_match(a.key, b.key));
}
internal DF_WatchViewPoint
df_watch_view_point_from_tbl(EV_BlockList *blocks, Vec2S64 tbl)
{
DF_WatchViewPoint pt = zero_struct;
pt.x = tbl.x;
pt.key = ev_key_from_block_list_row_num(blocks, tbl.y);
pt.parent_key = ev_parent_key_from_block_list_row_num(blocks, tbl.y);
return pt;
}
internal Vec2S64
df_tbl_from_watch_view_point(EV_BlockList *blocks, DF_WatchViewPoint pt)
{
Vec2S64 tbl = {0};
tbl.x = pt.x;
tbl.y = ev_row_num_from_block_list_key(blocks, pt.key);
return tbl;
}
//- rjf: table coordinates -> strings
internal String8
df_string_from_eval_viz_row_column(Arena *arena, EV_View *ev, EV_Row *row, DF_WatchViewColumn *col, B32 editable, U32 default_radix, FNT_Tag font, F32 font_size, F32 max_size_px)
{
String8 result = {0};
switch(col->kind)
{
default:{}break;
case DF_WatchViewColumnKind_Expr:
{
result = ev_expr_string_from_row(arena, row);
}break;
case DF_WatchViewColumnKind_Value:
{
E_Eval eval = e_eval_from_expr(arena, row->expr);
result = df_value_string_from_eval(arena, !editable * EV_StringFlag_ReadOnlyDisplayRules, default_radix, font, font_size, max_size_px, eval, row->member, row->view_rules);
}break;
case DF_WatchViewColumnKind_Type:
{
E_IRTreeAndType irtree = e_irtree_and_type_from_expr(arena, row->expr);
E_TypeKey type_key = irtree.type_key;
result = !e_type_key_match(type_key, e_type_key_zero()) ? e_type_string_from_key(arena, type_key) : str8_zero();
result = str8_skip_chop_whitespace(result);
}break;
case DF_WatchViewColumnKind_ViewRule:
{
result = ev_view_rule_from_key(ev, row->key);
}break;
case DF_WatchViewColumnKind_Module:
{
E_Eval eval = e_eval_from_expr(arena, row->expr);
CTRL_Entity *process = ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->process);
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, eval.value.u64);
result = push_str8_copy(arena, module->string);
}break;
case DF_WatchViewColumnKind_Member:
{
E_Expr *expr = e_expr_ref_member_access(arena, row->expr, str8(col->string_buffer, col->string_size));
E_Eval eval = e_eval_from_expr(arena, expr);
result = df_value_string_from_eval(arena, !editable * EV_StringFlag_ReadOnlyDisplayRules, default_radix, font, font_size, max_size_px, eval, row->member, row->view_rules);
}break;
}
if(col->dequote_string &&
result.size >= 2 &&
result.str[0] == '"' &&
result.str[result.size-1] == '"')
{
result = str8_skip(str8_chop(result, 1), 1);
}
return result;
}
//- rjf: table coordinates -> text edit state
internal DF_WatchViewTextEditState *
df_watch_view_text_edit_state_from_pt(DF_WatchViewState *wv, DF_WatchViewPoint pt)
{
DF_WatchViewTextEditState *result = &wv->dummy_text_edit_state;
if(wv->text_edit_state_slots_count != 0 && wv->text_editing != 0)
{
U64 hash = ev_hash_from_key(pt.key);
U64 slot_idx = hash%wv->text_edit_state_slots_count;
for(DF_WatchViewTextEditState *s = wv->text_edit_state_slots[slot_idx]; s != 0; s = s->pt_hash_next)
{
if(df_watch_view_point_match(pt, s->pt))
{
result = s;
break;
}
}
}
return result;
}
//- rjf: watch view column state mutation
internal DF_WatchViewColumn *
df_watch_view_column_alloc_(DF_WatchViewState *wv, DF_WatchViewColumnKind kind, F32 pct, DF_WatchViewColumnParams *params)
{
if(!wv->free_column)
{
DF_WatchViewColumn *col = push_array(wv->column_arena, DF_WatchViewColumn, 1);
SLLStackPush(wv->free_column, col);
}
DF_WatchViewColumn *col = wv->free_column;
SLLStackPop(wv->free_column);
DLLPushBack(wv->first_column, wv->last_column, col);
wv->column_count += 1;
col->kind = kind;
col->pct = pct;
col->string_size = Min(sizeof(col->string_buffer), params->string.size);
MemoryCopy(col->string_buffer, params->string.str, col->string_size);
col->display_string_size = Min(sizeof(col->display_string_buffer), params->display_string.size);
MemoryCopy(col->display_string_buffer, params->display_string.str, col->display_string_size);
col->view_rule_size = Min(sizeof(col->view_rule_buffer), params->view_rule.size);
MemoryCopy(col->view_rule_buffer, params->view_rule.str, col->view_rule_size);
col->is_non_code = params->is_non_code;
col->dequote_string = params->dequote_string;
return col;
}
internal void
df_watch_view_column_release(DF_WatchViewState *wv, DF_WatchViewColumn *col)
{
DLLRemove(wv->first_column, wv->last_column, col);
SLLStackPush(wv->free_column, col);
wv->column_count -= 1;
}
//- rjf: watch view main hooks
internal void
df_watch_view_init(DF_WatchViewState *ewv, DF_View *view, DF_WatchViewFillKind fill_kind)
{
if(ewv->initialized == 0)
{
ewv->initialized = 1;
ewv->fill_kind = fill_kind;
ewv->column_arena = df_view_push_arena_ext(view);
ewv->text_edit_arena = df_view_push_arena_ext(view);
}
}
internal void
df_watch_view_build(DF_View *view, DF_WatchViewState *ewv, B32 modifiable, U32 default_radix, Rng2F32 rect)
{
ProfBeginFunction();
DI_Scope *di_scope = di_scope_open();
FZY_Scope *fzy_scope = fzy_scope_open();
Temp scratch = scratch_begin(0, 0);
//////////////////////////////
//- rjf: unpack arguments
//
FNT_Tag code_font = df_font_from_slot(DF_FontSlot_Code);
String8 eval_view_key_string = push_str8f(scratch.arena, "eval_view_watch_%p", ewv);
EV_View *eval_view = df_ev_view_from_key(d_hash_from_string(eval_view_key_string));
String8 filter = str8(view->query_buffer, view->query_string_size);
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
S64 num_possible_visible_rows = (S64)(dim_2f32(rect).y/row_height_px);
D_EntityKind mutable_entity_kind = D_EntityKind_Nil;
F32 row_string_max_size_px = dim_2f32(rect).x;
//////////////////////////////
//- rjf: determine autocompletion string
//
String8 autocomplete_hint_string = {0};
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->kind == UI_EventKind_AutocompleteHint)
{
autocomplete_hint_string = evt->string;
break;
}
}
}
//////////////////////////////
//- rjf: consume events & perform navigations/edits - calculate state
//
typedef struct FrameRow FrameRow;
struct FrameRow
{
void *regs;
RDI_Parsed *rdi;
RDI_Procedure *procedure;
RDI_InlineSite *inline_site;
U64 unwind_idx;
U64 inline_depth;
};
U64 frame_rows_count = 0;
FrameRow *frame_rows = 0;
EV_ViewRuleList top_level_view_rules = {0};
EV_BlockList blocks = {0};
UI_ScrollListRowBlockArray row_blocks = {0};
Vec2S64 cursor_tbl = {0};
Vec2S64 mark_tbl = {0};
Rng2S64 selection_tbl = {0};
UI_Focus(UI_FocusKind_On)
{
B32 state_dirty = 1;
B32 snap_to_cursor = 0;
B32 cursor_dirty__tbl = 0;
B32 take_autocomplete = 0;
for(UI_Event *event = 0;;)
{
//////////////////////////
//- rjf: state -> viz blocks
//
if(state_dirty)
{
MemoryZeroStruct(&blocks);
String8 filter = str8(view->query_buffer, view->query_string_size);
RDI_SectionKind fzy_target = RDI_SectionKind_UDTs;
switch(ewv->fill_kind)
{
////////////////////////////
//- rjf: watch fill -> build blocks from top-level watch expressions
//
default:
case DF_WatchViewFillKind_Watch:
{
mutable_entity_kind = D_EntityKind_Watch;
D_EntityList watches = d_query_cached_entity_list_with_kind(mutable_entity_kind);
for(D_EntityNode *n = watches.first; n != 0; n = n->next)
{
D_Entity *watch = n->entity;
if(watch->flags & D_EntityFlag_MarkedForDeletion)
{
continue;
}
EV_Key parent_key = d_parent_ev_key_from_entity(watch);
EV_Key key = d_ev_key_from_entity(watch);
D_Entity *view_rule = d_entity_child_from_kind(watch, D_EntityKind_ViewRule);
ev_key_set_view_rule(eval_view, key, view_rule->string);
String8 expr_string = watch->string;
FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, filter, expr_string);
if(matches.count == matches.needle_part_count)
{
EV_BlockList watch_blocks = ev_block_list_from_view_expr_keys(scratch.arena, eval_view, &top_level_view_rules, expr_string, parent_key, key);
ev_block_list_concat__in_place(&blocks, &watch_blocks);
}
}
}break;
////////////////////////////
//- rjf: breakpoint fill -> build blocks from all breakpoints
//
case DF_WatchViewFillKind_Breakpoints:
{
mutable_entity_kind = D_EntityKind_Breakpoint;
ev_view_rule_list_push_string(scratch.arena, &top_level_view_rules, str8_lit("no_addr"));
D_EntityList bps = d_query_cached_entity_list_with_kind(mutable_entity_kind);
for(D_EntityNode *n = bps.first; n != 0; n = n->next)
{
D_Entity *bp = n->entity;
if(bp->flags & D_EntityFlag_MarkedForDeletion)
{
continue;
}
EV_Key parent_key = d_parent_ev_key_from_entity(bp);
EV_Key key = d_ev_key_from_entity(bp);
String8 title = d_display_string_from_entity(scratch.arena, bp);
FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, filter, title);
if(matches.count == matches.needle_part_count)
{
E_MemberList bp_members = {0};
{
e_member_list_push_new(scratch.arena, &bp_members, .name = str8_lit("enabled"), .off = 0, .type_key = e_type_key_basic(E_TypeKind_S64));
e_member_list_push_new(scratch.arena, &bp_members, .name = str8_lit("hit_count"),.off = 0+8, .type_key = e_type_key_basic(E_TypeKind_U64));
e_member_list_push_new(scratch.arena, &bp_members, .name = str8_lit("label"), .off = 0+8+8, .type_key = e_type_key_cons_ptr(arch_from_context(), e_type_key_basic(E_TypeKind_Char8)));
e_member_list_push_new(scratch.arena, &bp_members, .name = str8_lit("location"), .off = 0+8+8+8, .type_key = e_type_key_cons_ptr(arch_from_context(), e_type_key_basic(E_TypeKind_Char8)));
e_member_list_push_new(scratch.arena, &bp_members, .name = str8_lit("condition"),.off = 0+8+8+8+8,.type_key = e_type_key_cons_ptr(arch_from_context(), e_type_key_basic(E_TypeKind_Char8)));
}
E_MemberArray bp_members_array = e_member_array_from_list(scratch.arena, &bp_members);
E_TypeKey bp_type = e_type_key_cons(.arch = arch_from_context(), .kind = E_TypeKind_Struct, .name = str8_lit("Breakpoint"), .members = bp_members_array.v, .count = bp_members_array.count);
E_Expr *bp_expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
bp_expr->type_key = bp_type;
bp_expr->mode = E_Mode_Offset;
bp_expr->space = d_eval_space_from_entity(bp);
ev_append_expr_blocks__rec(scratch.arena, eval_view, parent_key, key, title, bp_expr, &top_level_view_rules, 0, &blocks);
}
}
}break;
////////////////////////////
//- rjf: watch pin fill -> build blocks from all watch pins
//
case DF_WatchViewFillKind_WatchPins:
{
mutable_entity_kind = D_EntityKind_WatchPin;
D_EntityList wps = d_query_cached_entity_list_with_kind(mutable_entity_kind);
for(D_EntityNode *n = wps.first; n != 0; n = n->next)
{
D_Entity *wp = n->entity;
if(wp->flags & D_EntityFlag_MarkedForDeletion)
{
continue;
}
EV_Key parent_key = d_parent_ev_key_from_entity(wp);
EV_Key key = d_ev_key_from_entity(wp);
String8 title = wp->string;
FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, filter, title);
if(matches.count == matches.needle_part_count)
{
E_MemberList wp_members = {0};
{
e_member_list_push_new(scratch.arena, &wp_members, .name = str8_lit("Location"), .off = 0, .type_key = e_type_key_cons_array(e_type_key_basic(E_TypeKind_Char8), 256));
}
E_MemberArray wp_members_array = e_member_array_from_list(scratch.arena, &wp_members);
E_TypeKey wp_type = e_type_key_cons(.arch = arch_from_context(), .kind = E_TypeKind_Struct, .name = str8_lit("Watch Pin"), .members = wp_members_array.v, .count = wp_members_array.count);
E_Expr *wp_expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
wp_expr->type_key = wp_type;
wp_expr->mode = E_Mode_Offset;
wp_expr->space = d_eval_space_from_entity(wp);
ev_append_expr_blocks__rec(scratch.arena, eval_view, parent_key, key, title, wp_expr, &top_level_view_rules, 0, &blocks);
}
}
}break;
////////////////////////////
//- rjf: call stack fill -> build blocks for each call frame
//
case DF_WatchViewFillKind_CallStack:
{
//- rjf: produce per-row info for callstack
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->thread);
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process);
CTRL_Unwind base_unwind = d_query_cached_unwind_from_thread(thread);
D_Unwind rich_unwind = d_unwind_from_ctrl_unwind(scratch.arena, di_scope, process, &base_unwind);
frame_rows_count = rich_unwind.frames.total_frame_count;
frame_rows = push_array(scratch.arena, FrameRow, frame_rows_count);
{
U64 concrete_frame_idx = 0;
U64 row_idx = 0;
for(;concrete_frame_idx < rich_unwind.frames.concrete_frame_count; concrete_frame_idx += 1, row_idx += 1)
{
D_UnwindFrame *f = &rich_unwind.frames.v[concrete_frame_idx];
// rjf: fill frame_rows for inline frames
{
U64 inline_unwind_idx = 0;
for(D_UnwindInlineFrame *fin = f->last_inline_frame; fin != 0; fin = fin->prev, row_idx += 1, inline_unwind_idx += 1)
{
frame_rows[row_idx].regs = f->regs;
frame_rows[row_idx].rdi = f->rdi;
frame_rows[row_idx].inline_site = fin->inline_site;
frame_rows[row_idx].unwind_idx = concrete_frame_idx;
frame_rows[row_idx].inline_depth = f->inline_frame_count - inline_unwind_idx;
}
}
// rjf: fill row for concrete frame
{
frame_rows[row_idx].regs = f->regs;
frame_rows[row_idx].rdi = f->rdi;
frame_rows[row_idx].procedure = f->procedure;
frame_rows[row_idx].unwind_idx= concrete_frame_idx;
}
}
}
//- rjf: build viz blocks
for(U64 row_idx = 0; row_idx < frame_rows_count; row_idx += 1)
{
FrameRow *row = &frame_rows[row_idx];
EV_Key parent_key = ev_key_make(5381, 0);
EV_Key key = ev_key_make(ev_hash_from_key(parent_key), row_idx+1);
EV_Block *block = ev_block_begin(scratch.arena, EV_BlockKind_Root, parent_key, key, 0);
{
E_TypeKey type_key = zero_struct;
if(row->procedure != 0)
{
type_key = e_type_key_ext(E_TypeKind_Function, row->procedure->type_idx, e_parse_ctx_module_idx_from_rdi(row->rdi));
}
else if(row->inline_site != 0)
{
type_key = e_type_key_ext(E_TypeKind_Function, row->inline_site->type_idx, e_parse_ctx_module_idx_from_rdi(row->rdi));
}
U64 row_vaddr = regs_rip_from_arch_block(thread->arch, row->regs);
E_OpList ops = {0};
e_oplist_push_op(scratch.arena, &ops, RDI_EvalOp_ConstU64, e_value_u64(row_vaddr));
String8 bytecode = e_bytecode_from_oplist(scratch.arena, &ops);
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafBytecode, 0);
expr->bytecode = bytecode;
expr->mode = E_Mode_Value;
expr->space = d_eval_space_from_ctrl_entity(process);
expr->type_key = type_key;
block->expr = expr;
block->visual_idx_range = r1u64(row_idx, row_idx+1);
block->semantic_idx_range = r1u64(row_idx, row_idx+1);
}
ev_block_end(&blocks, block);
}
}break;
////////////////////////////
//- rjf: registers fill -> build blocks via iterating all registers/aliases as root-level expressions
//
case DF_WatchViewFillKind_Registers:
{
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->thread);
Arch arch = thread->arch;
U64 reg_count = regs_reg_code_count_from_arch(arch);
String8 *reg_strings = regs_reg_code_string_table_from_arch(arch);
U64 alias_count = regs_alias_code_count_from_arch(arch);
String8 *alias_strings = regs_alias_code_string_table_from_arch(arch);
U64 num = 1;
for(U64 reg_idx = 1; reg_idx < reg_count; reg_idx += 1, num += 1)
{
String8 root_expr_string = push_str8f(scratch.arena, "reg:%S", reg_strings[reg_idx]);
FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, filter, root_expr_string);
if(matches.count == matches.needle_part_count)
{
EV_Key parent_key = ev_key_make(5381, 0);
EV_Key key = ev_key_make(ev_hash_from_key(parent_key), num);
EV_BlockList root_blocks = ev_block_list_from_view_expr_keys(scratch.arena, eval_view, &top_level_view_rules, root_expr_string, parent_key, key);
ev_block_list_concat__in_place(&blocks, &root_blocks);
}
}
for(U64 alias_idx = 1; alias_idx < alias_count; alias_idx += 1, num += 1)
{
String8 root_expr_string = push_str8f(scratch.arena, "reg:%S", alias_strings[alias_idx]);
FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, filter, root_expr_string);
if(matches.count == matches.needle_part_count)
{
EV_Key parent_key = ev_key_make(5381, 0);
EV_Key key = ev_key_make(ev_hash_from_key(parent_key), num);
EV_BlockList root_blocks = ev_block_list_from_view_expr_keys(scratch.arena, eval_view, &top_level_view_rules, root_expr_string, parent_key, key);
ev_block_list_concat__in_place(&blocks, &root_blocks);
}
}
}break;
////////////////////////////
//- rjf: locals fill -> build blocks via iterating all locals as root-level expressions
//
case DF_WatchViewFillKind_Locals:
{
E_String2NumMapNodeArray nodes = e_string2num_map_node_array_from_map(scratch.arena, e_parse_ctx->locals_map);
e_string2num_map_node_array_sort__in_place(&nodes);
for(U64 idx = 0; idx < nodes.count; idx += 1)
{
E_String2NumMapNode *n = nodes.v[idx];
String8 root_expr_string = n->string;
FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, filter, root_expr_string);
if(matches.count == matches.needle_part_count)
{
EV_Key parent_key = ev_key_make(5381, 0);
EV_Key key = ev_key_make(ev_hash_from_key(parent_key), idx+1);
EV_BlockList root_blocks = ev_block_list_from_view_expr_keys(scratch.arena, eval_view, &top_level_view_rules, root_expr_string, parent_key, key);
ev_block_list_concat__in_place(&blocks, &root_blocks);
}
}
}break;
////////////////////////////
//- rjf: debug info table fill -> build split debug info table blocks
//
case DF_WatchViewFillKind_Globals: fzy_target = RDI_SectionKind_GlobalVariables; goto dbgi_table;
case DF_WatchViewFillKind_ThreadLocals: fzy_target = RDI_SectionKind_ThreadVariables; goto dbgi_table;
case DF_WatchViewFillKind_Types: fzy_target = RDI_SectionKind_UDTs; goto dbgi_table;
case DF_WatchViewFillKind_Procedures: fzy_target = RDI_SectionKind_Procedures; goto dbgi_table;
dbgi_table:;
{
U64 endt_us = os_now_microseconds()+200;
//- rjf: unpack context
DI_KeyList dbgi_keys_list = d_push_active_dbgi_key_list(scratch.arena);
DI_KeyArray dbgi_keys = di_key_array_from_list(scratch.arena, &dbgi_keys_list);
U64 rdis_count = dbgi_keys.count;
RDI_Parsed **rdis = push_array(scratch.arena, RDI_Parsed *, rdis_count);
for(U64 idx = 0; idx < rdis_count; idx += 1)
{
rdis[idx] = di_rdi_from_key(di_scope, &dbgi_keys.v[idx], endt_us);
}
//- rjf: calculate top-level keys, expand root-level, grab root expansion node
EV_Key parent_key = ev_key_make(5381, 0);
EV_Key root_key = ev_key_make(ev_hash_from_key(parent_key), 0);
ev_key_set_expansion(eval_view, ev_key_zero(), parent_key, 1);
EV_ExpandNode *root_node = ev_expand_node_from_key(eval_view, parent_key);
//- rjf: query all filtered items from dbgi searching system
U128 fuzzy_search_key = {(U64)view, d_hash_from_string(str8_struct(&view))};
B32 items_stale = 0;
FZY_Params params = {fzy_target, dbgi_keys};
FZY_ItemArray items = fzy_items_from_key_params_query(fzy_scope, fuzzy_search_key, &params, filter, endt_us, &items_stale);
if(items_stale)
{
df_request_frame();
}
//- rjf: gather unsorted child expansion keys
//
// Nodes are sorted in the underlying expansion tree data structure, but
// ONLY by THEIR ORDER IN THE UNDERLYING DEBUG INFO TABLE. This is
// because debug info watch rows use the DEBUG INFO TABLE INDEX to form
// their key - this provides more stable/predictable behavior as rows
// are reordered, filtered, and shuffled around, as the user filters.
//
// When we actually build viz blocks, however, we want to produce viz
// blocks BY THE ORDER OF SUB-EXPANSIONS IN THE FILTERED ITEM ARRAY
// SPACE, so that all of the expansions come out in the right order.
//
EV_Key *sub_expand_keys = 0;
U64 *sub_expand_item_idxs = 0;
U64 sub_expand_keys_count = 0;
{
for(EV_ExpandNode *child = root_node->first; child != 0; child = child->next)
{
sub_expand_keys_count += 1;
}
sub_expand_keys = push_array(scratch.arena, EV_Key, sub_expand_keys_count);
sub_expand_item_idxs = push_array(scratch.arena, U64, sub_expand_keys_count);
U64 idx = 0;
for(EV_ExpandNode *child = root_node->first; child != 0; child = child->next)
{
U64 item_num = fzy_item_num_from_array_element_idx__linear_search(&items, child->key.child_num);
if(item_num != 0)
{
sub_expand_keys[idx] = child->key;
sub_expand_item_idxs[idx] = item_num-1;
idx += 1;
}
else
{
sub_expand_keys_count -= 1;
}
}
}
//- rjf: sort child expansion keys
{
for(U64 idx1 = 0; idx1 < sub_expand_keys_count; idx1 += 1)
{
U64 min_idx2 = 0;
U64 min_item_idx = sub_expand_item_idxs[idx1];
for(U64 idx2 = idx1+1; idx2 < sub_expand_keys_count; idx2 += 1)
{
if(sub_expand_item_idxs[idx2] < min_item_idx)
{
min_idx2 = idx2;
min_item_idx = sub_expand_item_idxs[idx2];
}
}
if(min_idx2 != 0)
{
Swap(EV_Key, sub_expand_keys[idx1], sub_expand_keys[min_idx2]);
Swap(U64, sub_expand_item_idxs[idx1], sub_expand_item_idxs[min_idx2]);
}
}
}
//- rjf: build blocks for all table items, split by sorted sub-expansions
EV_Block *last_vb = ev_block_begin(scratch.arena, EV_BlockKind_DebugInfoTable, parent_key, root_key, 0);
{
last_vb->visual_idx_range = last_vb->semantic_idx_range = r1u64(0, items.count);
last_vb->fzy_target = fzy_target;
last_vb->fzy_backing_items = items;
}
for(U64 sub_expand_idx = 0; sub_expand_idx < sub_expand_keys_count; sub_expand_idx += 1)
{
FZY_Item *item = &items.v[sub_expand_item_idxs[sub_expand_idx]];
E_Expr *child_expr = ev_expr_from_block_index(scratch.arena, last_vb, sub_expand_item_idxs[sub_expand_idx]);
// rjf: form split: truncate & complete last block; begin next block
last_vb = ev_block_split_and_continue(scratch.arena, &blocks, last_vb, sub_expand_item_idxs[sub_expand_idx]);
// rjf: build child view rules
EV_ViewRuleList *child_view_rules = ev_view_rule_list_from_inheritance(scratch.arena, &top_level_view_rules);
{
String8 view_rule_string = ev_view_rule_from_key(eval_view, sub_expand_keys[sub_expand_idx]);
if(view_rule_string.size != 0)
{
ev_view_rule_list_push_string(scratch.arena, child_view_rules, view_rule_string);
}
}
// rjf: recurse for child
ev_append_expr_blocks__rec(scratch.arena, eval_view, parent_key, sub_expand_keys[sub_expand_idx], str8_zero(), child_expr, child_view_rules, 0, &blocks);
}
ev_block_end(&blocks, last_vb);
}break;
}
}
//////////////////////////
//- rjf: does this eval watch view allow mutation? -> add extra block for editable empty row
//
EV_Key empty_row_parent_key = ev_key_make(max_U64, max_U64);
EV_Key empty_row_key = ev_key_make(ev_hash_from_key(empty_row_parent_key), 1);
if(state_dirty && modifiable)
{
EV_Block *b = ev_block_begin(scratch.arena, EV_BlockKind_Null, empty_row_parent_key, empty_row_key, 0);
b->visual_idx_range = b->semantic_idx_range = r1u64(0, 1);
ev_block_end(&blocks, b);
}
//////////////////////////
//- rjf: viz blocks -> ui row blocks
//
{
UI_ScrollListRowBlockChunkList row_block_chunks = {0};
for(EV_BlockNode *n = blocks.first; n != 0; n = n->next)
{
EV_Block *vb = &n->v;
UI_ScrollListRowBlock block = {0};
block.row_count = dim_1u64(vb->visual_idx_range);
block.item_count = dim_1u64(vb->semantic_idx_range);
ui_scroll_list_row_block_chunk_list_push(scratch.arena, &row_block_chunks, 256, &block);
}
row_blocks = ui_scroll_list_row_block_array_from_chunk_list(scratch.arena, &row_block_chunks);
}
//////////////////////////
//- rjf: conclude state update
//
if(state_dirty)
{
state_dirty = 0;
}
//////////////////////////////
//- rjf: 2D table coordinates * blocks -> stable cursor state
//
if(cursor_dirty__tbl)
{
cursor_dirty__tbl = 0;
struct
{
DF_WatchViewPoint *pt_state;
Vec2S64 pt_tbl;
}
points[] =
{
{&ewv->cursor, cursor_tbl},
{&ewv->mark, mark_tbl},
};
for(U64 point_idx = 0; point_idx < ArrayCount(points); point_idx += 1)
{
EV_Key last_key = points[point_idx].pt_state->key;
EV_Key last_parent_key = points[point_idx].pt_state->parent_key;
points[point_idx].pt_state[0] = df_watch_view_point_from_tbl(&blocks, points[point_idx].pt_tbl);
if(ev_key_match(ev_key_zero(), points[point_idx].pt_state->key))
{
points[point_idx].pt_state->key = last_parent_key;
EV_ExpandNode *node = ev_expand_node_from_key(eval_view, last_parent_key);
for(EV_ExpandNode *n = node; n != 0; n = n->parent)
{
points[point_idx].pt_state->key = n->key;
if(n->expanded == 0)
{
break;
}
}
}
if(point_idx == 0 &&
(!ev_key_match(ewv->cursor.key, last_key) ||
!ev_key_match(ewv->cursor.parent_key, last_parent_key)))
{
ewv->text_editing = 0;
}
}
ewv->next_cursor = ewv->cursor;
ewv->next_mark = ewv->mark;
}
//////////////////////////
//- rjf: stable cursor state * blocks -> 2D table coordinates
//
{
cursor_tbl = df_tbl_from_watch_view_point(&blocks, ewv->cursor);
mark_tbl = df_tbl_from_watch_view_point(&blocks, ewv->mark);
selection_tbl = r2s64p(Min(cursor_tbl.x, mark_tbl.x), Min(cursor_tbl.y, mark_tbl.y),
Max(cursor_tbl.x, mark_tbl.x), Max(cursor_tbl.y, mark_tbl.y));
}
//////////////////////////
//- rjf: [table] snap to cursor
//
if(snap_to_cursor)
{
Rng1S64 item_range = r1s64(0, 1 + blocks.total_visual_row_count);
Rng1S64 scroll_row_idx_range = r1s64(item_range.min, ClampBot(item_range.min, item_range.max-1));
S64 cursor_item_idx = cursor_tbl.y-1;
if(item_range.min <= cursor_item_idx && cursor_item_idx <= item_range.max)
{
UI_ScrollPt *scroll_pt = &view->scroll_pos.y;
//- rjf: compute visible row range
Rng1S64 visible_row_range = r1s64(scroll_pt->idx + 0 - !!(scroll_pt->off < 0),
scroll_pt->idx + 0 + num_possible_visible_rows + 1);
//- rjf: compute cursor row range from cursor item
Rng1S64 cursor_visibility_row_range = {0};
if(row_blocks.count == 0)
{
cursor_visibility_row_range = r1s64(cursor_item_idx-1, cursor_item_idx+3);
}
else
{
cursor_visibility_row_range.min = (S64)ui_scroll_list_row_from_item(&row_blocks, (U64)cursor_item_idx);
cursor_visibility_row_range.max = cursor_visibility_row_range.min + 4;
}
//- rjf: compute deltas & apply
S64 min_delta = Min(0, cursor_visibility_row_range.min-visible_row_range.min);
S64 max_delta = Max(0, cursor_visibility_row_range.max-visible_row_range.max);
S64 new_idx = scroll_pt->idx+min_delta+max_delta;
new_idx = clamp_1s64(scroll_row_idx_range, new_idx);
ui_scroll_pt_target_idx(scroll_pt, new_idx);
}
}
//////////////////////////////
//- rjf: apply cursor/mark rugpull change
//
B32 cursor_rugpull = 0;
if(!df_watch_view_point_match(ewv->cursor, ewv->next_cursor))
{
cursor_rugpull = 1;
ewv->cursor = ewv->next_cursor;
ewv->mark = ewv->next_mark;
}
//////////////////////////
//- rjf: grab next event, if any - otherwise exit the loop, as we now have
// the most up-to-date state
//
B32 next_event_good = ui_next_event(&event);
if(!cursor_rugpull && (!next_event_good || !ui_is_focus_active()))
{
break;
}
UI_Event dummy_evt = zero_struct;
UI_Event *evt = &dummy_evt;
if(next_event_good)
{
evt = event;
}
B32 taken = 0;
//////////////////////////
//- rjf: begin editing on some operations
//
if(!ewv->text_editing &&
(evt->kind == UI_EventKind_Text ||
evt->flags & UI_EventFlag_Paste ||
(evt->kind == UI_EventKind_Press && evt->slot == UI_EventActionSlot_Edit)) &&
selection_tbl.min.x == selection_tbl.max.x &&
(selection_tbl.min.y != 0 || selection_tbl.min.y != 0))
{
Vec2S64 selection_dim = dim_2s64(selection_tbl);
ewv->text_editing = 1;
arena_clear(ewv->text_edit_arena);
ewv->text_edit_state_slots_count = u64_up_to_pow2(selection_dim.y+1);
ewv->text_edit_state_slots_count = Max(ewv->text_edit_state_slots_count, 64);
ewv->text_edit_state_slots = push_array(ewv->text_edit_arena, DF_WatchViewTextEditState*, ewv->text_edit_state_slots_count);
EV_WindowedRowList rows = ev_windowed_row_list_from_block_list(scratch.arena, eval_view, r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1),
ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks);
EV_Row *row = rows.first;
for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1, row = row->next)
{
for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1)
{
DF_WatchViewColumn *col = df_watch_view_column_from_x(ewv, x);
String8 string = df_string_from_eval_viz_row_column(scratch.arena, eval_view, row, col, 1, default_radix, ui_top_font(), ui_top_font_size(), row_string_max_size_px);
string.size = Min(string.size, sizeof(ewv->dummy_text_edit_state.input_buffer));
DF_WatchViewPoint pt = {x, row->parent_key, row->key};
U64 hash = ev_hash_from_key(pt.key);
U64 slot_idx = hash%ewv->text_edit_state_slots_count;
DF_WatchViewTextEditState *edit_state = push_array(ewv->text_edit_arena, DF_WatchViewTextEditState, 1);
SLLStackPush_N(ewv->text_edit_state_slots[slot_idx], edit_state, pt_hash_next);
edit_state->pt = pt;
edit_state->cursor = txt_pt(1, string.size+1);
edit_state->mark = txt_pt(1, 1);
edit_state->input_size = string.size;
MemoryCopy(edit_state->input_buffer, string.str, string.size);
edit_state->initial_size = string.size;
MemoryCopy(edit_state->initial_buffer, string.str, string.size);
}
}
}
//////////////////////////
//- rjf: [table] do cell-granularity expansions / tab-opens
//
if(!ewv->text_editing && evt->slot == UI_EventActionSlot_Accept)
{
taken = 1;
EV_WindowedRowList rows = ev_windowed_row_list_from_block_list(scratch.arena, eval_view, r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1),
ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks);
EV_Row *row = rows.first;
for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row != 0; y += 1, row = row->next)
{
if(selection_tbl.min.x <= 0 && ev_row_is_expandable(row))
{
B32 is_expanded = ev_expansion_from_key(eval_view, row->key);
ev_key_set_expansion(eval_view, row->parent_key, row->key, !is_expanded);
}
DF_ViewRuleSpec *block_ui_rule_spec = &df_nil_view_rule_spec;
MD_Node *block_ui_rule_root = &md_nil_node;
for(EV_ViewRuleNode *n = row->view_rules->first; n != 0; n = n->next)
{
DF_ViewRuleSpec *spec = df_view_rule_spec_from_string(n->v.root->string);
if(spec->info.flags & DF_ViewRuleSpecInfoFlag_ViewUI)
{
block_ui_rule_spec = spec;
block_ui_rule_root = n->v.root;
}
}
if(block_ui_rule_spec != &df_nil_view_rule_spec)
{
df_cmd(DF_CmdKind_OpenTab,
.string = e_string_from_expr(scratch.arena, row->expr),
.params_tree = block_ui_rule_root);
}
}
}
//////////////////////////
//- rjf: [table] do cell-granularity go-to-locations / frame selections
//
if(!ewv->text_editing && evt->slot == UI_EventActionSlot_Accept &&
selection_tbl.min.x == selection_tbl.max.x &&
selection_tbl.min.y == selection_tbl.max.y &&
selection_tbl.min.x == 1)
{
taken = 1;
EV_WindowedRowList rows = ev_windowed_row_list_from_block_list(scratch.arena, eval_view, r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1),
ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks);
EV_Row *row = rows.first;
B32 row_is_editable = ev_row_is_editable(row);
if(!row_is_editable)
{
E_Eval eval = e_eval_from_expr(scratch.arena, row->expr);
U64 vaddr = eval.value.u64;
CTRL_Entity *eval_space_ctrl_entity = d_ctrl_entity_from_eval_space(eval.space);
if(eval_space_ctrl_entity->kind == CTRL_EntityKind_Process)
{
CTRL_Entity *module = ctrl_module_from_process_vaddr(eval_space_ctrl_entity, vaddr);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
U64 voff = ctrl_voff_from_vaddr(module, vaddr);
D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff);
String8 file_path = {0};
TxtPt pt = {0};
if(lines.first != 0)
{
file_path = lines.first->v.file_path;
pt = lines.first->v.pt;
}
df_cmd(DF_CmdKind_FindCodeLocation,
.process = eval_space_ctrl_entity->handle,
.vaddr = vaddr,
.file_path = file_path,
.cursor = pt);
}
}
if(1 <= selection_tbl.min.y && selection_tbl.min.y <= frame_rows_count)
{
FrameRow *frame_row = &frame_rows[selection_tbl.min.y-1];
df_cmd(DF_CmdKind_SelectUnwind,
.unwind_count = frame_row->unwind_idx,
.inline_depth = frame_row->inline_depth);
}
}
//////////////////////////
//- rjf: [text] apply textual edits
//
if(ewv->text_editing)
{
B32 editing_complete = ((evt->kind == UI_EventKind_Press && (evt->slot == UI_EventActionSlot_Cancel || evt->slot == UI_EventActionSlot_Accept)) ||
(evt->kind == UI_EventKind_Navigate && evt->delta_2s32.y != 0) ||
cursor_rugpull);
if(editing_complete ||
((evt->kind == UI_EventKind_Edit ||
evt->kind == UI_EventKind_Navigate ||
evt->kind == UI_EventKind_Text) &&
evt->delta_2s32.y == 0))
{
taken = 1;
for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1)
{
for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1)
{
DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, v2s64(x, y));
DF_WatchViewTextEditState *edit_state = df_watch_view_text_edit_state_from_pt(ewv, pt);
String8 string = str8(edit_state->input_buffer, edit_state->input_size);
UI_TxtOp op = ui_single_line_txt_op_from_event(scratch.arena, evt, string, edit_state->cursor, edit_state->mark);
// rjf: copy
if(op.flags & UI_TxtOpFlag_Copy && selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y)
{
os_set_clipboard_text(op.copy);
}
// rjf: any valid op & autocomplete hint? -> perform autocomplete first, then re-compute op
if(autocomplete_hint_string.size != 0)
{
take_autocomplete = 1;
String8 word_query = df_autocomp_query_word_from_input_string_off(string, edit_state->cursor.column-1);
U64 word_off = (U64)(word_query.str - string.str);
String8 new_string = ui_push_string_replace_range(scratch.arena, string, r1s64(word_off+1, word_off+1+word_query.size), autocomplete_hint_string);
new_string.size = Min(sizeof(edit_state->input_buffer), new_string.size);
MemoryCopy(edit_state->input_buffer, new_string.str, new_string.size);
edit_state->input_size = new_string.size;
edit_state->cursor = edit_state->mark = txt_pt(1, word_off+1+autocomplete_hint_string.size);
string = str8(edit_state->input_buffer, edit_state->input_size);
op = ui_single_line_txt_op_from_event(scratch.arena, evt, string, edit_state->cursor, edit_state->mark);
}
// rjf: cancel? -> revert to initial string
if(editing_complete && evt->slot == UI_EventActionSlot_Cancel)
{
string = str8(edit_state->initial_buffer, edit_state->initial_size);
}
// rjf: obtain edited string
String8 new_string = string;
if(!txt_pt_match(op.range.min, op.range.max) || op.replace.size != 0)
{
new_string = ui_push_string_replace_range(scratch.arena, string, r1s64(op.range.min.column, op.range.max.column), op.replace);
}
// rjf: commit to edit state
new_string.size = Min(new_string.size, sizeof(edit_state->input_buffer));
MemoryCopy(edit_state->input_buffer, new_string.str, new_string.size);
edit_state->input_size = new_string.size;
edit_state->cursor = op.cursor;
edit_state->mark = op.mark;
// rjf: commit edited cell string
Vec2S64 tbl = v2s64(x, y);
DF_WatchViewColumn *col = df_watch_view_column_from_x(ewv, x);
switch(col->kind)
{
default:{}break;
case DF_WatchViewColumnKind_Expr:
if(modifiable)
{
DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, tbl);
D_Entity *watch = d_entity_from_ev_key_and_kind(pt.key, mutable_entity_kind);
if(!d_entity_is_nil(watch))
{
d_entity_equip_name(watch, new_string);
state_dirty = 1;
snap_to_cursor = 1;
}
else if(editing_complete && new_string.size != 0 && ev_key_match(pt.key, empty_row_key))
{
watch = d_entity_alloc(d_entity_root(), mutable_entity_kind);
d_entity_equip_cfg_src(watch, D_CfgSrc_Project);
d_entity_equip_name(watch, new_string);
EV_Key key = d_ev_key_from_entity(watch);
ev_key_set_view_rule(eval_view, key, str8_zero());
state_dirty = 1;
snap_to_cursor = 1;
}
}break;
case DF_WatchViewColumnKind_Member:
case DF_WatchViewColumnKind_Value:
if(editing_complete && evt->slot != UI_EventActionSlot_Cancel)
{
EV_WindowedRowList rows = ev_windowed_row_list_from_block_list(scratch.arena, eval_view, r1s64(ui_scroll_list_row_from_item(&row_blocks, y-1),
ui_scroll_list_row_from_item(&row_blocks, y-1)+1), &blocks);
B32 success = 0;
if(rows.first != 0)
{
E_Expr *expr = rows.first->expr;
if(col->kind == DF_WatchViewColumnKind_Member)
{
expr = e_expr_ref_member_access(scratch.arena, expr, str8(col->string_buffer, col->string_size));
}
E_Eval dst_eval = e_eval_from_expr(scratch.arena, expr);
success = d_commit_eval_value_string(dst_eval, new_string);
}
if(!success)
{
log_user_error(str8_lit("Could not commit value successfully."));
}
}break;
case DF_WatchViewColumnKind_Type:{}break;
case DF_WatchViewColumnKind_ViewRule:
if(editing_complete)
{
DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, tbl);
ev_key_set_view_rule(eval_view, pt.key, new_string);
D_Entity *watch = d_entity_from_ev_key_and_kind(pt.key, mutable_entity_kind);
D_Entity *view_rule = d_entity_child_from_kind(watch, D_EntityKind_ViewRule);
if(new_string.size != 0 && d_entity_is_nil(view_rule))
{
view_rule = d_entity_alloc(watch, D_EntityKind_ViewRule);
}
else if(new_string.size == 0 && !d_entity_is_nil(view_rule))
{
d_entity_mark_for_deletion(view_rule);
}
if(new_string.size != 0)
{
d_entity_equip_name(view_rule, new_string);
}
state_dirty = 1;
snap_to_cursor = 1;
}break;
}
}
}
}
if(editing_complete)
{
ewv->text_editing = 0;
}
}
//////////////////////////
//- rjf: [table] do cell-granularity copies
//
if(!ewv->text_editing && evt->flags & UI_EventFlag_Copy)
{
taken = 1;
String8List strs = {0};
EV_WindowedRowList rows = ev_windowed_row_list_from_block_list(scratch.arena, eval_view, r1s64(ui_scroll_list_row_from_item(&row_blocks, selection_tbl.min.y-1),
ui_scroll_list_row_from_item(&row_blocks, selection_tbl.max.y-1)+1), &blocks);
EV_Row *row = rows.first;
for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row != 0; y += 1, row = row->next)
{
for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1)
{
DF_WatchViewColumn *col = df_watch_view_column_from_x(ewv, x);
String8 cell_string = df_string_from_eval_viz_row_column(scratch.arena, eval_view, row, col, 0, default_radix, ui_top_font(), ui_top_font_size(), row_string_max_size_px);
cell_string = str8_skip_chop_whitespace(cell_string);
U64 comma_pos = str8_find_needle(cell_string, 0, str8_lit(","), 0);
if(selection_tbl.min.x != selection_tbl.max.x || selection_tbl.min.y != selection_tbl.max.y)
{
str8_list_pushf(scratch.arena, &strs, "%s%S%s%s",
comma_pos < cell_string.size ? "\"" : "",
cell_string,
comma_pos < cell_string.size ? "\"" : "",
x+1 <= selection_tbl.max.x ? "," : "");
}
else
{
str8_list_push(scratch.arena, &strs, cell_string);
}
}
if(y+1 <= selection_tbl.max.y)
{
str8_list_push(scratch.arena, &strs, str8_lit("\n"));
}
}
String8 string = str8_list_join(scratch.arena, &strs, 0);
os_set_clipboard_text(string);
}
//////////////////////////
//- rjf: [table] do cell-granularity deletions
//
if(!ewv->text_editing && evt->flags & UI_EventFlag_Delete)
{
taken = 1;
state_dirty = 1;
snap_to_cursor = 1;
for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y; y += 1)
{
DF_WatchViewPoint pt = df_watch_view_point_from_tbl(&blocks, v2s64(0, y));
// rjf: row deletions
if(selection_tbl.min.x <= 0)
{
DF_WatchViewPoint fallback_pt_prev = df_watch_view_point_from_tbl(&blocks, v2s64(0, y - 1));
DF_WatchViewPoint fallback_pt_next = df_watch_view_point_from_tbl(&blocks, v2s64(0, y + 1));
D_Entity *watch = d_entity_from_ev_key_and_kind(pt.key, mutable_entity_kind);
if(!d_entity_is_nil(watch))
{
EV_Key new_cursor_key = empty_row_key;
EV_Key new_cursor_parent_key = empty_row_parent_key;
if((evt->delta_2s32.x < 0 || evt->delta_2s32.y < 0) && !ev_key_match(ev_key_zero(), fallback_pt_prev.key))
{
D_Entity *fallback_watch = d_entity_from_ev_key_and_kind(fallback_pt_prev.key, mutable_entity_kind);
if(!d_entity_is_nil(fallback_watch))
{
new_cursor_key = fallback_pt_prev.key;
new_cursor_parent_key = d_parent_ev_key_from_entity(fallback_watch);
}
}
else if(!ev_key_match(ev_key_zero(), fallback_pt_next.key))
{
D_Entity *fallback_watch = d_entity_from_ev_key_and_kind(fallback_pt_next.key, mutable_entity_kind);
if(!d_entity_is_nil(fallback_watch))
{
new_cursor_key = fallback_pt_next.key;
new_cursor_parent_key = d_parent_ev_key_from_entity(fallback_watch);
}
}
DF_WatchViewPoint new_cursor_pt = {0, new_cursor_parent_key, new_cursor_key};
d_entity_mark_for_deletion(watch);
ewv->cursor = ewv->mark = ewv->next_cursor = ewv->next_mark = new_cursor_pt;
}
}
// rjf: view rule deletions
else if(selection_tbl.min.x <= DF_WatchViewColumnKind_ViewRule && DF_WatchViewColumnKind_ViewRule <= selection_tbl.max.x)
{
D_Entity *watch = d_entity_from_ev_key_and_kind(pt.key, mutable_entity_kind);
D_Entity *view_rule = d_entity_child_from_kind(watch, D_EntityKind_ViewRule);
d_entity_mark_for_deletion(view_rule);
ev_key_set_view_rule(eval_view, pt.key, str8_zero());
}
}
}
//////////////////////////
//- rjf: [table] apply deltas to cursor & mark
//
if(!ewv->text_editing && !(evt->flags & UI_EventFlag_Delete) && !(evt->flags & UI_EventFlag_Reorder))
{
B32 cursor_tbl_min_is_empty_selection[Axis2_COUNT] = {0, 1};
Rng2S64 cursor_tbl_range = r2s64(v2s64(0, 0), v2s64(ewv->column_count-1, blocks.total_semantic_row_count));
Vec2S32 delta = evt->delta_2s32;
if(evt->flags & UI_EventFlag_PickSelectSide && !MemoryMatchStruct(&selection_tbl.min, &selection_tbl.max))
{
if(delta.x > 0 || delta.y > 0)
{
cursor_tbl.x = selection_tbl.max.x;
cursor_tbl.y = selection_tbl.max.y;
}
else if(delta.x < 0 || delta.y < 0)
{
cursor_tbl.x = selection_tbl.min.x;
cursor_tbl.y = selection_tbl.min.y;
}
}
if(evt->flags & UI_EventFlag_ZeroDeltaOnSelect && !MemoryMatchStruct(&selection_tbl.min, &selection_tbl.max))
{
MemoryZeroStruct(&delta);
}
B32 moved = 1;
switch(evt->delta_unit)
{
default:{moved = 0;}break;
case UI_EventDeltaUnit_Char:
{
for(EachEnumVal(Axis2, axis))
{
cursor_tbl.v[axis] += delta.v[axis];
if(cursor_tbl.v[axis] < cursor_tbl_range.min.v[axis])
{
cursor_tbl.v[axis] = cursor_tbl_range.max.v[axis];
}
if(cursor_tbl.v[axis] > cursor_tbl_range.max.v[axis])
{
cursor_tbl.v[axis] = cursor_tbl_range.min.v[axis];
}
cursor_tbl.v[axis] = clamp_1s64(r1s64(cursor_tbl_range.min.v[axis], cursor_tbl_range.max.v[axis]), cursor_tbl.v[axis]);
}
}break;
case UI_EventDeltaUnit_Word:
case UI_EventDeltaUnit_Line:
case UI_EventDeltaUnit_Page:
{
cursor_tbl.x = (delta.x>0 ? (cursor_tbl_range.max.x) :
delta.x<0 ? (cursor_tbl_range.min.x + !!cursor_tbl_min_is_empty_selection[Axis2_X]) :
cursor_tbl.x);
cursor_tbl.y += ((delta.y>0 ? +(num_possible_visible_rows-3) :
delta.y<0 ? -(num_possible_visible_rows-3) :
0));
cursor_tbl.y = clamp_1s64(r1s64(cursor_tbl_range.min.y + !!cursor_tbl_min_is_empty_selection[Axis2_Y],
cursor_tbl_range.max.y),
cursor_tbl.y);
}break;
case UI_EventDeltaUnit_Whole:
{
for(EachEnumVal(Axis2, axis))
{
cursor_tbl.v[axis] = (delta.v[axis]>0 ? cursor_tbl_range.max.v[axis] : delta.v[axis]<0 ? cursor_tbl_range.min.v[axis] + !!cursor_tbl_min_is_empty_selection[axis] : cursor_tbl.v[axis]);
}
}break;
}
if(moved)
{
taken = 1;
cursor_dirty__tbl = 1;
snap_to_cursor = 1;
}
}
//////////////////////////
//- rjf: [table] stick table mark to cursor if needed
//
if(!ewv->text_editing)
{
if(taken && !(evt->flags & UI_EventFlag_KeepMark))
{
mark_tbl = cursor_tbl;
}
}
//////////////////////////
//- rjf: [table] do cell-granularity reorders
//
if(!ewv->text_editing && evt->flags & UI_EventFlag_Reorder)
{
taken = 1;
EV_Key first_watch_key = ev_key_from_block_list_row_num(&blocks, selection_tbl.min.y);
EV_Key reorder_group_prev_watch_key = ev_key_from_block_list_row_num(&blocks, selection_tbl.min.y - 1);
EV_Key reorder_group_next_watch_key = ev_key_from_block_list_row_num(&blocks, selection_tbl.max.y + 1);
D_Entity *reorder_group_prev = d_entity_from_ev_key_and_kind(reorder_group_prev_watch_key, mutable_entity_kind);
D_Entity *reorder_group_next = d_entity_from_ev_key_and_kind(reorder_group_next_watch_key, mutable_entity_kind);
D_Entity *first_watch = d_entity_from_ev_key_and_kind(first_watch_key, mutable_entity_kind);
D_Entity *last_watch = first_watch;
if(!d_entity_is_nil(first_watch))
{
for(S64 y = selection_tbl.min.y+1; y <= selection_tbl.max.y; y += 1)
{
EV_Key key = ev_key_from_block_list_row_num(&blocks, y);
D_Entity *new_last = d_entity_from_ev_key_and_kind(key, mutable_entity_kind);
if(!d_entity_is_nil(new_last))
{
last_watch = new_last;
}
}
}
if(evt->delta_2s32.y < 0 && !d_entity_is_nil(first_watch) && !d_entity_is_nil(reorder_group_prev))
{
state_dirty = 1;
snap_to_cursor = 1;
d_entity_change_parent(reorder_group_prev, reorder_group_prev->parent, reorder_group_prev->parent, last_watch);
}
if(evt->delta_2s32.y > 0 && !d_entity_is_nil(last_watch) && !d_entity_is_nil(reorder_group_next))
{
state_dirty = 1;
snap_to_cursor = 1;
d_entity_change_parent(reorder_group_next, reorder_group_next->parent, reorder_group_next->parent, reorder_group_prev);
}
}
//////////////////////////
//- rjf: consume event, if taken
//
if(taken && evt != &dummy_evt)
{
ui_eat_event(evt);
}
}
if(take_autocomplete)
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->kind == UI_EventKind_AutocompleteHint)
{
ui_eat_event(evt);
break;
}
}
}
}
//////////////////////////////
//- rjf: build ui
//
F32 **col_pcts = push_array(scratch.arena, F32*, ewv->column_count);
{
S64 x = 0;
for(DF_WatchViewColumn *c = ewv->first_column; c != 0; c = c->next, x += 1)
{
col_pcts[x] = &c->pct;
}
}
B32 pressed = 0;
Rng1S64 visible_row_rng = {0};
UI_ScrollListParams scroll_list_params = {0};
{
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f);
scroll_list_params.dim_px = dim_2f32(rect);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, blocks.total_semantic_row_count));
scroll_list_params.item_range = r1s64(0, 1 + blocks.total_visual_row_count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
UI_ScrollListRowBlockChunkList row_block_chunks = {0};
for(EV_BlockNode *n = blocks.first; n != 0; n = n->next)
{
EV_Block *vb = &n->v;
UI_ScrollListRowBlock block = {0};
block.row_count = dim_1u64(vb->visual_idx_range);
block.item_count = dim_1u64(vb->semantic_idx_range);
ui_scroll_list_row_block_chunk_list_push(scratch.arena, &row_block_chunks, 256, &block);
}
scroll_list_params.row_blocks = ui_scroll_list_row_block_array_from_chunk_list(scratch.arena, &row_block_chunks);
}
UI_BoxFlags disabled_flags = ui_top_flags();
if(d_ctrl_targets_running())
{
disabled_flags |= UI_BoxFlag_Disabled;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params, &view->scroll_pos.y,
0,
0,
&visible_row_rng,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
UI_TableF(ewv->column_count, col_pcts, "table")
{
Vec2F32 scroll_list_view_off_px = ui_top_parent()->parent->view_off;
////////////////////////////
//- rjf: build table header
//
if(visible_row_rng.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
for(DF_WatchViewColumn *col = ewv->first_column; col != 0; col = col->next)
UI_TableCell
{
String8 name = str8(col->display_string_buffer, col->display_string_size);
if(name.size == 0)
{
switch(col->kind)
{
default:{}break;
case DF_WatchViewColumnKind_Expr: {name = str8_lit("Expression");}break;
case DF_WatchViewColumnKind_Value: {name = str8_lit("Value");}break;
case DF_WatchViewColumnKind_Type: {name = str8_lit("Type");}break;
case DF_WatchViewColumnKind_ViewRule:{name = str8_lit("View Rule");}break;
case DF_WatchViewColumnKind_Module: {name = str8_lit("Module");}break;
case DF_WatchViewColumnKind_Member:
{
name = str8(col->string_buffer, col->string_size);
}break;
}
}
switch(col->kind)
{
default:
{
ui_label(name);
}break;
case DF_WatchViewColumnKind_ViewRule:
{
if(df_help_label(name)) UI_Tooltip
{
F32 max_width = ui_top_font_size()*35;
ui_label_multiline(max_width, str8_lit("View rules are used to tweak the way evaluated expressions are visualized. Multiple rules can be specified on each row. They are specified in a key:(value) form. Some examples follow:"));
ui_spacer(ui_em(1.5f, 1));
DF_Font(DF_FontSlot_Code) ui_labelf("array:(N)");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that a pointer points to N elements, rather than only 1."));
ui_spacer(ui_em(1.5f, 1));
DF_Font(DF_FontSlot_Code) ui_labelf("omit:(member_1 ... member_n)");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Omits a list of member names from appearing in struct, union, or class evaluations."));
ui_spacer(ui_em(1.5f, 1));
DF_Font(DF_FontSlot_Code) ui_labelf("only:(member_1 ... member_n)");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that only the specified members should appear in struct, union, or class evaluations."));
DF_Font(DF_FontSlot_Code) ui_labelf("list:(next_link_member_name)");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that some struct, union, or class forms the top of a linked list, with next_link_member_name being the member which points at the next element in the list."));
ui_spacer(ui_em(1.5f, 1));
DF_Font(DF_FontSlot_Code) ui_labelf("dec");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-10 form."));
ui_spacer(ui_em(1.5f, 1));
DF_Font(DF_FontSlot_Code) ui_labelf("hex");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-16 form."));
ui_spacer(ui_em(1.5f, 1));
DF_Font(DF_FontSlot_Code) ui_labelf("oct");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-8 form."));
ui_spacer(ui_em(1.5f, 1));
DF_Font(DF_FontSlot_Code) ui_labelf("bin");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Specifies that all integral evaluations should appear in base-2 form."));
ui_spacer(ui_em(1.5f, 1));
DF_Font(DF_FontSlot_Code) ui_labelf("no_addr");
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label_multiline(max_width, str8_lit("Displays only what pointers point to, if possible, without the pointer's address value."));
ui_spacer(ui_em(1.5f, 1));
}
}break;
}
}
}
////////////////////////////
//- rjf: viz blocks -> rows
//
EV_WindowedRowList rows = {0};
{
rows = ev_windowed_row_list_from_block_list(scratch.arena, eval_view, r1s64(visible_row_rng.min-1, visible_row_rng.max), &blocks);
}
////////////////////////////
//- rjf: build table
//
ProfScope("build table")
{
U64 semantic_idx = rows.count_before_semantic;
for(EV_Row *row = rows.first; row != 0; row = row->next, semantic_idx += 1)
{
////////////////////////
//- rjf: unpack row info
//
U64 row_hash = ev_hash_from_key(row->key);
B32 row_selected = (selection_tbl.min.y <= (semantic_idx+1) && (semantic_idx+1) <= selection_tbl.max.y);
B32 row_expanded = ev_expansion_from_key(eval_view, row->key);
E_Eval row_eval = e_eval_from_expr(scratch.arena, row->expr);
B32 row_is_expandable = ev_row_is_expandable(row);
B32 row_is_editable = ev_row_is_editable(row);
B32 next_row_expanded = row_expanded;
DF_ViewRuleSpec *value_ui_rule_spec = &df_nil_view_rule_spec;
MD_Node *value_ui_rule_root = &md_nil_node;
DF_ViewRuleSpec *block_ui_rule_spec = &df_nil_view_rule_spec;
MD_Node *block_ui_rule_root = &md_nil_node;
for(EV_ViewRuleNode *n = row->view_rules->first; n != 0; n = n->next)
{
DF_ViewRuleSpec *spec = df_view_rule_spec_from_string(n->v.root->string);
if(spec->info.flags & DF_ViewRuleSpecInfoFlag_RowUI)
{
value_ui_rule_spec = spec;
value_ui_rule_root = n->v.root;
}
if(spec->info.flags & DF_ViewRuleSpecInfoFlag_ViewUI)
{
block_ui_rule_spec = spec;
block_ui_rule_root = n->v.root;
}
}
////////////////////////
//- rjf: determine if row's data is fresh and/or bad
//
B32 row_is_fresh = 0;
B32 row_is_bad = 0;
switch(row_eval.mode)
{
default:{}break;
case E_Mode_Offset:
{
D_Entity *space_entity = d_entity_from_eval_space(row_eval.space);
if(space_entity->kind == D_EntityKind_Process)
{
U64 size = e_type_byte_size_from_key(row_eval.type_key);
size = Min(size, 64);
Rng1U64 vaddr_rng = r1u64(row_eval.value.u64, row_eval.value.u64+size);
CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, space_entity->ctrl_handle, vaddr_rng, 0);
for(U64 idx = 0; idx < (slice.data.size+63)/64; idx += 1)
{
if(slice.byte_changed_flags[idx] != 0)
{
row_is_fresh = 1;
}
if(slice.byte_bad_flags[idx] != 0)
{
row_is_bad = 1;
}
}
}
}break;
}
////////////////////////
//- rjf: determine row's color palette
//
UI_BoxFlags row_flags = 0;
UI_Palette *palette = ui_top_palette();
{
if(row_is_fresh)
{
palette = ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay));
row_flags |= UI_BoxFlag_DrawBackground;
}
}
////////////////////////
//- rjf: build row box
//
ui_set_next_palette(palette);
ui_set_next_flags(disabled_flags);
ui_set_next_pref_width(ui_pct(1, 0));
ui_set_next_pref_height(ui_px(scroll_list_params.row_height_px*row->size_in_rows, 1.f));
ui_set_next_focus_hot(row_selected ? UI_FocusKind_On : UI_FocusKind_Off);
UI_Box *row_box = ui_build_box_from_stringf(row_flags|
UI_BoxFlag_DrawSideBottom|
UI_BoxFlag_Clickable|
((block_ui_rule_spec == &df_nil_view_rule_spec) * UI_BoxFlag_DisableFocusOverlay)|
((block_ui_rule_spec != &df_nil_view_rule_spec) * UI_BoxFlag_Clip),
"row_%I64x", row_hash);
ui_ts_vector_idx += 1;
ui_ts_cell_idx = 0;
////////////////////////
//- rjf: row with expand ui rule -> build large singular row for "escape hatch" ui
//
if(block_ui_rule_spec != &df_nil_view_rule_spec)
UI_Parent(row_box) UI_FocusHot(row_selected ? UI_FocusKind_On : UI_FocusKind_Off)
{
//- rjf: build canvas row contents
if(block_ui_rule_spec->info.flags & DF_ViewRuleSpecInfoFlag_ViewUI)
{
//- rjf: unpack
DF_WatchViewPoint pt = {0, row->parent_key, row->key};
DF_ViewSpec *canvas_view_spec = df_view_spec_from_string(block_ui_rule_spec->info.string);
DF_TransientViewNode *canvas_view_node = df_transient_view_node_from_ev_key(view, row->key);
DF_View *canvas_view = canvas_view_node->view;
String8 canvas_view_expr = e_string_from_expr(scratch.arena, row->expr);
B32 need_new_spec = (!str8_match(str8(canvas_view->query_buffer, canvas_view->query_string_size), canvas_view_expr, 0) ||
!md_tree_match(canvas_view_node->initial_params, block_ui_rule_root, 0));
if(need_new_spec)
{
arena_clear(canvas_view_node->initial_params_arena);
canvas_view_node->initial_params = md_tree_copy(canvas_view_node->initial_params_arena, block_ui_rule_root);
df_view_equip_spec(canvas_view, canvas_view_spec, canvas_view_expr, block_ui_rule_root);
}
Vec2F32 canvas_dim = v2f32(scroll_list_params.dim_px.x - ui_top_font_size()*1.5f,
(row->skipped_size_in_rows+row->size_in_rows+row->chopped_size_in_rows)*scroll_list_params.row_height_px);
Rng2F32 canvas_rect = r2f32p(rect.x0,
rect.y0 + ui_top_fixed_y(),
rect.x0 + canvas_dim.x,
rect.y0 + ui_top_fixed_y() + canvas_dim.y);
//- rjf: peek clicks in canvas region, mark clicked
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->kind == UI_EventKind_Press && evt->key == OS_Key_LeftMouseButton && contains_2f32(canvas_rect, evt->pos))
{
pressed = 1;
break;
}
}
//- rjf: build meta controls
ui_set_next_fixed_x(ui_top_font_size()*1.f);
ui_set_next_fixed_y(ui_top_font_size()*1.f + scroll_list_view_off_px.y*(row == rows.first));
ui_set_next_pref_width(ui_em(3, 1));
ui_set_next_pref_height(ui_em(3, 1));
UI_Flags(UI_BoxFlag_DrawDropShadow) UI_CornerRadius(ui_top_font_size()*0.5f)
{
UI_Signal sig = df_icon_buttonf(DF_IconKind_Window, 0, "###pop_out");
if(ui_hovering(sig)) UI_Tooltip
{
ui_labelf("Pop out");
}
if(ui_pressed(sig))
{
pressed = 1;
}
if(ui_clicked(sig))
{
df_cmd(DF_CmdKind_OpenTab,
.string = e_string_from_expr(scratch.arena, row->expr),
.params_tree = block_ui_rule_root);
}
}
//- rjf: build main column for canvas
ui_set_next_fixed_y(-1.f * (row->skipped_size_in_rows) * scroll_list_params.row_height_px);
ui_set_next_fixed_height((row->skipped_size_in_rows + row->size_in_rows + row->chopped_size_in_rows) * scroll_list_params.row_height_px);
ui_set_next_child_layout_axis(Axis2_X);
UI_Box *canvas_box = ui_build_box_from_stringf(UI_BoxFlag_FloatingY, "###canvas_%I64x", row_hash);
UI_Parent(canvas_box) UI_WidthFill UI_HeightFill
{
//- rjf: loading animation
df_loading_overlay(canvas_rect, canvas_view->loading_t, canvas_view->loading_progress_v, canvas_view->loading_progress_v_target);
//- rjf: push interaction registers, fill with per-view states
df_push_regs();
{
df_regs()->view = df_handle_from_view(canvas_view);
df_regs()->file_path = d_file_path_from_eval_string(d_frame_arena(), str8(canvas_view->query_buffer, canvas_view->query_string_size));
}
//- rjf: build
UI_PermissionFlags(UI_PermissionFlag_Clicks|UI_PermissionFlag_ScrollX)
{
canvas_view_spec->info.ui_hook(canvas_view, canvas_view->params_roots[canvas_view->params_read_gen%ArrayCount(canvas_view->params_roots)], str8(canvas_view->query_buffer, canvas_view->query_string_size), canvas_rect);
}
//- rjf: pop interaction registers
df_pop_regs();
}
}
}
////////////////////////
//- rjf: build non-canvas row contents
//
if(block_ui_rule_spec == &df_nil_view_rule_spec) UI_Parent(row_box) UI_HeightFill
{
//////////////////////
//- rjf: draw start of cache lines in expansions
//
if((row_eval.mode == E_Mode_Offset || row_eval.mode == E_Mode_Null) &&
row_eval.value.u64%64 == 0 && row->depth > 0 &&
!row_expanded)
{
ui_set_next_fixed_x(0);
ui_set_next_fixed_y(0);
ui_set_next_fixed_height(ui_top_font_size()*0.1f);
ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay)));
ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground, ui_key_zero());
}
//////////////////////
//- rjf: draw mid-row cache line boundaries in expansions
//
if((row_eval.mode == E_Mode_Offset || row_eval.mode == E_Mode_Null) &&
row_eval.value.u64%64 != 0 &&
row->depth > 0 &&
!row_expanded)
{
U64 next_off = (row_eval.value.u64 + e_type_byte_size_from_key(row_eval.type_key));
if(next_off%64 != 0 && row_eval.value.u64/64 < next_off/64)
{
ui_set_next_fixed_x(0);
ui_set_next_fixed_y(scroll_list_params.row_height_px - ui_top_font_size()*0.5f);
ui_set_next_fixed_height(ui_top_font_size()*1.f);
Vec4F32 boundary_color = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay);
ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = boundary_color));
ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground, ui_key_zero());
}
}
//////////////////////
//- rjf: build all columns
//
{
S64 x = 0;
for(DF_WatchViewColumn *col = ewv->first_column; col != 0; col = col->next, x += 1)
{
//- rjf: unpack cell info
DF_WatchViewPoint cell_pt = {x, row->parent_key, row->key};
DF_WatchViewTextEditState *cell_edit_state = df_watch_view_text_edit_state_from_pt(ewv, cell_pt);
B32 cell_selected = (row_selected && selection_tbl.min.x <= cell_pt.x && cell_pt.x <= selection_tbl.max.x);
String8 cell_pre_edit_string = df_string_from_eval_viz_row_column(scratch.arena, eval_view, row, col, 0, default_radix, ui_top_font(), ui_top_font_size(), row_string_max_size_px);
//- rjf: unpack column-kind-specific info
E_Eval cell_eval = row_eval;
B32 cell_can_edit = 0;
FuzzyMatchRangeList cell_matches = {0};
String8 cell_inheritance_string = {0};
String8 cell_error_string = {0};
String8 cell_error_tooltip_string = {0};
DF_AutoCompListerFlags cell_autocomp_flags = 0;
DF_ViewRuleRowUIFunctionType *cell_ui_hook = 0;
MD_Node *cell_ui_params = &md_nil_node;
Vec4F32 cell_base_color = ui_top_palette()->text;
DF_IconKind cell_icon = DF_IconKind_Null;
switch(col->kind)
{
default:{}break;
case DF_WatchViewColumnKind_Expr:
{
cell_can_edit = (row->depth == 0 && modifiable);
if(filter.size != 0)
{
cell_matches = fuzzy_match_find(scratch.arena, filter, ev_expr_string_from_row(scratch.arena, row));
}
cell_autocomp_flags = DF_AutoCompListerFlag_Locals;
if(row->member != 0 && row->member->inheritance_key_chain.first != 0)
{
String8List inheritance_chain_type_names = {0};
for(E_TypeKeyNode *n = row->member->inheritance_key_chain.first; n != 0; n = n->next)
{
String8 inherited_type_name = e_type_string_from_key(scratch.arena, n->v);
inherited_type_name = str8_skip_chop_whitespace(inherited_type_name);
str8_list_push(scratch.arena, &inheritance_chain_type_names, inherited_type_name);
}
if(inheritance_chain_type_names.node_count != 0)
{
StringJoin join = {0};
join.sep = str8_lit("::");
String8 inheritance_type = str8_list_join(scratch.arena, &inheritance_chain_type_names, &join);
cell_inheritance_string = inheritance_type;
}
}
}break;
case DF_WatchViewColumnKind_Value:
{
}goto value_cell;
case DF_WatchViewColumnKind_Member:
{
cell_eval = e_member_eval_from_eval_member_name(cell_eval, str8(col->string_buffer, col->string_size));
}goto value_cell;
value_cell:;
{
E_MsgList msgs = cell_eval.msgs;
if(row->depth == 0 && row->string.size != 0)
{
E_TokenArray tokens = e_token_array_from_text(scratch.arena, row->string);
E_Parse parse = e_parse_expr_from_text_tokens(scratch.arena, row->string, &tokens);
e_msg_list_concat_in_place(&parse.msgs, &msgs);
msgs = parse.msgs;
}
if(msgs.max_kind > E_MsgKind_Null)
{
String8List strings = {0};
for(E_Msg *msg = msgs.first; msg != 0; msg = msg->next)
{
str8_list_push(scratch.arena, &strings, msg->text);
}
StringJoin join = {str8_lit(""), str8_lit(" "), str8_lit("")};
cell_error_string = str8_list_join(scratch.arena, &strings, &join);
}
if(row_is_bad)
{
cell_error_tooltip_string = str8_lit("Could not read memory successfully.");
}
cell_autocomp_flags = DF_AutoCompListerFlag_Locals;
if(value_ui_rule_spec != &df_nil_view_rule_spec && value_ui_rule_spec != 0)
{
cell_ui_hook = value_ui_rule_spec->info.row_ui;
cell_ui_params = value_ui_rule_root;
}
cell_can_edit = ev_type_key_is_editable(cell_eval.type_key);
}break;
case DF_WatchViewColumnKind_Type:
{
cell_can_edit = 0;
}break;
case DF_WatchViewColumnKind_ViewRule:
{
cell_can_edit = 1;
cell_autocomp_flags = DF_AutoCompListerFlag_ViewRules;
}break;
case DF_WatchViewColumnKind_FrameSelection:
{
if(semantic_idx == df_regs()->unwind_count - df_regs()->inline_depth)
{
cell_icon = DF_IconKind_RightArrow;
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->thread);
if(thread->rgba != 0)
{
cell_base_color = rgba_from_u32(thread->rgba);
}
}
}break;
}
//- rjf: apply column-specified view rules
if(col->view_rule_size != 0)
{
String8 col_view_rule = str8(col->view_rule_buffer, col->view_rule_size);
EV_ViewRuleList *view_rules = ev_view_rule_list_from_string(scratch.arena, col_view_rule);
for(EV_ViewRuleNode *n = view_rules->first; n != 0; n = n->next)
{
EV_ViewRule *vr = &n->v;
DF_ViewRuleSpec *spec = df_view_rule_spec_from_string(vr->root->string);
if(spec != &df_nil_view_rule_spec && spec->info.flags & DF_ViewRuleSpecInfoFlag_RowUI)
{
cell_ui_hook = spec->info.row_ui;
cell_ui_params = vr->root;
}
}
}
//- rjf: determine cell's palette
UI_BoxFlags cell_flags = 0;
UI_Palette *palette = ui_top_palette();
{
if(cell_error_tooltip_string.size != 0 ||
cell_error_string.size != 0)
{
palette = ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative), .text_weak = df_rgba_from_theme_color(DF_ThemeColor_TextNegative), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlayError));
cell_flags |= UI_BoxFlag_DrawBackground;
}
else
{
palette = ui_build_palette(ui_top_palette(), .text = cell_base_color);
}
}
//- rjf: build cell
UI_Signal sig = {0};
UI_Palette(palette) UI_TableCell
UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off)
UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off)
DF_Font(col->is_non_code ? DF_FontSlot_Main : DF_FontSlot_Code)
UI_FlagsAdd(cell_flags | (row->depth > 0 ? UI_BoxFlag_DrawTextWeak : 0))
{
// rjf: cell has errors? -> build error box
if(cell_error_string.size != 0) DF_Font(DF_FontSlot_Main)
{
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "###%I64x_row_%I64x", x, row_hash);
sig = ui_signal_from_box(box);
UI_Parent(box) UI_Flags(0)
{
df_error_label(cell_error_string);
}
}
// rjf: cell has hook? -> build ui by calling hook
else if(cell_ui_hook != 0)
{
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "###val_%I64x", row_hash);
UI_Parent(box)
{
String8 row_expr = e_string_from_expr(scratch.arena, row->expr);
cell_ui_hook(row->key, cell_ui_params, row_expr);
}
sig = ui_signal_from_box(box);
}
// rjf: cell has icon? build icon
else if(cell_icon != DF_IconKind_Null)
{
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "###cell_%I64x", row_hash);
UI_Parent(box) DF_Font(DF_FontSlot_Icons) UI_WidthFill UI_TextAlignment(UI_TextAlign_Center)
{
ui_label(df_g_icon_kind_text_table[cell_icon]);
}
sig = ui_signal_from_box(box);
}
// rjf: build cell line edit
else
{
sig = df_line_editf((DF_LineEditFlag_CodeContents*(!col->is_non_code)|
DF_LineEditFlag_NoBackground|
DF_LineEditFlag_DisableEdit*(!cell_can_edit)|
DF_LineEditFlag_Expander*!!(x == 0 && row_is_expandable && col->kind == DF_WatchViewColumnKind_Expr)|
DF_LineEditFlag_ExpanderPlaceholder*(x == 0 && row->depth==0 && col->kind == DF_WatchViewColumnKind_Expr)|
DF_LineEditFlag_ExpanderSpace*(x == 0 && row->depth!=0 && col->kind == DF_WatchViewColumnKind_Expr)),
x == 0 ? row->depth : 0,
&cell_matches,
&cell_edit_state->cursor, &cell_edit_state->mark, cell_edit_state->input_buffer, sizeof(cell_edit_state->input_buffer), &cell_edit_state->input_size, &next_row_expanded,
cell_pre_edit_string,
"###%I64x_row_%I64x", x, row_hash);
if(ui_is_focus_active() &&
selection_tbl.min.x == selection_tbl.max.x && selection_tbl.min.y == selection_tbl.max.y &&
txt_pt_match(cell_edit_state->cursor, cell_edit_state->mark))
{
String8 input = str8(cell_edit_state->input_buffer, cell_edit_state->input_size);
DF_AutoCompListerParams params = {cell_autocomp_flags};
if(col->kind == DF_WatchViewColumnKind_ViewRule)
{
params = df_view_rule_autocomp_lister_params_from_input_cursor(scratch.arena, input, cell_edit_state->cursor.column-1);
if(params.flags == 0)
{
params.flags = cell_autocomp_flags;
}
}
df_set_autocomp_lister_query(sig.box->key, &params, input, cell_edit_state->cursor.column-1);
}
}
}
//- rjf: handle interactions
{
// rjf: single-click -> move selection here
if(ui_pressed(sig))
{
ewv->next_cursor = ewv->next_mark = cell_pt;
pressed = 1;
}
// rjf: double-click -> start editing
if(ui_double_clicked(sig) && cell_can_edit)
{
ui_kill_action();
df_cmd(DF_CmdKind_Edit);
}
// rjf: double-click, not editable -> go-to-location
if(ui_double_clicked(sig) && !cell_can_edit)
{
U64 vaddr = cell_eval.value.u64;
CTRL_Entity *process = d_ctrl_entity_from_eval_space(cell_eval.space);
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, vaddr);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
U64 voff = ctrl_voff_from_vaddr(module, vaddr);
D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, voff);
String8 file_path = {0};
TxtPt pt = {0};
if(lines.first != 0)
{
file_path = lines.first->v.file_path;
pt = lines.first->v.pt;
}
df_cmd(DF_CmdKind_FindCodeLocation,
.process = process->handle,
.vaddr = vaddr,
.file_path = file_path,
.cursor = pt);
}
// rjf: double-click, not editable, callstack frame -> select frame
if(ui_double_clicked(sig) && !cell_can_edit && semantic_idx < frame_rows_count)
{
FrameRow *frame_row = &frame_rows[semantic_idx];
df_cmd(DF_CmdKind_SelectUnwind,
.unwind_count = frame_row->unwind_idx,
.inline_depth = frame_row->inline_depth);
}
// rjf: hovering with inheritance string -> show tooltip
if(ui_hovering(sig) && cell_inheritance_string.size != 0) UI_Tooltip
{
UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(1, 1))
{
ui_labelf("Inherited from ");
DF_Font(DF_FontSlot_Code) df_code_label(1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeDefault), cell_inheritance_string);
}
}
// rjf: hovering with error tooltip -> show tooltip
if(ui_hovering(sig) && cell_error_tooltip_string.size != 0) UI_Tooltip
{
UI_PrefWidth(ui_children_sum(1)) df_error_label(cell_error_tooltip_string);
}
}
//- rjf: [DEV] hovering -> tooltips
if(DEV_eval_compiler_tooltips && x == 0 && ui_hovering(sig)) UI_Tooltip DF_Font(DF_FontSlot_Code)
{
local_persist char *spaces = " ";
String8 string = ev_expr_string_from_row(scratch.arena, row);
E_TokenArray tokens = e_token_array_from_text(scratch.arena, string);
E_Parse parse = e_parse_expr_from_text_tokens(scratch.arena, string, &tokens);
E_IRTreeAndType irtree = e_irtree_and_type_from_expr(scratch.arena, parse.expr);
E_OpList oplist = e_oplist_from_irtree(scratch.arena, irtree.root);
String8 bytecode = e_bytecode_from_oplist(scratch.arena, &oplist);
UI_Flags(UI_BoxFlag_DrawTextWeak) ui_labelf("Text:");
ui_label(string);
ui_spacer(ui_em(2.f, 1.f));
UI_Flags(UI_BoxFlag_DrawTextWeak) ui_labelf("Tokens:");
for(U64 idx = 0; idx < tokens.count; idx += 1)
{
ui_labelf("%S: '%S'", e_token_kind_strings[tokens.v[idx].kind], str8_substr(string, tokens.v[idx].range));
}
ui_spacer(ui_em(2.f, 1.f));
UI_Flags(UI_BoxFlag_DrawTextWeak) ui_labelf("Expression:");
{
typedef struct Task Task;
struct Task
{
Task *next;
Task *prev;
E_Expr *expr;
S64 depth;
};
Task start_task = {0, 0, parse.expr};
Task *first_task = &start_task;
Task *last_task = first_task;
for(Task *t = first_task; t != 0; t = t->next)
{
String8 ext = {0};
switch(t->expr->kind)
{
default:
{
if(t->expr->string.size != 0)
{
ext = push_str8f(scratch.arena, "'%S'", t->expr->string);
}
else if(t->expr->value.u32 != 0)
{
ext = push_str8f(scratch.arena, "0x%x", t->expr->value.u32);
}
else if(t->expr->value.f32 != 0)
{
ext = push_str8f(scratch.arena, "%f", t->expr->value.f32);
}
else if(t->expr->value.f64 != 0)
{
ext = push_str8f(scratch.arena, "%f", t->expr->value.f64);
}
else if(t->expr->value.u64 != 0)
{
ext = push_str8f(scratch.arena, "0x%I64x", t->expr->value.u64);
}
}break;
}
ui_labelf("%.*s%S%s%S", (int)t->depth*2, spaces, e_expr_kind_strings[t->expr->kind], ext.size ? " " : "", ext);
for(E_Expr *child = t->expr->first; child != &e_expr_nil; child = child->next)
{
Task *task = push_array(scratch.arena, Task, 1);
task->expr = child;
task->depth = t->depth+1;
DLLInsert(first_task, last_task, t, task);
}
}
}
ui_spacer(ui_em(2.f, 1.f));
UI_Flags(UI_BoxFlag_DrawTextWeak) ui_labelf("IR Tree:");
{
typedef struct Task Task;
struct Task
{
Task *next;
Task *prev;
E_IRNode *node;
S64 depth;
};
Task start_task = {0, 0, irtree.root};
Task *first_task = &start_task;
Task *last_task = first_task;
for(Task *t = first_task; t != 0; t = t->next)
{
String8 op_string = {0};
switch(t->node->op)
{
default:{}break;
case E_IRExtKind_Bytecode:{op_string = str8_lit("Bytecode");}break;
case E_IRExtKind_SetSpace:{op_string = str8_lit("SetSpace");}break;
#define X(name) case RDI_EvalOp_##name:{op_string = str8_lit(#name);}break;
RDI_EvalOp_XList
#undef X
}
String8 ext = {0};
ui_labelf("%.*s%S", (int)t->depth*2, spaces, op_string);
for(E_IRNode *child = t->node->first; child != &e_irnode_nil; child = child->next)
{
Task *task = push_array(scratch.arena, Task, 1);
task->node = child;
task->depth = t->depth+1;
DLLInsert(first_task, last_task, t, task);
}
}
}
ui_spacer(ui_em(2.f, 1.f));
UI_Flags(UI_BoxFlag_DrawTextWeak) ui_labelf("Op List:");
{
for(E_Op *op = oplist.first; op != 0; op = op->next)
{
String8 op_string = {0};
switch(op->opcode)
{
default:{}break;
case E_IRExtKind_Bytecode:{op_string = str8_lit("Bytecode");}break;
case E_IRExtKind_SetSpace:{op_string = str8_lit("SetSpace");}break;
#define X(name) case RDI_EvalOp_##name:{op_string = str8_lit(#name);}break;
RDI_EvalOp_XList
#undef X
}
String8 ext = {0};
switch(op->opcode)
{
case E_IRExtKind_Bytecode:{ext = str8_lit("[bytecode]");}break;
default:
{
ext = str8_from_u64(scratch.arena, op->value.u64, 16, 0, 0);
}break;
}
ui_labelf(" %S%s%S", op_string, ext.size ? " " : "", ext);
}
}
ui_spacer(ui_em(2.f, 1.f));
UI_Flags(UI_BoxFlag_DrawTextWeak) ui_labelf("Bytecode:");
{
for(U64 idx = 0; idx < bytecode.size; idx += 1)
{
ui_labelf(" 0x%x ('%c')", (U32)bytecode.str[idx], (char)bytecode.str[idx]);
}
}
}
}
}
//////////////////////
//- rjf: commit expansion state changes
//
if(next_row_expanded != row_expanded)
{
ev_key_set_expansion(eval_view, row->parent_key, row->key, next_row_expanded);
}
}
}
}
}
//////////////////////////////
//- rjf: general table-wide press logic
//
if(pressed)
{
df_cmd(DF_CmdKind_FocusPanel);
}
scratch_end(scratch);
fzy_scope_close(fzy_scope);
di_scope_close(di_scope);
ProfEnd();
}
////////////////////////////////
//~ rjf: null @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(null) {}
DF_VIEW_CMD_FUNCTION_DEF(null) {}
DF_VIEW_UI_FUNCTION_DEF(null) {}
////////////////////////////////
//~ rjf: empty @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(empty) {}
DF_VIEW_CMD_FUNCTION_DEF(empty) {}
DF_VIEW_UI_FUNCTION_DEF(empty)
{
ui_set_next_flags(UI_BoxFlag_DefaultFocusNav);
UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(str8_lit("empty_view")) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
UI_Padding(ui_pct(1, 0)) UI_Focus(UI_FocusKind_Null)
{
UI_PrefHeight(ui_em(3.f, 1.f))
UI_Row
UI_Padding(ui_pct(1, 0))
UI_TextAlignment(UI_TextAlign_Center)
UI_PrefWidth(ui_em(15.f, 1.f))
UI_CornerRadius(ui_top_font_size()/2.f)
DF_Palette(DF_PaletteCode_NegativePopButton)
{
if(ui_clicked(df_icon_buttonf(DF_IconKind_X, 0, "Close Panel")))
{
df_cmd(DF_CmdKind_ClosePanel);
}
}
}
}
////////////////////////////////
//~ rjf: getting_started @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(getting_started) {}
DF_VIEW_CMD_FUNCTION_DEF(getting_started) {}
DF_VIEW_UI_FUNCTION_DEF(getting_started)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
ui_set_next_flags(UI_BoxFlag_DefaultFocusNav);
UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(str8_lit("empty_view"))
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
UI_Padding(ui_pct(1, 0)) UI_Focus(UI_FocusKind_Null)
{
D_EntityList targets = d_push_active_target_list(scratch.arena);
D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process);
//- rjf: icon & info
UI_Padding(ui_em(2.f, 1.f))
{
//- rjf: icon
{
F32 icon_dim = ui_top_font_size()*10.f;
UI_PrefHeight(ui_px(icon_dim, 1.f))
UI_Row
UI_Padding(ui_pct(1, 0))
UI_PrefWidth(ui_px(icon_dim, 1.f))
{
R_Handle texture = df_state->icon_texture;
Vec2S32 texture_dim = r_size_from_tex2d(texture);
ui_image(texture, R_Tex2DSampleKind_Linear, r2f32p(0, 0, texture_dim.x, texture_dim.y), v4f32(1, 1, 1, 1), 0, str8_lit(""));
}
}
//- rjf: info
UI_Padding(ui_em(2.f, 1.f))
UI_WidthFill UI_PrefHeight(ui_em(2.f, 1.f))
UI_Row
UI_Padding(ui_pct(1, 0))
UI_TextAlignment(UI_TextAlign_Center)
UI_PrefWidth(ui_text_dim(10, 1))
{
ui_label(str8_lit(BUILD_TITLE_STRING_LITERAL));
}
}
//- rjf: targets state dependent helper
B32 helper_built = 0;
if(processes.count == 0)
{
helper_built = 1;
switch(targets.count)
{
//- rjf: user has no targets. build helper for adding them
case 0:
{
UI_PrefHeight(ui_em(3.75f, 1.f))
UI_Row
UI_Padding(ui_pct(1, 0))
UI_TextAlignment(UI_TextAlign_Center)
UI_PrefWidth(ui_em(22.f, 1.f))
UI_CornerRadius(ui_top_font_size()/2.f)
DF_Palette(DF_PaletteCode_NeutralPopButton)
if(ui_clicked(df_icon_buttonf(DF_IconKind_Add, 0, "Add Target")))
{
df_cmd(DF_CmdKind_RunCommand, .string = df_cmd_kind_info_table[DF_CmdKind_AddTarget].string);
}
}break;
//- rjf: user has 1 target. build helper for launching it
case 1:
{
D_Entity *target = d_first_entity_from_list(&targets);
String8 target_full_path = target->string;
String8 target_name = str8_skip_last_slash(target_full_path);
UI_PrefHeight(ui_em(3.75f, 1.f))
UI_Row
UI_Padding(ui_pct(1, 0))
UI_TextAlignment(UI_TextAlign_Center)
UI_PrefWidth(ui_em(22.f, 1.f))
UI_CornerRadius(ui_top_font_size()/2.f)
DF_Palette(DF_PaletteCode_PositivePopButton)
{
if(ui_clicked(df_icon_buttonf(DF_IconKind_Play, 0, "Launch %S", target_name)))
{
df_cmd(DF_CmdKind_LaunchAndRun, .entity = d_handle_from_entity(target));
}
ui_spacer(ui_em(1.5f, 1));
if(ui_clicked(df_icon_buttonf(DF_IconKind_Play, 0, "Step Into %S", target_name)))
{
df_cmd(DF_CmdKind_LaunchAndInit, .entity = d_handle_from_entity(target));
}
}
}break;
//- rjf: user has N targets.
default:
{
helper_built = 0;
}break;
}
}
//- rjf: or text
if(helper_built)
{
UI_PrefHeight(ui_em(2.25f, 1.f))
UI_Row
UI_Padding(ui_pct(1, 0))
UI_TextAlignment(UI_TextAlign_Center)
UI_WidthFill
ui_labelf("- or -");
}
//- rjf: helper text for command lister activation
UI_PrefHeight(ui_em(2.25f, 1.f)) UI_Row
UI_PrefWidth(ui_text_dim(10, 1))
UI_TextAlignment(UI_TextAlign_Center)
UI_Padding(ui_pct(1, 0))
{
ui_labelf("use");
UI_Flags(UI_BoxFlag_DrawBorder) UI_TextAlignment(UI_TextAlign_Center) df_cmd_binding_buttons(df_cmd_kind_info_table[DF_CmdKind_RunCommand].string);
ui_labelf("to open command menu");
}
}
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: commands @view_hook_impl
typedef struct DF_CmdListerItem DF_CmdListerItem;
struct DF_CmdListerItem
{
String8 cmd_name;
U64 registrar_idx;
U64 ordering_idx;
FuzzyMatchRangeList name_match_ranges;
FuzzyMatchRangeList desc_match_ranges;
FuzzyMatchRangeList tags_match_ranges;
};
typedef struct DF_CmdListerItemNode DF_CmdListerItemNode;
struct DF_CmdListerItemNode
{
DF_CmdListerItemNode *next;
DF_CmdListerItem item;
};
typedef struct DF_CmdListerItemList DF_CmdListerItemList;
struct DF_CmdListerItemList
{
DF_CmdListerItemNode *first;
DF_CmdListerItemNode *last;
U64 count;
};
typedef struct DF_CmdListerItemArray DF_CmdListerItemArray;
struct DF_CmdListerItemArray
{
DF_CmdListerItem *v;
U64 count;
};
internal DF_CmdListerItemList
df_cmd_lister_item_list_from_needle(Arena *arena, String8 needle)
{
Temp scratch = scratch_begin(&arena, 1);
DF_CmdListerItemList result = {0};
// TODO(rjf): @msgs extend this with dynamically-registered command info
for(EachNonZeroEnumVal(DF_CmdKind, k))
{
DF_CmdKindInfo *info = &df_cmd_kind_info_table[k];
if(info->flags & DF_CmdKindFlag_ListInUI)
{
String8 cmd_display_name = info->display_name;
String8 cmd_desc = info->description;
String8 cmd_tags = info->search_tags;
FuzzyMatchRangeList name_matches = fuzzy_match_find(arena, needle, cmd_display_name);
FuzzyMatchRangeList desc_matches = fuzzy_match_find(arena, needle, cmd_desc);
FuzzyMatchRangeList tags_matches = fuzzy_match_find(arena, needle, cmd_tags);
if(name_matches.count == name_matches.needle_part_count ||
desc_matches.count == name_matches.needle_part_count ||
tags_matches.count > 0 ||
name_matches.needle_part_count == 0)
{
DF_CmdListerItemNode *node = push_array(arena, DF_CmdListerItemNode, 1);
node->item.cmd_name = info->string;
node->item.registrar_idx = (U64)k;
node->item.ordering_idx = (U64)k;
node->item.name_match_ranges = name_matches;
node->item.desc_match_ranges = desc_matches;
node->item.tags_match_ranges = tags_matches;
SLLQueuePush(result.first, result.last, node);
result.count += 1;
}
}
}
scratch_end(scratch);
return result;
}
internal DF_CmdListerItemArray
df_cmd_lister_item_array_from_list(Arena *arena, DF_CmdListerItemList list)
{
DF_CmdListerItemArray result = {0};
result.count = list.count;
result.v = push_array(arena, DF_CmdListerItem, result.count);
U64 idx = 0;
for(DF_CmdListerItemNode *n = list.first; n != 0; n = n->next, idx += 1)
{
result.v[idx] = n->item;
}
return result;
}
internal int
df_qsort_compare_cmd_lister__strength(DF_CmdListerItem *a, DF_CmdListerItem *b)
{
int result = 0;
if(a->name_match_ranges.count > b->name_match_ranges.count)
{
result = -1;
}
else if(a->name_match_ranges.count < b->name_match_ranges.count)
{
result = +1;
}
else if(a->desc_match_ranges.count > b->desc_match_ranges.count)
{
result = -1;
}
else if(a->desc_match_ranges.count < b->desc_match_ranges.count)
{
result = +1;
}
else if(a->tags_match_ranges.count > b->tags_match_ranges.count)
{
result = -1;
}
else if(a->tags_match_ranges.count < b->tags_match_ranges.count)
{
result = +1;
}
else if(a->registrar_idx < b->registrar_idx)
{
result = -1;
}
else if(a->registrar_idx > b->registrar_idx)
{
result = +1;
}
else if(a->ordering_idx < b->ordering_idx)
{
result = -1;
}
else if(a->ordering_idx > b->ordering_idx)
{
result = +1;
}
return result;
}
internal void
df_cmd_lister_item_array_sort_by_strength__in_place(DF_CmdListerItemArray array)
{
quick_sort(array.v, array.count, sizeof(DF_CmdListerItem), df_qsort_compare_cmd_lister__strength);
}
DF_VIEW_SETUP_FUNCTION_DEF(commands) {}
DF_VIEW_CMD_FUNCTION_DEF(commands) {}
DF_VIEW_UI_FUNCTION_DEF(commands)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
//- rjf: grab state
typedef struct DF_CmdsViewState DF_CmdsViewState;
struct DF_CmdsViewState
{
U64 selected_cmd_hash;
};
DF_CmdsViewState *cv = df_view_user_state(view, DF_CmdsViewState);
//- rjf: build filtered array of commands
DF_CmdListerItemList cmd_list = df_cmd_lister_item_list_from_needle(scratch.arena, string);
DF_CmdListerItemArray cmd_array = df_cmd_lister_item_array_from_list(scratch.arena, cmd_list);
df_cmd_lister_item_array_sort_by_strength__in_place(cmd_array);
//- rjf: submit best match when hitting enter w/ no selection
if(cv->selected_cmd_hash == 0 && ui_slot_press(UI_EventActionSlot_Accept))
{
df_cmd(DF_CmdKind_CompleteQuery, .string = (cmd_array.count > 0 ? cmd_array.v[0].cmd_name : str8_zero()));
}
//- rjf: selected kind -> cursor
Vec2S64 cursor = {0};
{
for(U64 idx = 0; idx < cmd_array.count; idx += 1)
{
if(d_hash_from_string(cmd_array.v[idx].cmd_name) == cv->selected_cmd_hash)
{
cursor.y = (S64)idx+1;
break;
}
}
}
//- rjf: build contents
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*6.5f);
scroll_list_params.dim_px = dim_2f32(rect);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, cmd_array.count));
scroll_list_params.item_range = r1s64(0, cmd_array.count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
&cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
//- rjf: build buttons
for(S64 row_idx = visible_row_range.min;
row_idx <= visible_row_range.max && row_idx < cmd_array.count;
row_idx += 1)
{
DF_CmdListerItem *item = &cmd_array.v[row_idx];
DF_CmdKindInfo *info = df_cmd_kind_info_from_string(item->cmd_name);
//- rjf: build row contents
ui_set_next_hover_cursor(OS_Cursor_HandPoint);
ui_set_next_child_layout_axis(Axis2_X);
UI_Box *box = &ui_g_nil_box;
UI_Focus(cursor.y == row_idx+1 ? UI_FocusKind_On : UI_FocusKind_Off)
{
box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawActiveEffects,
"###cmd_button_%S", item->cmd_name);
}
UI_Parent(box) UI_PrefHeight(ui_em(1.65f, 1.f))
{
//- rjf: icon
UI_PrefWidth(ui_em(3.f, 1.f))
UI_HeightFill
UI_Column
DF_Font(DF_FontSlot_Icons)
UI_FontSize(df_font_size_from_slot(DF_FontSlot_Icons))
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
UI_HeightFill
UI_TextAlignment(UI_TextAlign_Center)
{
DF_IconKind icon = info->icon_kind;
if(icon != DF_IconKind_Null)
{
ui_label(df_g_icon_kind_text_table[icon]);
}
}
//- rjf: name + description
ui_set_next_pref_height(ui_pct(1, 0));
UI_Column UI_Padding(ui_pct(1, 0))
{
FNT_Tag font = ui_top_font();
F32 font_size = ui_top_font_size();
FNT_Metrics font_metrics = fnt_metrics_from_tag_size(font, font_size);
F32 font_line_height = fnt_line_height_from_metrics(&font_metrics);
String8 cmd_display_name = info->display_name;
String8 cmd_desc = info->description;
UI_Box *name_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##name_%S", cmd_display_name, info->string);
UI_Box *desc_box = &ui_g_nil_box;
UI_PrefHeight(ui_em(1.8f, 1.f)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
desc_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##desc_%S", cmd_desc, info->string);
}
ui_box_equip_fuzzy_match_ranges(name_box, &item->name_match_ranges);
ui_box_equip_fuzzy_match_ranges(desc_box, &item->desc_match_ranges);
}
//- rjf: bindings
ui_set_next_flags(UI_BoxFlag_Clickable);
UI_PrefWidth(ui_children_sum(1.f)) UI_HeightFill UI_NamedColumn(str8_lit("binding_column")) UI_Padding(ui_em(1.5f, 1.f))
{
ui_set_next_flags(UI_BoxFlag_Clickable);
UI_NamedRow(str8_lit("binding_row")) UI_Padding(ui_em(1.f, 1.f))
{
df_cmd_binding_buttons(item->cmd_name);
}
}
}
//- rjf: interact
UI_Signal sig = ui_signal_from_box(box);
if(ui_clicked(sig))
{
df_cmd(DF_CmdKind_CompleteQuery, .string = item->cmd_name);
}
}
}
//- rjf: map selected num -> selected kind
if(1 <= cursor.y && cursor.y <= cmd_array.count)
{
cv->selected_cmd_hash = d_hash_from_string(cmd_array.v[cursor.y-1].cmd_name);
}
else
{
cv->selected_cmd_hash = 0;
}
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: file_system @view_hook_impl
typedef enum DF_FileSortKind
{
DF_FileSortKind_Null,
DF_FileSortKind_Filename,
DF_FileSortKind_LastModified,
DF_FileSortKind_Size,
DF_FileSortKind_COUNT
}
DF_FileSortKind;
typedef struct DF_FileInfo DF_FileInfo;
struct DF_FileInfo
{
String8 filename;
FileProperties props;
FuzzyMatchRangeList match_ranges;
};
typedef struct DF_FileInfoNode DF_FileInfoNode;
struct DF_FileInfoNode
{
DF_FileInfoNode *next;
DF_FileInfo file_info;
};
typedef struct DF_FileSystemViewPathState DF_FileSystemViewPathState;
struct DF_FileSystemViewPathState
{
DF_FileSystemViewPathState *hash_next;
String8 normalized_path;
Vec2S64 cursor;
};
typedef struct DF_FileSystemViewState DF_FileSystemViewState;
struct DF_FileSystemViewState
{
B32 initialized;
U64 path_state_table_size;
DF_FileSystemViewPathState **path_state_table;
DF_FileSortKind sort_kind;
Side sort_side;
Arena *cached_files_arena;
String8 cached_files_path;
DF_FileSortKind cached_files_sort_kind;
Side cached_files_sort_side;
U64 cached_file_count;
DF_FileInfo *cached_files;
F32 col_pcts[3];
};
typedef struct DF_PathQuery DF_PathQuery;
struct DF_PathQuery
{
String8 prefix;
String8 path;
String8 search;
};
internal DF_PathQuery
df_path_query_from_string(String8 string)
{
String8 dir_str_in_input = {0};
for(U64 i = 0; i < string.size; i += 1)
{
String8 substr1 = str8_substr(string, r1u64(i, i+1));
String8 substr2 = str8_substr(string, r1u64(i, i+2));
String8 substr3 = str8_substr(string, r1u64(i, i+3));
if(str8_match(substr1, str8_lit("/"), StringMatchFlag_SlashInsensitive))
{
dir_str_in_input = str8_substr(string, r1u64(i, string.size));
}
else if(i != 0 && str8_match(substr2, str8_lit(":/"), StringMatchFlag_SlashInsensitive))
{
dir_str_in_input = str8_substr(string, r1u64(i-1, string.size));
}
else if(str8_match(substr2, str8_lit("./"), StringMatchFlag_SlashInsensitive))
{
dir_str_in_input = str8_substr(string, r1u64(i, string.size));
}
else if(str8_match(substr3, str8_lit("../"), StringMatchFlag_SlashInsensitive))
{
dir_str_in_input = str8_substr(string, r1u64(i, string.size));
}
if(dir_str_in_input.size != 0)
{
break;
}
}
DF_PathQuery path_query = {0};
if(dir_str_in_input.size != 0)
{
String8 dir = dir_str_in_input;
String8 search = {0};
U64 one_past_last_slash = dir.size;
for(U64 i = 0; i < dir_str_in_input.size; i += 1)
{
if(dir_str_in_input.str[i] == '/' || dir_str_in_input.str[i] == '\\')
{
one_past_last_slash = i+1;
}
}
dir.size = one_past_last_slash;
search = str8_substr(dir_str_in_input, r1u64(one_past_last_slash, dir_str_in_input.size));
path_query.path = dir;
path_query.search = search;
path_query.prefix = str8_substr(string, r1u64(0, path_query.path.str - string.str));
}
return path_query;
}
internal int
df_qsort_compare_file_info__filename(DF_FileInfo *a, DF_FileInfo *b)
{
return strncmp((char *)a->filename.str, (char *)b->filename.str, Min(a->filename.size, b->filename.size));
}
internal int
df_qsort_compare_file_info__default(DF_FileInfo *a, DF_FileInfo *b)
{
int result = 0;
if(a->props.flags & FilePropertyFlag_IsFolder && !(b->props.flags & FilePropertyFlag_IsFolder))
{
result = -1;
}
else if(b->props.flags & FilePropertyFlag_IsFolder && !(a->props.flags & FilePropertyFlag_IsFolder))
{
result = +1;
}
else
{
result = df_qsort_compare_file_info__filename(a, b);
}
return result;
}
internal int
df_qsort_compare_file_info__default_filtered(DF_FileInfo *a, DF_FileInfo *b)
{
int result = 0;
if(a->filename.size < b->filename.size)
{
result = -1;
}
else if(a->filename.size > b->filename.size)
{
result = +1;
}
return result;
}
internal int
df_qsort_compare_file_info__last_modified(DF_FileInfo *a, DF_FileInfo *b)
{
return ((a->props.modified < b->props.modified) ? -1 :
(a->props.modified > b->props.modified) ? +1 :
0);
}
internal int
df_qsort_compare_file_info__size(DF_FileInfo *a, DF_FileInfo *b)
{
return ((a->props.size < b->props.size) ? -1 :
(a->props.size > b->props.size) ? +1 :
0);
}
DF_VIEW_SETUP_FUNCTION_DEF(file_system){}
DF_VIEW_CMD_FUNCTION_DEF(file_system){}
DF_VIEW_UI_FUNCTION_DEF(file_system)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
String8 query = string;
String8 query_normalized = path_normalized_from_string(scratch.arena, query);
B32 query_has_slash = (query.size != 0 && char_to_correct_slash(query.str[query.size-1]) == '/');
String8 query_normalized_with_opt_slash = push_str8f(scratch.arena, "%S%s", query_normalized, query_has_slash ? "/" : "");
DF_PathQuery path_query = df_path_query_from_string(query_normalized_with_opt_slash);
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f);
DF_Window *window = df_window_from_handle(df_regs()->window);
DF_CmdKindInfo *cmd_kind_info = df_cmd_kind_info_from_string(window->query_cmd_name);
B32 file_selection = !!(cmd_kind_info->query.flags & DF_QueryFlag_AllowFiles);
B32 dir_selection = !!(cmd_kind_info->query.flags & DF_QueryFlag_AllowFolders);
//- rjf: get extra state for this view
DF_FileSystemViewState *fs = df_view_user_state(view, DF_FileSystemViewState);
if(fs->initialized == 0)
{
fs->initialized = 1;
fs->path_state_table_size = 256;
fs->path_state_table = push_array(view->arena, DF_FileSystemViewPathState *, fs->path_state_table_size);
fs->cached_files_arena = df_view_push_arena_ext(view);
fs->col_pcts[0] = 0.60f;
fs->col_pcts[1] = 0.20f;
fs->col_pcts[2] = 0.20f;
}
//- rjf: grab state for the current path
DF_FileSystemViewPathState *ps = 0;
{
String8 key = query_normalized;
U64 hash = d_hash_from_string(key);
U64 slot = hash % fs->path_state_table_size;
for(DF_FileSystemViewPathState *p = fs->path_state_table[slot]; p != 0; p = p->hash_next)
{
if(str8_match(p->normalized_path, key, 0))
{
ps = p;
break;
}
}
if(ps == 0)
{
ps = push_array(view->arena, DF_FileSystemViewPathState, 1);
ps->hash_next = fs->path_state_table[slot];
fs->path_state_table[slot] = ps;
ps->normalized_path = push_str8_copy(view->arena, key);
}
}
//- rjf: get file array from the current path
U64 file_count = fs->cached_file_count;
DF_FileInfo *files = fs->cached_files;
if(!str8_match(fs->cached_files_path, query_normalized_with_opt_slash, 0) ||
fs->cached_files_sort_kind != fs->sort_kind ||
fs->cached_files_sort_side != fs->sort_side)
{
arena_clear(fs->cached_files_arena);
//- rjf: store off path that we're gathering from
fs->cached_files_path = push_str8_copy(fs->cached_files_arena, query_normalized_with_opt_slash);
fs->cached_files_sort_kind = fs->sort_kind;
fs->cached_files_sort_side = fs->sort_side;
//- rjf: use stored path as the new browse path for the whole frontend
// (multiple file system views may conflict here. that's okay. we'll just always
// choose the most recent change to a file browser path, and live with the
// consequences).
{
df_cmd(DF_CmdKind_SetCurrentPath, .file_path = path_query.path);
}
//- rjf: get files, filtered
U64 new_file_count = 0;
DF_FileInfoNode *first_file = 0;
DF_FileInfoNode *last_file = 0;
{
OS_FileIter *it = os_file_iter_begin(scratch.arena, path_query.path, 0);
for(OS_FileInfo info = {0}; os_file_iter_next(scratch.arena, it, &info);)
{
FuzzyMatchRangeList match_ranges = fuzzy_match_find(fs->cached_files_arena, path_query.search, info.name);
B32 fits_search = (path_query.search.size == 0 || match_ranges.count == match_ranges.needle_part_count);
B32 fits_dir_only = !!(info.props.flags & FilePropertyFlag_IsFolder) || !dir_selection;
if(fits_search && fits_dir_only)
{
DF_FileInfoNode *node = push_array(scratch.arena, DF_FileInfoNode, 1);
node->file_info.filename = push_str8_copy(fs->cached_files_arena, info.name);
node->file_info.props = info.props;
node->file_info.match_ranges = match_ranges;
SLLQueuePush(first_file, last_file, node);
new_file_count += 1;
}
}
os_file_iter_end(it);
}
//- rjf: convert list to array
DF_FileInfo *new_files = push_array(fs->cached_files_arena, DF_FileInfo, new_file_count);
{
U64 idx = 0;
for(DF_FileInfoNode *n = first_file; n != 0; n = n->next, idx += 1)
{
new_files[idx] = n->file_info;
}
}
//- rjf: apply sort
switch(fs->sort_kind)
{
default:
{
if(path_query.search.size != 0)
{
quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__default_filtered);
}
else
{
quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__default);
}
}break;
case DF_FileSortKind_Filename:
{
quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__filename);
}break;
case DF_FileSortKind_LastModified:
{
quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__last_modified);
}break;
case DF_FileSortKind_Size:
{
quick_sort(new_files, new_file_count, sizeof(DF_FileInfo), df_qsort_compare_file_info__size);
}break;
}
//- rjf: apply reverse
if(fs->sort_kind != DF_FileSortKind_Null && fs->sort_side == Side_Max)
{
for(U64 idx = 0; idx < new_file_count/2; idx += 1)
{
U64 rev_idx = new_file_count - idx - 1;
Swap(DF_FileInfo, new_files[idx], new_files[rev_idx]);
}
}
fs->cached_file_count = file_count = new_file_count;
fs->cached_files = files = new_files;
}
//- rjf: submit best match when hitting enter w/ no selection
if(ps->cursor.y == 0 && ui_slot_press(UI_EventActionSlot_Accept))
{
FileProperties query_normalized_with_opt_slash_props = os_properties_from_file_path(query_normalized_with_opt_slash);
FileProperties path_query_path_props = os_properties_from_file_path(path_query.path);
// rjf: command search part is empty, but directory matches some file:
if(path_query_path_props.created != 0 && path_query.search.size == 0)
{
df_cmd(DF_CmdKind_CompleteQuery, .file_path = query_normalized_with_opt_slash);
}
// rjf: command argument exactly matches some file:
else if(query_normalized_with_opt_slash_props.created != 0 && path_query.search.size != 0)
{
// rjf: is a folder -> autocomplete to slash
if(query_normalized_with_opt_slash_props.flags & FilePropertyFlag_IsFolder)
{
String8 new_path = push_str8f(scratch.arena, "%S%S/", path_query.path, path_query.search);
df_view_equip_query(view, new_path);
}
// rjf: is a file -> complete view
else
{
df_cmd(DF_CmdKind_CompleteQuery, .file_path = query_normalized_with_opt_slash);
}
}
// rjf: command argument is empty, picking folders -> use current folder
else if(path_query.search.size == 0 && dir_selection)
{
df_cmd(DF_CmdKind_CompleteQuery, .file_path = path_query.path);
}
// rjf: command argument does not exactly match any file, but lister results are in:
else if(file_count != 0)
{
String8 filename = files[0].filename;
if(files[0].props.flags & FilePropertyFlag_IsFolder)
{
String8 existing_path = str8_chop_last_slash(path_query.path);
String8 new_path = push_str8f(scratch.arena, "%S/%S/", existing_path, files[0].filename);
df_view_equip_query(view, new_path);
}
else
{
String8 file_path = push_str8f(scratch.arena, "%S%S", path_query.path, filename);
df_cmd(DF_CmdKind_CompleteQuery, .file_path = file_path);
}
}
// rjf: command argument does not match any file, and lister is empty (new file)
else
{
df_cmd(DF_CmdKind_CompleteQuery, .file_path = query);
}
}
//- rjf: build non-scrolled table header
U64 row_num = 1;
F32 **col_pcts = push_array(scratch.arena, F32 *, ArrayCount(fs->col_pcts));
for(U64 idx = 0; idx < ArrayCount(fs->col_pcts); idx += 1)
{
col_pcts[idx] = &fs->col_pcts[idx];
}
UI_PrefHeight(ui_px(row_height_px, 1)) UI_Focus(UI_FocusKind_Off) UI_TableF(ArrayCount(fs->col_pcts), col_pcts, "###fs_tbl")
{
UI_TableVector
{
struct
{
DF_FileSortKind kind;
String8 string;
}
kinds[] =
{
{ DF_FileSortKind_Filename, str8_lit_comp("Filename") },
{ DF_FileSortKind_LastModified, str8_lit_comp("Last Modified") },
{ DF_FileSortKind_Size, str8_lit_comp("Size") },
};
for(U64 idx = 0; idx < ArrayCount(kinds); idx += 1)
{
B32 sorting = (fs->sort_kind == kinds[idx].kind);
UI_TableCell UI_FlagsAdd(sorting ? 0 : UI_BoxFlag_DrawTextWeak)
{
UI_Signal sig = ui_sort_header(sorting,
fs->cached_files_sort_side == Side_Min,
kinds[idx].string);
if(ui_clicked(sig))
{
if(fs->sort_kind != kinds[idx].kind)
{
fs->sort_kind = kinds[idx].kind;
fs->sort_side = Side_Max;
}
else if(fs->sort_kind == kinds[idx].kind && fs->sort_side == Side_Max)
{
fs->sort_side = Side_Min;
}
else if(fs->sort_kind == kinds[idx].kind && fs->sort_side == Side_Min)
{
fs->sort_kind = DF_FileSortKind_Null;
}
}
}
}
}
}
//- rjf: build file list
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
Vec2F32 content_dim = dim_2f32(rect);
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y-row_height_px);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, file_count+1));
scroll_list_params.item_range = r1s64(0, file_count+1);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
&ps->cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
// rjf: up-one-directory button (at idx 0)
if(visible_row_range.min == 0)
{
// rjf: build
UI_Signal sig = {0};
UI_FocusHot(ps->cursor.y == row_num ? UI_FocusKind_On : UI_FocusKind_Off)
{
sig = ui_buttonf("###up_one");
}
// rjf: make content
UI_Parent(sig.box)
{
// rjf: icons
DF_Font(DF_FontSlot_Icons)
UI_FontSize(df_font_size_from_slot(DF_FontSlot_Icons))
UI_PrefWidth(ui_em(3.f, 1.f))
UI_TextAlignment(UI_TextAlign_Center)
{
ui_label(df_g_icon_kind_text_table[DF_IconKind_LeftArrow]);
}
// rjf: text
{
ui_label(str8_lit("Up One Directory"));
}
row_num += 1;
}
// rjf: click => up one directory
if(ui_clicked(sig))
{
String8 new_path = str8_chop_last_slash(str8_chop_last_slash(path_query.path));
new_path = path_normalized_from_string(scratch.arena, new_path);
String8 new_cmd = push_str8f(scratch.arena, "%S%s", new_path, new_path.size != 0 ? "/" : "");
df_view_equip_query(view, new_cmd);
}
}
// rjf: file buttons
for(U64 row_idx = Max(visible_row_range.min, 1);
row_idx <= visible_row_range.max && row_idx <= file_count;
row_idx += 1, row_num += 1)
{
U64 file_idx = row_idx-1;
DF_FileInfo *file = &files[file_idx];
B32 file_kb_focus = (ps->cursor.y == (row_idx+1));
// rjf: make button
UI_Signal file_sig = {0};
UI_FocusHot(file_kb_focus ? UI_FocusKind_On : UI_FocusKind_Off)
{
file_sig = ui_buttonf("##%S_%p", file->filename, view);
}
// rjf: make content
UI_Parent(file_sig.box)
{
UI_PrefWidth(ui_pct(fs->col_pcts[0], 1)) UI_Row
{
// rjf: icon to signify directory
DF_Font(DF_FontSlot_Icons)
UI_FontSize(df_font_size_from_slot(DF_FontSlot_Icons))
UI_PrefWidth(ui_em(3.f, 1.f))
UI_TextAlignment(UI_TextAlign_Center)
{
if(file->props.flags & FilePropertyFlag_IsFolder)
{
ui_label((ui_key_match(ui_hot_key(), file_sig.box->key) || file_kb_focus)
? df_g_icon_kind_text_table[DF_IconKind_FolderOpenFilled]
: df_g_icon_kind_text_table[DF_IconKind_FolderClosedFilled]);
}
else
{
ui_label(df_g_icon_kind_text_table[DF_IconKind_FileOutline]);
}
}
// rjf: filename
UI_PrefWidth(ui_pct(1, 0))
{
UI_Box *box = ui_build_box_from_string(UI_BoxFlag_DrawText|UI_BoxFlag_DisableIDString, file->filename);
ui_box_equip_fuzzy_match_ranges(box, &file->match_ranges);
}
}
// rjf: last-modified time
UI_PrefWidth(ui_pct(fs->col_pcts[1], 1)) UI_Row
UI_PrefWidth(ui_pct(1, 0))
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
DateTime time = date_time_from_dense_time(file->props.modified);
DateTime time_local = os_local_time_from_universal(&time);
String8 string = push_date_time_string(scratch.arena, &time_local);
ui_label(string);
}
// rjf: file size
UI_PrefWidth(ui_pct(fs->col_pcts[2], 1)) UI_Row
UI_PrefWidth(ui_pct(1, 0))
{
if(file->props.size != 0)
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(str8_from_memory_size(scratch.arena, file->props.size));
}
}
}
// rjf: click => activate this file
if(ui_clicked(file_sig))
{
String8 existing_path = str8_chop_last_slash(path_query.path);
String8 new_path = push_str8f(scratch.arena, "%S%s%S/", existing_path, existing_path.size != 0 ? "/" : "", file->filename);
new_path = path_normalized_from_string(scratch.arena, new_path);
if(file->props.flags & FilePropertyFlag_IsFolder)
{
String8 new_cmd = push_str8f(scratch.arena, "%S%s", new_path, new_path.size != 0 ? "/" : "");
df_view_equip_query(view, new_cmd);
}
else
{
df_cmd(DF_CmdKind_CompleteQuery, .file_path = new_path);
}
}
}
}
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: system_processes @view_hook_impl
typedef struct DF_ProcessInfo DF_ProcessInfo;
struct DF_ProcessInfo
{
DMN_ProcessInfo info;
B32 is_attached;
FuzzyMatchRangeList attached_match_ranges;
FuzzyMatchRangeList name_match_ranges;
FuzzyMatchRangeList pid_match_ranges;
};
typedef struct DF_ProcessInfoNode DF_ProcessInfoNode;
struct DF_ProcessInfoNode
{
DF_ProcessInfoNode *next;
DF_ProcessInfo info;
};
typedef struct DF_ProcessInfoList DF_ProcessInfoList;
struct DF_ProcessInfoList
{
DF_ProcessInfoNode *first;
DF_ProcessInfoNode *last;
U64 count;
};
typedef struct DF_ProcessInfoArray DF_ProcessInfoArray;
struct DF_ProcessInfoArray
{
DF_ProcessInfo *v;
U64 count;
};
internal DF_ProcessInfoList
df_process_info_list_from_query(Arena *arena, String8 query)
{
Temp scratch = scratch_begin(&arena, 1);
//- rjf: gather PIDs that we're currently attached to
U64 attached_process_count = 0;
U32 *attached_process_pids = 0;
{
D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process);
attached_process_count = processes.count;
attached_process_pids = push_array(scratch.arena, U32, attached_process_count);
U64 idx = 0;
for(D_EntityNode *n = processes.first; n != 0; n = n->next, idx += 1)
{
D_Entity *process = n->entity;
attached_process_pids[idx] = process->ctrl_id;
}
}
//- rjf: build list
DF_ProcessInfoList list = {0};
{
DMN_ProcessIter iter = {0};
dmn_process_iter_begin(&iter);
for(DMN_ProcessInfo info = {0}; dmn_process_iter_next(scratch.arena, &iter, &info);)
{
// rjf: skip root-level or otherwise 0-pid processes
if(info.pid == 0)
{
continue;
}
// rjf: determine if this process is attached
B32 is_attached = 0;
for(U64 attached_idx = 0; attached_idx < attached_process_count; attached_idx += 1)
{
if(attached_process_pids[attached_idx] == info.pid)
{
is_attached = 1;
break;
}
}
// rjf: gather fuzzy matches
FuzzyMatchRangeList attached_match_ranges = {0};
FuzzyMatchRangeList name_match_ranges = fuzzy_match_find(arena, query, info.name);
FuzzyMatchRangeList pid_match_ranges = fuzzy_match_find(arena, query, push_str8f(scratch.arena, "%i", info.pid));
if(is_attached)
{
attached_match_ranges = fuzzy_match_find(arena, query, str8_lit("[attached]"));
}
// rjf: determine if this item is filtered out
B32 matches_query = (query.size == 0 ||
(attached_match_ranges.needle_part_count != 0 && attached_match_ranges.count >= attached_match_ranges.needle_part_count) ||
(name_match_ranges.count != 0 && name_match_ranges.count >= name_match_ranges.needle_part_count) ||
(pid_match_ranges.count != 0 && pid_match_ranges.count >= pid_match_ranges.needle_part_count));
// rjf: push if unfiltered
if(matches_query)
{
DF_ProcessInfoNode *n = push_array(arena, DF_ProcessInfoNode, 1);
n->info.info = info;
n->info.info.name = push_str8_copy(arena, info.name);
n->info.is_attached = is_attached;
n->info.attached_match_ranges = attached_match_ranges;
n->info.name_match_ranges = name_match_ranges;
n->info.pid_match_ranges = pid_match_ranges;
SLLQueuePush(list.first, list.last, n);
list.count += 1;
}
}
dmn_process_iter_end(&iter);
}
scratch_end(scratch);
return list;
}
internal DF_ProcessInfoArray
df_process_info_array_from_list(Arena *arena, DF_ProcessInfoList list)
{
DF_ProcessInfoArray array = {0};
array.count = list.count;
array.v = push_array(arena, DF_ProcessInfo, array.count);
U64 idx = 0;
for(DF_ProcessInfoNode *n = list.first; n != 0; n = n->next, idx += 1)
{
array.v[idx] = n->info;
}
return array;
}
internal int
df_qsort_compare_process_info(DF_ProcessInfo *a, DF_ProcessInfo *b)
{
int result = 0;
if(a->pid_match_ranges.count > b->pid_match_ranges.count)
{
result = -1;
}
else if(a->pid_match_ranges.count < b->pid_match_ranges.count)
{
result = +1;
}
else if(a->name_match_ranges.count < b->name_match_ranges.count)
{
result = -1;
}
else if(a->name_match_ranges.count > b->name_match_ranges.count)
{
result = +1;
}
else if(a->attached_match_ranges.count < b->attached_match_ranges.count)
{
result = -1;
}
else if(a->attached_match_ranges.count > b->attached_match_ranges.count)
{
result = +1;
}
return result;
}
internal void
df_process_info_array_sort_by_strength__in_place(DF_ProcessInfoArray array)
{
quick_sort(array.v, array.count, sizeof(DF_ProcessInfo), df_qsort_compare_process_info);
}
DF_VIEW_SETUP_FUNCTION_DEF(system_processes){}
DF_VIEW_CMD_FUNCTION_DEF(system_processes){}
DF_VIEW_UI_FUNCTION_DEF(system_processes)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
//- rjf: grab state
typedef struct DF_SystemProcessesViewState DF_SystemProcessesViewState;
struct DF_SystemProcessesViewState
{
B32 initialized;
B32 need_initial_gather;
U32 selected_pid;
Arena *cached_process_arena;
String8 cached_process_arg;
DF_ProcessInfoArray cached_process_array;
};
DF_SystemProcessesViewState *sp = df_view_user_state(view, DF_SystemProcessesViewState);
if(sp->initialized == 0)
{
sp->initialized = 1;
sp->need_initial_gather = 1;
sp->cached_process_arena = df_view_push_arena_ext(view);
}
//- rjf: gather list of filtered process infos
String8 query = string;
DF_ProcessInfoArray process_info_array = sp->cached_process_array;
if(sp->need_initial_gather || !str8_match(sp->cached_process_arg, query, 0))
{
arena_clear(sp->cached_process_arena);
sp->need_initial_gather = 0;
sp->cached_process_arg = push_str8_copy(sp->cached_process_arena, query);
DF_ProcessInfoList list = df_process_info_list_from_query(sp->cached_process_arena, query);
sp->cached_process_array = df_process_info_array_from_list(sp->cached_process_arena, list);
process_info_array = sp->cached_process_array;
df_process_info_array_sort_by_strength__in_place(process_info_array);
}
//- rjf: submit best match when hitting enter w/ no selection
if(sp->selected_pid == 0 && process_info_array.count > 0 && ui_slot_press(UI_EventActionSlot_Accept))
{
DF_ProcessInfo *info = &process_info_array.v[0];
df_cmd(DF_CmdKind_CompleteQuery, .pid = info->info.pid);
}
//- rjf: selected PID -> cursor
Vec2S64 cursor = {0};
{
for(U64 idx = 0; idx < process_info_array.count; idx += 1)
{
if(process_info_array.v[idx].info.pid == sp->selected_pid)
{
cursor.y = idx+1;
break;
}
}
}
//- rjf: build contents
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
Vec2F32 content_dim = dim_2f32(rect);
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, process_info_array.count));
scroll_list_params.item_range = r1s64(0, process_info_array.count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
&cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
//- rjf: build rows
for(U64 idx = visible_row_range.min;
idx <= visible_row_range.max && idx < process_info_array.count;
idx += 1)
{
DF_ProcessInfo *info = &process_info_array.v[idx];
B32 is_attached = info->is_attached;
UI_Signal sig = {0};
UI_FocusHot(cursor.y == idx+1 ? UI_FocusKind_On : UI_FocusKind_Off)
{
sig = ui_buttonf("###proc_%i", info->info.pid);
}
UI_Parent(sig.box)
{
// rjf: icon
DF_Font(DF_FontSlot_Icons)
UI_FontSize(df_font_size_from_slot(DF_FontSlot_Icons))
UI_PrefWidth(ui_em(3.f, 1.f))
UI_TextAlignment(UI_TextAlign_Center)
{
ui_label(df_g_icon_kind_text_table[DF_IconKind_Threads]);
}
// rjf: attached indicator
if(is_attached) UI_PrefWidth(ui_text_dim(10, 1)) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
UI_Box *attached_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "[attached]##attached_label_%i", (int)info->info.pid);
ui_box_equip_fuzzy_match_ranges(attached_label, &info->attached_match_ranges);
}
// rjf: process name
UI_PrefWidth(ui_text_dim(10, 1))
{
UI_Box *name_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##name_label_%i", info->info.name, (int)info->info.pid);
ui_box_equip_fuzzy_match_ranges(name_label, &info->name_match_ranges);
}
// rjf: process number
UI_PrefWidth(ui_text_dim(1, 1)) UI_TextAlignment(UI_TextAlign_Center) UI_TextPadding(0)
{
ui_labelf("[PID: ");
UI_Box *pid_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%i##pid_label", info->info.pid);
ui_box_equip_fuzzy_match_ranges(pid_label, &info->pid_match_ranges);
ui_labelf("]");
}
}
// rjf: click => activate this specific process
if(ui_clicked(sig))
{
df_cmd(DF_CmdKind_CompleteQuery, .pid = info->info.pid);
}
}
}
//- rjf: selected num -> selected PID
{
if(1 <= cursor.y && cursor.y <= process_info_array.count)
{
sp->selected_pid = process_info_array.v[cursor.y-1].info.pid;
}
else
{
sp->selected_pid = 0;
}
}
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: entity_lister @view_hook_impl
typedef struct DF_EntityListerItem DF_EntityListerItem;
struct DF_EntityListerItem
{
D_Entity *entity;
FuzzyMatchRangeList name_match_ranges;
};
typedef struct DF_EntityListerItemNode DF_EntityListerItemNode;
struct DF_EntityListerItemNode
{
DF_EntityListerItemNode *next;
DF_EntityListerItem item;
};
typedef struct DF_EntityListerItemList DF_EntityListerItemList;
struct DF_EntityListerItemList
{
DF_EntityListerItemNode *first;
DF_EntityListerItemNode *last;
U64 count;
};
typedef struct DF_EntityListerItemArray DF_EntityListerItemArray;
struct DF_EntityListerItemArray
{
DF_EntityListerItem *v;
U64 count;
};
internal DF_EntityListerItemList
df_entity_lister_item_list_from_needle(Arena *arena, D_EntityKind kind, D_EntityFlags omit_flags, String8 needle)
{
Temp scratch = scratch_begin(&arena, 1);
DF_EntityListerItemList result = {0};
D_EntityList ent_list = d_query_cached_entity_list_with_kind(kind);
for(D_EntityNode *n = ent_list.first; n != 0; n = n->next)
{
D_Entity *entity = n->entity;
if(!(entity->flags & omit_flags))
{
String8 display_string = d_display_string_from_entity(scratch.arena, entity);
FuzzyMatchRangeList match_rngs = fuzzy_match_find(arena, needle, display_string);
if(match_rngs.count != 0 || needle.size == 0)
{
DF_EntityListerItemNode *item_n = push_array(arena, DF_EntityListerItemNode, 1);
item_n->item.entity = entity;
item_n->item.name_match_ranges = match_rngs;
SLLQueuePush(result.first, result.last, item_n);
result.count += 1;
}
}
}
scratch_end(scratch);
return result;
}
internal DF_EntityListerItemArray
df_entity_lister_item_array_from_list(Arena *arena, DF_EntityListerItemList list)
{
DF_EntityListerItemArray result = {0};
result.count = list.count;
result.v = push_array(arena, DF_EntityListerItem, result.count);
{
U64 idx = 0;
for(DF_EntityListerItemNode *n = list.first; n != 0; n = n->next, idx += 1)
{
result.v[idx] = n->item;
}
}
return result;
}
internal int
df_qsort_compare_entity_lister__strength(DF_EntityListerItem *a, DF_EntityListerItem *b)
{
int result = 0;
if(a->name_match_ranges.count > b->name_match_ranges.count)
{
result = -1;
}
else if(a->name_match_ranges.count < b->name_match_ranges.count)
{
result = +1;
}
return result;
}
internal void
df_entity_lister_item_array_sort_by_strength__in_place(DF_EntityListerItemArray array)
{
quick_sort(array.v, array.count, sizeof(DF_EntityListerItem), df_qsort_compare_entity_lister__strength);
}
DF_VIEW_SETUP_FUNCTION_DEF(entity_lister){}
DF_VIEW_CMD_FUNCTION_DEF(entity_lister){}
DF_VIEW_UI_FUNCTION_DEF(entity_lister)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
DF_Window *window = df_window_from_handle(df_regs()->window);
DF_CmdKindInfo *cmd_kind_info = df_cmd_kind_info_from_string(window->query_cmd_name);
D_EntityKind entity_kind = cmd_kind_info->query.entity_kind;
D_EntityFlags entity_flags_omit = D_EntityFlag_IsFolder;
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f);
//- rjf: grab state
typedef struct DF_EntityListerViewState DF_EntityListerViewState;
struct DF_EntityListerViewState
{
D_Handle selected_entity_handle;
};
DF_EntityListerViewState *fev = df_view_user_state(view, DF_EntityListerViewState);
D_Handle selected_entity_handle = fev->selected_entity_handle;
D_Entity *selected_entity = d_entity_from_handle(selected_entity_handle);
//- rjf: build filtered array of entities
DF_EntityListerItemList ent_list = df_entity_lister_item_list_from_needle(scratch.arena, entity_kind, entity_flags_omit, string);
DF_EntityListerItemArray ent_arr = df_entity_lister_item_array_from_list(scratch.arena, ent_list);
df_entity_lister_item_array_sort_by_strength__in_place(ent_arr);
//- rjf: submit best match when hitting enter w/ no selection
if(d_entity_is_nil(d_entity_from_handle(fev->selected_entity_handle)) && ent_arr.count != 0 && ui_slot_press(UI_EventActionSlot_Accept))
{
D_Entity *ent = ent_arr.v[0].entity;
df_cmd(DF_CmdKind_CompleteQuery, .entity = d_handle_from_entity(ent));
}
//- rjf: selected entity -> cursor
Vec2S64 cursor = {0};
{
for(U64 idx = 0; idx < ent_arr.count; idx += 1)
{
if(ent_arr.v[idx].entity == selected_entity)
{
cursor.y = (S64)(idx+1);
break;
}
}
}
//- rjf: build list
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
Vec2F32 content_dim = dim_2f32(rect);
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, ent_arr.count));
scroll_list_params.item_range = r1s64(0, ent_arr.count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
&cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
for(S64 idx = visible_row_range.min;
idx <= visible_row_range.max && idx < ent_arr.count;
idx += 1)
{
DF_EntityListerItem item = ent_arr.v[idx];
D_Entity *ent = item.entity;
ui_set_next_hover_cursor(OS_Cursor_HandPoint);
ui_set_next_child_layout_axis(Axis2_X);
UI_Box *box = &ui_g_nil_box;
UI_FocusHot(idx+1 == cursor.y ? UI_FocusKind_On : UI_FocusKind_Off)
{
box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawActiveEffects,
"###ent_btn_%p", ent);
}
UI_Parent(box)
{
DF_IconKind icon_kind = df_entity_kind_icon_kind_table[ent->kind];
if(icon_kind != DF_IconKind_Null)
{
UI_TextAlignment(UI_TextAlign_Center)
DF_Font(DF_FontSlot_Icons)
UI_FontSize(df_font_size_from_slot(DF_FontSlot_Icons))
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
UI_PrefWidth(ui_text_dim(10, 1))
ui_label(df_g_icon_kind_text_table[icon_kind]);
}
String8 display_string = d_display_string_from_entity(scratch.arena, ent);
Vec4F32 color = d_rgba_from_entity(ent);
if(color.w != 0)
{
ui_set_next_palette(ui_build_palette(ui_top_palette(), .text = color));
}
UI_Box *name_label = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S##label_%p", display_string, ent);
ui_box_equip_fuzzy_match_ranges(name_label, &item.name_match_ranges);
}
if(ui_clicked(ui_signal_from_box(box)))
{
df_cmd(DF_CmdKind_CompleteQuery, .entity = d_handle_from_entity(ent));
}
}
}
//- rjf: selected entity num -> handle
{
fev->selected_entity_handle = (1 <= cursor.y && cursor.y <= ent_arr.count) ? d_handle_from_entity(ent_arr.v[cursor.y-1].entity) : d_handle_zero();
}
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: symbol_lister @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(symbol_lister){}
DF_VIEW_CMD_FUNCTION_DEF(symbol_lister){}
DF_VIEW_UI_FUNCTION_DEF(symbol_lister)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
DI_Scope *di_scope = di_scope_open();
FZY_Scope *fzy_scope = fzy_scope_open();
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
DI_KeyList dbgi_keys_list = d_push_active_dbgi_key_list(scratch.arena);
DI_KeyArray dbgi_keys = di_key_array_from_list(scratch.arena, &dbgi_keys_list);
FZY_Params fuzzy_search_params = {RDI_SectionKind_Procedures, dbgi_keys};
U64 endt_us = os_now_microseconds()+200;
//- rjf: grab rdis, make type graphs for each
U64 rdis_count = dbgi_keys.count;
RDI_Parsed **rdis = push_array(scratch.arena, RDI_Parsed *, rdis_count);
{
for(U64 idx = 0; idx < rdis_count; idx += 1)
{
RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_keys.v[idx], endt_us);
RDI_TopLevelInfo *tli = rdi_element_from_name_idx(rdi, TopLevelInfo, 0);
rdis[idx] = rdi;
}
}
//- rjf: grab state
typedef struct DF_SymbolListerViewState DF_SymbolListerViewState;
struct DF_SymbolListerViewState
{
Vec2S64 cursor;
};
DF_SymbolListerViewState *slv = df_view_user_state(view, DF_SymbolListerViewState);
//- rjf: query -> raddbg, filtered items
U128 fuzzy_search_key = {(U64)view, d_hash_from_string(str8_struct(&view))};
B32 items_stale = 0;
FZY_ItemArray items = fzy_items_from_key_params_query(fzy_scope, fuzzy_search_key, &fuzzy_search_params, string, endt_us, &items_stale);
if(items_stale)
{
df_request_frame();
}
//- rjf: submit best match when hitting enter w/ no selection
if(slv->cursor.y == 0 && items.count != 0 && ui_slot_press(UI_EventActionSlot_Accept))
{
FZY_Item *item = &items.v[0];
U64 base_idx = 0;
for(U64 rdi_idx = 0; rdi_idx < rdis_count; rdi_idx += 1)
{
RDI_Parsed *rdi = rdis[rdi_idx];
U64 rdi_procedures_count = 0;
rdi_section_raw_table_from_kind(rdi, RDI_SectionKind_Procedures, &rdi_procedures_count);
if(base_idx <= item->idx && item->idx < base_idx + rdi_procedures_count)
{
RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, item->idx-base_idx);
U64 name_size = 0;
U8 *name_base = rdi_string_from_idx(rdi, procedure->name_string_idx, &name_size);
String8 name = str8(name_base, name_size);
if(name.size != 0)
{
df_cmd(DF_CmdKind_CompleteQuery, .string = name);
}
break;
}
base_idx += rdi_procedures_count;
}
}
//- rjf: build contents
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
Vec2F32 content_dim = dim_2f32(rect);
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = v2f32(content_dim.x, content_dim.y);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, items.count));
scroll_list_params.item_range = r1s64(0, items.count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
&slv->cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
UI_TextRasterFlags(df_raster_flags_from_slot(DF_FontSlot_Code))
{
//- rjf: build rows
for(U64 idx = visible_row_range.min;
idx <= visible_row_range.max && idx < items.count;
idx += 1)
UI_Focus((slv->cursor.y == idx+1) ? UI_FocusKind_On : UI_FocusKind_Off)
{
FZY_Item *item = &items.v[idx];
//- rjf: determine dbgi/rdi to which this item belongs
DI_Key dbgi_key = {0};
RDI_Parsed *rdi = &di_rdi_parsed_nil;
U64 base_idx = 0;
{
for(U64 rdi_idx = 0; rdi_idx < rdis_count; rdi_idx += 1)
{
U64 procedures_count = 0;
rdi_section_raw_table_from_kind(rdis[rdi_idx], RDI_SectionKind_Procedures, &procedures_count);
if(base_idx <= item->idx && item->idx < base_idx + procedures_count)
{
dbgi_key = dbgi_keys.v[rdi_idx];
rdi = rdis[rdi_idx];
break;
}
base_idx += procedures_count;
}
}
//- rjf: unpack this item's info
RDI_Procedure *procedure = rdi_element_from_name_idx(rdi, Procedures, item->idx-base_idx);
U64 name_size = 0;
U8 *name_base = rdi_string_from_idx(rdi, procedure->name_string_idx, &name_size);
String8 name = str8(name_base, name_size);
RDI_TypeNode *type_node = rdi_element_from_name_idx(rdi, TypeNodes, procedure->type_idx);
E_TypeKey type_key = e_type_key_ext(e_type_kind_from_rdi(type_node->kind), procedure->type_idx, e_parse_ctx_module_idx_from_rdi(rdi));
//- rjf: build item button
ui_set_next_hover_cursor(OS_Cursor_HandPoint);
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawText|
UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawActiveEffects,
"###procedure_%I64x", item->idx);
UI_Parent(box) UI_PrefWidth(ui_text_dim(10, 1)) DF_Font(DF_FontSlot_Code)
{
UI_Box *box = df_code_label(1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol), name);
ui_box_equip_fuzzy_match_ranges(box, &item->match_ranges);
if(!e_type_key_match(e_type_key_zero(), type_key))
{
String8 type_string = e_type_string_from_key(scratch.arena, type_key);
df_code_label(0.5f, 0, df_rgba_from_theme_color(DF_ThemeColor_TextWeak), type_string);
}
}
//- rjf: interact
UI_Signal sig = ui_signal_from_box(box);
if(ui_clicked(sig))
{
df_cmd(DF_CmdKind_CompleteQuery, .string = name);
}
if(ui_hovering(sig)) UI_Tooltip
{
DF_Font(DF_FontSlot_Code) df_code_label(1.f, 0, df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol), name);
DF_Font(DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
ui_labelf("Procedure #%I64u", item->idx);
U64 binary_voff = d_voff_from_dbgi_key_symbol_name(&dbgi_key, name);
D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, binary_voff);
if(lines.first != 0)
{
String8 file_path = lines.first->v.file_path;
S64 line_num = lines.first->v.pt.line;
DF_Font(DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
ui_labelf("%S:%I64d", file_path, line_num);
}
else
{
DF_Font(DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
ui_label(str8_lit("(No source code location found)"));
}
}
}
}
fzy_scope_close(fzy_scope);
di_scope_close(di_scope);
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: target @view_hook_impl
typedef struct DF_TargetViewState DF_TargetViewState;
struct DF_TargetViewState
{
B32 initialized;
// rjf: pick file kind
D_EntityKind pick_dst_kind;
// rjf: selection cursor
Vec2S64 cursor;
// rjf: text input state
TxtPt input_cursor;
TxtPt input_mark;
U8 input_buffer[1024];
U64 input_size;
B32 input_editing;
// rjf: table column pcts
F32 key_pct;
F32 value_pct;
};
DF_VIEW_SETUP_FUNCTION_DEF(target)
{
DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState);
}
DF_VIEW_CMD_FUNCTION_DEF(target)
{
DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState);
D_Entity *entity = d_entity_from_eval_string(string);
#if 0 // TODO(rjf): @msgs
// rjf: process commands
for(DF_Cmd *cmd = 0; df_next_cmd(&cmd);)
{
// rjf: mismatched window/panel => skip
if(!d_handle_match(df_regs()->window, cmd->params.window) ||
!d_handle_match(df_regs()->panel, cmd->params.panel))
{
continue;
}
// rjf: process command
DF_CmdKind kind = df_cmd_kind_from_string(cmd->spec->info.string);
switch(kind)
{
default:break;
case DF_CmdKind_PickFile:
case DF_CmdKind_PickFolder:
{
String8 pick_string = cmd->params.file_path;
D_Entity *storage_entity = entity;
if(tv->pick_dst_kind != D_EntityKind_Nil)
{
D_Entity *child = d_entity_child_from_kind(entity, tv->pick_dst_kind);
if(d_entity_is_nil(child))
{
child = d_entity_alloc(entity, tv->pick_dst_kind);
}
storage_entity = child;
}
d_entity_equip_name(storage_entity, pick_string);
}break;
}
}
#endif
}
DF_VIEW_UI_FUNCTION_DEF(target)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
D_Entity *entity = d_entity_from_eval_string(string);
D_EntityList custom_entry_points = d_push_entity_child_list_with_kind(scratch.arena, entity, D_EntityKind_EntryPoint);
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
//- rjf: grab state
DF_TargetViewState *tv = df_view_user_state(view, DF_TargetViewState);
if(tv->initialized == 0)
{
tv->initialized = 1;
tv->key_pct = 0.2f;
tv->value_pct = 0.8f;
}
//- rjf: set up key-value-pair info
struct
{
B32 fill_with_file;
B32 fill_with_folder;
B32 use_code_font;
String8 key;
D_EntityKind storage_child_kind;
String8 current_text;
}
kv_info[] =
{
{ 0, 0, 0, str8_lit("Label"), D_EntityKind_Nil, entity->string },
{ 1, 0, 0, str8_lit("Executable"), D_EntityKind_Executable, d_entity_child_from_kind(entity, D_EntityKind_Executable)->string },
{ 0, 0, 0, str8_lit("Arguments"), D_EntityKind_Arguments, d_entity_child_from_kind(entity, D_EntityKind_Arguments)->string },
{ 0, 1, 0, str8_lit("Working Directory"), D_EntityKind_WorkingDirectory, d_entity_child_from_kind(entity, D_EntityKind_WorkingDirectory)->string },
{ 0, 0, 1, str8_lit("Entry Point Override"), D_EntityKind_EntryPoint, d_entity_child_from_kind(entity, D_EntityKind_EntryPoint)->string },
};
//- rjf: take controls to start/end editing
B32 edit_begin = 0;
B32 edit_end = 0;
B32 edit_commit = 0;
B32 edit_submit = 0;
UI_Focus(UI_FocusKind_On) if(ui_is_focus_active())
{
if(!tv->input_editing)
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->string.size != 0 || evt->flags & UI_EventFlag_Paste)
{
edit_begin = 1;
break;
}
}
if(ui_slot_press(UI_EventActionSlot_Edit))
{
edit_begin = 1;
}
if(ui_slot_press(UI_EventActionSlot_Accept))
{
edit_begin = 1;
}
}
if(tv->input_editing)
{
if(ui_slot_press(UI_EventActionSlot_Cancel))
{
edit_end = 1;
edit_commit = 0;
}
if(ui_slot_press(UI_EventActionSlot_Accept))
{
edit_end = 1;
edit_commit = 1;
edit_submit = 1;
}
}
}
//- rjf: build
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = dim_2f32(rect);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, (S64)ArrayCount(kv_info)));
scroll_list_params.item_range = r1s64(0, (S64)ArrayCount(kv_info));
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
D_EntityKind commit_storage_child_kind = D_EntityKind_Nil;
Vec2S64 next_cursor = tv->cursor;
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
tv->input_editing ? 0 : &tv->cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
next_cursor = tv->cursor;
F32 *col_pcts[] = {&tv->key_pct, &tv->value_pct};
UI_TableF(ArrayCount(col_pcts), col_pcts, "###target_%p", view)
{
//- rjf: build fixed rows
S64 row_idx = 0;
for(S64 idx = visible_row_range.min;
idx <= visible_row_range.max && idx < ArrayCount(kv_info);
idx += 1, row_idx += 1)
UI_TableVector
{
B32 row_selected = (tv->cursor.y == idx+1);
B32 has_browse = kv_info[idx].fill_with_file || kv_info[idx].fill_with_folder;
//- rjf: key (label)
UI_TableCell UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
if(kv_info[idx].storage_child_kind == D_EntityKind_EntryPoint)
{
if(df_help_label(str8_lit("Custom Entry Point"))) UI_Tooltip
{
ui_label_multiline(ui_top_font_size()*30.f, str8_lit("By default, the debugger attempts to find a target's entry point with a set of default names, such as:"));
ui_spacer(ui_em(1.5f, 1.f));
DF_Font(DF_FontSlot_Code) UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol)))
{
ui_label(str8_lit("WinMain"));
ui_label(str8_lit("wWinMain"));
ui_label(str8_lit("main"));
ui_label(str8_lit("wmain"));
ui_label(str8_lit("WinMainCRTStartup"));
ui_label(str8_lit("wWinMainCRTStartup"));
}
ui_spacer(ui_em(1.5f, 1.f));
ui_label_multiline(ui_top_font_size()*30.f, str8_lit("A Custom Entry Point can be used to override these default symbol names with a symbol name of your choosing. If a symbol matching the Custom Entry Point is not found, the debugger will fall back to its default rules."));
}
}
else
{
ui_build_box_from_string(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, kv_info[idx].key);
}
}
//- rjf: value
UI_TableCell
{
// rjf: value editor
UI_WidthFill DF_Font(kv_info[idx].use_code_font ? DF_FontSlot_Code : DF_FontSlot_Main)
{
// rjf: * => focus
B32 value_selected = row_selected && (next_cursor.x == 0 || !has_browse);
// rjf: begin editing
if(value_selected && edit_begin)
{
tv->input_editing = 1;
tv->input_size = Min(sizeof(tv->input_buffer), kv_info[idx].current_text.size);
MemoryCopy(tv->input_buffer, kv_info[idx].current_text.str, tv->input_size);
tv->input_cursor = txt_pt(1, 1+tv->input_size);
tv->input_mark = txt_pt(1, 1);
}
// rjf: build main editor ui
UI_Signal sig = {0};
UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off)
UI_FocusActive((value_selected && tv->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off)
{
sig = df_line_editf(DF_LineEditFlag_NoBackground, 0, 0, &tv->input_cursor, &tv->input_mark, tv->input_buffer, sizeof(tv->input_buffer), &tv->input_size, 0, kv_info[idx].current_text, "###kv_editor_%i", (S32)idx);
edit_commit = edit_commit || ui_committed(sig);
}
// rjf: focus panel on press
if(ui_pressed(sig))
{
df_cmd(DF_CmdKind_FocusPanel);
}
// rjf: begin editing on double-click
if(!tv->input_editing && ui_double_clicked(sig))
{
ui_kill_action();
tv->input_editing = 1;
tv->input_size = Min(sizeof(tv->input_buffer), kv_info[idx].current_text.size);
MemoryCopy(tv->input_buffer, kv_info[idx].current_text.str, tv->input_size);
tv->input_cursor = txt_pt(1, 1+tv->input_size);
tv->input_mark = txt_pt(1, 1);
}
// rjf: press on non-selected => commit edit, change selected cell
if(ui_pressed(sig) && !value_selected)
{
edit_end = 1;
edit_commit = tv->input_editing;
next_cursor = v2s64(0, idx+1);
}
// rjf: apply commit deltas
if(ui_committed(sig))
{
next_cursor.y += 1;
}
// rjf: grab commit destination
if(value_selected)
{
commit_storage_child_kind = kv_info[idx].storage_child_kind;
}
}
// rjf: browse button to fill text field
if(has_browse) UI_PrefWidth(ui_text_dim(10, 1))
{
UI_FocusHot((row_selected && next_cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off)
UI_TextAlignment(UI_TextAlign_Center)
if(ui_clicked(ui_buttonf("Browse...")))
{
DF_CmdKind cmd_kind = kv_info[idx].fill_with_file ? DF_CmdKind_PickFile : DF_CmdKind_PickFolder;
df_cmd(DF_CmdKind_RunCommand, .string = df_cmd_kind_info_table[cmd_kind].string);
tv->pick_dst_kind = kv_info[idx].storage_child_kind;
}
}
}
}
}
}
//- rjf: apply commit
if(edit_commit)
{
String8 new_string = str8(tv->input_buffer, tv->input_size);
switch(commit_storage_child_kind)
{
default:
{
D_Entity *child = d_entity_child_from_kind(entity, commit_storage_child_kind);
if(d_entity_is_nil(child))
{
child = d_entity_alloc(entity, commit_storage_child_kind);
}
d_entity_equip_name(child, new_string);
}break;
case D_EntityKind_Nil:
{
d_entity_equip_name(entity, new_string);
}break;
}
}
//- rjf: apply editing finish
if(edit_end)
{
tv->input_editing = 0;
}
if(edit_submit)
{
next_cursor.y += 1;
}
//- rjf: apply moves to selection
tv->cursor = next_cursor;
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: targets @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(targets){}
DF_VIEW_CMD_FUNCTION_DEF(targets){}
DF_VIEW_UI_FUNCTION_DEF(targets)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
D_EntityList targets_list = d_query_cached_entity_list_with_kind(D_EntityKind_Target);
D_EntityFuzzyItemArray targets = d_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &targets_list, string);
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
//- rjf: grab state
typedef struct DF_TargetsViewState DF_TargetsViewState;
struct DF_TargetsViewState
{
B32 selected_add;
D_Handle selected_target_handle;
S64 selected_column;
};
DF_TargetsViewState *tv = df_view_user_state(view, DF_TargetsViewState);
//- rjf: determine table bounds
Vec2S64 table_bounds = {5, (S64)targets.count+1};
//- rjf: selection state => cursor
// NOTE(rjf): 0 => nothing, 1 => add new, 2 => first target
Vec2S64 cursor = {0};
{
D_Entity *selected_target = d_entity_from_handle(tv->selected_target_handle);
for(U64 idx = 0; idx < targets.count; idx += 1)
{
if(selected_target == targets.v[idx].entity)
{
cursor.y = (S64)idx+2;
break;
}
}
if(tv->selected_add)
{
cursor.y = 1;
}
cursor.x = tv->selected_column;
}
//- rjf: build
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = dim_2f32(rect);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(5, Max(0, (S64)targets.count+1)));
scroll_list_params.item_range = r1s64(0, Max(0, (S64)targets.count+1));
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
&cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
// rjf: add new ctrl
if(visible_row_range.min == 0)
{
UI_Signal add_sig = {0};
UI_FocusHot(cursor.y == 1 ? UI_FocusKind_On : UI_FocusKind_Off)
add_sig = df_icon_buttonf(DF_IconKind_Add, 0, "Add New Target");
if(ui_clicked(add_sig))
{
df_cmd(DF_CmdKind_RunCommand, .string = df_cmd_kind_info_table[DF_CmdKind_AddTarget].string);
}
}
// rjf: target rows
for(S64 row_idx = Max(1, visible_row_range.min);
row_idx <= visible_row_range.max && row_idx <= targets.count;
row_idx += 1)
UI_Row
{
D_Entity *target = targets.v[row_idx-1].entity;
B32 row_selected = ((U64)cursor.y == row_idx+1);
// rjf: enabled
UI_PrefWidth(ui_em(2.25f, 1))
UI_FocusHot((row_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off)
{
UI_Signal sig = df_icon_buttonf(!target->disabled ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow, 0, "###ebl_%p", target);
if(ui_clicked(sig))
{
df_cmd(!target->disabled ? DF_CmdKind_DisableTarget : DF_CmdKind_EnableTarget, .entity = d_handle_from_entity(target));
}
}
// rjf: target name
UI_WidthFill UI_FocusHot((row_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off)
{
df_entity_desc_button(target, &targets.v[row_idx-1].matches, string, 0);
}
// rjf: controls
UI_PrefWidth(ui_em(2.25f, 1.f))
{
struct
{
DF_IconKind icon;
String8 text;
DF_CmdKind cmd;
}
ctrls[] =
{
{ DF_IconKind_PlayStepForward, str8_lit("Launch and Initialize"), DF_CmdKind_LaunchAndInit },
{ DF_IconKind_Play, str8_lit("Launch and Run"), DF_CmdKind_LaunchAndRun },
{ DF_IconKind_Pencil, str8_lit("Edit"), DF_CmdKind_Target },
{ DF_IconKind_Trash, str8_lit("Delete"), DF_CmdKind_RemoveTarget },
};
for(U64 ctrl_idx = 0; ctrl_idx < ArrayCount(ctrls); ctrl_idx += 1)
{
UI_Signal sig = {0};
UI_FocusHot((row_selected && cursor.x == 2+ctrl_idx) ? UI_FocusKind_On : UI_FocusKind_Off)
{
sig = df_icon_buttonf(ctrls[ctrl_idx].icon, 0, "###%p_ctrl_%i", target, (int)ctrl_idx);
}
if(ui_hovering(sig)) UI_Tooltip
{
ui_label(ctrls[ctrl_idx].text);
}
if(ui_clicked(sig))
{
df_cmd(ctrls[ctrl_idx].cmd, .entity = d_handle_from_entity(target));
}
}
}
}
}
//- rjf: commit cursor to selection state
{
tv->selected_column = cursor.x;
tv->selected_target_handle = (1 < cursor.y && cursor.y < targets.count+2) ? d_handle_from_entity(targets.v[cursor.y-2].entity) : d_handle_zero();
tv->selected_add = (cursor.y == 1);
}
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: file_path_map @view_hook_impl
typedef struct DF_FilePathMapViewState DF_FilePathMapViewState;
struct DF_FilePathMapViewState
{
B32 initialized;
Vec2S64 cursor;
TxtPt input_cursor;
TxtPt input_mark;
U8 input_buffer[1024];
U64 input_size;
B32 input_editing;
D_Handle pick_file_dst_map;
Side pick_file_dst_side;
F32 src_column_pct;
F32 dst_column_pct;
};
DF_VIEW_SETUP_FUNCTION_DEF(file_path_map)
{
DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState);
if(fpms->initialized == 0)
{
fpms->initialized = 1;
fpms->src_column_pct = 0.5f;
fpms->dst_column_pct = 0.5f;
}
}
DF_VIEW_CMD_FUNCTION_DEF(file_path_map)
{
DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState);
// rjf: process commands
for(DF_Cmd *cmd = 0; df_next_cmd(&cmd);)
{
// rjf: mismatched window/panel => skip
if(!d_handle_match(df_regs()->window, cmd->regs->window) ||
!d_handle_match(df_regs()->panel, cmd->regs->panel))
{
continue;
}
//rjf: process
DF_CmdKind kind = df_cmd_kind_from_string(cmd->name);
switch(kind)
{
default:break;
case DF_CmdKind_PickFile:
case DF_CmdKind_PickFolder:
case DF_CmdKind_PickFileOrFolder:
{
String8 pick_string = cmd->regs->file_path;
Side pick_side = fpms->pick_file_dst_side;
D_Entity *storage_entity = d_entity_from_handle(fpms->pick_file_dst_map);
df_cmd(pick_side == Side_Min ?
DF_CmdKind_SetFileOverrideLinkSrc :
DF_CmdKind_SetFileOverrideLinkDst,
.entity = d_handle_from_entity(storage_entity),
.file_path = pick_string);
}break;
}
}
}
DF_VIEW_UI_FUNCTION_DEF(file_path_map)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
D_EntityList maps_list = d_query_cached_entity_list_with_kind(D_EntityKind_FilePathMap);
D_EntityArray maps = d_entity_array_from_list(scratch.arena, &maps_list);
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
//- rjf: grab state
DF_FilePathMapViewState *fpms = df_view_user_state(view, DF_FilePathMapViewState);
//- rjf: take controls to start/end editing
B32 edit_begin = 0;
B32 edit_end = 0;
B32 edit_commit = 0;
B32 edit_submit = 0;
UI_Focus(UI_FocusKind_On) if(ui_is_focus_active())
{
if(!fpms->input_editing)
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->string.size != 0 || evt->flags & UI_EventFlag_Paste)
{
edit_begin = 1;
break;
}
}
if(ui_slot_press(UI_EventActionSlot_Edit))
{
edit_begin = 1;
}
}
if(fpms->input_editing)
{
if(ui_slot_press(UI_EventActionSlot_Cancel))
{
edit_end = 1;
edit_commit = 0;
}
if(ui_slot_press(UI_EventActionSlot_Accept))
{
edit_end = 1;
edit_commit = 1;
edit_submit = 1;
}
}
}
//- rjf: build
D_Handle commit_map = d_handle_zero();
Side commit_side = Side_Invalid;
F32 *col_pcts[] = { &fpms->src_column_pct, &fpms->dst_column_pct };
Vec2S64 next_cursor = fpms->cursor;
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = dim_2f32(rect);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, maps.count + 1));
scroll_list_params.item_range = r1s64(0, maps.count+2);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
fpms->input_editing ? 0 : &fpms->cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
UI_TableF(ArrayCount(col_pcts), col_pcts, "###tbl")
{
next_cursor = fpms->cursor;
//- rjf: header
if(visible_row_range.min == 0) UI_TableVector UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
UI_TableCell if(df_help_label(str8_lit("Source Path"))) UI_Tooltip
{
ui_label_multiline(ui_top_font_size()*30, str8_lit("When the debugger attempts to open a file or folder at a Source Path specified in this table, it will redirect to the file or folder specified by the Destination Path."));
}
UI_TableCell ui_label(str8_lit("Destination Path"));
}
//- rjf: map rows
for(S64 row_idx = Max(1, visible_row_range.min);
row_idx <= visible_row_range.max && row_idx <= maps.count+1;
row_idx += 1) UI_TableVector
{
U64 map_idx = row_idx-1;
D_Entity *map = (map_idx < maps.count ? maps.v[map_idx] : &d_nil_entity);
D_Entity *map_src = d_entity_child_from_kind(map, D_EntityKind_Source);
D_Entity *map_dst = d_entity_child_from_kind(map, D_EntityKind_Dest);
String8 map_src_path = map_src->string;
String8 map_dst_path = map_dst->string;
B32 row_selected = (fpms->cursor.y == row_idx);
//- rjf: src
UI_TableCell UI_WidthFill
{
//- rjf: editor
{
B32 value_selected = (row_selected && fpms->cursor.x == 0);
// rjf: begin editing
if(value_selected && edit_begin)
{
fpms->input_editing = 1;
fpms->input_size = Min(sizeof(fpms->input_buffer), map_src_path.size);
MemoryCopy(fpms->input_buffer, map_src_path.str, fpms->input_size);
fpms->input_cursor = txt_pt(1, 1+fpms->input_size);
fpms->input_mark = txt_pt(1, 1);
}
// rjf: build
UI_Signal sig = {0};
UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off)
UI_FocusActive((value_selected && fpms->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off)
{
sig = df_line_editf(DF_LineEditFlag_NoBackground, 0, 0, &fpms->input_cursor, &fpms->input_mark, fpms->input_buffer, sizeof(fpms->input_buffer), &fpms->input_size, 0, map_src_path, "###src_editor_%p", map);
edit_commit = edit_commit || ui_committed(sig);
}
// rjf: focus panel on press
if(ui_pressed(sig))
{
df_cmd(DF_CmdKind_FocusPanel);
}
// rjf: begin editing on double-click
if(!fpms->input_editing && ui_double_clicked(sig))
{
fpms->input_editing = 1;
fpms->input_size = Min(sizeof(fpms->input_buffer), map_src_path.size);
MemoryCopy(fpms->input_buffer, map_src_path.str, fpms->input_size);
fpms->input_cursor = txt_pt(1, 1+fpms->input_size);
fpms->input_mark = txt_pt(1, 1);
}
// rjf: press on non-selected => commit edit, change selected cell
if(ui_pressed(sig) && !value_selected)
{
edit_end = 1;
edit_commit = fpms->input_editing;
next_cursor.x = 0;
next_cursor.y = map_idx+1;
}
// rjf: store commit information
if(value_selected)
{
commit_side = Side_Min;
commit_map = d_handle_from_entity(map);
}
}
//- rjf: browse button
UI_FocusHot((row_selected && fpms->cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off)
UI_PrefWidth(ui_text_dim(10, 1))
if(ui_clicked(ui_buttonf("Browse...")))
{
df_cmd(DF_CmdKind_RunCommand, .string = df_cmd_kind_info_table[DF_CmdKind_PickFileOrFolder].string);
fpms->pick_file_dst_map = d_handle_from_entity(map);
fpms->pick_file_dst_side = Side_Min;
}
}
//- rjf: dst
UI_TableCell UI_WidthFill
{
//- rjf: editor
{
B32 value_selected = (row_selected && fpms->cursor.x == 2);
// rjf: begin editing
if(value_selected && edit_begin)
{
fpms->input_editing = 1;
fpms->input_size = Min(sizeof(fpms->input_buffer), map_dst_path.size);
MemoryCopy(fpms->input_buffer, map_dst_path.str, fpms->input_size);
fpms->input_cursor = txt_pt(1, 1+fpms->input_size);
fpms->input_mark = txt_pt(1, 1);
}
// rjf: build
UI_Signal sig = {0};
UI_FocusHot(value_selected ? UI_FocusKind_On : UI_FocusKind_Off)
UI_FocusActive((value_selected && fpms->input_editing) ? UI_FocusKind_On : UI_FocusKind_Off)
{
sig = df_line_editf(DF_LineEditFlag_NoBackground, 0, 0, &fpms->input_cursor, &fpms->input_mark, fpms->input_buffer, sizeof(fpms->input_buffer), &fpms->input_size, 0, map_dst_path, "###dst_editor_%p", map);
edit_commit = edit_commit || ui_committed(sig);
}
// rjf: focus panel on press
if(ui_pressed(sig))
{
df_cmd(DF_CmdKind_FocusPanel);
}
// rjf: begin editing on double-click
if(!fpms->input_editing && ui_double_clicked(sig))
{
fpms->input_editing = 1;
fpms->input_size = Min(sizeof(fpms->input_buffer), map_dst_path.size);
MemoryCopy(fpms->input_buffer, map_dst_path.str, fpms->input_size);
fpms->input_cursor = txt_pt(1, 1+fpms->input_size);
fpms->input_mark = txt_pt(1, 1);
}
// rjf: press on non-selected => commit edit, change selected cell
if(ui_pressed(sig) && !value_selected)
{
edit_end = 1;
edit_commit = fpms->input_editing;
next_cursor.x = 2;
next_cursor.y = map_idx+1;
}
// rjf: store commit information
if(value_selected)
{
commit_side = Side_Max;
commit_map = d_handle_from_entity(map);
}
}
//- rjf: browse button
{
UI_FocusHot((row_selected && fpms->cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off)
UI_PrefWidth(ui_text_dim(10, 1))
if(ui_clicked(ui_buttonf("Browse...")))
{
df_cmd(DF_CmdKind_RunCommand, .string = df_cmd_kind_info_table[DF_CmdKind_PickFileOrFolder].string);
fpms->pick_file_dst_map = d_handle_from_entity(map);
fpms->pick_file_dst_side = Side_Max;
}
}
}
}
}
//- rjf: apply commit
if(edit_commit && commit_side != Side_Invalid)
{
String8 new_string = str8(fpms->input_buffer, fpms->input_size);
df_cmd(commit_side == Side_Min ?
DF_CmdKind_SetFileOverrideLinkSrc :
DF_CmdKind_SetFileOverrideLinkDst,
.entity = commit_map,
.file_path = new_string);
}
//- rjf: apply editing finish
if(edit_end)
{
fpms->input_editing = 0;
}
//- rjf: move down one row if submitted
if(edit_submit)
{
next_cursor.y += 1;
}
//- rjf: apply moves to selection
fpms->cursor = next_cursor;
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: auto_view_rules @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(auto_view_rules){}
DF_VIEW_CMD_FUNCTION_DEF(auto_view_rules){}
DF_VIEW_UI_FUNCTION_DEF(auto_view_rules){}
////////////////////////////////
//~ rjf: breakpoints @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(breakpoints)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_Breakpoints);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Member, 0.25f, .string = str8_lit("label"), .display_string = str8_lit("Label"), .dequote_string = 1, .is_non_code = 1);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Member, 0.35f, .string = str8_lit("location"), .display_string = str8_lit("Location"), .dequote_string = 1, .is_non_code = 1);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Member, 0.20f, .string = str8_lit("condition"), .display_string = str8_lit("Condition"), .dequote_string = 1);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Member, 0.10f, .string = str8_lit("enabled"), .display_string = str8_lit("Enabled"), .view_rule = str8_lit("checkbox"));
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Member, 0.10f, .string = str8_lit("hit_count"), .display_string = str8_lit("Hit Count"));
}
DF_VIEW_CMD_FUNCTION_DEF(breakpoints){}
DF_VIEW_UI_FUNCTION_DEF(breakpoints)
{
ProfBeginFunction();
DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, ewv, 0, 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: watch_pins @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(watch_pins)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_WatchPins);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Member, 0.5f, .string = str8_lit("Label"), .dequote_string = 1, .is_non_code = 1);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Member, 0.5f, .string = str8_lit("Location"), .dequote_string = 1, .is_non_code = 1);
}
DF_VIEW_CMD_FUNCTION_DEF(watch_pins){}
DF_VIEW_UI_FUNCTION_DEF(watch_pins)
{
ProfBeginFunction();
DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, ewv, 0, 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: scheduler @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(scheduler) {}
DF_VIEW_CMD_FUNCTION_DEF(scheduler) {}
DF_VIEW_UI_FUNCTION_DEF(scheduler)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
String8 query = string;
//- rjf: get state
typedef struct DF_SchedulerViewState DF_SchedulerViewState;
struct DF_SchedulerViewState
{
D_Handle selected_entity;
S64 selected_column;
};
DF_SchedulerViewState *sv = df_view_user_state(view, DF_SchedulerViewState);
//- rjf: get entities
D_EntityList machines = d_query_cached_entity_list_with_kind(D_EntityKind_Machine);
D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process);
D_EntityList threads = d_query_cached_entity_list_with_kind(D_EntityKind_Thread);
//- rjf: produce list of items; no query -> all entities, in tree; query -> only show threads
D_EntityFuzzyItemArray items = {0};
ProfScope("query -> entities")
{
if(query.size == 0)
{
//- rjf: build flat array of entities, arranged into row order
D_EntityArray entities = {0};
{
entities.count = machines.count+processes.count+threads.count;
entities.v = push_array_no_zero(scratch.arena, D_Entity *, entities.count);
U64 idx = 0;
for(D_EntityNode *machine_n = machines.first; machine_n != 0; machine_n = machine_n->next)
{
D_Entity *machine = machine_n->entity;
entities.v[idx] = machine;
idx += 1;
for(D_EntityNode *process_n = processes.first; process_n != 0; process_n = process_n->next)
{
D_Entity *process = process_n->entity;
if(d_entity_ancestor_from_kind(process, D_EntityKind_Machine) != machine)
{
continue;
}
entities.v[idx] = process;
idx += 1;
for(D_EntityNode *thread_n = threads.first; thread_n != 0; thread_n = thread_n->next)
{
D_Entity *thread = thread_n->entity;
if(d_entity_ancestor_from_kind(thread, D_EntityKind_Process) != process)
{
continue;
}
entities.v[idx] = thread;
idx += 1;
}
}
}
}
//- rjf: entities -> fuzzy-filtered entities
items = d_entity_fuzzy_item_array_from_entity_array_needle(scratch.arena, &entities, query);
}
else
{
items = d_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &threads, query);
}
}
//- rjf: selected column/entity -> selected cursor
Vec2S64 cursor = {sv->selected_column};
for(U64 idx = 0; idx < items.count; idx += 1)
{
if(items.v[idx].entity == d_entity_from_handle(sv->selected_entity))
{
cursor.y = (S64)(idx+1);
break;
}
}
//- rjf: build table
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f);
scroll_list_params.dim_px = dim_2f32(rect);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(4, items.count));
scroll_list_params.item_range = r1s64(0, items.count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
&cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
UI_TableF(0, 0, "scheduler_table")
{
Vec2S64 next_cursor = cursor;
for(U64 idx = visible_row_range.min;
idx <= visible_row_range.max && idx < items.count;
idx += 1)
{
D_Entity *entity = items.v[idx].entity;
CTRL_Entity *entity_ctrl = ctrl_entity_from_handle(d_state->ctrl_entity_store, entity->ctrl_handle);
B32 row_is_selected = (cursor.y == (S64)(idx+1));
F32 depth = 0.f;
if(query.size == 0) switch(entity->kind)
{
default:{}break;
case D_EntityKind_Machine:{depth = 0.f;}break;
case D_EntityKind_Process:{depth = 1.f;}break;
case D_EntityKind_Thread: {depth = 2.f;}break;
}
Rng1S64 desc_col_rng = r1s64(1, 1);
switch(entity->kind)
{
default:{}break;
case D_EntityKind_Machine:{desc_col_rng = r1s64(1, 4);}break;
case D_EntityKind_Process:{desc_col_rng = r1s64(1, 1);}break;
case D_EntityKind_Thread: {desc_col_rng = r1s64(1, 1);}break;
}
UI_NamedTableVectorF("entity_row_%p", entity)
{
UI_TableCellSized(ui_em(1.5f*depth, 1.f)) {}
UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off)
{
B32 frozen = ctrl_entity_tree_is_frozen(entity_ctrl);
UI_Palette *palette = ui_top_palette();
if(frozen)
{
palette = df_palette_from_code(DF_PaletteCode_NegativePopButton);
}
else
{
palette = df_palette_from_code(DF_PaletteCode_PositivePopButton);
}
UI_Signal sig = {0};
UI_Palette(palette) sig = df_icon_buttonf(frozen ? DF_IconKind_Locked : DF_IconKind_Unlocked, 0, "###lock_%p", entity);
if(ui_clicked(sig))
{
D_CmdKind cmd_kind = frozen ? D_CmdKind_ThawEntity : D_CmdKind_FreezeEntity;
df_cmd(cmd_kind, .entity = d_handle_from_entity(entity));
}
}
UI_TableCellSized(ui_pct(1, 0))
UI_FocusHot((row_is_selected && desc_col_rng.min <= cursor.x && cursor.x <= desc_col_rng.max) ? UI_FocusKind_On : UI_FocusKind_Off)
{
df_entity_desc_button(entity, &items.v[idx].matches, query, 0);
}
switch(entity->kind)
{
default:{}break;
case D_EntityKind_Machine:
{
}break;
case D_EntityKind_Process:
{
UI_TableCellSized(ui_children_sum(1.f)) UI_FocusHot((row_is_selected && cursor.x == 2) ? UI_FocusKind_On : UI_FocusKind_Off)
{
UI_PrefWidth(ui_text_dim(10, 1))
UI_TextAlignment(UI_TextAlign_Center)
if(ui_clicked(ui_buttonf("Detach")))
{
df_cmd(D_CmdKind_Detach, .entity = d_handle_from_entity(entity));
}
}
UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 3) ? UI_FocusKind_On : UI_FocusKind_Off)
{
if(ui_clicked(df_icon_buttonf(DF_IconKind_Redo, 0, "###retry")))
{
df_cmd(D_CmdKind_Restart, .entity = d_handle_from_entity(entity));
}
}
UI_TableCellSized(ui_em(2.25f, 1.f)) UI_FocusHot((row_is_selected && cursor.x == 4) ? UI_FocusKind_On : UI_FocusKind_Off)
{
DF_Palette(DF_PaletteCode_NegativePopButton)
if(ui_clicked(df_icon_buttonf(DF_IconKind_X, 0, "###kill")))
{
df_cmd(D_CmdKind_Kill, .entity = d_handle_from_entity(entity));
}
}
}break;
case D_EntityKind_Thread:
{
UI_TableCellSized(ui_children_sum(1.f)) UI_FocusHot((row_is_selected && cursor.x >= 2) ? UI_FocusKind_On : UI_FocusKind_Off)
{
CTRL_Entity *entity_ctrl = ctrl_entity_from_handle(d_state->ctrl_entity_store, entity->ctrl_handle);
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(entity_ctrl, D_EntityKind_Process);
U64 rip_vaddr = d_query_cached_rip_from_thread(entity_ctrl);
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr);
U64 rip_voff = ctrl_voff_from_vaddr(module, rip_vaddr);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff);
if(lines.first != 0)
{
UI_PrefWidth(ui_children_sum(0)) df_src_loc_button(lines.first->v.file_path, lines.first->v.pt);
}
}
}break;
}
}
}
cursor = next_cursor;
}
//- rjf: selected num -> selected entity
sv->selected_column = cursor.x;
sv->selected_entity = (1 <= cursor.y && cursor.y <= items.count) ? d_handle_from_entity(items.v[cursor.y-1].entity) : d_handle_zero();
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: call_stack @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(call_stack)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_CallStack);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_FrameSelection, 0.05f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Value, 0.7f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Module, 0.25f, .is_non_code = 1);
}
DF_VIEW_CMD_FUNCTION_DEF(call_stack){}
DF_VIEW_UI_FUNCTION_DEF(call_stack)
{
ProfBeginFunction();
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, wv, 0, 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: modules @view_hook_impl
#if 0 // TODO(rjf): @msgs
typedef struct DF_ModulesViewState DF_ModulesViewState;
struct DF_ModulesViewState
{
B32 initialized;
D_Handle selected_entity;
S64 selected_column;
B32 txt_editing;
TxtPt txt_cursor;
TxtPt txt_mark;
U8 txt_buffer[1024];
U64 txt_size;
D_Handle pick_file_dst_entity;
F32 idx_col_pct;
F32 desc_col_pct;
F32 range_col_pct;
F32 dbg_col_pct;
};
#endif
DF_VIEW_SETUP_FUNCTION_DEF(modules)
{
#if 0 // TODO(rjf): @msgs
DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState);
if(mv->initialized == 0)
{
mv->initialized = 1;
mv->idx_col_pct = 0.05f;
mv->desc_col_pct = 0.15f;
mv->range_col_pct = 0.30f;
mv->dbg_col_pct = 0.50f;
}
#endif
}
DF_VIEW_CMD_FUNCTION_DEF(modules)
{
#if 0 // TODO(rjf): @msgs
DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState);
for(DF_Cmd *cmd = 0; df_next_cmd(&cmd);)
{
// rjf: mismatched window/panel => skip
if(!d_handle_match(df_regs()->window, cmd->regs->window) ||
!d_handle_match(df_regs()->panel, cmd->regs->panel))
{
continue;
}
//rjf: process
DF_CmdKind kind = df_cmd_kind_from_string(cmd->name);
switch(kind)
{
default:break;
case DF_CmdKind_PickFile:
{
Temp scratch = scratch_begin(0, 0);
String8 pick_string = cmd->regs->file_path;
D_Entity *module = d_entity_from_handle(mv->pick_file_dst_entity);
if(module->kind == D_EntityKind_Module)
{
String8 exe_path = module->string;
String8 dbg_path = pick_string;
// TODO(rjf)
}
scratch_end(scratch);
}break;
}
}
#endif
}
DF_VIEW_UI_FUNCTION_DEF(modules)
{
#if 0 // TODO(rjf): @msgs
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
DI_Scope *scope = di_scope_open();
String8 query = str8(view->query_buffer, view->query_string_size);
//- rjf: get state
DF_ModulesViewState *mv = df_view_user_state(view, DF_ModulesViewState);
F32 *col_pcts[] = {&mv->idx_col_pct, &mv->desc_col_pct, &mv->range_col_pct, &mv->dbg_col_pct};
//- rjf: get entities
D_EntityList processes = d_query_cached_entity_list_with_kind(D_EntityKind_Process);
D_EntityList modules = d_query_cached_entity_list_with_kind(D_EntityKind_Module);
//- rjf: make filtered item array
D_EntityFuzzyItemArray items = {0};
if(query.size == 0)
{
D_EntityArray entities = {0};
{
entities.count = processes.count+modules.count;
entities.v = push_array_no_zero(scratch.arena, D_Entity *, entities.count);
U64 idx = 0;
for(D_EntityNode *process_n = processes.first; process_n != 0; process_n = process_n->next)
{
D_Entity *process = process_n->entity;
entities.v[idx] = process;
idx += 1;
for(D_EntityNode *module_n = modules.first; module_n != 0; module_n = module_n->next)
{
D_Entity *module = module_n->entity;
if(d_entity_ancestor_from_kind(module, D_EntityKind_Process) != process)
{
continue;
}
entities.v[idx] = module;
idx += 1;
}
}
}
items = d_entity_fuzzy_item_array_from_entity_array_needle(scratch.arena, &entities, query);
}
else
{
items = d_entity_fuzzy_item_array_from_entity_list_needle(scratch.arena, &modules, query);
}
//- rjf: selected column/entity -> selected cursor
Vec2S64 cursor = {mv->selected_column};
for(U64 idx = 0; idx < items.count; idx += 1)
{
if(items.v[idx].entity == d_entity_from_handle(mv->selected_entity))
{
cursor.y = (S64)(idx+1);
break;
}
}
//////////////////////////////
//- rjf: do start/end editing interaction
//
B32 edit_begin = 0;
B32 edit_commit = 0;
B32 edit_end = 0;
B32 edit_submit = 0;
if(!mv->txt_editing && ui_is_focus_active())
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->string.size != 0 || evt->flags & UI_EventFlag_Paste)
{
edit_begin = 1;
break;
}
}
if(ui_slot_press(UI_EventActionSlot_Edit))
{
edit_begin = 1;
}
}
if(mv->txt_editing && ui_is_focus_active())
{
if(ui_slot_press(UI_EventActionSlot_Cancel))
{
edit_end = 1;
edit_commit = 0;
}
if(ui_slot_press(UI_EventActionSlot_Accept))
{
edit_end = 1;
edit_commit = 1;
edit_submit = 1;
}
}
//- rjf: build table
D_Entity *commit_module = &d_nil_entity;
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = floor_f32(ui_top_font_size()*2.5f);
scroll_list_params.dim_px = dim_2f32(rect);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(3, items.count));
scroll_list_params.item_range = r1s64(0, items.count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
mv->txt_editing ? 0 : &cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
UI_TableF(ArrayCount(col_pcts), col_pcts, "modules_table")
{
Vec2S64 next_cursor = cursor;
U64 idx_in_process = 0;
for(U64 idx = 0; idx < items.count; idx += 1)
{
D_Entity *entity = items.v[idx].entity;
B32 row_is_selected = (cursor.y == (S64)(idx+1));
idx_in_process += (entity->kind == D_EntityKind_Module);
if(visible_row_range.min <= idx && idx <= visible_row_range.max)
{
switch(entity->kind)
{
default:{}break;
case D_EntityKind_Process:
{
UI_NamedTableVectorF("process_%p", entity)
{
UI_TableCellSized(ui_pct(1, 0)) UI_FocusHot((row_is_selected) ? UI_FocusKind_On : UI_FocusKind_Off)
{
df_entity_desc_button(entity, &items.v[idx].matches, query, 0);
}
}
idx_in_process = 0;
}break;
case D_EntityKind_Module:
UI_NamedTableVectorF("module_%p", entity)
{
UI_TableCell UI_TextAlignment(UI_TextAlign_Center) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
ui_labelf("%I64u", idx_in_process);
}
UI_TableCell UI_FocusHot((row_is_selected && cursor.x == 0) ? UI_FocusKind_On : UI_FocusKind_Off)
{
df_entity_desc_button(entity, &items.v[idx].matches, query, 1);
}
UI_TableCell DF_Font(DF_FontSlot_Code) UI_FocusHot((row_is_selected && cursor.x == 1) ? UI_FocusKind_On : UI_FocusKind_Off)
{
UI_Box *range_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, "[0x%I64x, 0x%I64x)###vaddr_range_%p", entity->vaddr_rng.min, entity->vaddr_rng.max, entity);
UI_Signal sig = ui_signal_from_box(range_box);
if(ui_pressed(sig))
{
next_cursor = v2s64(1, (S64)idx+1);
df_cmd(DF_CmdKind_FocusPanel);
}
}
UI_TableCell
{
B32 txt_is_selected = (row_is_selected && cursor.x == 2);
B32 brw_is_selected = (row_is_selected && cursor.x == 3);
// rjf: unpack module info
DI_Key dbgi_key = d_dbgi_key_from_module(entity);
String8 dbgi_path = dbgi_key.path;
RDI_Parsed *rdi = di_rdi_from_key(scope, &dbgi_key, 0);
B32 dbgi_is_valid = (rdi != &di_rdi_parsed_nil);
// rjf: begin editing
if(txt_is_selected && edit_begin)
{
mv->txt_editing = 1;
mv->txt_size = Min(sizeof(mv->txt_buffer), dbgi_path.size);
MemoryCopy(mv->txt_buffer, dbgi_path.str, mv->txt_size);
mv->txt_cursor = txt_pt(1, 1+mv->txt_size);
mv->txt_mark = txt_pt(1, 1);
}
// rjf: build
UI_Signal sig = {0};
UI_FocusHot(txt_is_selected ? UI_FocusKind_On : UI_FocusKind_Off)
UI_FocusActive((txt_is_selected && mv->txt_editing) ? UI_FocusKind_On : UI_FocusKind_Off)
UI_WidthFill
{
UI_Palette(dbgi_is_valid ? ui_top_palette() : ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative)))
sig = df_line_editf(DF_LineEditFlag_NoBackground, 0, 0, &mv->txt_cursor, &mv->txt_mark, mv->txt_buffer, sizeof(mv->txt_buffer), &mv->txt_size, 0, dbgi_path, "###dbg_path_%p", entity);
edit_commit = (edit_commit || ui_committed(sig));
}
// rjf: press -> focus
if(ui_pressed(sig))
{
edit_commit = (mv->txt_editing && !txt_is_selected);
next_cursor = v2s64(2, (S64)idx+1);
df_cmd(DF_CmdKind_FocusPanel);
}
// rjf: double-click -> begin editing
if(ui_double_clicked(sig) && !mv->txt_editing)
{
ui_kill_action();
mv->txt_editing = 1;
mv->txt_size = Min(sizeof(mv->txt_buffer), dbgi_path.size);
MemoryCopy(mv->txt_buffer, dbgi_path.str, mv->txt_size);
mv->txt_cursor = txt_pt(1, 1+mv->txt_size);
mv->txt_mark = txt_pt(1, 1);
}
// rjf: store commit info
if(txt_is_selected && edit_commit)
{
commit_module = entity;
}
// rjf: build browse button
UI_FocusHot(brw_is_selected ? UI_FocusKind_On : UI_FocusKind_Off) UI_PrefWidth(ui_text_dim(10, 1))
{
if(ui_clicked(ui_buttonf("Browse...")) || (brw_is_selected && edit_begin))
{
df_cmd(DF_CmdKind_RunCommand, .string = df_cmd_kind_info_table[DF_CmdKind_PickFile].string);
mv->pick_file_dst_entity = d_handle_from_entity(entity);
}
}
}
}break;
}
}
}
cursor = next_cursor;
}
//- rjf: apply commits
if(edit_commit)
{
mv->txt_editing = 0;
if(!d_entity_is_nil(commit_module))
{
String8 exe_path = commit_module->string;
String8 dbg_path = str8(mv->txt_buffer, mv->txt_size);
// TODO(rjf)
}
if(edit_submit)
{
cursor.y += 1;
}
}
//- rjf: apply edit state changes
if(edit_end)
{
mv->txt_editing = 0;
}
//- rjf: selected num -> selected entity
mv->selected_column = cursor.x;
mv->selected_entity = (1 <= cursor.y && cursor.y <= items.count) ? d_handle_from_entity(items.v[cursor.y-1].entity) : d_handle_zero();
di_scope_close(scope);
scratch_end(scratch);
ProfEnd();
#endif
}
////////////////////////////////
//~ rjf: watch @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(watch)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_Watch);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Expr, 0.25f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Value, 0.3f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Type, 0.15f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_ViewRule, 0.30f);
}
DF_VIEW_CMD_FUNCTION_DEF(watch){}
DF_VIEW_UI_FUNCTION_DEF(watch)
{
ProfBeginFunction();
DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, ewv, 1*(view->query_string_size == 0), 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: locals @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(locals)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_Locals);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Expr, 0.25f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Value, 0.3f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Type, 0.15f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_ViewRule, 0.30f);
}
DF_VIEW_CMD_FUNCTION_DEF(locals) {}
DF_VIEW_UI_FUNCTION_DEF(locals)
{
ProfBeginFunction();
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, wv, 0, 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: registers @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(registers)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_Registers);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Expr, 0.25f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Value, 0.3f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Type, 0.15f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_ViewRule, 0.30f);
}
DF_VIEW_CMD_FUNCTION_DEF(registers) {}
DF_VIEW_UI_FUNCTION_DEF(registers)
{
ProfBeginFunction();
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, wv, 0, 16, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: globals @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(globals)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_Globals);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Expr, 0.25f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Value, 0.3f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Type, 0.15f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_ViewRule, 0.30f);
}
DF_VIEW_CMD_FUNCTION_DEF(globals) {}
DF_VIEW_UI_FUNCTION_DEF(globals)
{
ProfBeginFunction();
DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, ewv, 0, 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: thread_locals @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(thread_locals)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_ThreadLocals);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Expr, 0.25f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Value, 0.3f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Type, 0.15f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_ViewRule, 0.30f);
}
DF_VIEW_CMD_FUNCTION_DEF(thread_locals) {}
DF_VIEW_UI_FUNCTION_DEF(thread_locals)
{
ProfBeginFunction();
DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, ewv, 0, 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: types @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(types)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_Types);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Expr, 0.25f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Value, 0.3f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Type, 0.15f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_ViewRule, 0.30f);
}
DF_VIEW_CMD_FUNCTION_DEF(types) {}
DF_VIEW_UI_FUNCTION_DEF(types)
{
ProfBeginFunction();
DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, ewv, 0, 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: procedures @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(procedures)
{
DF_WatchViewState *wv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_init(wv, view, DF_WatchViewFillKind_Procedures);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Expr, 0.2f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_Value, 0.6f);
df_watch_view_column_alloc(wv, DF_WatchViewColumnKind_ViewRule, 0.2f);
}
DF_VIEW_CMD_FUNCTION_DEF(procedures) {}
DF_VIEW_UI_FUNCTION_DEF(procedures)
{
ProfBeginFunction();
DF_WatchViewState *ewv = df_view_user_state(view, DF_WatchViewState);
df_watch_view_build(view, ewv, 0, 10, rect);
ProfEnd();
}
////////////////////////////////
//~ rjf: pending_file @view_hook_impl
typedef struct DF_PendingFileViewState DF_PendingFileViewState;
struct DF_PendingFileViewState
{
Arena *deferred_cmd_arena;
DF_CmdList deferred_cmds;
};
DF_VIEW_SETUP_FUNCTION_DEF(pending_file)
{
DF_PendingFileViewState *pves = df_view_user_state(view, DF_PendingFileViewState);
pves->deferred_cmd_arena = df_view_push_arena_ext(view);
}
DF_VIEW_CMD_FUNCTION_DEF(pending_file)
{
Temp scratch = scratch_begin(0, 0);
DF_PendingFileViewState *pves = df_view_user_state(view, DF_PendingFileViewState);
//- rjf: process commands
for(DF_Cmd *cmd = 0; df_next_cmd(&cmd);)
{
// rjf: mismatched window/panel => skip
if(!d_handle_match(df_regs()->window, cmd->regs->window) ||
!d_handle_match(df_regs()->panel, cmd->regs->panel))
{
continue;
}
// rjf: process
DF_CmdKind kind = df_cmd_kind_from_string(cmd->name);
switch(kind)
{
default:break;
// rjf: gather deferred commands to redispatch when entity is ready
case DF_CmdKind_GoToLine:
case DF_CmdKind_GoToAddress:
case DF_CmdKind_CenterCursor:
case DF_CmdKind_ContainCursor:
{
df_cmd_list_push_new(pves->deferred_cmd_arena, &pves->deferred_cmds, cmd->name, cmd->regs);
}break;
}
}
//- rjf: determine if file is ready, and which viewer to use
String8 file_path = d_file_path_from_eval_string(scratch.arena, str8(view->query_buffer, view->query_string_size));
Rng1U64 file_range = r1u64(0, 1024);
U128 file_hash = fs_hash_from_path_range(file_path, file_range, 0);
B32 file_is_ready = 0;
DF_ViewKind viewer_kind = DF_ViewKind_Text;
{
HS_Scope *hs_scope = hs_scope_open();
String8 data = hs_data_from_hash(hs_scope, file_hash);
if(data.size != 0)
{
U64 num_utf8_bytes = 0;
U64 num_unknown_bytes = 0;
for(U64 idx = 0; idx < data.size && idx < file_range.max;)
{
UnicodeDecode decode = utf8_decode(data.str+idx, data.size-idx);
if(decode.codepoint != max_U32 && (decode.inc > 1 ||
(10 <= decode.codepoint && decode.codepoint <= 13) ||
(32 <= decode.codepoint && decode.codepoint <= 126)))
{
num_utf8_bytes += decode.inc;
idx += decode.inc;
}
else
{
num_unknown_bytes += 1;
idx += 1;
}
}
file_is_ready = 1;
if(num_utf8_bytes > num_unknown_bytes*4)
{
viewer_kind = DF_ViewKind_Text;
}
else
{
viewer_kind = DF_ViewKind_Memory;
}
}
hs_scope_close(hs_scope);
}
//- rjf: if entity is ready, dispatch all deferred commands
if(file_is_ready)
{
for(DF_CmdNode *cmd_node = pves->deferred_cmds.first; cmd_node != 0; cmd_node = cmd_node->next)
{
DF_Cmd *cmd = &cmd_node->cmd;
df_push_cmd(cmd->name, cmd->regs);
}
arena_clear(pves->deferred_cmd_arena);
MemoryZeroStruct(&pves->deferred_cmds);
}
//- rjf: if entity is ready, move params tree to scratch for new command
MD_Node *params_copy = &md_nil_node;
if(file_is_ready)
{
params_copy = md_tree_copy(scratch.arena, params);
}
//- rjf: if entity is ready, replace this view with the correct one, if any viewer is specified
if(file_is_ready && viewer_kind != DF_ViewKind_Null)
{
DF_ViewSpec *view_spec = df_view_spec_from_string(params_copy->string);
if(view_spec == &df_nil_view_spec)
{
view_spec = df_view_spec_from_kind(viewer_kind);
}
String8 query = d_eval_string_from_file_path(scratch.arena, file_path);
df_view_equip_spec(view, view_spec, query, params_copy);
}
//- rjf: if entity is ready, but we have no viewer for it, then just close this tab
if(file_is_ready && viewer_kind == DF_ViewKind_Null)
{
df_cmd(DF_CmdKind_CloseTab);
}
scratch_end(scratch);
}
DF_VIEW_UI_FUNCTION_DEF(pending_file)
{
view->loading_t = view->loading_t_target = 1.f;
df_request_frame();
}
////////////////////////////////
//~ rjf: text @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(text)
{
DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState);
df_code_view_init(cv, view);
df_view_equip_loading_info(view, 1, 0, 0);
view->loading_t = view->loading_t_target = 1.f;
}
DF_VIEW_CMD_FUNCTION_DEF(text)
{
DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState);
Temp scratch = scratch_begin(0, 0);
HS_Scope *hs_scope = hs_scope_open();
TXT_Scope *txt_scope = txt_scope_open();
E_Eval eval = e_eval_from_string(scratch.arena, string);
Rng1U64 range = d_range_from_eval_params(eval, params);
df_regs()->text_key = d_key_from_eval_space_range(eval.space, range, 1);
df_regs()->lang_kind = d_lang_kind_from_eval_params(eval, params);
U128 hash = {0};
TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_regs()->text_key, df_regs()->lang_kind, &hash);
String8 data = hs_data_from_hash(hs_scope, hash);
//- rjf: process general code-view commands
df_code_view_cmds(view, cv, data, &info, 0, r1u64(0, 0), di_key_zero());
//- rjf: process code-file commands
for(DF_Cmd *cmd = 0; df_next_cmd(&cmd);)
{
// rjf: mismatched window/panel => skip
if(!d_handle_match(df_regs()->window, cmd->regs->window) ||
!d_handle_match(df_regs()->panel, cmd->regs->panel))
{
continue;
}
// rjf: process
DF_CmdKind kind = df_cmd_kind_from_string(cmd->name);
switch(kind)
{
default:{}break;
// rjf: override file picking
case DF_CmdKind_PickFile:
{
String8 src = d_file_path_from_eval_string(scratch.arena, str8(view->query_buffer, view->query_string_size));
String8 dst = cmd->regs->file_path;
if(src.size != 0 && dst.size != 0)
{
// rjf: record src -> dst mapping
df_cmd(DF_CmdKind_SetFileReplacementPath, .string = src, .file_path = dst);
// rjf: switch this view to viewing replacement file
view->query_string_size = Min(sizeof(view->query_buffer), dst.size);
MemoryCopy(view->query_buffer, dst.str, view->query_string_size);
}
}break;
}
}
txt_scope_close(txt_scope);
hs_scope_close(hs_scope);
scratch_end(scratch);
}
DF_VIEW_UI_FUNCTION_DEF(text)
{
DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState);
Temp scratch = scratch_begin(0, 0);
HS_Scope *hs_scope = hs_scope_open();
TXT_Scope *txt_scope = txt_scope_open();
//////////////////////////////
//- rjf: set up invariants
//
F32 bottom_bar_height = ui_top_font_size()*2.f;
Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height);
Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1);
//////////////////////////////
//- rjf: unpack parameterization info
//
df_regs()->cursor.line = d_value_from_params_key(params, str8_lit("cursor_line")).s64;
df_regs()->cursor.column = d_value_from_params_key(params, str8_lit("cursor_column")).s64;
df_regs()->mark.line = d_value_from_params_key(params, str8_lit("mark_line")).s64;
df_regs()->mark.column = d_value_from_params_key(params, str8_lit("mark_column")).s64;
String8 path = d_file_path_from_eval_string(scratch.arena, string);
E_Eval eval = e_eval_from_string(scratch.arena, string);
Rng1U64 range = d_range_from_eval_params(eval, params);
df_regs()->text_key = d_key_from_eval_space_range(eval.space, range, 1);
df_regs()->lang_kind = d_lang_kind_from_eval_params(eval, params);
U128 hash = {0};
TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_regs()->text_key, df_regs()->lang_kind, &hash);
String8 data = hs_data_from_hash(hs_scope, hash);
B32 file_is_missing = (path.size != 0 && os_properties_from_file_path(path).modified == 0);
B32 key_has_data = !u128_match(hash, u128_zero()) && info.lines_count;
//////////////////////////////
//- rjf: build missing file interface
//
if(file_is_missing && !key_has_data)
{
UI_WidthFill UI_HeightFill UI_Column UI_Padding(ui_pct(1, 0))
{
Temp scratch = scratch_begin(0, 0);
UI_PrefWidth(ui_children_sum(1)) UI_PrefHeight(ui_em(3, 1))
UI_Row UI_Padding(ui_pct(1, 0))
UI_PrefWidth(ui_text_dim(10, 1))
UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative)))
{
DF_Font(DF_FontSlot_Icons) ui_label(df_g_icon_kind_text_table[DF_IconKind_WarningBig]);
ui_labelf("Could not find \"%S\".", path);
}
UI_PrefHeight(ui_em(3, 1))
UI_Row UI_Padding(ui_pct(1, 0))
UI_PrefWidth(ui_text_dim(10, 1))
UI_CornerRadius(ui_top_font_size()/3)
UI_PrefWidth(ui_text_dim(10, 1))
UI_Focus(UI_FocusKind_On)
DF_Palette(DF_PaletteCode_NeutralPopButton)
UI_TextAlignment(UI_TextAlign_Center)
if(ui_clicked(ui_buttonf("Find alternative...")))
{
df_cmd(DF_CmdKind_RunCommand, .string = df_cmd_kind_info_table[DF_CmdKind_PickFile].string);
}
scratch_end(scratch);
}
}
//////////////////////////////
//- rjf: code is not missing, but not ready -> equip loading info to this view
//
if(!file_is_missing && info.lines_count == 0 && eval.msgs.max_kind == E_MsgKind_Null)
{
df_view_equip_loading_info(view, 1, info.bytes_processed, info.bytes_to_process);
}
//////////////////////////////
//- rjf: build code contents
//
DI_KeyList dbgi_keys = {0};
if(!file_is_missing && key_has_data)
{
DF_CodeViewBuildResult result = df_code_view_build(scratch.arena, view, cv, DF_CodeViewBuildFlag_All, code_area_rect, data, &info, 0, r1u64(0, 0), di_key_zero());
dbgi_keys = result.dbgi_keys;
}
//////////////////////////////
//- rjf: unpack cursor info
//
if(path.size != 0)
{
df_regs()->lines = d_lines_from_file_path_line_num(d_frame_arena(), path, df_regs()->cursor.line);
}
//////////////////////////////
//- rjf: determine if file is out-of-date
//
B32 file_is_out_of_date = 0;
String8 out_of_date_dbgi_name = {0};
{
U64 file_timestamp = fs_timestamp_from_path(path);
if(file_timestamp != 0)
{
for(DI_KeyNode *n = dbgi_keys.first; n != 0; n = n->next)
{
DI_Key key = n->v;
if(key.min_timestamp < file_timestamp)
{
file_is_out_of_date = 1;
out_of_date_dbgi_name = str8_skip_last_slash(key.path);
break;
}
}
}
}
//////////////////////////////
//- rjf: build bottom bar
//
if(!file_is_missing && key_has_data)
{
ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f)));
ui_set_next_flags(UI_BoxFlag_DrawBackground);
UI_Palette *palette = ui_top_palette();
if(file_is_out_of_date)
{
palette = df_palette_from_code(DF_PaletteCode_NegativePopButton);
}
UI_Palette(palette)
UI_Row
UI_TextAlignment(UI_TextAlign_Center)
UI_PrefWidth(ui_text_dim(10, 1))
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
if(file_is_out_of_date)
{
UI_Box *box = &ui_g_nil_box;
UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNegative)))
DF_Font(DF_FontSlot_Icons)
{
box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_Clickable, "%S###file_ood_warning", df_g_icon_kind_text_table[DF_IconKind_WarningBig]);
}
UI_Signal sig = ui_signal_from_box(box);
if(ui_hovering(sig)) UI_Tooltip
{
UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(1, 1))
{
ui_labelf("This file has changed since ", out_of_date_dbgi_name);
UI_Palette(ui_build_palette(ui_top_palette(), .text = df_rgba_from_theme_color(DF_ThemeColor_TextNeutral))) ui_label(out_of_date_dbgi_name);
ui_labelf(" was produced.");
}
}
}
DF_Font(DF_FontSlot_Code)
{
if(path.size != 0)
{
ui_label(path);
ui_spacer(ui_em(1.5f, 1));
}
ui_labelf("Line: %I64d, Column: %I64d", df_regs()->cursor.line, df_regs()->cursor.column);
ui_spacer(ui_pct(1, 0));
ui_labelf("(read only)");
ui_labelf("%s",
info.line_end_kind == TXT_LineEndKind_LF ? "lf" :
info.line_end_kind == TXT_LineEndKind_CRLF ? "crlf" :
"bin");
}
}
}
//////////////////////////////
//- rjf: store params
//
df_view_store_param_s64(view, str8_lit("cursor_line"), df_regs()->cursor.line);
df_view_store_param_s64(view, str8_lit("cursor_column"), df_regs()->cursor.column);
df_view_store_param_s64(view, str8_lit("mark_line"), df_regs()->mark.line);
df_view_store_param_s64(view, str8_lit("mark_column"), df_regs()->mark.column);
txt_scope_close(txt_scope);
hs_scope_close(hs_scope);
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: disasm @view_hook_impl
typedef struct DF_DisasmViewState DF_DisasmViewState;
struct DF_DisasmViewState
{
B32 initialized;
TxtPt cursor;
TxtPt mark;
DASM_StyleFlags style_flags;
U64 goto_vaddr;
DF_CodeViewState cv;
};
DF_VIEW_SETUP_FUNCTION_DEF(disasm)
{
DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState);
if(dv->initialized == 0)
{
dv->initialized = 1;
dv->cursor = txt_pt(1, 1);
dv->mark = txt_pt(1, 1);
dv->style_flags = DASM_StyleFlag_Addresses|DASM_StyleFlag_SourceFilesNames|DASM_StyleFlag_SourceLines|DASM_StyleFlag_SymbolNames;
df_code_view_init(&dv->cv, view);
}
}
DF_VIEW_CMD_FUNCTION_DEF(disasm)
{
DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState);
Temp scratch = scratch_begin(0, 0);
DASM_Scope *dasm_scope = dasm_scope_open();
HS_Scope *hs_scope = hs_scope_open();
TXT_Scope *txt_scope = txt_scope_open();
//////////////////////////////
//- rjf: if disassembly views are not parameterized by anything, they
// automatically snap to the selected thread's RIP, rounded down to the
// nearest 16K boundary
//
B32 auto_selected_thread = 0;
if(string.size == 0)
{
auto_selected_thread = 1;
string = str8_lit("(rip.u64 & (~(0x4000 - 1))");
}
//////////////////////////////
//- rjf: unpack parameterization info
//
E_Eval eval = e_eval_from_string(scratch.arena, string);
E_Space space = eval.space;
if(auto_selected_thread)
{
space = d_eval_space_from_ctrl_entity(ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->process));
}
Rng1U64 range = d_range_from_eval_params(eval, params);
Arch arch = d_arch_from_eval_params(eval, params);
CTRL_Entity *space_entity = d_ctrl_entity_from_eval_space(space);
CTRL_Entity *dasm_module = &ctrl_entity_nil;
DI_Key dbgi_key = {0};
U64 base_vaddr = 0;
switch(space_entity->kind)
{
default:{}break;
case CTRL_EntityKind_Process:
{
arch = space_entity->arch;
dasm_module = ctrl_module_from_process_vaddr(space_entity, range.min);
dbgi_key = ctrl_dbgi_key_from_module(dasm_module);
base_vaddr = dasm_module->vaddr_range.min;
}break;
}
U128 dasm_key = d_key_from_eval_space_range(space, range, 0);
U128 dasm_data_hash = {0};
DASM_Params dasm_params = {0};
{
dasm_params.vaddr = range.min;
dasm_params.arch = arch;
dasm_params.style_flags = dv->style_flags;
dasm_params.syntax = DASM_Syntax_Intel;
dasm_params.base_vaddr = base_vaddr;
dasm_params.dbgi_key = dbgi_key;
}
DASM_Info dasm_info = dasm_info_from_key_params(dasm_scope, dasm_key, &dasm_params, &dasm_data_hash);
df_regs()->text_key = dasm_info.text_key;
df_regs()->lang_kind = txt_lang_kind_from_arch(arch);
U128 dasm_text_hash = {0};
TXT_TextInfo dasm_text_info = txt_text_info_from_key_lang(txt_scope, df_regs()->text_key, df_regs()->lang_kind, &dasm_text_hash);
String8 dasm_text_data = hs_data_from_hash(hs_scope, dasm_text_hash);
B32 has_disasm = (dasm_info.lines.count != 0 && dasm_text_info.lines_count != 0);
B32 is_loading = (!has_disasm && dim_1u64(range) != 0 && eval.msgs.max_kind == E_MsgKind_Null);
//////////////////////////////
//- rjf: process general code-view commands
//
df_code_view_cmds(view, &dv->cv, dasm_text_data, &dasm_text_info, &dasm_info.lines, range, dbgi_key);
//////////////////////////////
//- rjf: process disassembly-specific commands
//
for(DF_Cmd *cmd = 0; df_next_cmd(&cmd);)
{
// rjf: mismatched window/panel => skip
if(!d_handle_match(df_regs()->window, cmd->regs->window) ||
!d_handle_match(df_regs()->panel, cmd->regs->panel))
{
continue;
}
// rjf: process
DF_CmdKind kind = df_cmd_kind_from_string(cmd->name);
switch(kind)
{
default: break;
case DF_CmdKind_GoToAddress:
{
D_Entity *process = &d_nil_entity;
{
D_Entity *entity = d_entity_from_handle(cmd->regs->entity);
if(!d_entity_is_nil(entity) &&
(entity->kind == D_EntityKind_Process ||
entity->kind == D_EntityKind_Thread ||
entity->kind == D_EntityKind_Module))
{
process = entity;
if(entity->kind == D_EntityKind_Thread ||
entity->kind == D_EntityKind_Module)
{
process = d_entity_ancestor_from_kind(process, D_EntityKind_Process);
}
}
}
dv->goto_vaddr = cmd->regs->vaddr;
}break;
case DF_CmdKind_ToggleCodeBytesVisibility: {dv->style_flags ^= DASM_StyleFlag_CodeBytes;}break;
case DF_CmdKind_ToggleAddressVisibility: {dv->style_flags ^= DASM_StyleFlag_Addresses;}break;
}
}
txt_scope_close(txt_scope);
hs_scope_close(hs_scope);
dasm_scope_close(dasm_scope);
scratch_end(scratch);
}
DF_VIEW_UI_FUNCTION_DEF(disasm)
{
DF_DisasmViewState *dv = df_view_user_state(view, DF_DisasmViewState);
DF_CodeViewState *cv = &dv->cv;
Temp scratch = scratch_begin(0, 0);
HS_Scope *hs_scope = hs_scope_open();
DASM_Scope *dasm_scope = dasm_scope_open();
TXT_Scope *txt_scope = txt_scope_open();
//////////////////////////////
//- rjf: if disassembly views are not parameterized by anything, they
// automatically snap to the selected thread's RIP, rounded down to the
// nearest 16K boundary
//
B32 auto_selected_thread = 0;
if(string.size == 0)
{
auto_selected_thread = 1;
string = str8_lit("(rip.u64 & (~(0x4000 - 1))");
}
//////////////////////////////
//- rjf: set up invariants
//
F32 bottom_bar_height = ui_top_font_size()*2.f;
Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height);
Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1);
df_regs()->cursor = dv->cursor;
df_regs()->mark = dv->mark;
//////////////////////////////
//- rjf: unpack parameterization info
//
E_Eval eval = e_eval_from_string(scratch.arena, string);
E_Space space = eval.space;
if(auto_selected_thread)
{
space = d_eval_space_from_ctrl_entity(ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->process));
}
Rng1U64 range = d_range_from_eval_params(eval, params);
Arch arch = d_arch_from_eval_params(eval, params);
CTRL_Entity *space_entity = d_ctrl_entity_from_eval_space(space);
CTRL_Entity *dasm_module = &ctrl_entity_nil;
DI_Key dbgi_key = {0};
U64 base_vaddr = 0;
switch(space_entity->kind)
{
default:{}break;
case CTRL_EntityKind_Process:
{
arch = space_entity->arch;
dasm_module = ctrl_module_from_process_vaddr(space_entity, range.min);
dbgi_key = ctrl_dbgi_key_from_module(dasm_module);
base_vaddr = dasm_module->vaddr_range.min;
}break;
}
U128 dasm_key = d_key_from_eval_space_range(space, range, 0);
U128 dasm_data_hash = {0};
DASM_Params dasm_params = {0};
{
dasm_params.vaddr = range.min;
dasm_params.arch = arch;
dasm_params.style_flags = dv->style_flags;
dasm_params.syntax = DASM_Syntax_Intel;
dasm_params.base_vaddr = base_vaddr;
dasm_params.dbgi_key = dbgi_key;
}
DASM_Info dasm_info = dasm_info_from_key_params(dasm_scope, dasm_key, &dasm_params, &dasm_data_hash);
df_regs()->text_key = dasm_info.text_key;
df_regs()->lang_kind = txt_lang_kind_from_arch(arch);
U128 dasm_text_hash = {0};
TXT_TextInfo dasm_text_info = txt_text_info_from_key_lang(txt_scope, df_regs()->text_key, df_regs()->lang_kind, &dasm_text_hash);
String8 dasm_text_data = hs_data_from_hash(hs_scope, dasm_text_hash);
B32 has_disasm = (dasm_info.lines.count != 0 && dasm_text_info.lines_count != 0);
B32 is_loading = (!has_disasm && dim_1u64(range) != 0 && eval.msgs.max_kind == E_MsgKind_Null);
//////////////////////////////
//- rjf: is loading -> equip view with loading information
//
if(is_loading && !d_ctrl_targets_running())
{
df_view_equip_loading_info(view, is_loading, 0, 0);
}
//////////////////////////////
//- rjf: do goto vaddr
//
if(!is_loading && has_disasm && dv->goto_vaddr != 0)
{
U64 vaddr = dv->goto_vaddr;
dv->goto_vaddr = 0;
U64 line_idx = dasm_line_array_idx_from_code_off__linear_scan(&dasm_info.lines, vaddr-range.min);
S64 line_num = (S64)(line_idx+1);
cv->goto_line_num = line_num;
}
//////////////////////////////
//- rjf: build code contents
//
if(!is_loading && has_disasm)
{
df_code_view_build(scratch.arena, view, cv, DF_CodeViewBuildFlag_All, code_area_rect, dasm_text_data, &dasm_text_info, &dasm_info.lines, range, dbgi_key);
}
//////////////////////////////
//- rjf: unpack cursor info
//
if(!is_loading && has_disasm)
{
U64 off = dasm_line_array_code_off_from_idx(&dasm_info.lines, df_regs()->cursor.line-1);
df_regs()->vaddr_range = r1u64(base_vaddr+off, base_vaddr+off);
df_regs()->voff_range = ctrl_voff_range_from_vaddr_range(dasm_module, df_regs()->vaddr_range);
df_regs()->lines = d_lines_from_dbgi_key_voff(d_frame_arena(), &dbgi_key, df_regs()->voff_range.min);
}
//////////////////////////////
//- rjf: build bottom bar
//
if(!is_loading && has_disasm)
{
ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f)));
ui_set_next_flags(UI_BoxFlag_DrawBackground);
UI_Row
UI_TextAlignment(UI_TextAlign_Center)
UI_PrefWidth(ui_text_dim(10, 1))
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
DF_Font(DF_FontSlot_Code)
{
U64 cursor_vaddr = (1 <= df_regs()->cursor.line && df_regs()->cursor.line <= dasm_info.lines.count) ? (range.min+dasm_info.lines.v[df_regs()->cursor.line-1].code_off) : 0;
if(dasm_module != &ctrl_entity_nil)
{
ui_labelf("%S", path_normalized_from_string(scratch.arena, dasm_module->string));
ui_spacer(ui_em(1.5f, 1));
}
ui_labelf("Address: 0x%I64x, Line: %I64d, Column: %I64d", cursor_vaddr, df_regs()->cursor.line, df_regs()->cursor.column);
ui_spacer(ui_pct(1, 0));
ui_labelf("(read only)");
ui_labelf("bin");
}
}
//////////////////////////////
//- rjf: commit storage
//
dv->cursor = df_regs()->cursor;
dv->mark = df_regs()->mark;
txt_scope_close(txt_scope);
dasm_scope_close(dasm_scope);
hs_scope_close(hs_scope);
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: output @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(output)
{
DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState);
df_code_view_init(cv, view);
}
DF_VIEW_CMD_FUNCTION_DEF(output)
{
DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState);
Temp scratch = scratch_begin(0, 0);
HS_Scope *hs_scope = hs_scope_open();
TXT_Scope *txt_scope = txt_scope_open();
df_regs()->text_key = d_state->output_log_key;
df_regs()->lang_kind = TXT_LangKind_Null;
U128 hash = {0};
TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, df_regs()->text_key, df_regs()->lang_kind, &hash);
String8 data = hs_data_from_hash(hs_scope, hash);
df_code_view_cmds(view, cv, data, &info, 0, r1u64(0, 0), di_key_zero());
txt_scope_close(txt_scope);
hs_scope_close(hs_scope);
scratch_end(scratch);
}
DF_VIEW_UI_FUNCTION_DEF(output)
{
DF_CodeViewState *cv = df_view_user_state(view, DF_CodeViewState);
Temp scratch = scratch_begin(0, 0);
HS_Scope *hs_scope = hs_scope_open();
TXT_Scope *txt_scope = txt_scope_open();
//////////////////////////////
//- rjf: set up invariants
//
F32 bottom_bar_height = ui_top_font_size()*2.f;
Rng2F32 code_area_rect = r2f32p(rect.x0, rect.y0, rect.x1, rect.y1 - bottom_bar_height);
Rng2F32 bottom_bar_rect = r2f32p(rect.x0, rect.y1 - bottom_bar_height, rect.x1, rect.y1);
//////////////////////////////
//- rjf: unpack text info
//
U128 key = d_state->output_log_key;
TXT_LangKind lang_kind = TXT_LangKind_Null;
U128 hash = {0};
TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, key, lang_kind, &hash);
String8 data = hs_data_from_hash(hs_scope, hash);
Rng1U64 empty_range = {0};
if(info.lines_count == 0)
{
info.lines_count = 1;
info.lines_ranges = &empty_range;
}
//////////////////////////////
//- rjf: build code contents
//
{
df_code_view_build(scratch.arena, view, cv, 0, code_area_rect, data, &info, 0, r1u64(0, 0), di_key_zero());
}
//////////////////////////////
//- rjf: build bottom bar
//
{
ui_set_next_rect(shift_2f32(bottom_bar_rect, scale_2f32(rect.p0, -1.f)));
ui_set_next_flags(UI_BoxFlag_DrawBackground);
UI_Row
UI_TextAlignment(UI_TextAlign_Center)
UI_PrefWidth(ui_text_dim(10, 1))
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
DF_Font(DF_FontSlot_Code)
{
ui_labelf("(Debug String Output)");
ui_spacer(ui_em(1.5f, 1));
ui_labelf("Line: %I64d, Column: %I64d", df_regs()->cursor.line, df_regs()->cursor.column);
ui_spacer(ui_pct(1, 0));
ui_labelf("(read only)");
}
}
txt_scope_close(txt_scope);
hs_scope_close(hs_scope);
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: memory @view_hook_impl
typedef struct DF_MemoryViewState DF_MemoryViewState;
struct DF_MemoryViewState
{
B32 center_cursor;
B32 contain_cursor;
};
DF_VIEW_SETUP_FUNCTION_DEF(memory)
{
DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState);
}
DF_VIEW_CMD_FUNCTION_DEF(memory)
{
DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState);
for(DF_Cmd *cmd = 0; df_next_cmd(&cmd);)
{
DF_CmdKind kind = df_cmd_kind_from_string(cmd->name);
switch(kind)
{
default: break;
case DF_CmdKind_CenterCursor:
if(df_view_from_handle(cmd->regs->view) == view)
{
mv->center_cursor = 1;
}break;
case DF_CmdKind_ContainCursor:
if(df_view_from_handle(cmd->regs->view) == view)
{
mv->contain_cursor = 1;
}break;
case DF_CmdKind_GoToAddress:
{
// NOTE(rjf): go-to-address occurs with disassembly snaps, and we don't
// generally want to respond to those in thise view, so just skip any
// go-to-address commands that haven't been *explicitly* parameterized
// with this view.
if(df_view_from_handle(cmd->regs->view) == view)
{
// TODO(rjf)
}
}break;
case DF_CmdKind_SetColumns:
if(df_view_from_handle(cmd->regs->view) == view)
{
// TODO(rjf)
}break;
}
}
}
DF_VIEW_UI_FUNCTION_DEF(memory)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
HS_Scope *hs_scope = hs_scope_open();
DF_MemoryViewState *mv = df_view_user_state(view, DF_MemoryViewState);
//////////////////////////////
//- rjf: unpack parameterization info
//
E_Eval eval = e_eval_from_string(scratch.arena, string);
if(eval.space.kind == 0)
{
eval.space = d_eval_space_from_ctrl_entity(ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->process));
}
Rng1U64 space_range = d_whole_range_from_eval_space(eval.space);
U64 cursor = d_value_from_params_key(params, str8_lit("cursor_vaddr")).u64;
U64 mark = d_value_from_params_key(params, str8_lit("mark_vaddr")).u64;
U64 bytes_per_cell = d_value_from_params_key(params, str8_lit("bytes_per_cell")).u64;
U64 num_columns = d_value_from_params_key(params, str8_lit("num_columns")).u64;
if(num_columns == 0)
{
num_columns = 16;
}
num_columns = ClampBot(1, num_columns);
bytes_per_cell = ClampBot(1, bytes_per_cell);
//////////////////////////////
//- rjf: unpack visual params
//
FNT_Tag font = df_font_from_slot(DF_FontSlot_Code);
F32 font_size = df_font_size_from_slot(DF_FontSlot_Code);
F32 big_glyph_advance = fnt_dim_from_tag_size_string(font, font_size, 0, 0, str8_lit("H")).x;
F32 row_height_px = floor_f32(font_size*2.f);
F32 cell_width_px = floor_f32(font_size*2.f * bytes_per_cell);
F32 scroll_bar_dim = floor_f32(ui_top_font_size()*1.5f);
Vec2F32 panel_dim = dim_2f32(rect);
F32 footer_dim = font_size*10.f;
Rng2F32 header_rect = r2f32p(0, 0, panel_dim.x, row_height_px);
Rng2F32 footer_rect = r2f32p(0, panel_dim.y-footer_dim, panel_dim.x, panel_dim.y);
Rng2F32 content_rect = r2f32p(0, row_height_px, panel_dim.x-scroll_bar_dim, footer_rect.y0);
//////////////////////////////
//- rjf: determine legal scroll range
//
Rng1S64 scroll_idx_rng = r1s64(0, space_range.max/num_columns);
//////////////////////////////
//- rjf: determine info about visible range of rows
//
Rng1S64 viz_range_rows = {0};
Rng1U64 viz_range_bytes = {0};
S64 num_possible_visible_rows = 0;
{
num_possible_visible_rows = dim_2f32(content_rect).y/row_height_px;
viz_range_rows.min = view->scroll_pos.y.idx + (S64)view->scroll_pos.y.off - !!(view->scroll_pos.y.off<0);
viz_range_rows.max = view->scroll_pos.y.idx + (S64)view->scroll_pos.y.off + num_possible_visible_rows,
viz_range_rows.min = clamp_1s64(scroll_idx_rng, viz_range_rows.min);
viz_range_rows.max = clamp_1s64(scroll_idx_rng, viz_range_rows.max);
viz_range_bytes.min = viz_range_rows.min*num_columns;
viz_range_bytes.max = (viz_range_rows.max+1)*num_columns+1;
if(viz_range_bytes.min > viz_range_bytes.max)
{
Swap(U64, viz_range_bytes.min, viz_range_bytes.max);
}
}
//////////////////////////////
//- rjf: take keyboard controls
//
UI_Focus(UI_FocusKind_On) if(ui_is_focus_active())
{
U64 next_cursor = cursor;
U64 next_mark = mark;
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
Vec2S64 cell_delta = {0};
switch(evt->delta_unit)
{
default:{}break;
case UI_EventDeltaUnit_Char:
{
cell_delta.x = (S64)evt->delta_2s32.x;
cell_delta.y = (S64)evt->delta_2s32.y;
}break;
case UI_EventDeltaUnit_Word:
case UI_EventDeltaUnit_Page:
{
if(evt->delta_2s32.x < 0)
{
cell_delta.x = -(S64)(cursor%num_columns);
}
else if(evt->delta_2s32.x > 0)
{
cell_delta.x = (num_columns-1) - (S64)(cursor%num_columns);
}
if(evt->delta_2s32.y < 0)
{
cell_delta.y = -4;
}
else if(evt->delta_2s32.y > 0)
{
cell_delta.y = +4;
}
}break;
}
B32 good_action = 0;
if(evt->delta_2s32.x != 0 || evt->delta_2s32.y != 0)
{
good_action = 1;
}
if(good_action && evt->flags & UI_EventFlag_ZeroDeltaOnSelect && cursor != mark)
{
MemoryZeroStruct(&cell_delta);
}
if(good_action)
{
cell_delta.x = ClampBot(cell_delta.x, (S64)-next_cursor);
cell_delta.y = ClampBot(cell_delta.y, (S64)-(next_cursor/num_columns));
next_cursor += cell_delta.x;
next_cursor += cell_delta.y*num_columns;
next_cursor = ClampTop(0x7FFFFFFFFFFFull, next_cursor);
}
if(good_action && evt->flags & UI_EventFlag_PickSelectSide && cursor != mark)
{
if(evt->delta_2s32.x < 0 || evt->delta_2s32.y < 0)
{
next_cursor = Min(cursor, mark);
}
else
{
next_cursor = Max(cursor, mark);
}
}
if(good_action && !(evt->flags & UI_EventFlag_KeepMark))
{
next_mark = next_cursor;
}
if(good_action)
{
mv->contain_cursor = 1;
ui_eat_event(evt);
}
}
cursor = next_cursor;
mark = next_mark;
}
//////////////////////////////
//- rjf: clamp cursor
//
{
Rng1U64 cursor_valid_rng = space_range;
cursor = clamp_1u64(cursor_valid_rng, cursor);
mark = clamp_1u64(cursor_valid_rng, mark);
}
//////////////////////////////
//- rjf: center cursor
//
if(mv->center_cursor)
{
mv->center_cursor = 0;
S64 cursor_row_idx = cursor/num_columns;
S64 new_idx = (cursor_row_idx-num_possible_visible_rows/2+1);
new_idx = clamp_1s64(scroll_idx_rng, new_idx);
ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx);
}
//////////////////////////////
//- rjf: contain cursor
//
if(mv->contain_cursor)
{
mv->contain_cursor = 0;
S64 cursor_row_idx = cursor/num_columns;
Rng1S64 cursor_viz_range = r1s64(clamp_1s64(scroll_idx_rng, cursor_row_idx-2), clamp_1s64(scroll_idx_rng, cursor_row_idx+3));
S64 min_delta = Min(0, cursor_viz_range.min-viz_range_rows.min);
S64 max_delta = Max(0, cursor_viz_range.max-viz_range_rows.max);
S64 new_idx = view->scroll_pos.y.idx+min_delta+max_delta;
new_idx = clamp_1s64(scroll_idx_rng, new_idx);
ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx);
}
//////////////////////////////
//- rjf: produce fancy string runs for all possible byte values in all cells
//
DR_FancyStringList byte_fancy_strings[256] = {0};
{
Vec4F32 full_color = df_rgba_from_theme_color(DF_ThemeColor_TextPositive);
Vec4F32 zero_color = df_rgba_from_theme_color(DF_ThemeColor_TextWeak);
for(U64 idx = 0; idx < ArrayCount(byte_fancy_strings); idx += 1)
{
U8 byte = (U8)idx;
F32 pct = (byte/255.f);
Vec4F32 text_color = mix_4f32(zero_color, full_color, pct);
if(byte == 0)
{
text_color.w *= 0.5f;
}
DR_FancyString fstr = {font, push_str8f(scratch.arena, "%02x", byte), text_color, font_size, 0, 0};
dr_fancy_string_list_push(scratch.arena, &byte_fancy_strings[idx], &fstr);
}
}
//////////////////////////////
//- rjf: grab windowed memory
//
U64 visible_memory_size = dim_1u64(viz_range_bytes);
U8 *visible_memory = push_array(scratch.arena, U8, visible_memory_size);
{
e_space_read(eval.space, visible_memory, viz_range_bytes);
}
//////////////////////////////
//- rjf: grab annotations for windowed range of memory
//
typedef struct Annotation Annotation;
struct Annotation
{
Annotation *next;
String8 name_string;
String8 kind_string;
String8 type_string;
Vec4F32 color;
Rng1U64 vaddr_range;
};
typedef struct AnnotationList AnnotationList;
struct AnnotationList
{
Annotation *first;
Annotation *last;
};
AnnotationList *visible_memory_annotations = push_array(scratch.arena, AnnotationList, visible_memory_size);
{
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, df_regs()->thread);
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process);
CTRL_Unwind unwind = d_query_cached_unwind_from_thread(thread);
//- rjf: fill unwind frame annotations
if(unwind.frames.count != 0)
{
U64 last_stack_top = regs_rsp_from_arch_block(thread->arch, unwind.frames.v[0].regs);
for(U64 idx = 1; idx < unwind.frames.count; idx += 1)
{
CTRL_UnwindFrame *f = &unwind.frames.v[idx];
U64 f_stack_top = regs_rsp_from_arch_block(thread->arch, f->regs);
Rng1U64 frame_vaddr_range = r1u64(last_stack_top, f_stack_top);
Rng1U64 frame_vaddr_range_in_viz = intersect_1u64(frame_vaddr_range, viz_range_bytes);
last_stack_top = f_stack_top;
if(dim_1u64(frame_vaddr_range_in_viz) != 0)
{
U64 f_rip = regs_rip_from_arch_block(thread->arch, f->regs);
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, f_rip);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
U64 rip_voff = ctrl_voff_from_vaddr(module, f_rip);
String8 symbol_name = d_symbol_name_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff, 1);
Annotation *annotation = push_array(scratch.arena, Annotation, 1);
annotation->name_string = symbol_name.size != 0 ? symbol_name : str8_lit("[external code]");
annotation->kind_string = str8_lit("Call Stack Frame");
annotation->color = symbol_name.size != 0 ? df_rgba_from_theme_color(DF_ThemeColor_CodeSymbol) : df_rgba_from_theme_color(DF_ThemeColor_TextWeak);
annotation->vaddr_range = frame_vaddr_range;
for(U64 vaddr = frame_vaddr_range_in_viz.min; vaddr < frame_vaddr_range_in_viz.max; vaddr += 1)
{
U64 visible_byte_idx = vaddr - viz_range_bytes.min;
SLLQueuePush(visible_memory_annotations[visible_byte_idx].first, visible_memory_annotations[visible_byte_idx].last, annotation);
}
}
}
}
//- rjf: fill selected thread stack range annotation
if(unwind.frames.count > 0)
{
U64 stack_base_vaddr = thread->stack_base;
U64 stack_top_vaddr = regs_rsp_from_arch_block(thread->arch, unwind.frames.v[0].regs);
Rng1U64 stack_vaddr_range = r1u64(stack_base_vaddr, stack_top_vaddr);
Rng1U64 stack_vaddr_range_in_viz = intersect_1u64(stack_vaddr_range, viz_range_bytes);
if(dim_1u64(stack_vaddr_range_in_viz) != 0)
{
Annotation *annotation = push_array(scratch.arena, Annotation, 1);
annotation->name_string = thread->string.size ? thread->string : push_str8f(scratch.arena, "TID: %I64u", thread->id);
annotation->kind_string = str8_lit("Stack");
annotation->color = (thread->rgba != 0) ? rgba_from_u32(thread->rgba) : df_rgba_from_theme_color(DF_ThemeColor_Text);
annotation->vaddr_range = stack_vaddr_range;
for(U64 vaddr = stack_vaddr_range_in_viz.min; vaddr < stack_vaddr_range_in_viz.max; vaddr += 1)
{
U64 visible_byte_idx = vaddr - viz_range_bytes.min;
SLLQueuePush(visible_memory_annotations[visible_byte_idx].first, visible_memory_annotations[visible_byte_idx].last, annotation);
}
}
}
//- rjf: fill local variable annotations
{
DI_Scope *scope = di_scope_open();
Vec4F32 color_gen_table[] =
{
df_rgba_from_theme_color(DF_ThemeColor_Thread0),
df_rgba_from_theme_color(DF_ThemeColor_Thread1),
df_rgba_from_theme_color(DF_ThemeColor_Thread2),
df_rgba_from_theme_color(DF_ThemeColor_Thread3),
df_rgba_from_theme_color(DF_ThemeColor_Thread4),
df_rgba_from_theme_color(DF_ThemeColor_Thread5),
df_rgba_from_theme_color(DF_ThemeColor_Thread6),
df_rgba_from_theme_color(DF_ThemeColor_Thread7),
};
U64 thread_rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, df_regs()->unwind_count);
for(E_String2NumMapNode *n = e_parse_ctx->locals_map->first; n != 0; n = n->order_next)
{
String8 local_name = n->string;
E_Eval local_eval = e_eval_from_string(scratch.arena, local_name);
if(local_eval.mode == E_Mode_Offset)
{
E_TypeKind local_eval_type_kind = e_type_kind_from_key(local_eval.type_key);
U64 local_eval_type_size = e_type_byte_size_from_key(local_eval.type_key);
Rng1U64 vaddr_rng = r1u64(local_eval.value.u64, local_eval.value.u64+local_eval_type_size);
Rng1U64 vaddr_rng_in_visible = intersect_1u64(viz_range_bytes, vaddr_rng);
if(vaddr_rng_in_visible.max != vaddr_rng_in_visible.min)
{
Annotation *annotation = push_array(scratch.arena, Annotation, 1);
{
annotation->name_string = push_str8_copy(scratch.arena, local_name);
annotation->kind_string = str8_lit("Local");
annotation->type_string = e_type_string_from_key(scratch.arena, local_eval.type_key);
annotation->color = color_gen_table[(vaddr_rng.min/8)%ArrayCount(color_gen_table)];
annotation->vaddr_range = vaddr_rng;
}
for(U64 vaddr = vaddr_rng_in_visible.min; vaddr < vaddr_rng_in_visible.max; vaddr += 1)
{
SLLQueuePushFront(visible_memory_annotations[vaddr-viz_range_bytes.min].first, visible_memory_annotations[vaddr-viz_range_bytes.min].last, annotation);
}
}
}
}
di_scope_close(scope);
}
}
//////////////////////////////
//- rjf: build main container
//
UI_Box *container_box = &ui_g_nil_box;
{
Vec2F32 dim = dim_2f32(rect);
ui_set_next_fixed_width(dim.x);
ui_set_next_fixed_height(dim.y);
ui_set_next_child_layout_axis(Axis2_Y);
container_box = ui_build_box_from_stringf(0, "memory_view_container_%p", view);
}
//////////////////////////////
//- rjf: build header
//
UI_Box *header_box = &ui_g_nil_box;
UI_Parent(container_box)
{
UI_WidthFill UI_PrefHeight(ui_px(row_height_px, 1.f)) UI_Row
header_box = ui_build_box_from_stringf(UI_BoxFlag_DrawSideBottom, "table_header");
UI_Parent(header_box)
DF_Font(DF_FontSlot_Code)
UI_FontSize(font_size)
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
UI_PrefWidth(ui_px(big_glyph_advance*18.f, 1.f)) ui_labelf("Address");
UI_PrefWidth(ui_px(cell_width_px, 1.f))
UI_TextAlignment(UI_TextAlign_Center)
{
Rng1U64 col_selection_rng = r1u64(cursor%num_columns, mark%num_columns);
for(U64 row_off = 0; row_off < num_columns*bytes_per_cell; row_off += bytes_per_cell)
{
if(!(col_selection_rng.min <= row_off && row_off <= col_selection_rng.max))
{
ui_set_next_flags(UI_BoxFlag_DrawTextWeak);
}
ui_labelf("%I64X", row_off);
}
}
ui_spacer(ui_px(big_glyph_advance*1.5f, 1.f));
UI_WidthFill ui_labelf("ASCII");
}
}
//////////////////////////////
//- rjf: build scroll bar
//
UI_Parent(container_box)
{
ui_set_next_fixed_x(content_rect.x1);
ui_set_next_fixed_y(content_rect.y0);
ui_set_next_fixed_width(scroll_bar_dim);
ui_set_next_fixed_height(dim_2f32(content_rect).y);
{
view->scroll_pos.y = ui_scroll_bar(Axis2_Y,
ui_px(scroll_bar_dim, 1.f),
view->scroll_pos.y,
scroll_idx_rng,
num_possible_visible_rows);
}
}
//////////////////////////////
//- rjf: build scrollable box
//
UI_Box *scrollable_box = &ui_g_nil_box;
UI_Parent(container_box)
{
ui_set_next_fixed_x(content_rect.x0);
ui_set_next_fixed_y(content_rect.y0);
ui_set_next_fixed_width(dim_2f32(content_rect).x);
ui_set_next_fixed_height(dim_2f32(content_rect).y);
ui_set_next_child_layout_axis(Axis2_Y);
scrollable_box = ui_build_box_from_stringf(UI_BoxFlag_Clip|
UI_BoxFlag_Scroll|
UI_BoxFlag_AllowOverflowX|
UI_BoxFlag_AllowOverflowY,
"scrollable_box");
container_box->view_off.x = container_box->view_off_target.x = view->scroll_pos.x.idx + view->scroll_pos.x.off;
scrollable_box->view_off.y = scrollable_box->view_off_target.y = floor_f32(row_height_px*mod_f32(view->scroll_pos.y.off, 1.f) + row_height_px*(view->scroll_pos.y.off < 0));
}
//////////////////////////////
//- rjf: build row container/overlay
//
UI_Box *row_container_box = &ui_g_nil_box;
UI_Box *row_overlay_box = &ui_g_nil_box;
UI_Parent(scrollable_box) UI_WidthFill UI_HeightFill
{
ui_set_next_child_layout_axis(Axis2_Y);
ui_set_next_hover_cursor(OS_Cursor_IBar);
row_container_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "row_container");
UI_Parent(row_container_box)
{
row_overlay_box = ui_build_box_from_stringf(UI_BoxFlag_Floating, "row_overlay");
}
}
//////////////////////////////
//- rjf: interact with row container
//
U64 mouse_hover_byte_num = 0;
{
UI_Signal sig = ui_signal_from_box(row_container_box);
// rjf: calculate hovered byte
if(ui_hovering(sig) || ui_dragging(sig))
{
Vec2F32 mouse_rel = sub_2f32(ui_mouse(), row_container_box->rect.p0);
U64 row_idx = ClampBot(0, mouse_rel.y) / row_height_px;
// rjf: try from cells
if(mouse_hover_byte_num == 0)
{
U64 col_idx = ClampBot(mouse_rel.x-big_glyph_advance*18.f, 0)/cell_width_px;
if(col_idx < num_columns)
{
mouse_hover_byte_num = viz_range_bytes.min + row_idx*num_columns + col_idx + 1;
}
}
// rjf: try from ascii
if(mouse_hover_byte_num == 0)
{
U64 col_idx = ClampBot(mouse_rel.x - (big_glyph_advance*18.f + cell_width_px*num_columns + big_glyph_advance*1.5f), 0)/big_glyph_advance;
col_idx = ClampTop(col_idx, num_columns-1);
mouse_hover_byte_num = viz_range_bytes.min + row_idx*num_columns + col_idx + 1;
}
mouse_hover_byte_num = Clamp(1, mouse_hover_byte_num, 0x7FFFFFFFFFFFull+1);
}
// rjf: press -> focus panel
if(ui_pressed(sig))
{
df_cmd(DF_CmdKind_FocusPanel);
}
// rjf: click & drag -> select
if(ui_dragging(sig) && mouse_hover_byte_num != 0)
{
if(!contains_2f32(sig.box->rect, ui_mouse()))
{
mv->contain_cursor = 1;
}
cursor = mouse_hover_byte_num-1;
if(ui_pressed(sig))
{
mark = cursor;
}
}
// rjf: ctrl+scroll -> change font size
if(ui_hovering(sig))
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->kind == UI_EventKind_Scroll && evt->modifiers & OS_EventFlag_Ctrl)
{
ui_eat_event(evt);
if(evt->delta_2f32.y < 0)
{
df_cmd(DF_CmdKind_IncCodeFontScale);
}
else if(evt->delta_2f32.y > 0)
{
df_cmd(DF_CmdKind_DecCodeFontScale);
}
}
}
}
}
//////////////////////////////
//- rjf: build rows
//
UI_Parent(row_container_box) DF_Font(DF_FontSlot_Code) UI_FontSize(font_size)
{
Rng1U64 selection = r1u64(cursor, mark);
U8 *row_ascii_buffer = push_array(scratch.arena, U8, num_columns);
UI_WidthFill UI_PrefHeight(ui_px(row_height_px, 1.f))
for(S64 row_idx = viz_range_rows.min; row_idx <= viz_range_rows.max; row_idx += 1)
{
Rng1U64 row_range_bytes = r1u64(row_idx*num_columns, (row_idx+1)*num_columns);
B32 row_is_boundary = 0;
Vec4F32 row_boundary_color = {0};
if(row_range_bytes.min%64 == 0)
{
row_is_boundary = 1;
row_boundary_color = df_rgba_from_theme_color(DF_ThemeColor_BaseBorder);
}
UI_Box *row = ui_build_box_from_stringf(UI_BoxFlag_DrawSideTop*!!row_is_boundary, "row_%I64x", row_range_bytes.min);
UI_Parent(row)
{
UI_PrefWidth(ui_px(big_glyph_advance*18.f, 1.f))
{
if(!(selection.max >= row_range_bytes.min && selection.min < row_range_bytes.max))
{
ui_set_next_flags(UI_BoxFlag_DrawTextWeak);
}
ui_labelf("%016I64X", row_range_bytes.min);
}
UI_PrefWidth(ui_px(cell_width_px, 1.f))
UI_TextAlignment(UI_TextAlign_Center)
UI_CornerRadius(0)
{
for(U64 col_idx = 0; col_idx < num_columns; col_idx += 1)
{
U64 visible_byte_idx = (row_idx-viz_range_rows.min)*num_columns + col_idx;
U64 global_byte_idx = viz_range_bytes.min+visible_byte_idx;
U64 global_byte_num = global_byte_idx+1;
U8 byte_value = visible_memory[visible_byte_idx];
Annotation *annotation = visible_memory_annotations[visible_byte_idx].first;
UI_BoxFlags cell_flags = 0;
Vec4F32 cell_border_rgba = {0};
Vec4F32 cell_bg_rgba = {0};
if(global_byte_num == mouse_hover_byte_num)
{
cell_flags |= UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawSideTop|UI_BoxFlag_DrawSideBottom|UI_BoxFlag_DrawSideLeft|UI_BoxFlag_DrawSideRight;
cell_border_rgba = df_rgba_from_theme_color(DF_ThemeColor_Hover);
}
if(annotation != 0)
{
cell_flags |= UI_BoxFlag_DrawBackground;
cell_bg_rgba = annotation->color;
if(contains_1u64(annotation->vaddr_range, mouse_hover_byte_num-1))
{
cell_bg_rgba.w *= 0.15f;
}
else
{
cell_bg_rgba.w *= 0.08f;
}
}
if(selection.min <= global_byte_idx && global_byte_idx <= selection.max)
{
cell_flags |= UI_BoxFlag_DrawBackground;
cell_bg_rgba = df_rgba_from_theme_color(DF_ThemeColor_SelectionOverlay);
}
ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = cell_bg_rgba));
UI_Box *cell_box = ui_build_box_from_key(UI_BoxFlag_DrawText|cell_flags, ui_key_zero());
ui_box_equip_display_fancy_strings(cell_box, &byte_fancy_strings[byte_value]);
{
F32 off = 0;
for(Annotation *a = annotation; a != 0; a = a->next)
{
if(global_byte_idx == a->vaddr_range.min) UI_Parent(row_overlay_box)
{
ui_set_next_palette(ui_build_palette(ui_top_palette(), .background = annotation->color));
ui_set_next_fixed_x(big_glyph_advance*18.f + col_idx*cell_width_px + -cell_width_px/8.f + off);
ui_set_next_fixed_y((row_idx-viz_range_rows.min)*row_height_px + -cell_width_px/8.f);
ui_set_next_fixed_width(cell_width_px/4.f);
ui_set_next_fixed_height(cell_width_px/4.f);
ui_set_next_corner_radius_00(cell_width_px/8.f);
ui_set_next_corner_radius_01(cell_width_px/8.f);
ui_set_next_corner_radius_10(cell_width_px/8.f);
ui_set_next_corner_radius_11(cell_width_px/8.f);
ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, ui_key_zero());
off += cell_width_px/8.f + cell_width_px/16.f;
}
}
}
if(annotation != 0 && mouse_hover_byte_num == global_byte_num) UI_Tooltip UI_FontSize(ui_top_font_size()) UI_PrefHeight(ui_px(ui_top_font_size()*1.75f, 1.f))
{
for(Annotation *a = annotation; a != 0; a = a->next)
{
UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(10, 1))
{
DF_Font(DF_FontSlot_Code) ui_label(a->name_string);
DF_Font(DF_FontSlot_Main) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(a->kind_string);
}
if(a->type_string.size != 0)
{
df_code_label(1.f, 1, df_rgba_from_theme_color(DF_ThemeColor_CodeType), a->type_string);
}
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(str8_from_memory_size(scratch.arena, dim_1u64(a->vaddr_range)));
if(a->next != 0)
{
ui_spacer(ui_em(1.5f, 1.f));
}
}
}
}
}
ui_spacer(ui_px(big_glyph_advance*1.5f, 1.f));
UI_WidthFill
{
MemoryZero(row_ascii_buffer, num_columns);
for(U64 col_idx = 0; col_idx < num_columns; col_idx += 1)
{
U8 byte_value = visible_memory[(row_idx-viz_range_rows.min)*num_columns + col_idx];
row_ascii_buffer[col_idx] = byte_value;
if(byte_value <= 32 || 127 < byte_value)
{
row_ascii_buffer[col_idx] = '.';
}
}
String8 ascii_text = str8(row_ascii_buffer, num_columns);
UI_Box *ascii_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S###ascii_row_%I64x", ascii_text, row_range_bytes.min);
if(selection.max >= row_range_bytes.min && selection.min < row_range_bytes.max)
{
Rng1U64 selection_in_row = intersect_1u64(row_range_bytes, selection);
DR_Bucket *bucket = dr_bucket_make();
D_BucketScope(bucket)
{
Vec2F32 text_pos = ui_box_text_position(ascii_box);
dr_rect(r2f32p(text_pos.x + fnt_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, selection_in_row.min+0-row_range_bytes.min)).x - font_size/8.f,
ascii_box->rect.y0,
text_pos.x + fnt_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, selection_in_row.max+1-row_range_bytes.min)).x + font_size/4.f,
ascii_box->rect.y1),
df_rgba_from_theme_color(DF_ThemeColor_SelectionOverlay),
0, 0, 1.f);
}
ui_box_equip_draw_bucket(ascii_box, bucket);
}
if(mouse_hover_byte_num != 0 && contains_1u64(row_range_bytes, mouse_hover_byte_num-1))
{
DR_Bucket *bucket = dr_bucket_make();
D_BucketScope(bucket)
{
Vec2F32 text_pos = ui_box_text_position(ascii_box);
Vec4F32 color = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay);
dr_rect(r2f32p(text_pos.x + fnt_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, mouse_hover_byte_num-1-row_range_bytes.min)).x - font_size/8.f,
ascii_box->rect.y0,
text_pos.x + fnt_dim_from_tag_size_string(font, font_size, 0, 0, str8_prefix(ascii_text, mouse_hover_byte_num+0-row_range_bytes.min)).x + font_size/4.f,
ascii_box->rect.y1),
color,
1.f, 3.f, 1.f);
}
ui_box_equip_draw_bucket(ascii_box, bucket);
}
}
}
}
}
//////////////////////////////
//- rjf: build footer
//
UI_Box *footer_box = &ui_g_nil_box;
UI_Parent(container_box)
{
ui_set_next_fixed_x(footer_rect.x0);
ui_set_next_fixed_y(footer_rect.y0);
ui_set_next_fixed_width(dim_2f32(footer_rect).x);
ui_set_next_fixed_height(dim_2f32(footer_rect).y);
footer_box = ui_build_box_from_stringf(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, "footer");
UI_Parent(footer_box) DF_Font(DF_FontSlot_Code) UI_FontSize(font_size)
{
UI_PrefWidth(ui_em(7.5f, 1.f)) UI_HeightFill UI_Column UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
UI_PrefHeight(ui_px(row_height_px, 0.f))
{
ui_labelf("Address:");
ui_labelf("U8:");
ui_labelf("U16:");
ui_labelf("U32:");
ui_labelf("U64:");
}
UI_PrefWidth(ui_em(45.f, 1.f)) UI_HeightFill UI_Column
UI_PrefHeight(ui_px(row_height_px, 0.f))
{
B32 cursor_in_range = (viz_range_bytes.min <= cursor && cursor+8 <= viz_range_bytes.max);
ui_labelf("%016I64X", cursor);
if(cursor_in_range)
{
U64 as_u8 = 0;
U64 as_u16 = 0;
U64 as_u32 = 0;
U64 as_u64 = 0;
U64 cursor_off = cursor-viz_range_bytes.min;
as_u8 = (U64)*(U8 *)(visible_memory + cursor_off);
as_u16 = (U64)*(U16*)(visible_memory + cursor_off);
as_u32 = (U64)*(U32*)(visible_memory + cursor_off);
as_u64 = (U64)*(U64*)(visible_memory + cursor_off);
ui_labelf("%02X (%I64u)", as_u8, as_u8);
ui_labelf("%04X (%I64u)", as_u16, as_u16);
ui_labelf("%08X (%I64u)", as_u32, as_u32);
ui_labelf("%016I64X (%I64u)", as_u64, as_u64);
}
}
}
}
//////////////////////////////
//- rjf: scroll
//
{
UI_Signal sig = ui_signal_from_box(scrollable_box);
if(sig.scroll.y != 0)
{
S64 new_idx = view->scroll_pos.y.idx + sig.scroll.y;
new_idx = clamp_1s64(scroll_idx_rng, new_idx);
ui_scroll_pt_target_idx(&view->scroll_pos.y, new_idx);
}
}
//////////////////////////////
//- rjf: save parameters
//
df_view_store_param_u64(view, str8_lit("cursor_vaddr"), cursor);
df_view_store_param_u64(view, str8_lit("mark_vaddr"), mark);
df_view_store_param_u64(view, str8_lit("bytes_per_cell"), bytes_per_cell);
df_view_store_param_u64(view, str8_lit("num_columns"), num_columns);
hs_scope_close(hs_scope);
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: bitmap @view_hook_impl
typedef struct DF_BitmapBoxDrawData DF_BitmapBoxDrawData;
struct DF_BitmapBoxDrawData
{
Rng2F32 src;
R_Handle texture;
F32 loaded_t;
B32 hovered;
Vec2S32 mouse_px;
F32 ui_per_bmp_px;
};
typedef struct DF_BitmapCanvasBoxDrawData DF_BitmapCanvasBoxDrawData;
struct DF_BitmapCanvasBoxDrawData
{
Vec2F32 view_center_pos;
F32 zoom;
};
internal Vec2F32
df_bitmap_screen_from_canvas_pos(Vec2F32 view_center_pos, F32 zoom, Rng2F32 rect, Vec2F32 cvs)
{
Vec2F32 scr =
{
(rect.x0+rect.x1)/2 + (cvs.x - view_center_pos.x) * zoom,
(rect.y0+rect.y1)/2 + (cvs.y - view_center_pos.y) * zoom,
};
return scr;
}
internal Rng2F32
df_bitmap_screen_from_canvas_rect(Vec2F32 view_center_pos, F32 zoom, Rng2F32 rect, Rng2F32 cvs)
{
Rng2F32 scr = r2f32(df_bitmap_screen_from_canvas_pos(view_center_pos, zoom, rect, cvs.p0), df_bitmap_screen_from_canvas_pos(view_center_pos, zoom, rect, cvs.p1));
return scr;
}
internal Vec2F32
df_bitmap_canvas_from_screen_pos(Vec2F32 view_center_pos, F32 zoom, Rng2F32 rect, Vec2F32 scr)
{
Vec2F32 cvs =
{
(scr.x - (rect.x0+rect.x1)/2) / zoom + view_center_pos.x,
(scr.y - (rect.y0+rect.y1)/2) / zoom + view_center_pos.y,
};
return cvs;
}
internal Rng2F32
df_bitmap_canvas_from_screen_rect(Vec2F32 view_center_pos, F32 zoom, Rng2F32 rect, Rng2F32 scr)
{
Rng2F32 cvs = r2f32(df_bitmap_canvas_from_screen_pos(view_center_pos, zoom, rect, scr.p0), df_bitmap_canvas_from_screen_pos(view_center_pos, zoom, rect, scr.p1));
return cvs;
}
internal UI_BOX_CUSTOM_DRAW(df_bitmap_box_draw)
{
DF_BitmapBoxDrawData *draw_data = (DF_BitmapBoxDrawData *)user_data;
Vec4F32 bg_color = box->palette->background;
dr_img(box->rect, draw_data->src, draw_data->texture, v4f32(1, 1, 1, 1), 0, 0, 0);
if(draw_data->loaded_t < 0.98f)
{
Rng2F32 clip = box->rect;
for(UI_Box *b = box->parent; !ui_box_is_nil(b); b = b->parent)
{
if(b->flags & UI_BoxFlag_Clip)
{
clip = intersect_2f32(b->rect, clip);
}
}
dr_blur(intersect_2f32(clip, box->rect), 10.f-9.f*draw_data->loaded_t, 0);
}
if(r_handle_match(draw_data->texture, r_handle_zero()))
{
dr_rect(box->rect, v4f32(0, 0, 0, 1), 0, 0, 0);
}
dr_rect(box->rect, v4f32(bg_color.x*bg_color.w, bg_color.y*bg_color.w, bg_color.z*bg_color.w, 1.f-draw_data->loaded_t), 0, 0, 0);
if(draw_data->hovered)
{
Vec4F32 indicator_color = v4f32(1, 1, 1, 1);
dr_rect(pad_2f32(r2f32p(box->rect.x0 + draw_data->mouse_px.x*draw_data->ui_per_bmp_px,
box->rect.y0 + draw_data->mouse_px.y*draw_data->ui_per_bmp_px,
box->rect.x0 + draw_data->mouse_px.x*draw_data->ui_per_bmp_px + draw_data->ui_per_bmp_px,
box->rect.y0 + draw_data->mouse_px.y*draw_data->ui_per_bmp_px + draw_data->ui_per_bmp_px),
3.f),
indicator_color, 3.f, 4.f, 1.f);
}
}
internal UI_BOX_CUSTOM_DRAW(df_bitmap_view_canvas_box_draw)
{
DF_BitmapCanvasBoxDrawData *draw_data = (DF_BitmapCanvasBoxDrawData *)user_data;
Rng2F32 rect_scrn = box->rect;
Rng2F32 rect_cvs = df_bitmap_canvas_from_screen_rect(draw_data->view_center_pos, draw_data->zoom, rect_scrn, rect_scrn);
F32 grid_cell_size_cvs = box->font_size*10.f;
F32 grid_line_thickness_px = Max(2.f, box->font_size*0.1f);
Vec4F32 grid_line_color = df_rgba_from_theme_color(DF_ThemeColor_TextWeak);
for(EachEnumVal(Axis2, axis))
{
for(F32 v = rect_cvs.p0.v[axis] - mod_f32(rect_cvs.p0.v[axis], grid_cell_size_cvs);
v < rect_cvs.p1.v[axis];
v += grid_cell_size_cvs)
{
Vec2F32 p_cvs = {0};
p_cvs.v[axis] = v;
Vec2F32 p_scr = df_bitmap_screen_from_canvas_pos(draw_data->view_center_pos, draw_data->zoom, rect_scrn, p_cvs);
Rng2F32 rect = {0};
rect.p0.v[axis] = p_scr.v[axis] - grid_line_thickness_px/2;
rect.p1.v[axis] = p_scr.v[axis] + grid_line_thickness_px/2;
rect.p0.v[axis2_flip(axis)] = box->rect.p0.v[axis2_flip(axis)];
rect.p1.v[axis2_flip(axis)] = box->rect.p1.v[axis2_flip(axis)];
dr_rect(rect, grid_line_color, 0, 0, 1.f);
}
}
}
DF_VIEW_SETUP_FUNCTION_DEF(bitmap)
{
df_view_equip_loading_info(view, 1, 0, 0);
view->loading_t = view->loading_t_target = 1.f;
}
DF_VIEW_CMD_FUNCTION_DEF(bitmap) {}
DF_VIEW_UI_FUNCTION_DEF(bitmap)
{
Temp scratch = scratch_begin(0, 0);
HS_Scope *hs_scope = hs_scope_open();
TEX_Scope *tex_scope = tex_scope_open();
//////////////////////////////
//- rjf: evaluate expression
//
E_Eval eval = e_eval_from_string(scratch.arena, string);
Vec2S32 dim = d_dim2s32_from_eval_params(eval, params);
R_Tex2DFormat fmt = d_tex2dformat_from_eval_params(eval, params);
U64 base_offset = d_base_offset_from_eval(eval);
U64 expected_size = dim.x*dim.y*r_tex2d_format_bytes_per_pixel_table[fmt];
Rng1U64 offset_range = r1u64(base_offset, base_offset + expected_size);
//////////////////////////////
//- rjf: unpack params
//
F32 zoom = d_value_from_params_key(params, str8_lit("zoom")).f32;
Vec2F32 view_center_pos =
{
d_value_from_params_key(params, str8_lit("x")).f32,
d_value_from_params_key(params, str8_lit("y")).f32,
};
if(zoom == 0)
{
F32 available_dim_y = dim_2f32(rect).y;
F32 image_dim_y = (F32)dim.y;
if(image_dim_y != 0)
{
zoom = (available_dim_y / image_dim_y) * 0.8f;
}
else
{
zoom = 1.f;
}
}
//////////////////////////////
//- rjf: map expression artifacts -> texture
//
U128 texture_key = d_key_from_eval_space_range(eval.space, offset_range, 0);
TEX_Topology topology = tex_topology_make(dim, fmt);
U128 data_hash = {0};
R_Handle texture = tex_texture_from_key_topology(tex_scope, texture_key, topology, &data_hash);
String8 data = hs_data_from_hash(hs_scope, data_hash);
//////////////////////////////
//- rjf: equip loading info
//
if(offset_range.max != offset_range.min &&
eval.msgs.max_kind == E_MsgKind_Null &&
(u128_match(data_hash, u128_zero()) ||
r_handle_match(texture, r_handle_zero()) ||
data.size == 0))
{
df_view_equip_loading_info(view, 1, 0, 0);
}
//////////////////////////////
//- rjf: build canvas box
//
UI_Box *canvas_box = &ui_g_nil_box;
Vec2F32 canvas_dim = dim_2f32(rect);
Rng2F32 canvas_rect = r2f32p(0, 0, canvas_dim.x, canvas_dim.y);
UI_Rect(canvas_rect)
{
canvas_box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable|UI_BoxFlag_Scroll, "bmp_canvas_%p", view);
}
//////////////////////////////
//- rjf: canvas dragging
//
UI_Signal canvas_sig = ui_signal_from_box(canvas_box);
{
if(ui_dragging(canvas_sig))
{
if(ui_pressed(canvas_sig))
{
df_cmd(DF_CmdKind_FocusPanel);
ui_store_drag_struct(&view_center_pos);
}
Vec2F32 start_view_center_pos = *ui_get_drag_struct(Vec2F32);
Vec2F32 drag_delta_scr = ui_drag_delta();
Vec2F32 drag_delta_cvs = scale_2f32(drag_delta_scr, 1.f/zoom);
Vec2F32 new_view_center_pos = sub_2f32(start_view_center_pos, drag_delta_cvs);
view_center_pos = new_view_center_pos;
}
if(canvas_sig.scroll.y != 0)
{
F32 new_zoom = zoom - zoom*canvas_sig.scroll.y/10.f;
new_zoom = Clamp(1.f/256.f, new_zoom, 256.f);
Vec2F32 mouse_scr_pre = sub_2f32(ui_mouse(), rect.p0);
Vec2F32 mouse_cvs = df_bitmap_canvas_from_screen_pos(view_center_pos, zoom, canvas_rect, mouse_scr_pre);
zoom = new_zoom;
Vec2F32 mouse_scr_pst = df_bitmap_screen_from_canvas_pos(view_center_pos, zoom, canvas_rect, mouse_cvs);
Vec2F32 drift_scr = sub_2f32(mouse_scr_pst, mouse_scr_pre);
view_center_pos = add_2f32(view_center_pos, scale_2f32(drift_scr, 1.f/new_zoom));
}
if(ui_double_clicked(canvas_sig))
{
ui_kill_action();
MemoryZeroStruct(&view_center_pos);
zoom = 1.f;
}
}
//////////////////////////////
//- rjf: equip canvas draw info
//
{
DF_BitmapCanvasBoxDrawData *draw_data = push_array(ui_build_arena(), DF_BitmapCanvasBoxDrawData, 1);
draw_data->view_center_pos = view_center_pos;
draw_data->zoom = zoom;
ui_box_equip_custom_draw(canvas_box, df_bitmap_view_canvas_box_draw, draw_data);
}
//////////////////////////////
//- rjf: calculate image coordinates
//
Rng2F32 img_rect_cvs = r2f32p(-topology.dim.x/2, -topology.dim.y/2, +topology.dim.x/2, +topology.dim.y/2);
Rng2F32 img_rect_scr = df_bitmap_screen_from_canvas_rect(view_center_pos, zoom, canvas_rect, img_rect_cvs);
//////////////////////////////
//- rjf: image-region canvas interaction
//
Vec2S32 mouse_bmp = {-1, -1};
if(ui_hovering(canvas_sig) && !ui_dragging(canvas_sig))
{
Vec2F32 mouse_scr = sub_2f32(ui_mouse(), rect.p0);
Vec2F32 mouse_cvs = df_bitmap_canvas_from_screen_pos(view_center_pos, zoom, canvas_rect, mouse_scr);
if(contains_2f32(img_rect_cvs, mouse_cvs))
{
mouse_bmp = v2s32((S32)(mouse_cvs.x-img_rect_cvs.x0), (S32)(mouse_cvs.y-img_rect_cvs.y0));
S64 off_px = mouse_bmp.y*topology.dim.x + mouse_bmp.x;
S64 off_bytes = off_px*r_tex2d_format_bytes_per_pixel_table[topology.fmt];
if(0 <= off_bytes && off_bytes+r_tex2d_format_bytes_per_pixel_table[topology.fmt] <= data.size &&
r_tex2d_format_bytes_per_pixel_table[topology.fmt] != 0)
{
B32 color_is_good = 1;
Vec4F32 color = {0};
switch(topology.fmt)
{
default:{color_is_good = 0;}break;
case R_Tex2DFormat_R8: {color = v4f32(((U8 *)(data.str+off_bytes))[0]/255.f, 0, 0, 1);}break;
case R_Tex2DFormat_RG8: {color = v4f32(((U8 *)(data.str+off_bytes))[0]/255.f, ((U8 *)(data.str+off_bytes))[1]/255.f, 0, 1);}break;
case R_Tex2DFormat_RGBA8: {color = v4f32(((U8 *)(data.str+off_bytes))[0]/255.f, ((U8 *)(data.str+off_bytes))[1]/255.f, ((U8 *)(data.str+off_bytes))[2]/255.f, ((U8 *)(data.str+off_bytes))[3]/255.f);}break;
case R_Tex2DFormat_BGRA8: {color = v4f32(((U8 *)(data.str+off_bytes))[3]/255.f, ((U8 *)(data.str+off_bytes))[2]/255.f, ((U8 *)(data.str+off_bytes))[1]/255.f, ((U8 *)(data.str+off_bytes))[0]/255.f);}break;
case R_Tex2DFormat_R16: {color = v4f32(((U16 *)(data.str+off_bytes))[0]/(F32)max_U16, 0, 0, 1);}break;
case R_Tex2DFormat_RGBA16: {color = v4f32(((U16 *)(data.str+off_bytes))[0]/(F32)max_U16, ((U16 *)(data.str+off_bytes))[1]/(F32)max_U16, ((U16 *)(data.str+off_bytes))[2]/(F32)max_U16, ((U16 *)(data.str+off_bytes))[3]/(F32)max_U16);}break;
case R_Tex2DFormat_R32: {color = v4f32(((F32 *)(data.str+off_bytes))[0], 0, 0, 1);}break;
case R_Tex2DFormat_RG32: {color = v4f32(((F32 *)(data.str+off_bytes))[0], ((F32 *)(data.str+off_bytes))[1], 0, 1);}break;
case R_Tex2DFormat_RGBA32: {color = v4f32(((F32 *)(data.str+off_bytes))[0], ((F32 *)(data.str+off_bytes))[1], ((F32 *)(data.str+off_bytes))[2], ((F32 *)(data.str+off_bytes))[3]);}break;
}
if(color_is_good)
{
Vec4F32 hsva = hsva_from_rgba(color);
ui_do_color_tooltip_hsva(hsva);
}
}
}
}
//////////////////////////////
//- rjf: build image
//
UI_Parent(canvas_box)
{
if(0 <= mouse_bmp.x && mouse_bmp.x < dim.x &&
0 <= mouse_bmp.x && mouse_bmp.x < dim.y)
{
F32 pixel_size_scr = 1.f*zoom;
Rng2F32 indicator_rect_scr = r2f32p(img_rect_scr.x0 + mouse_bmp.x*pixel_size_scr,
img_rect_scr.y0 + mouse_bmp.y*pixel_size_scr,
img_rect_scr.x0 + (mouse_bmp.x+1)*pixel_size_scr,
img_rect_scr.y0 + (mouse_bmp.y+1)*pixel_size_scr);
UI_Rect(indicator_rect_scr)
{
ui_build_box_from_key(UI_BoxFlag_DrawBorder|UI_BoxFlag_Floating, ui_key_zero());
}
}
UI_Rect(img_rect_scr) UI_Flags(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawDropShadow|UI_BoxFlag_Floating)
{
ui_image(texture, R_Tex2DSampleKind_Nearest, r2f32p(0, 0, (F32)dim.x, (F32)dim.y), v4f32(1, 1, 1, 1), 0, str8_lit("bmp_image"));
}
}
//////////////////////////////
//- rjf: store params
//
df_view_store_param_f32(view, str8_lit("zoom"), zoom);
df_view_store_param_f32(view, str8_lit("x"), view_center_pos.x);
df_view_store_param_f32(view, str8_lit("y"), view_center_pos.y);
hs_scope_close(hs_scope);
tex_scope_close(tex_scope);
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: color_rgba @view_hook_impl
internal Vec4F32
df_rgba_from_eval_params(E_Eval eval, MD_Node *params)
{
Vec4F32 rgba = {0};
{
E_Eval value_eval = e_value_eval_from_eval(eval);
E_TypeKey type_key = eval.type_key;
E_TypeKind type_kind = e_type_kind_from_key(type_key);
U64 type_size = e_type_byte_size_from_key(type_key);
if(16 <= type_size)
{
e_space_read(eval.space, &rgba, r1u64(eval.value.u64, eval.value.u64 + 16));
}
else if(4 <= type_size)
{
U32 hex_val = value_eval.value.u32;
rgba = rgba_from_u32(hex_val);
}
}
return rgba;
}
DF_VIEW_SETUP_FUNCTION_DEF(color_rgba) {}
DF_VIEW_CMD_FUNCTION_DEF(color_rgba) {}
DF_VIEW_UI_FUNCTION_DEF(color_rgba)
{
Temp scratch = scratch_begin(0, 0);
Vec2F32 dim = dim_2f32(rect);
F32 padding = ui_top_font_size()*3.f;
E_Eval eval = e_eval_from_string(scratch.arena, string);
Vec4F32 rgba = df_rgba_from_eval_params(eval, params);
Vec4F32 hsva = hsva_from_rgba(rgba);
UI_WidthFill UI_HeightFill UI_Column UI_Padding(ui_px(padding, 1.f)) UI_Row UI_Padding(ui_pct(1.f, 0.f)) UI_HeightFill
{
UI_PrefWidth(ui_px(dim.y - padding*2, 1.f))
{
UI_Signal sv_sig = ui_sat_val_pickerf(hsva.x, &hsva.y, &hsva.z, "sat_val_picker");
}
UI_PrefWidth(ui_em(3.f, 1.f))
{
UI_Signal h_sig = ui_hue_pickerf(&hsva.x, hsva.y, hsva.z, "hue_picker");
}
UI_PrefWidth(ui_children_sum(1)) UI_Column UI_PrefWidth(ui_text_dim(10, 1)) UI_PrefHeight(ui_em(2.f, 0.f)) DF_Font(DF_FontSlot_Code) UI_FlagsAdd(UI_BoxFlag_DrawTextWeak)
{
ui_labelf("Hex");
ui_labelf("R");
ui_labelf("G");
ui_labelf("B");
ui_labelf("H");
ui_labelf("S");
ui_labelf("V");
ui_labelf("A");
}
UI_PrefWidth(ui_children_sum(1)) UI_Column UI_PrefWidth(ui_text_dim(10, 1)) UI_PrefHeight(ui_em(2.f, 0.f)) DF_Font(DF_FontSlot_Code)
{
String8 hex_string = hex_string_from_rgba_4f32(scratch.arena, rgba);
ui_label(hex_string);
ui_labelf("%.2f", rgba.x);
ui_labelf("%.2f", rgba.y);
ui_labelf("%.2f", rgba.z);
ui_labelf("%.2f", hsva.x);
ui_labelf("%.2f", hsva.y);
ui_labelf("%.2f", hsva.z);
ui_labelf("%.2f", rgba.w);
}
}
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: geo3d @view_hook_impl
typedef struct DF_Geo3DViewState DF_Geo3DViewState;
struct DF_Geo3DViewState
{
F32 yaw;
F32 pitch;
F32 zoom;
};
typedef struct DF_Geo3DBoxDrawData DF_Geo3DBoxDrawData;
struct DF_Geo3DBoxDrawData
{
F32 yaw;
F32 pitch;
F32 zoom;
R_Handle vertex_buffer;
R_Handle index_buffer;
};
internal UI_BOX_CUSTOM_DRAW(df_geo3d_box_draw)
{
DF_Geo3DBoxDrawData *draw_data = (DF_Geo3DBoxDrawData *)user_data;
// rjf: get clip
Rng2F32 clip = box->rect;
for(UI_Box *b = box->parent; !ui_box_is_nil(b); b = b->parent)
{
if(b->flags & UI_BoxFlag_Clip)
{
clip = intersect_2f32(b->rect, clip);
}
}
// rjf: calculate eye/target
Vec3F32 target = {0};
Vec3F32 eye = v3f32(draw_data->zoom*cos_f32(draw_data->yaw)*sin_f32(draw_data->pitch),
draw_data->zoom*sin_f32(draw_data->yaw)*sin_f32(draw_data->pitch),
draw_data->zoom*cos_f32(draw_data->pitch));
// rjf: mesh
Vec2F32 box_dim = dim_2f32(box->rect);
R_PassParams_Geo3D *pass = dr_geo3d_begin(box->rect,
make_look_at_4x4f32(eye, target, v3f32(0, 0, 1)),
make_perspective_4x4f32(0.25f, box_dim.x/box_dim.y, 0.1f, 500.f));
pass->clip = clip;
dr_mesh(draw_data->vertex_buffer, draw_data->index_buffer, R_GeoTopologyKind_Triangles, R_GeoVertexFlag_TexCoord|R_GeoVertexFlag_Normals|R_GeoVertexFlag_RGB, r_handle_zero(), mat_4x4f32(1.f));
}
DF_VIEW_SETUP_FUNCTION_DEF(geo3d)
{
df_view_equip_loading_info(view, 1, 0, 0);
view->loading_t = view->loading_t_target = 1.f;
}
DF_VIEW_CMD_FUNCTION_DEF(geo3d) {}
DF_VIEW_UI_FUNCTION_DEF(geo3d)
{
Temp scratch = scratch_begin(0, 0);
GEO_Scope *geo_scope = geo_scope_open();
DF_Geo3DViewState *state = df_view_user_state(view, DF_Geo3DViewState);
//////////////////////////////
//- rjf: unpack parameters
//
U64 count = d_value_from_params_key(params, str8_lit("count")).u64;
U64 vtx_base_off = d_value_from_params_key(params, str8_lit("vtx")).u64;
U64 vtx_size = d_value_from_params_key(params, str8_lit("vtx_size")).u64;
F32 yaw_target = d_value_from_params_key(params, str8_lit("yaw")).f32;
F32 pitch_target = d_value_from_params_key(params, str8_lit("pitch")).f32;
F32 zoom_target = d_value_from_params_key(params, str8_lit("zoom")).f32;
//////////////////////////////
//- rjf: evaluate & unpack expression
//
E_Eval eval = e_eval_from_string(scratch.arena, string);
U64 base_offset = d_base_offset_from_eval(eval);
Rng1U64 idxs_range = r1u64(base_offset, base_offset+count*sizeof(U32));
Rng1U64 vtxs_range = r1u64(vtx_base_off, vtx_base_off+vtx_size);
U128 idxs_key = d_key_from_eval_space_range(eval.space, idxs_range, 0);
U128 vtxs_key = d_key_from_eval_space_range(eval.space, vtxs_range, 0);
R_Handle idxs_buffer = geo_buffer_from_key(geo_scope, idxs_key);
R_Handle vtxs_buffer = geo_buffer_from_key(geo_scope, vtxs_key);
//////////////////////////////
//- rjf: equip loading info
//
if(eval.msgs.max_kind == E_MsgKind_Null &&
(r_handle_match(idxs_buffer, r_handle_zero()) ||
r_handle_match(vtxs_buffer, r_handle_zero())))
{
df_view_equip_loading_info(view, 1, 0, 0);
}
//////////////////////////////
//- rjf: do first-time camera initialization, if needed
//
if(zoom_target == 0)
{
yaw_target = -0.125f;
pitch_target = -0.125f;
zoom_target = 3.5f;
}
//////////////////////////////
//- rjf: animate camera
//
{
F32 fast_rate = 1 - pow_f32(2, (-60.f * df_state->frame_dt));
F32 slow_rate = 1 - pow_f32(2, (-30.f * df_state->frame_dt));
state->zoom += (zoom_target - state->zoom) * slow_rate;
state->yaw += (yaw_target - state->yaw) * fast_rate;
state->pitch += (pitch_target - state->pitch) * fast_rate;
if(abs_f32(state->zoom - zoom_target) > 0.001f ||
abs_f32(state->yaw - yaw_target) > 0.001f ||
abs_f32(state->pitch - pitch_target) > 0.001f)
{
df_request_frame();
}
}
//////////////////////////////
//- rjf: build
//
if(count != 0 && !r_handle_match(idxs_buffer, r_handle_zero()) && !r_handle_match(vtxs_buffer, r_handle_zero()))
{
Vec2F32 dim = dim_2f32(rect);
UI_Box *box = &ui_g_nil_box;
UI_FixedSize(dim)
{
box = ui_build_box_from_stringf(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground|UI_BoxFlag_Clickable|UI_BoxFlag_Scroll, "geo_box");
}
UI_Signal sig = ui_signal_from_box(box);
if(ui_dragging(sig))
{
if(ui_pressed(sig))
{
df_cmd(DF_CmdKind_FocusPanel);
Vec2F32 data = v2f32(yaw_target, pitch_target);
ui_store_drag_struct(&data);
}
Vec2F32 drag_delta = ui_drag_delta();
Vec2F32 drag_start_data = *ui_get_drag_struct(Vec2F32);
yaw_target = drag_start_data.x + drag_delta.x/dim.x;
pitch_target = drag_start_data.y + drag_delta.y/dim.y;
}
zoom_target += sig.scroll.y;
zoom_target = Clamp(0.1f, zoom_target, 100.f);
pitch_target = Clamp(-0.49f, pitch_target, -0.01f);
DF_Geo3DBoxDrawData *draw_data = push_array(ui_build_arena(), DF_Geo3DBoxDrawData, 1);
draw_data->yaw = state->yaw;
draw_data->pitch = state->pitch;
draw_data->zoom = state->zoom;
draw_data->vertex_buffer = vtxs_buffer;
draw_data->index_buffer = idxs_buffer;
ui_box_equip_custom_draw(box, df_geo3d_box_draw, draw_data);
}
//////////////////////////////
//- rjf: commit parameters
//
df_view_store_param_f32(view, str8_lit("yaw"), yaw_target);
df_view_store_param_f32(view, str8_lit("pitch"), pitch_target);
df_view_store_param_f32(view, str8_lit("zoom"), zoom_target);
geo_scope_close(geo_scope);
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: exception_filters @view_hook_impl
DF_VIEW_SETUP_FUNCTION_DEF(exception_filters) {}
DF_VIEW_CMD_FUNCTION_DEF(exception_filters) {}
DF_VIEW_UI_FUNCTION_DEF(exception_filters)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
String8 query = string;
//- rjf: get state
typedef struct DF_ExceptionFiltersViewState DF_ExceptionFiltersViewState;
struct DF_ExceptionFiltersViewState
{
Vec2S64 cursor;
};
DF_ExceptionFiltersViewState *sv = df_view_user_state(view, DF_ExceptionFiltersViewState);
//- rjf: get list of options
typedef struct DF_ExceptionFiltersOption DF_ExceptionFiltersOption;
struct DF_ExceptionFiltersOption
{
String8 name;
FuzzyMatchRangeList matches;
B32 is_enabled;
CTRL_ExceptionCodeKind exception_code_kind;
};
typedef struct DF_ExceptionFiltersOptionChunkNode DF_ExceptionFiltersOptionChunkNode;
struct DF_ExceptionFiltersOptionChunkNode
{
DF_ExceptionFiltersOptionChunkNode *next;
DF_ExceptionFiltersOption *v;
U64 cap;
U64 count;
};
typedef struct DF_ExceptionFiltersOptionChunkList DF_ExceptionFiltersOptionChunkList;
struct DF_ExceptionFiltersOptionChunkList
{
DF_ExceptionFiltersOptionChunkNode *first;
DF_ExceptionFiltersOptionChunkNode *last;
U64 option_count;
U64 node_count;
};
typedef struct DF_ExceptionFiltersOptionArray DF_ExceptionFiltersOptionArray;
struct DF_ExceptionFiltersOptionArray
{
DF_ExceptionFiltersOption *v;
U64 count;
};
DF_ExceptionFiltersOptionChunkList opts_list = {0};
for(CTRL_ExceptionCodeKind k = (CTRL_ExceptionCodeKind)(CTRL_ExceptionCodeKind_Null+1);
k < CTRL_ExceptionCodeKind_COUNT;
k = (CTRL_ExceptionCodeKind)(k+1))
{
DF_ExceptionFiltersOptionChunkNode *node = opts_list.last;
String8 name = push_str8f(scratch.arena, "0x%x %S", ctrl_exception_code_kind_code_table[k], ctrl_exception_code_kind_display_string_table[k]);
FuzzyMatchRangeList matches = fuzzy_match_find(scratch.arena, query, name);
if(matches.count >= matches.needle_part_count)
{
if(node == 0 || node->count >= node->cap)
{
node = push_array(scratch.arena, DF_ExceptionFiltersOptionChunkNode, 1);
node->cap = 256;
node->v = push_array_no_zero(scratch.arena, DF_ExceptionFiltersOption, node->cap);
SLLQueuePush(opts_list.first, opts_list.last, node);
opts_list.node_count += 1;
}
node->v[node->count].name = name;
node->v[node->count].matches = matches;
node->v[node->count].is_enabled = !!(d_state->ctrl_exception_code_filters[k/64] & (1ull<<(k%64)));
node->v[node->count].exception_code_kind = k;
node->count += 1;
opts_list.option_count += 1;
}
}
DF_ExceptionFiltersOptionArray opts = {0};
{
opts.count = opts_list.option_count;
opts.v = push_array_no_zero(scratch.arena, DF_ExceptionFiltersOption, opts.count);
U64 idx = 0;
for(DF_ExceptionFiltersOptionChunkNode *n = opts_list.first; n != 0; n = n->next)
{
MemoryCopy(opts.v+idx, n->v, n->count*sizeof(DF_ExceptionFiltersOption));
idx += n->count;
}
}
//- rjf: build option table
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
Vec2F32 rect_dim = dim_2f32(rect);
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = rect_dim;
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, opts.count));
scroll_list_params.item_range = r1s64(0, opts.count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params,
&view->scroll_pos.y,
&sv->cursor,
0,
&visible_row_range,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
for(S64 row = visible_row_range.min; row <= visible_row_range.max && row < opts.count; row += 1)
UI_FocusHot(sv->cursor.y == row+1 ? UI_FocusKind_On : UI_FocusKind_Off)
{
DF_ExceptionFiltersOption *opt = &opts.v[row];
UI_Signal sig = df_icon_buttonf(opt->is_enabled ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow, &opt->matches, "%S", opt->name);
if(ui_clicked(sig))
{
if(opt->exception_code_kind != CTRL_ExceptionCodeKind_Null)
{
CTRL_ExceptionCodeKind k = opt->exception_code_kind;
if(opt->is_enabled)
{
d_state->ctrl_exception_code_filters[k/64] &= ~(1ull<<(k%64));
}
else
{
d_state->ctrl_exception_code_filters[k/64] |= (1ull<<(k%64));
}
}
}
}
}
scratch_end(scratch);
ProfEnd();
}
////////////////////////////////
//~ rjf: settings @view_hook_impl
typedef enum DF_SettingsItemKind
{
DF_SettingsItemKind_CategoryHeader,
DF_SettingsItemKind_GlobalSetting,
DF_SettingsItemKind_WindowSetting,
DF_SettingsItemKind_ThemeColor,
DF_SettingsItemKind_ThemePreset,
DF_SettingsItemKind_COUNT
}
DF_SettingsItemKind;
typedef struct DF_SettingsItem DF_SettingsItem;
struct DF_SettingsItem
{
DF_SettingsItemKind kind;
String8 kind_string;
String8 string;
FuzzyMatchRangeList kind_string_matches;
FuzzyMatchRangeList string_matches;
DF_IconKind icon_kind;
DF_SettingCode code;
DF_ThemeColor color;
DF_ThemePreset preset;
DF_SettingsItemKind category;
};
typedef struct DF_SettingsItemNode DF_SettingsItemNode;
struct DF_SettingsItemNode
{
DF_SettingsItemNode *next;
DF_SettingsItem v;
};
typedef struct DF_SettingsItemList DF_SettingsItemList;
struct DF_SettingsItemList
{
DF_SettingsItemNode *first;
DF_SettingsItemNode *last;
U64 count;
};
typedef struct DF_SettingsItemArray DF_SettingsItemArray;
struct DF_SettingsItemArray
{
DF_SettingsItem *v;
U64 count;
};
internal int
df_qsort_compare_settings_item(DF_SettingsItem *a, DF_SettingsItem *b)
{
int result = 0;
if(a->string_matches.count > b->string_matches.count)
{
result = -1;
}
else if(a->string_matches.count < b->string_matches.count)
{
result = +1;
}
else if(a->kind_string_matches.count > b->kind_string_matches.count)
{
result = -1;
}
else if(a->kind_string_matches.count < b->kind_string_matches.count)
{
result = +1;
}
return result;
}
DF_VIEW_SETUP_FUNCTION_DEF(settings){}
DF_VIEW_CMD_FUNCTION_DEF(settings){}
DF_VIEW_UI_FUNCTION_DEF(settings)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
F32 row_height_px = floor_f32(ui_top_font_size()*2.5f);
String8 query = string;
DF_Window *window = df_window_from_handle(df_regs()->window);
//////////////////////////////
//- rjf: get state
//
typedef struct DF_SettingsViewState DF_SettingsViewState;
struct DF_SettingsViewState
{
B32 initialized;
Vec2S64 cursor;
TxtPt txt_cursor;
TxtPt txt_mark;
U8 txt_buffer[1024];
U64 txt_size;
DF_ThemeColor color_ctx_menu_color;
Vec4F32 color_ctx_menu_color_hsva;
DF_ThemePreset preset_apply_confirm;
B32 category_opened[DF_SettingsItemKind_COUNT];
};
DF_SettingsViewState *sv = df_view_user_state(view, DF_SettingsViewState);
if(!sv->initialized)
{
sv->initialized = 1;
sv->preset_apply_confirm = DF_ThemePreset_COUNT;
}
//////////////////////////////
//- rjf: gather all filtered settings items
//
DF_SettingsItemArray items = {0};
{
DF_SettingsItemList items_list = {0};
//- rjf: global settings header
if(query.size == 0)
{
DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1);
SLLQueuePush(items_list.first, items_list.last, n);
items_list.count += 1;
n->v.kind = DF_SettingsItemKind_CategoryHeader;
n->v.string = str8_lit("Global Interface Settings");
n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_GlobalSetting] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret;
n->v.category = DF_SettingsItemKind_GlobalSetting;
}
//- rjf: gather all global settings
if(sv->category_opened[DF_SettingsItemKind_GlobalSetting] || query.size != 0)
{
for(EachEnumVal(DF_SettingCode, code))
{
if(df_g_setting_code_default_is_per_window_table[code])
{
continue;
}
String8 kind_string = str8_lit("Global Interface Setting");
String8 string = df_g_setting_code_display_string_table[code];
FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string);
FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string);
if(string_matches.count == string_matches.needle_part_count ||
kind_string_matches.count == kind_string_matches.needle_part_count)
{
DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1);
SLLQueuePush(items_list.first, items_list.last, n);
items_list.count += 1;
n->v.kind = DF_SettingsItemKind_GlobalSetting;
n->v.kind_string = kind_string;
n->v.string = string;
n->v.kind_string_matches = kind_string_matches;
n->v.string_matches = string_matches;
n->v.icon_kind = DF_IconKind_Window;
n->v.code = code;
}
}
}
//- rjf: window settings header
if(query.size == 0)
{
DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1);
SLLQueuePush(items_list.first, items_list.last, n);
items_list.count += 1;
n->v.kind = DF_SettingsItemKind_CategoryHeader;
n->v.string = str8_lit("Window Interface Settings");
n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_WindowSetting] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret;
n->v.category = DF_SettingsItemKind_WindowSetting;
}
//- rjf: gather all window settings
if(sv->category_opened[DF_SettingsItemKind_WindowSetting] || query.size != 0)
{
for(EachEnumVal(DF_SettingCode, code))
{
if(!df_g_setting_code_default_is_per_window_table[code])
{
continue;
}
String8 kind_string = str8_lit("Window Interface Setting");
String8 string = df_g_setting_code_display_string_table[code];
FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string);
FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string);
if(string_matches.count == string_matches.needle_part_count ||
kind_string_matches.count == kind_string_matches.needle_part_count)
{
DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1);
SLLQueuePush(items_list.first, items_list.last, n);
items_list.count += 1;
n->v.kind = DF_SettingsItemKind_WindowSetting;
n->v.kind_string = kind_string;
n->v.string = string;
n->v.kind_string_matches = kind_string_matches;
n->v.string_matches = string_matches;
n->v.icon_kind = DF_IconKind_Window;
n->v.code = code;
}
}
}
//- rjf: theme presets header
if(query.size == 0)
{
DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1);
SLLQueuePush(items_list.first, items_list.last, n);
items_list.count += 1;
n->v.kind = DF_SettingsItemKind_CategoryHeader;
n->v.string = str8_lit("Theme Presets");
n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_ThemePreset] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret;
n->v.category = DF_SettingsItemKind_ThemePreset;
}
//- rjf: gather theme presets
if(sv->category_opened[DF_SettingsItemKind_ThemePreset] || query.size != 0)
{
for(EachEnumVal(DF_ThemePreset, preset))
{
String8 kind_string = str8_lit("Theme Preset");
String8 string = df_g_theme_preset_display_string_table[preset];
FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string);
FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string);
if(string_matches.count == string_matches.needle_part_count ||
kind_string_matches.count == kind_string_matches.needle_part_count)
{
DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1);
SLLQueuePush(items_list.first, items_list.last, n);
items_list.count += 1;
n->v.kind = DF_SettingsItemKind_ThemePreset;
n->v.kind_string = kind_string;
n->v.string = string;
n->v.kind_string_matches = kind_string_matches;
n->v.string_matches = string_matches;
n->v.icon_kind = DF_IconKind_Palette;
n->v.preset = preset;
}
}
}
//- rjf: theme colors header
if(query.size == 0)
{
DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1);
SLLQueuePush(items_list.first, items_list.last, n);
items_list.count += 1;
n->v.kind = DF_SettingsItemKind_CategoryHeader;
n->v.string = str8_lit("Theme Colors");
n->v.icon_kind = sv->category_opened[DF_SettingsItemKind_ThemeColor] ? DF_IconKind_DownCaret : DF_IconKind_RightCaret;
n->v.category = DF_SettingsItemKind_ThemeColor;
}
//- rjf: gather all theme colors
if(sv->category_opened[DF_SettingsItemKind_ThemeColor] || query.size != 0)
{
for(EachNonZeroEnumVal(DF_ThemeColor, color))
{
String8 kind_string = str8_lit("Theme Color");
String8 string = df_g_theme_color_display_string_table[color];
FuzzyMatchRangeList kind_string_matches = fuzzy_match_find(scratch.arena, query, kind_string);
FuzzyMatchRangeList string_matches = fuzzy_match_find(scratch.arena, query, string);
if(string_matches.count == string_matches.needle_part_count ||
kind_string_matches.count == kind_string_matches.needle_part_count)
{
DF_SettingsItemNode *n = push_array(scratch.arena, DF_SettingsItemNode, 1);
SLLQueuePush(items_list.first, items_list.last, n);
items_list.count += 1;
n->v.kind = DF_SettingsItemKind_ThemeColor;
n->v.kind_string = kind_string;
n->v.string = string;
n->v.kind_string_matches = kind_string_matches;
n->v.string_matches = string_matches;
n->v.icon_kind = DF_IconKind_Palette;
n->v.color = color;
}
}
}
//- rjf: convert to array
items.count = items_list.count;
items.v = push_array(scratch.arena, DF_SettingsItem, items.count);
{
U64 idx = 0;
for(DF_SettingsItemNode *n = items_list.first; n != 0; n = n->next, idx += 1)
{
items.v[idx] = n->v;
}
}
}
//////////////////////////////
//- rjf: sort filtered settings item list
//
if(query.size != 0)
{
quick_sort(items.v, items.count, sizeof(items.v[0]), df_qsort_compare_settings_item);
}
//////////////////////////////
//- rjf: produce per-color context menu keys
//
UI_Key *color_ctx_menu_keys = push_array(scratch.arena, UI_Key, DF_ThemeColor_COUNT);
{
for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1);
color < DF_ThemeColor_COUNT;
color = (DF_ThemeColor)(color+1))
{
color_ctx_menu_keys[color] = ui_key_from_stringf(ui_key_zero(), "###settings_color_ctx_menu_%I64x", (U64)color);
}
}
//////////////////////////////
//- rjf: build color context menus
//
for(DF_ThemeColor color = (DF_ThemeColor)(DF_ThemeColor_Null+1);
color < DF_ThemeColor_COUNT;
color = (DF_ThemeColor)(color+1))
{
DF_Palette(DF_PaletteCode_Floating)
UI_CtxMenu(color_ctx_menu_keys[color])
UI_Padding(ui_em(1.5f, 1.f))
UI_PrefWidth(ui_em(28.5f, 1)) UI_PrefHeight(ui_children_sum(1.f))
{
// rjf: build title
UI_Row
{
ui_spacer(ui_em(1.5f, 1.f));
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) ui_label(df_g_theme_color_display_string_table[color]);
}
ui_spacer(ui_em(1.5f, 1.f));
// rjf: build picker
{
ui_set_next_pref_height(ui_em(22.f, 1.f));
UI_Row UI_Padding(ui_pct(1, 0))
{
UI_PrefWidth(ui_em(22.f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip)
{
ui_sat_val_pickerf(sv->color_ctx_menu_color_hsva.x, &sv->color_ctx_menu_color_hsva.y, &sv->color_ctx_menu_color_hsva.z, "###settings_satval_picker");
}
ui_spacer(ui_em(0.75f, 1.f));
UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip)
ui_hue_pickerf(&sv->color_ctx_menu_color_hsva.x, sv->color_ctx_menu_color_hsva.y, sv->color_ctx_menu_color_hsva.z, "###settings_hue_picker");
UI_PrefWidth(ui_em(1.5f, 1.f)) UI_PrefHeight(ui_em(22.f, 1.f)) UI_Flags(UI_BoxFlag_FocusNavSkip)
ui_alpha_pickerf(&sv->color_ctx_menu_color_hsva.w, "###settings_alpha_picker");
}
}
ui_spacer(ui_em(1.5f, 1.f));
// rjf: build line edits
UI_Row
UI_WidthFill
UI_Padding(ui_em(1.5f, 1.f))
UI_PrefHeight(ui_children_sum(1.f))
UI_Column
UI_PrefHeight(ui_em(2.25f, 1.f))
{
Vec4F32 hsva = sv->color_ctx_menu_color_hsva;
Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z);
Vec3F32 rgb = rgb_from_hsv(hsv);
Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w);
String8 hex_string = hex_string_from_rgba_4f32(scratch.arena, rgba);
hex_string = push_str8f(scratch.arena, "#%S", hex_string);
String8 r_string = push_str8f(scratch.arena, "%.2f", rgba.x);
String8 g_string = push_str8f(scratch.arena, "%.2f", rgba.y);
String8 b_string = push_str8f(scratch.arena, "%.2f", rgba.z);
String8 h_string = push_str8f(scratch.arena, "%.2f", hsva.x);
String8 s_string = push_str8f(scratch.arena, "%.2f", hsva.y);
String8 v_string = push_str8f(scratch.arena, "%.2f", hsva.z);
String8 a_string = push_str8f(scratch.arena, "%.2f", rgba.w);
UI_Row DF_Font(DF_FontSlot_Code)
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("Hex");
UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, hex_string, "###hex_edit");
if(ui_committed(sig))
{
String8 string = str8(sv->txt_buffer, sv->txt_size);
Vec4F32 new_rgba = rgba_from_hex_string_4f32(string);
Vec4F32 new_hsva = hsva_from_rgba(new_rgba);
sv->color_ctx_menu_color_hsva = new_hsva;
}
}
ui_spacer(ui_em(0.75f, 1.f));
UI_Row
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("R");
UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, r_string, "###r_edit");
if(ui_committed(sig))
{
String8 string = str8(sv->txt_buffer, sv->txt_size);
Vec4F32 new_rgba = v4f32((F32)f64_from_str8(string), rgba.y, rgba.z, rgba.w);
Vec4F32 new_hsva = hsva_from_rgba(new_rgba);
sv->color_ctx_menu_color_hsva = new_hsva;
}
}
UI_Row
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("G");
UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, g_string, "###g_edit");
if(ui_committed(sig))
{
String8 string = str8(sv->txt_buffer, sv->txt_size);
Vec4F32 new_rgba = v4f32(rgba.x, (F32)f64_from_str8(string), rgba.z, rgba.w);
Vec4F32 new_hsva = hsva_from_rgba(new_rgba);
sv->color_ctx_menu_color_hsva = new_hsva;
}
}
UI_Row
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("B");
UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, b_string, "###b_edit");
if(ui_committed(sig))
{
String8 string = str8(sv->txt_buffer, sv->txt_size);
Vec4F32 new_rgba = v4f32(rgba.x, rgba.y, (F32)f64_from_str8(string), rgba.w);
Vec4F32 new_hsva = hsva_from_rgba(new_rgba);
sv->color_ctx_menu_color_hsva = new_hsva;
}
}
ui_spacer(ui_em(0.75f, 1.f));
UI_Row
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("H");
UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, h_string, "###h_edit");
if(ui_committed(sig))
{
String8 string = str8(sv->txt_buffer, sv->txt_size);
Vec4F32 new_hsva = v4f32((F32)f64_from_str8(string), hsva.y, hsva.z, hsva.w);
sv->color_ctx_menu_color_hsva = new_hsva;
}
}
UI_Row
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("S");
UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, s_string, "###s_edit");
if(ui_committed(sig))
{
String8 string = str8(sv->txt_buffer, sv->txt_size);
Vec4F32 new_hsva = v4f32(hsva.x, (F32)f64_from_str8(string), hsva.z, hsva.w);
sv->color_ctx_menu_color_hsva = new_hsva;
}
}
UI_Row
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("V");
UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, v_string, "###v_edit");
if(ui_committed(sig))
{
String8 string = str8(sv->txt_buffer, sv->txt_size);
Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, (F32)f64_from_str8(string), hsva.w);
sv->color_ctx_menu_color_hsva = new_hsva;
}
}
ui_spacer(ui_em(0.75f, 1.f));
UI_Row
{
UI_FlagsAdd(UI_BoxFlag_DrawTextWeak) UI_PrefWidth(ui_em(4.5f, 1.f)) ui_labelf("A");
UI_Signal sig = df_line_editf(DF_LineEditFlag_Border, 0, 0, &sv->txt_cursor, &sv->txt_mark, sv->txt_buffer, sizeof(sv->txt_buffer), &sv->txt_size, 0, a_string, "###a_edit");
if(ui_committed(sig))
{
String8 string = str8(sv->txt_buffer, sv->txt_size);
Vec4F32 new_hsva = v4f32(hsva.x, hsva.y, hsva.z, (F32)f64_from_str8(string));
sv->color_ctx_menu_color_hsva = new_hsva;
}
}
}
// rjf: commit state to theme
Vec4F32 hsva = sv->color_ctx_menu_color_hsva;
Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z);
Vec3F32 rgb = rgb_from_hsv(hsv);
Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, sv->color_ctx_menu_color_hsva.w);
df_state->cfg_theme_target.colors[sv->color_ctx_menu_color] = rgba;
}
}
//////////////////////////////
//- rjf: cancels
//
UI_Focus(UI_FocusKind_On) if(ui_is_focus_active() && sv->preset_apply_confirm < DF_ThemePreset_COUNT && ui_slot_press(UI_EventActionSlot_Cancel))
{
sv->preset_apply_confirm = DF_ThemePreset_COUNT;
}
//////////////////////////////
//- rjf: build items list
//
Rng1S64 visible_row_range = {0};
UI_ScrollListParams scroll_list_params = {0};
{
Vec2F32 rect_dim = dim_2f32(rect);
scroll_list_params.flags = UI_ScrollListFlag_All;
scroll_list_params.row_height_px = row_height_px;
scroll_list_params.dim_px = v2f32(rect_dim.x, rect_dim.y);
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, items.count));
scroll_list_params.item_range = r1s64(0, items.count);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
}
UI_ScrollListSignal scroll_list_sig = {0};
UI_Focus(UI_FocusKind_On)
UI_ScrollList(&scroll_list_params, &view->scroll_pos.y, &sv->cursor, 0, &visible_row_range, &scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
for(S64 row_num = visible_row_range.min; row_num <= visible_row_range.max && row_num < items.count; row_num += 1)
{
//- rjf: unpack item
DF_SettingsItem *item = &items.v[row_num];
UI_Palette *palette = ui_top_palette();
Vec4F32 rgba = ui_top_palette()->text_weak;
OS_Cursor cursor = OS_Cursor_HandPoint;
Rng1S32 s32_range = {0};
B32 is_toggler = 0;
B32 is_toggled = 0;
B32 is_slider = 0;
S32 slider_s32_val = 0;
F32 slider_pct = 0.f;
UI_BoxFlags flags = UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawHotEffects|UI_BoxFlag_DrawActiveEffects;
DF_SettingVal *val_table = &df_state->cfg_setting_vals[D_CfgSrc_User][0];
switch(item->kind)
{
case DF_SettingsItemKind_COUNT:{}break;
case DF_SettingsItemKind_CategoryHeader:
{
cursor = OS_Cursor_HandPoint;
flags = UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawHotEffects;
}break;
case DF_SettingsItemKind_ThemePreset:
{
Vec4F32 *colors = df_g_theme_preset_colors_table[item->preset];
Vec4F32 bg_color = colors[DF_ThemeColor_BaseBackground];
Vec4F32 tx_color = colors[DF_ThemeColor_Text];
Vec4F32 tw_color = colors[DF_ThemeColor_TextWeak];
Vec4F32 bd_color = colors[DF_ThemeColor_BaseBorder];
palette = ui_build_palette(ui_top_palette(),
.text = tx_color,
.text_weak = tw_color,
.border = bd_color,
.background = bg_color);
}break;
case DF_SettingsItemKind_ThemeColor:
{
rgba = df_rgba_from_theme_color(item->color);
}break;
case DF_SettingsItemKind_WindowSetting: {val_table = &window->setting_vals[0];}goto setting;
case DF_SettingsItemKind_GlobalSetting:{}goto setting;
setting:;
{
s32_range = df_g_setting_code_s32_range_table[item->code];
if(s32_range.min != 0 || s32_range.max != 1)
{
cursor = OS_Cursor_LeftRight;
is_slider = 1;
slider_s32_val = val_table[item->code].s32;
slider_pct = (F32)(slider_s32_val - s32_range.min) / dim_1s32(s32_range);
}
else
{
is_toggler = 1;
is_toggled = !!val_table[item->code].s32;
}
}break;
}
//- rjf: build item widget
UI_Box *item_box = &ui_g_nil_box;
UI_Row
{
if(query.size == 0 && item->kind != DF_SettingsItemKind_CategoryHeader)
{
ui_set_next_flags(UI_BoxFlag_DrawSideLeft);
ui_spacer(ui_em(2.f, 1.f));
}
UI_Focus(row_num+1 == sv->cursor.y ? UI_FocusKind_On : UI_FocusKind_Off) UI_Palette(palette)
{
ui_set_next_hover_cursor(cursor);
item_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|flags, "###option_%S_%S", item->kind_string, item->string);
UI_Parent(item_box)
{
if(item->icon_kind != DF_IconKind_Null)
{
UI_PrefWidth(ui_em(3.f, 1.f))
DF_Font(DF_FontSlot_Icons)
UI_Palette(ui_build_palette(ui_top_palette(), .text = rgba))
UI_TextAlignment(UI_TextAlign_Center)
ui_label(df_g_icon_kind_text_table[item->icon_kind]);
}
if(query.size != 0 && item->kind_string.size != 0) UI_PrefWidth(ui_text_dim(10, 1))
{
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_DrawTextWeak, "%S", item->kind_string);
ui_box_equip_fuzzy_match_ranges(box, &item->kind_string_matches);
}
UI_PrefWidth(ui_text_dim(10, 1))
{
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_DrawText, "%S", item->string);
ui_box_equip_fuzzy_match_ranges(box, &item->string_matches);
}
if(is_slider) UI_PrefWidth(ui_text_dim(10, 1))
{
UI_Flags(UI_BoxFlag_DrawTextWeak)
ui_labelf("(%i)", slider_s32_val);
UI_PrefWidth(ui_pct(slider_pct, 1.f)) UI_HeightFill UI_FixedX(0) UI_FixedY(0)
UI_Palette(ui_build_palette(ui_top_palette(), .background = df_rgba_from_theme_color(DF_ThemeColor_HighlightOverlay)))
ui_build_box_from_key(UI_BoxFlag_DrawBackground, ui_key_zero());
}
if(is_toggler)
{
ui_spacer(ui_pct(1, 0));
UI_PrefWidth(ui_em(2.5f, 1.f))
DF_Font(DF_FontSlot_Icons)
UI_Flags(UI_BoxFlag_DrawTextWeak)
ui_label(df_g_icon_kind_text_table[is_toggled ? DF_IconKind_CheckFilled : DF_IconKind_CheckHollow]);
}
if(item->kind == DF_SettingsItemKind_ThemePreset && sv->preset_apply_confirm == item->preset)
{
ui_spacer(ui_pct(1, 0));
UI_PrefWidth(ui_text_dim(10, 1))
DF_Palette(DF_PaletteCode_NegativePopButton)
UI_CornerRadius(ui_top_font_size()*0.5f)
UI_FontSize(ui_top_font_size()*0.9f)
UI_TextAlignment(UI_TextAlign_Center)
ui_build_box_from_stringf(UI_BoxFlag_DrawText|UI_BoxFlag_DrawBackground, "Click Again To Apply");
}
}
}
}
//- rjf: interact
UI_Signal sig = ui_signal_from_box(item_box);
if(item->kind == DF_SettingsItemKind_ThemeColor && ui_clicked(sig))
{
Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z);
Vec3F32 hsv = hsv_from_rgb(rgb);
Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w);
ui_ctx_menu_open(color_ctx_menu_keys[item->color], item_box->key, v2f32(0, dim_2f32(item_box->rect).y));
sv->color_ctx_menu_color = item->color;
sv->color_ctx_menu_color_hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w);
df_cmd(DF_CmdKind_FocusPanel);
}
if((item->kind == DF_SettingsItemKind_GlobalSetting || item->kind == DF_SettingsItemKind_WindowSetting) &&
is_toggler && ui_clicked(sig))
{
val_table[item->code].s32 ^= 1;
val_table[item->code].set = 1;
}
if((item->kind == DF_SettingsItemKind_GlobalSetting || item->kind == DF_SettingsItemKind_WindowSetting) &&
is_slider && ui_dragging(sig))
{
if(ui_pressed(sig))
{
ui_store_drag_struct(&slider_s32_val);
}
S32 pre_drag_val = *ui_get_drag_struct(S32);
Vec2F32 delta = ui_drag_delta();
S32 pst_drag_val = pre_drag_val + (S32)(delta.x/(ui_top_font_size()*2.f));
pst_drag_val = clamp_1s32(s32_range, pst_drag_val);
val_table[item->code].s32 = pst_drag_val;
val_table[item->code].set = 1;
}
if(item->kind == DF_SettingsItemKind_ThemePreset && ui_clicked(sig))
{
if(sv->preset_apply_confirm == item->preset)
{
Vec4F32 *colors = df_g_theme_preset_colors_table[item->preset];
MemoryCopy(df_state->cfg_theme_target.colors, colors, sizeof(df_state->cfg_theme_target.colors));
sv->preset_apply_confirm = DF_ThemePreset_COUNT;
}
else
{
sv->preset_apply_confirm = item->preset;
}
}
if(item->kind != DF_SettingsItemKind_ThemePreset && ui_pressed(sig))
{
sv->preset_apply_confirm = DF_ThemePreset_COUNT;
}
if(item->kind != DF_SettingsItemKind_ThemePreset && ui_pressed(sig))
{
sv->preset_apply_confirm = DF_ThemePreset_COUNT;
}
if(item->kind == DF_SettingsItemKind_CategoryHeader && ui_pressed(sig))
{
sv->category_opened[item->category] ^= 1;
}
}
}
scratch_end(scratch);
ProfEnd();
}