Files
raddebugger/src/raddbg/raddbg_core.c
T

16015 lines
676 KiB
C

// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
#undef MARKUP_LAYER_COLOR
#define MARKUP_LAYER_COLOR 0.10f, 0.20f, 0.25f
////////////////////////////////
//~ rjf: Generated Code
#include "generated/raddbg.meta.c"
////////////////////////////////
//~ rjf: Config ID Type Functions
internal void
rd_cfg_id_list_push(Arena *arena, RD_CfgIDList *list, RD_CfgID id)
{
RD_CfgIDNode *n = push_array(arena, RD_CfgIDNode, 1);
n->v = id;
SLLQueuePush(list->first, list->last, n);
list->count += 1;
}
internal RD_CfgIDList
rd_cfg_id_list_copy(Arena *arena, RD_CfgIDList *src)
{
RD_CfgIDList result = {0};
for(RD_CfgIDNode *n = src->first; n != 0; n = n->next)
{
rd_cfg_id_list_push(arena, &result, n->v);
}
return result;
}
////////////////////////////////
//~ rjf: Registers Type Functions
internal void
rd_regs_copy_contents(Arena *arena, RD_Regs *dst, RD_Regs *src)
{
MemoryCopyStruct(dst, src);
dst->cfg_list = rd_cfg_id_list_copy(arena, &src->cfg_list);
dst->file_path = push_str8_copy(arena, src->file_path);
dst->lines = d_line_list_copy(arena, &src->lines);
dst->dbgi_key = di_key_copy(arena, &src->dbgi_key);
dst->expr = push_str8_copy(arena, src->expr);
dst->string = push_str8_copy(arena, src->string);
dst->cmd_name = push_str8_copy(arena, src->cmd_name);
dst->params_tree = md_tree_copy(arena, src->params_tree);
if(dst->cfg_list.count == 0 && dst->cfg != 0)
{
rd_cfg_id_list_push(arena, &dst->cfg_list, dst->cfg);
}
}
internal RD_Regs *
rd_regs_copy(Arena *arena, RD_Regs *src)
{
RD_Regs *dst = push_array(arena, RD_Regs, 1);
rd_regs_copy_contents(arena, dst, src);
return dst;
}
////////////////////////////////
//~ rjf: Commands Type Functions
internal void
rd_cmd_list_push_new(Arena *arena, RD_CmdList *cmds, String8 name, RD_Regs *regs)
{
RD_CmdNode *n = push_array(arena, RD_CmdNode, 1);
n->cmd.name = push_str8_copy(arena, name);
n->cmd.regs = rd_regs_copy(arena, regs);
DLLPushBack(cmds->first, cmds->last, n);
cmds->count += 1;
}
////////////////////////////////
//~ rjf: View UI Rule Functions
internal RD_ViewUIRuleMap *
rd_view_ui_rule_map_make(Arena *arena, U64 slots_count)
{
RD_ViewUIRuleMap *map = push_array(arena, RD_ViewUIRuleMap, 1);
map->slots_count = slots_count;
map->slots = push_array(arena, RD_ViewUIRuleSlot, map->slots_count);
return map;
}
internal void
rd_view_ui_rule_map_insert(Arena *arena, RD_ViewUIRuleMap *map, String8 string, RD_ViewUIFunctionType *ui)
{
U64 hash = d_hash_from_string(string);
U64 slot_idx = hash%map->slots_count;
RD_ViewUIRuleNode *n = push_array(arena, RD_ViewUIRuleNode, 1);
n->v.name = push_str8_copy(arena, string);
n->v.ui = ui;
SLLQueuePush(map->slots[slot_idx].first, map->slots[slot_idx].last, n);
}
internal RD_ViewUIRule *
rd_view_ui_rule_from_string(String8 string)
{
RD_ViewUIRule *rule = &rd_nil_view_ui_rule;
{
RD_ViewUIRuleMap *map = rd_state->view_ui_rule_map;
U64 hash = d_hash_from_string(string);
U64 slot_idx = hash%map->slots_count;
for(RD_ViewUIRuleNode *n = map->slots[slot_idx].first; n != 0; n = n->next)
{
if(str8_match(n->v.name, string, 0))
{
rule = &n->v;
break;
}
}
}
return rule;
}
////////////////////////////////
//~ rjf: Global Cross-Window UI Interaction State Functions
internal B32
rd_drag_is_active(void)
{
return ((rd_state->drag_drop_state == RD_DragDropState_Dragging) ||
(rd_state->drag_drop_state == RD_DragDropState_Dropping));
}
internal void
rd_drag_begin(RD_RegSlot slot)
{
if(!rd_drag_is_active())
{
arena_clear(rd_state->drag_drop_arena);
rd_state->drag_drop_regs = rd_regs_copy(rd_state->drag_drop_arena, rd_regs());
rd_state->drag_drop_regs_slot = slot;
rd_state->drag_drop_state = RD_DragDropState_Dragging;
}
}
internal B32
rd_drag_drop(void)
{
B32 result = 0;
if(rd_state->drag_drop_state == RD_DragDropState_Dropping)
{
result = 1;
rd_state->drag_drop_state = RD_DragDropState_Null;
}
return result;
}
internal void
rd_drag_kill(void)
{
rd_state->drag_drop_state = RD_DragDropState_Null;
}
internal void
rd_set_hover_regs(RD_RegSlot slot)
{
rd_state->next_hover_regs = rd_regs_copy(rd_frame_arena(), rd_regs());
rd_state->next_hover_regs_slot = slot;
}
internal RD_Regs *
rd_get_hover_regs(void)
{
return rd_state->hover_regs;
}
////////////////////////////////
//~ rjf: Name Allocation
internal U64
rd_name_bucket_num_from_string_size(U64 size)
{
U64 bucket_num = 0;
if(size > 0)
{
for EachElement(idx, rd_name_bucket_chunk_sizes)
{
if(size <= rd_name_bucket_chunk_sizes[idx])
{
bucket_num = idx+1;
break;
}
}
}
return bucket_num;
}
internal String8
rd_name_alloc(String8 string)
{
//- rjf: allocate node
RD_NameChunkNode *node = 0;
{
U64 bucket_num = rd_name_bucket_num_from_string_size(string.size);
if(bucket_num == ArrayCount(rd_name_bucket_chunk_sizes))
{
RD_NameChunkNode *best_node = 0;
RD_NameChunkNode *best_node_prev = 0;
U64 best_node_size = max_U64;
{
for(RD_NameChunkNode *n = rd_state->free_name_chunks[bucket_num-1], *prev = 0; n != 0; (prev = n, n = n->next))
{
if(n->size >= string.size && n->size < best_node_size)
{
best_node = n;
best_node_prev = prev;
best_node_size = n->size;
}
}
}
if(best_node != 0)
{
node = best_node;
if(best_node_prev)
{
best_node_prev->next = best_node->next;
}
else
{
rd_state->free_name_chunks[bucket_num-1] = best_node->next;
}
}
else
{
U64 chunk_size = u64_up_to_pow2(string.size);
node = (RD_NameChunkNode *)push_array(rd_state->arena, U8, chunk_size);
}
}
else if(bucket_num != 0)
{
node = rd_state->free_name_chunks[bucket_num-1];
if(node != 0)
{
SLLStackPop(rd_state->free_name_chunks[bucket_num-1]);
}
else
{
node = (RD_NameChunkNode *)push_array(rd_state->arena, U8, rd_name_bucket_chunk_sizes[bucket_num-1]);
}
}
}
//- rjf: fill node
String8 result = {0};
if(node != 0)
{
result.str = (U8 *)node;
result.size = string.size;
MemoryCopy(result.str, string.str, result.size);
}
return result;
}
internal void
rd_name_release(String8 string)
{
U64 bucket_num = rd_name_bucket_num_from_string_size(string.size);
if(1 <= bucket_num && bucket_num <= ArrayCount(rd_name_bucket_chunk_sizes))
{
U64 bucket_idx = bucket_num-1;
RD_NameChunkNode *node = (RD_NameChunkNode *)string.str;
SLLStackPush(rd_state->free_name_chunks[bucket_idx], node);
node->size = u64_up_to_pow2(string.size);
}
}
////////////////////////////////
//~ rjf: Config Tree Functions
internal RD_Cfg *
rd_cfg_alloc(void)
{
rd_state->cfg_change_gen += 1;
// rjf: allocate
RD_Cfg *result = rd_state->free_cfg;
{
if(result)
{
SLLStackPop(rd_state->free_cfg);
}
else
{
result = push_array_no_zero(rd_state->arena, RD_Cfg, 1);
}
}
// rjf: generate ID & fill
rd_state->cfg_id_gen += 1;
MemoryZeroStruct(result);
result->first = result->last = result->next = result->prev = result->parent = &rd_nil_cfg;
result->id = rd_state->cfg_id_gen;
// rjf: store to ID -> cfg map
{
RD_CfgNode *cfg_id_node = rd_state->free_cfg_id_node;
if(cfg_id_node != 0)
{
SLLStackPop(rd_state->free_cfg_id_node);
}
else
{
cfg_id_node = push_array(rd_state->arena, RD_CfgNode, 1);
}
U64 hash = d_hash_from_string(str8_struct(&result->id));
U64 slot_idx = hash%rd_state->cfg_id_slots_count;
DLLPushBack(rd_state->cfg_id_slots[slot_idx].first, rd_state->cfg_id_slots[slot_idx].last, cfg_id_node);
cfg_id_node->v = result;
}
return result;
}
internal void
rd_cfg_release(RD_Cfg *cfg)
{
rd_state->cfg_change_gen += 1;
Temp scratch = scratch_begin(0, 0);
// rjf: unhook from context
rd_cfg_unhook(cfg->parent, cfg);
// rjf: gather root & all descendants
RD_CfgList nodes = {0};
for(RD_Cfg *c = cfg; c != &rd_nil_cfg; c = rd_cfg_rec__depth_first(cfg, c).next)
{
rd_cfg_list_push(scratch.arena, &nodes, c);
}
// rjf: release all nodes
for(RD_CfgNode *n = nodes.first; n != 0; n = n->next)
{
RD_Cfg *c = n->v;
rd_name_release(c->string);
SLLStackPush(rd_state->free_cfg, c);
c->first = c->last = c->prev = c->parent = 0;
c->id = 0;
c->string = str8_zero();
U64 hash = d_hash_from_string(str8_struct(&c->id));
U64 slot_idx = hash%rd_state->cfg_id_slots_count;
for(RD_CfgNode *n = rd_state->cfg_id_slots[slot_idx].first; n != 0; n = n->next)
{
if(n->v == c)
{
DLLRemove(rd_state->cfg_id_slots[slot_idx].first, rd_state->cfg_id_slots[slot_idx].last, n);
SLLStackPush(rd_state->free_cfg_id_node, n);
break;
}
}
}
scratch_end(scratch);
}
internal void
rd_cfg_release_all_children(RD_Cfg *cfg)
{
for(RD_Cfg *child = cfg->first, *next = &rd_nil_cfg; child != &rd_nil_cfg; child = next)
{
next = child->next;
rd_cfg_release(child);
}
}
internal RD_Cfg *
rd_cfg_from_id(RD_CfgID id)
{
RD_Cfg *result = &rd_nil_cfg;
if(id != 0 &&
id == rd_state->cfg_last_accessed_id &&
id == rd_state->cfg_last_accessed->id)
{
result = rd_state->cfg_last_accessed;
}
else
{
U64 hash = d_hash_from_string(str8_struct(&id));
U64 slot_idx = hash%rd_state->cfg_id_slots_count;
for(RD_CfgNode *n = rd_state->cfg_id_slots[slot_idx].first; n != 0; n = n->next)
{
if(n->v->id == id)
{
result = n->v;
break;
}
}
}
rd_state->cfg_last_accessed_id = id;
rd_state->cfg_last_accessed = result;
return result;
}
internal RD_Cfg *
rd_cfg_new(RD_Cfg *parent, String8 string)
{
RD_Cfg *cfg = rd_cfg_alloc();
rd_cfg_insert_child(parent, parent->last, cfg);
rd_cfg_equip_string(cfg, string);
return cfg;
}
internal RD_Cfg *
rd_cfg_newf(RD_Cfg *parent, char *fmt, ...)
{
Temp scratch = scratch_begin(0, 0);
va_list args;
va_start(args, fmt);
String8 string = push_str8fv(scratch.arena, fmt, args);
RD_Cfg *result = rd_cfg_new(parent, string);
va_end(args);
scratch_end(scratch);
return result;
}
internal RD_Cfg *
rd_cfg_new_replace(RD_Cfg *parent, String8 string)
{
Temp scratch = scratch_begin(0, 0);
string = push_str8_copy(scratch.arena, string);
for(RD_Cfg *child = parent->first->next, *next = &rd_nil_cfg; child != &rd_nil_cfg; child = next)
{
next = child->next;
rd_cfg_release(child);
}
if(parent->first == &rd_nil_cfg)
{
rd_cfg_new(parent, str8_zero());
}
RD_Cfg *child = parent->first;
rd_cfg_equip_string(child, string);
scratch_end(scratch);
return child;
}
internal RD_Cfg *
rd_cfg_new_replacef(RD_Cfg *parent, char *fmt, ...)
{
Temp scratch = scratch_begin(0, 0);
va_list args;
va_start(args, fmt);
String8 string = push_str8fv(scratch.arena, fmt, args);
RD_Cfg *result = rd_cfg_new_replace(parent, string);
va_end(args);
scratch_end(scratch);
return result;
}
internal RD_Cfg *
rd_cfg_deep_copy(RD_Cfg *src_root)
{
RD_CfgRec rec = {0};
RD_Cfg *dst_root = &rd_nil_cfg;
RD_Cfg *dst_parent = &rd_nil_cfg;
for(RD_Cfg *src = src_root; src != &rd_nil_cfg; src = rec.next)
{
RD_Cfg *dst = rd_cfg_new(dst_parent, src->string);
if(dst_root == &rd_nil_cfg)
{
dst_root = dst;
}
rec = rd_cfg_rec__depth_first(src_root, src);
if(rec.push_count > 0)
{
dst_parent = dst;
}
else for(S32 pop_idx = 0; pop_idx < rec.pop_count; pop_idx += 1)
{
dst_parent = dst_parent->parent;
}
}
return dst_root;
}
internal void
rd_cfg_equip_string(RD_Cfg *cfg, String8 string)
{
rd_name_release(cfg->string);
cfg->string = rd_name_alloc(string);
}
internal void
rd_cfg_equip_stringf(RD_Cfg *cfg, char *fmt, ...)
{
Temp scratch = scratch_begin(0, 0);
va_list args;
va_start(args, fmt);
String8 string = push_str8fv(scratch.arena, fmt, args);
rd_cfg_equip_string(cfg, string);
va_end(args);
scratch_end(scratch);
}
internal void
rd_cfg_insert_child(RD_Cfg *parent, RD_Cfg *prev_child, RD_Cfg *new_child)
{
if(parent != &rd_nil_cfg)
{
if(new_child->parent != &rd_nil_cfg)
{
rd_cfg_unhook(new_child->parent, new_child);
}
DLLInsert_NPZ(&rd_nil_cfg, parent->first, parent->last, prev_child, new_child, next, prev);
new_child->parent = parent;
}
}
internal void
rd_cfg_unhook(RD_Cfg *parent, RD_Cfg *child)
{
if(child != &rd_nil_cfg && parent == child->parent && parent != &rd_nil_cfg)
{
DLLRemove_NPZ(&rd_nil_cfg, parent->first, parent->last, child, next, prev);
child->parent = &rd_nil_cfg;
}
}
internal RD_Cfg *
rd_cfg_child_from_string(RD_Cfg *parent, String8 string)
{
RD_Cfg *child = &rd_nil_cfg;
if(string.size != 0)
{
for(RD_Cfg *c = parent->first; c != &rd_nil_cfg; c = c->next)
{
if(str8_match(c->string, string, 0))
{
child = c;
break;
}
}
}
return child;
}
internal RD_Cfg *
rd_cfg_child_from_string_or_alloc(RD_Cfg *parent, String8 string)
{
RD_Cfg *child = rd_cfg_child_from_string(parent, string);
if(child == &rd_nil_cfg)
{
child = rd_cfg_new(parent, string);
}
return child;
}
internal RD_Cfg *
rd_cfg_child_from_string_or_parent(RD_Cfg *parent, String8 string)
{
RD_Cfg *result = rd_cfg_child_from_string(parent, string);
if(result == &rd_nil_cfg)
{
result = parent;
}
return result;
}
internal RD_CfgList
rd_cfg_child_list_from_string(Arena *arena, RD_Cfg *parent, String8 string)
{
RD_CfgList result = {0};
for(RD_Cfg *child = parent->first; child != &rd_nil_cfg; child = child->next)
{
if(str8_match(child->string, string, 0))
{
rd_cfg_list_push(arena, &result, child);
}
}
return result;
}
internal RD_CfgList
rd_cfg_top_level_list_from_string(Arena *arena, String8 string)
{
RD_CfgList result = {0};
for(RD_Cfg *bucket = rd_state->root_cfg->first; bucket != &rd_nil_cfg; bucket = bucket->next)
{
for(RD_Cfg *tln = bucket->first; tln != &rd_nil_cfg; tln = tln->next)
{
if(str8_match(tln->string, string, 0))
{
rd_cfg_list_push(arena, &result, tln);
}
}
}
return result;
}
internal RD_CfgArray
rd_cfg_array_from_list(Arena *arena, RD_CfgList *list)
{
RD_CfgArray array = {0};
array.count = list->count;
array.v = push_array_no_zero(arena, RD_Cfg *, array.count);
U64 idx = 0;
for(RD_CfgNode *n = list->first; n != 0; n = n->next, idx += 1)
{
array.v[idx] = n->v;
}
return array;
}
internal RD_CfgList
rd_cfg_tree_list_from_string(Arena *arena, String8 root_path, String8 string)
{
RD_CfgList result = {0};
Temp scratch = scratch_begin(&arena, 1);
//- rjf: parse the string as metadesk
MD_Node *root = md_tree_from_string(scratch.arena, string);
//- rjf: iterate the top-level metadesk trees, generate new cfg trees for each
for MD_EachNode(tln, root->first)
{
RD_Cfg *dst_root_n = &rd_nil_cfg;
RD_Cfg *dst_active_parent_n = &rd_nil_cfg;
MD_NodeRec rec = {0};
for(MD_Node *src_n = tln; !md_node_is_nil(src_n); src_n = rec.next)
{
// rjf: lookup schema for this string
MD_Node *schema = &md_nil_node;
{
MD_NodePtrList schemas = rd_schemas_from_name(dst_active_parent_n->parent->string);
for(MD_NodePtrNode *n = schemas.first; n != 0 && schema == &md_nil_node; n = n->next)
{
schema = md_child_from_string(n->v, dst_active_parent_n->string, 0);
}
}
// rjf: extract & transform metadesk node's string (it is raw textual data, so we need to
// go escaped -> raw, and derelativize paths)
String8 dst_n_string = {0};
{
String8 src_n_string = src_n->string;
String8 src_n_string__raw = raw_from_escaped_str8(scratch.arena, src_n_string);
if(str8_match(schema->first->string, str8_lit("path"), 0) &&
!md_node_has_tag(schema->first, str8_lit("absolute"), 0))
{
src_n_string__raw = path_absolute_dst_from_relative_dst_src(scratch.arena, src_n_string__raw, root_path);
}
dst_n_string = src_n_string__raw;
}
// rjf: allocate, fill, & insert new cfg for this metadesk node
RD_Cfg *dst_n = rd_cfg_alloc();
rd_cfg_equip_string(dst_n, dst_n_string);
if(dst_active_parent_n != &rd_nil_cfg)
{
rd_cfg_insert_child(dst_active_parent_n, dst_active_parent_n->last, dst_n);
}
// rjf: recurse
rec = md_node_rec_depth_first_pre(src_n, tln);
if(dst_active_parent_n == &rd_nil_cfg)
{
dst_root_n = dst_n;
}
if(rec.push_count > 0)
{
dst_active_parent_n = dst_n;
}
else for(S32 pop_idx = 0; pop_idx < rec.pop_count; pop_idx += 1)
{
dst_active_parent_n = dst_active_parent_n->parent;
}
}
rd_cfg_list_push(arena, &result, dst_root_n);
}
scratch_end(scratch);
return result;
}
internal String8
rd_string_from_cfg_tree(Arena *arena, String8 root_path, RD_Cfg *cfg)
{
Temp scratch = scratch_begin(&arena, 1);
String8List strings = {0};
{
typedef struct NestTask NestTask;
struct NestTask
{
NestTask *next;
RD_Cfg *cfg;
MD_Node *schema;
B32 is_simple;
};
NestTask *top_nest_task = 0;
RD_CfgRec rec = {0};
for(RD_Cfg *c = cfg; c != &rd_nil_cfg; c = rec.next)
{
// rjf: look up parent's schemas
MD_NodePtrList schemas = {0};
if(top_nest_task != 0)
{
RD_Cfg *parent = top_nest_task->cfg;
schemas = rd_schemas_from_name(parent->string);
}
// rjf: look up child schema
MD_Node *c_schema = &md_nil_node;
for(MD_NodePtrNode *n = schemas.first; n != 0 && c_schema == &md_nil_node; n = n->next)
{
c_schema = md_child_from_string(n->v, c->string, 0);
}
// rjf: push name of this node
if(c->string.size != 0 || c->first == &rd_nil_cfg)
{
// rjf: extract the textualized form for this string (we may need to escape / relativize)
String8 c_serialized_string = c->string;
{
MD_Node *c_schema = &md_nil_node;
if(top_nest_task != 0)
{
c_schema = top_nest_task->schema;
}
// rjf: paths -> relativize
if(str8_match(c_schema->first->string, str8_lit("path"), 0) &&
!md_node_has_tag(c_schema->first, str8_lit("absolute"), 0))
{
String8 path_absolute = c->string;
String8 path_relative = path_relative_dst_from_absolute_dst_src(arena, path_absolute, root_path);
c_serialized_string = path_relative;
}
// rjf: all strings -> escape
c_serialized_string = escaped_from_raw_str8(arena, c_serialized_string);
}
// rjf: generate all strings for this node's string
String8List c_name_strings = {0};
{
B32 name_can_be_pushed_standalone = 0;
{
Temp temp = temp_begin(scratch.arena);
MD_TokenizeResult c_name_tokenize = md_tokenize_from_text(temp.arena, c_serialized_string);
name_can_be_pushed_standalone = (c_name_tokenize.tokens.count == 1 && c_name_tokenize.tokens.v[0].flags & (MD_TokenFlag_Identifier|
MD_TokenFlag_Numeric|
MD_TokenFlag_StringLiteral|
MD_TokenFlag_Symbol));
temp_end(temp);
}
if(name_can_be_pushed_standalone)
{
str8_list_push(scratch.arena, &c_name_strings, c_serialized_string);
}
else
{
str8_list_push(scratch.arena, &c_name_strings, str8_lit("\""));
str8_list_push(scratch.arena, &c_name_strings, c_serialized_string);
str8_list_push(scratch.arena, &c_name_strings, str8_lit("\""));
}
}
// rjf: if we're in a simple nesting task, then just break children by space
if(top_nest_task != 0 && top_nest_task->is_simple)
{
str8_list_push(scratch.arena, &strings, str8_lit(" "));
}
// rjf: join c's strings with main string list
str8_list_concat_in_place(&strings, &c_name_strings);
}
// rjf: grab next recursion
rec = rd_cfg_rec__depth_first(cfg, c);
// rjf: push a new nesting task before descending to children
if(c->first != &rd_nil_cfg)
{
B32 is_simple_children_list = 1;
for(RD_Cfg *child = c->first; child != &rd_nil_cfg; child = child->next)
{
if(child->first != &rd_nil_cfg && child != c->last)
{
is_simple_children_list = 0;
break;
}
}
NestTask *task = push_array(scratch.arena, NestTask, 1);
task->cfg = c;
task->schema = c_schema;
task->is_simple = is_simple_children_list;
SLLStackPush(top_nest_task, task);
}
// rjf: tree navigations -> encode hierarchy
if(rec.push_count > 0)
{
if(top_nest_task->is_simple && c->string.size != 0)
{
str8_list_push(scratch.arena, &strings, str8_lit(":"));
}
else
{
if(c->string.size != 0)
{
str8_list_push(scratch.arena, &strings, str8_lit(":\n"));
}
str8_list_push(scratch.arena, &strings, str8_lit("{"));
}
}
else
{
for(S32 pop_idx = 0; pop_idx < rec.pop_count; pop_idx += 1, SLLStackPop(top_nest_task))
{
if(top_nest_task->is_simple)
{
if(top_nest_task->cfg->string.size == 0)
{
str8_list_push(scratch.arena, &strings, str8_lit(" }"));
}
}
else
{
str8_list_push(scratch.arena, &strings, str8_lit("\n}"));
}
}
}
if(!top_nest_task || top_nest_task->is_simple == 0)
{
str8_list_push(scratch.arena, &strings, str8_lit("\n"));
}
}
}
String8 result_unindented = str8_list_join(scratch.arena, &strings, 0);
String8 result = indented_from_string(arena, result_unindented);
scratch_end(scratch);
return result;
}
internal RD_CfgRec
rd_cfg_rec__depth_first(RD_Cfg *root, RD_Cfg *cfg)
{
RD_CfgRec rec = {&rd_nil_cfg};
if(cfg->first != &rd_nil_cfg)
{
rec.next = cfg->first;
rec.push_count = 1;
}
else for(RD_Cfg *p = cfg; p != root; p = p->parent, rec.pop_count += 1)
{
if(p->next != &rd_nil_cfg)
{
rec.next = p->next;
break;
}
}
return rec;
}
internal void
rd_cfg_list_push(Arena *arena, RD_CfgList *list, RD_Cfg *cfg)
{
RD_CfgNode *n = push_array(arena, RD_CfgNode, 1);
n->v = cfg;
DLLPushBack(list->first, list->last, n);
list->count += 1;
}
internal void
rd_cfg_list_push_front(Arena *arena, RD_CfgList *list, RD_Cfg *cfg)
{
RD_CfgNode *n = push_array(arena, RD_CfgNode, 1);
n->v = cfg;
if(list->first != 0)
{
n->next = list->first;
}
else
{
list->last = n;
}
list->first = n;
list->count += 1;
}
internal RD_PanelTree
rd_panel_tree_from_cfg(Arena *arena, RD_Cfg *cfg)
{
Temp scratch = scratch_begin(&arena, 1);
RD_Cfg *wcfg = rd_window_from_cfg(cfg);
RD_Cfg *src_root = rd_cfg_child_from_string(wcfg, str8_lit("panels"));
RD_PanelNode *dst_root = &rd_nil_panel_node;
RD_PanelNode *dst_focused = &rd_nil_panel_node;
{
Axis2 active_split_axis = rd_cfg_child_from_string(wcfg, str8_lit("split_x")) != &rd_nil_cfg ? Axis2_X : Axis2_Y;
RD_CfgRec rec = {0};
RD_PanelNode *dst_active_parent = &rd_nil_panel_node;
for(RD_Cfg *src = src_root; src != &rd_nil_cfg; src = rec.next)
{
// rjf: build a panel node
RD_PanelNode *dst = push_array(arena, RD_PanelNode, 1);
MemoryCopyStruct(dst, &rd_nil_panel_node);
dst->parent = dst_active_parent;
if(dst_active_parent != &rd_nil_panel_node)
{
DLLPushBack_NPZ(&rd_nil_panel_node, dst_active_parent->first, dst_active_parent->last, dst, next, prev);
dst_active_parent->child_count += 1;
}
if(dst_root == &rd_nil_panel_node)
{
dst_root = dst;
}
// rjf: extract cfg info
B32 panel_has_children = 0;
dst->cfg = src;
dst->pct_of_parent = (src == src_root ? 1.f : (F32)f64_from_str8(src->string));
dst->tab_side = (rd_cfg_child_from_string(src, str8_lit("tabs_on_bottom")) != &rd_nil_cfg ? Side_Max : Side_Min);
dst->split_axis = active_split_axis;
for(RD_Cfg *src_child = src->first; src_child != &rd_nil_cfg; src_child = src_child->next)
{
MD_TokenizeResult tokenize = md_tokenize_from_text(scratch.arena, src_child->string);
if(tokenize.tokens.count == 1 && tokenize.tokens.v[0].flags & MD_TokenFlag_Numeric)
{
panel_has_children = 1;
}
else if(str8_match(src_child->string, str8_lit("tabs_on_bottom"), 0))
{
// NOTE(rjf): skip - this is a panel option.
}
else if(str8_match(src_child->string, str8_lit("selected"), 0))
{
dst_focused = dst;
}
else if(tokenize.tokens.count == 1 && tokenize.tokens.v[0].flags & MD_TokenFlag_Identifier)
{
rd_cfg_list_push(arena, &dst->tabs, src_child);
if(rd_cfg_child_from_string(src_child, str8_lit("selected")) != &rd_nil_cfg)
{
dst->selected_tab = src_child;
}
}
}
// rjf: recurse
rec = rd_cfg_rec__depth_first(src_root, src);
if(!panel_has_children)
{
MemoryZeroStruct(&rec);
rec.next = &rd_nil_cfg;
for(RD_Cfg *p = src; p != src_root && p != &rd_nil_cfg; p = p->parent, rec.pop_count += 1)
{
if(p->next != &rd_nil_cfg)
{
rec.next = p->next;
break;
}
}
}
if(rec.push_count > 0)
{
dst_active_parent = dst;
active_split_axis = axis2_flip(active_split_axis);
}
else for(S32 pop_idx = 0; pop_idx < rec.pop_count; pop_idx += 1)
{
dst_active_parent = dst_active_parent->parent;
active_split_axis = axis2_flip(active_split_axis);
}
}
}
scratch_end(scratch);
RD_PanelTree tree = {dst_root, dst_focused};
return tree;
}
internal RD_PanelNodeRec
rd_panel_node_rec__depth_first(RD_PanelNode *root, RD_PanelNode *panel, U64 sib_off, U64 child_off)
{
RD_PanelNodeRec rec = {&rd_nil_panel_node};
if(*MemberFromOffset(RD_PanelNode **, panel, child_off) != &rd_nil_panel_node)
{
rec.next = *MemberFromOffset(RD_PanelNode **, panel, child_off);
rec.push_count += 1;
}
else for(RD_PanelNode *p = panel; p != &rd_nil_panel_node && p != root; p = p->parent, rec.pop_count += 1)
{
if(*MemberFromOffset(RD_PanelNode **, p, sib_off) != &rd_nil_panel_node)
{
rec.next = *MemberFromOffset(RD_PanelNode **, p, sib_off);
break;
}
}
return rec;
}
internal RD_PanelNode *
rd_panel_node_from_tree_cfg(RD_PanelNode *root, RD_Cfg *cfg)
{
RD_PanelNode *result = &rd_nil_panel_node;
for(RD_PanelNode *p = root;
p != &rd_nil_panel_node;
p = rd_panel_node_rec__depth_first_pre(root, p).next)
{
if(p->cfg == cfg)
{
result = p;
break;
}
}
return result;
}
internal Rng2F32
rd_target_rect_from_panel_node_child(Rng2F32 parent_rect, RD_PanelNode *parent, RD_PanelNode *panel)
{
Rng2F32 rect = parent_rect;
if(parent != &rd_nil_panel_node)
{
Vec2F32 parent_rect_size = dim_2f32(parent_rect);
Axis2 axis = parent->split_axis;
rect.p1.v[axis] = rect.p0.v[axis];
for(RD_PanelNode *child = parent->first; child != &rd_nil_panel_node; child = child->next)
{
rect.p1.v[axis] += parent_rect_size.v[axis] * child->pct_of_parent;
if(child == panel)
{
break;
}
rect.p0.v[axis] = rect.p1.v[axis];
}
//rect.p0.v[axis] += parent_rect_size.v[axis] * panel->off_pct_of_parent.v[axis];
//rect.p0.v[axis2_flip(axis)] += parent_rect_size.v[axis2_flip(axis)] * panel->off_pct_of_parent.v[axis2_flip(axis)];
}
rect.x0 = round_f32(rect.x0);
rect.x1 = round_f32(rect.x1);
rect.y0 = round_f32(rect.y0);
rect.y1 = round_f32(rect.y1);
return rect;
}
internal Rng2F32
rd_target_rect_from_panel_node(Rng2F32 root_rect, RD_PanelNode *root, RD_PanelNode *panel)
{
Temp scratch = scratch_begin(0, 0);
// rjf: count ancestors
U64 ancestor_count = 0;
for(RD_PanelNode *p = panel->parent; p != &rd_nil_panel_node; p = p->parent)
{
ancestor_count += 1;
}
// rjf: gather ancestors
RD_PanelNode **ancestors = push_array(scratch.arena, RD_PanelNode *, ancestor_count);
{
U64 ancestor_idx = 0;
for(RD_PanelNode *p = panel->parent; p != &rd_nil_panel_node; p = p->parent)
{
ancestors[ancestor_idx] = p;
ancestor_idx += 1;
}
}
// rjf: go from highest ancestor => panel and calculate rect
Rng2F32 parent_rect = root_rect;
for(S64 ancestor_idx = (S64)ancestor_count-1;
0 <= ancestor_idx && ancestor_idx < ancestor_count;
ancestor_idx -= 1)
{
RD_PanelNode *ancestor = ancestors[ancestor_idx];
RD_PanelNode *parent = ancestor->parent;
if(parent != &rd_nil_panel_node)
{
parent_rect = rd_target_rect_from_panel_node_child(parent_rect, parent, ancestor);
}
}
// rjf: calculate final rect
Rng2F32 rect = rd_target_rect_from_panel_node_child(parent_rect, panel->parent, panel);
scratch_end(scratch);
return rect;
}
internal B32
rd_cfg_is_project_filtered(RD_Cfg *cfg)
{
RD_Cfg *project = rd_cfg_child_from_string(cfg, str8_lit("project"));
B32 result = (project != &rd_nil_cfg && !path_match_normalized(rd_state->project_path, project->first->string));
return result;
}
internal RD_KeyMapNodePtrList
rd_key_map_node_ptr_list_from_name(Arena *arena, String8 string)
{
RD_KeyMapNodePtrList list = {0};
{
U64 hash = d_hash_from_string(string);
U64 slot_idx = hash%rd_state->key_map->name_slots_count;
for(RD_KeyMapNode *n = rd_state->key_map->name_slots[slot_idx].first; n != 0; n = n->name_hash_next)
{
if(str8_match(n->name, string, 0))
{
RD_KeyMapNodePtr *ptr = push_array(arena, RD_KeyMapNodePtr, 1);
ptr->v = n;
SLLQueuePush(list.first, list.last, ptr);
list.count += 1;
}
}
}
return list;
}
internal RD_KeyMapNodePtrList
rd_key_map_node_ptr_list_from_binding(Arena *arena, RD_Binding binding)
{
RD_KeyMapNodePtrList list = {0};
{
U64 hash = d_hash_from_string(str8_struct(&binding));
U64 slot_idx = hash%rd_state->key_map->binding_slots_count;
for(RD_KeyMapNode *n = rd_state->key_map->binding_slots[slot_idx].first; n != 0; n = n->binding_hash_next)
{
if(MemoryMatchStruct(&binding, &n->binding))
{
RD_KeyMapNodePtr *ptr = push_array(arena, RD_KeyMapNodePtr, 1);
ptr->v = n;
SLLQueuePush(list.first, list.last, ptr);
list.count += 1;
}
}
}
return list;
}
internal Vec4F32
rd_hsva_from_cfg(RD_Cfg *cfg)
{
Vec4F32 hsva = {0};
RD_Cfg *hsva_root = rd_cfg_child_from_string(cfg, str8_lit("hsva"));
RD_Cfg *h = hsva_root->first;
RD_Cfg *s = h->next;
RD_Cfg *v = s->next;
RD_Cfg *a = v->next;
hsva.x = (F32)f64_from_str8(h->string);
hsva.y = (F32)f64_from_str8(s->string);
hsva.z = (F32)f64_from_str8(v->string);
hsva.w = (F32)f64_from_str8(a->string);
return hsva;
}
internal Vec4F32
rd_color_from_cfg(RD_Cfg *cfg)
{
Vec4F32 hsva = rd_hsva_from_cfg(cfg);
Vec4F32 rgba = linear_from_srgba(rgba_from_hsva(hsva));
return rgba;
}
internal B32
rd_disabled_from_cfg(RD_Cfg *cfg)
{
MD_Node *child_schema = &md_nil_node;
MD_NodePtrList schemas = rd_schemas_from_name(cfg->string);
for(MD_NodePtrNode *n = schemas.first; n != 0 && child_schema == &md_nil_node; n = n->next)
{
child_schema = md_child_from_string(n->v, str8_lit("enabled"), 0);
}
MD_Node *default_tag = md_tag_from_string(child_schema, str8_lit("default"), 0);
String8 value_string = rd_cfg_child_from_string(cfg, str8_lit("enabled"))->first->string;
if(value_string.size == 0)
{
value_string = default_tag->first->string;
}
B32 is_enabled = !!e_value_from_string(value_string).u64;
B32 is_disabled = !is_enabled;
if(value_string.size == 0)
{
is_disabled = 0;
}
return is_disabled;
}
internal RD_Location
rd_location_from_cfg(RD_Cfg *cfg)
{
RD_Location dst_loc = {0};
{
RD_Cfg *src_loc = rd_cfg_child_from_string(cfg, str8_lit("source_location"));
RD_Cfg *addr_loc = rd_cfg_child_from_string(cfg, str8_lit("address_location"));
if(src_loc != &rd_nil_cfg)
{
String8TxtPtPair loc_description = str8_txt_pt_pair_from_string(src_loc->first->string);
dst_loc.file_path = loc_description.string;
dst_loc.pt = loc_description.pt;
}
else if(addr_loc != &rd_nil_cfg)
{
dst_loc.expr = addr_loc->first->string;
}
}
return dst_loc;
}
internal String8
rd_label_from_cfg(RD_Cfg *cfg)
{
RD_Cfg *label_root = rd_cfg_child_from_string(cfg, str8_lit("label"));
String8 result = label_root->first->string;
return result;
}
internal String8
rd_expr_from_cfg(RD_Cfg *cfg)
{
RD_Cfg *expr_root = rd_cfg_child_from_string(cfg, str8_lit("expression"));
String8 result = expr_root->first->string;
return result;
}
internal String8
rd_path_from_cfg(RD_Cfg *cfg)
{
RD_Cfg *root = rd_cfg_child_from_string(cfg, str8_lit("path"));
String8 result = root->first->string;
return result;
}
internal D_Target
rd_target_from_cfg(Arena *arena, RD_Cfg *cfg)
{
D_Target target = {0};
target.exe = rd_cfg_child_from_string(cfg, str8_lit("executable"))->first->string;
target.args = rd_cfg_child_from_string(cfg, str8_lit("arguments"))->first->string;
target.working_directory = rd_cfg_child_from_string(cfg, str8_lit("working_directory"))->first->string;
target.custom_entry_point_name = rd_cfg_child_from_string(cfg, str8_lit("entry_point"))->first->string;
target.stdout_path = rd_cfg_child_from_string(cfg, str8_lit("stdout_path"))->first->string;
target.stderr_path = rd_cfg_child_from_string(cfg, str8_lit("stderr_path"))->first->string;
target.stdin_path = rd_cfg_child_from_string(cfg, str8_lit("stdin_path"))->first->string;
target.debug_subprocesses = (rd_cfg_child_from_string(cfg, str8_lit("debug_subprocesses")) != &rd_nil_cfg);
for(RD_Cfg *child = cfg->first; child != &rd_nil_cfg; child = child->next)
{
if(str8_match(child->string, str8_lit("environment"), 0))
{
str8_list_push(arena, &target.env, child->first->string);
}
}
return target;
}
internal MD_NodePtrList
rd_schemas_from_name(String8 name)
{
MD_NodePtrList schemas = {0};
for EachElement(idx, rd_name_schema_info_table)
{
if(str8_match(name, rd_name_schema_info_table[idx].name, 0))
{
schemas = rd_state->schemas[idx];
break;
}
}
return schemas;
}
internal String8
rd_default_setting_from_names(String8 schema_name, String8 setting_name)
{
String8 result = {0};
{
MD_Node *setting_schema = &md_nil_node;
MD_NodePtrList schemas = rd_schemas_from_name(schema_name);
for(MD_NodePtrNode *n = schemas.first; n != 0 && setting_schema == &md_nil_node; n = n->next)
{
setting_schema = md_child_from_string(n->v, setting_name, 0);
}
if(setting_schema != &md_nil_node)
{
MD_Node *default_tag = md_tag_from_string(setting_schema, str8_lit("default"), 0);
if(default_tag != &md_nil_node)
{
result = default_tag->first->string;
}
}
}
return result;
}
internal String8
rd_setting_from_name(String8 name)
{
String8 result = {0};
if(name.size != 0)
{
Temp scratch = scratch_begin(0, 0);
// rjf: find most-granular config scopes to begin looking for the setting
typedef struct CfgSeedTask CfgSeedTask;
struct CfgSeedTask
{
CfgSeedTask *next;
RD_Cfg *cfg;
B32 allow_bucket_chains;
};
RD_Cfg *view_cfg = rd_cfg_from_id(rd_regs()->view);
if(view_cfg == &rd_nil_cfg)
{
view_cfg = rd_cfg_from_id(rd_regs()->tab);
}
CfgSeedTask panel_task = {0, &rd_nil_cfg, 1};
if(panel_task.cfg == &rd_nil_cfg) { panel_task.cfg = rd_cfg_from_id(rd_regs()->panel); }
if(panel_task.cfg == &rd_nil_cfg) { panel_task.cfg = rd_cfg_from_id(rd_regs()->window); }
CfgSeedTask view_task = {&panel_task, view_cfg, 1};
CfgSeedTask *first_task = &view_task;
CfgSeedTask *last_task = &panel_task;
// rjf: for each task, look for the setting, follow parent chain upwards
RD_Cfg *setting = &rd_nil_cfg;
for(CfgSeedTask *t = first_task; t != 0; t = t->next)
{
for(RD_Cfg *cfg = t->cfg; cfg != &rd_nil_cfg; cfg = cfg->parent)
{
setting = rd_cfg_child_from_string(cfg, name);
if(setting != &rd_nil_cfg)
{
goto break_all;
}
if(cfg->parent == rd_state->root_cfg && t->allow_bucket_chains)
{
String8 next_bucket = {0};
B32 allow_bucket_chains = 0;
if(str8_match(cfg->string, str8_lit("user"), 0))
{
next_bucket = str8_lit("project");
}
else if(str8_match(cfg->string, str8_lit("project"), 0))
{
next_bucket = str8_lit("user");
}
else
{
allow_bucket_chains = 1;
next_bucket = str8_lit("user");
}
if(next_bucket.size != 0)
{
CfgSeedTask *task = push_array(scratch.arena, CfgSeedTask, 1);
SLLQueuePush(first_task, last_task, task);
task->cfg = rd_cfg_child_from_string(rd_state->root_cfg, next_bucket);
task->allow_bucket_chains = allow_bucket_chains;
}
}
}
}
break_all:;
// rjf: return resultant child string stored under this key
result = setting->first->string;
// rjf: no result -> look for default in schemas
if(result.size == 0)
{
for(CfgSeedTask *t = first_task; t != 0; t = t->next)
{
for(RD_Cfg *cfg = t->cfg; cfg != &rd_nil_cfg; cfg = cfg->parent)
{
result = rd_default_setting_from_names(cfg->string, name);
if(result.size != 0)
{
goto break_all2;
}
}
}
break_all2:;
}
scratch_end(scratch);
}
return result;
}
internal B32
rd_setting_b32_from_name(String8 name)
{
B32 result = 0;
String8 value = rd_setting_from_name(name);
if(value.size != 0)
{
Temp scratch = scratch_begin(0, 0);
String8 expr = push_str8f(scratch.arena, "(bool)(%S)", value);
E_Eval eval = e_eval_from_string(expr);
result = !!e_value_eval_from_eval(eval).value.u64;
scratch_end(scratch);
}
return result;
}
internal U64
rd_setting_u64_from_name(String8 name)
{
U64 result = 0;
String8 value = rd_setting_from_name(name);
if(value.size != 0)
{
Temp scratch = scratch_begin(0, 0);
String8 expr = push_str8f(scratch.arena, "(uint64)(%S)", value);
E_Eval eval = e_eval_from_string(expr);
result = e_value_eval_from_eval(eval).value.u64;
scratch_end(scratch);
}
return result;
}
internal F32
rd_setting_f32_from_name(String8 name)
{
F32 result = 0.f;
String8 value = rd_setting_from_name(name);
if(value.size != 0)
{
Temp scratch = scratch_begin(0, 0);
String8 expr = push_str8f(scratch.arena, "(float32)(%S)", value);
E_Eval eval = e_eval_from_string(expr);
result = e_value_eval_from_eval(eval).value.f32;
scratch_end(scratch);
}
return result;
}
internal RD_Cfg *
rd_immediate_cfg_from_key(String8 string)
{
RD_Cfg *transient = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("transient"));
RD_Cfg *immediate = &rd_nil_cfg;
RD_Cfg *cfg = &rd_nil_cfg;
for(RD_Cfg *child = transient->first; child != &rd_nil_cfg; child = child->next)
{
if(str8_match(child->string, str8_lit("immediate"), 0))
{
cfg = rd_cfg_child_from_string(child, string);
if(cfg != &rd_nil_cfg)
{
immediate = child;
break;
}
}
}
if(cfg == &rd_nil_cfg)
{
immediate = rd_cfg_new(transient, str8_lit("immediate"));
cfg = rd_cfg_new(immediate, string);
}
rd_cfg_child_from_string_or_alloc(immediate, str8_lit("hot"));
return cfg;
}
internal RD_Cfg *
rd_immediate_cfg_from_keyf(char *fmt, ...)
{
Temp scratch = scratch_begin(0, 0);
va_list args;
va_start(args, fmt);
String8 key = push_str8fv(scratch.arena, fmt, args);
RD_Cfg *result = rd_immediate_cfg_from_key(key);
va_end(args);
scratch_end(scratch);
return result;
}
internal String8
rd_mapped_from_file_path(Arena *arena, String8 file_path)
{
Temp scratch = scratch_begin(&arena, 1);
String8 result = file_path;
if(file_path.size != 0)
{
String8 file_path__normalized = path_normalized_from_string(scratch.arena, file_path);
String8List file_path_parts = str8_split_path(scratch.arena, file_path__normalized);
RD_CfgList maps = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("file_path_map"));
String8 best_map_dst = {0};
U64 best_map_match_length = max_U64;
String8Node *best_map_remaining_suffix_first = 0;
for(RD_CfgNode *n = maps.first; n != 0; n = n->next)
{
String8 map_src = rd_cfg_child_from_string(n->v, str8_lit("source"))->first->string;
String8 map_src__normalized = path_normalized_from_string(scratch.arena, map_src);
String8List map_src_parts = str8_split_path(scratch.arena, map_src__normalized);
B32 matches = 1;
U64 match_length = 0;
String8Node *file_path_part_n = file_path_parts.first;
for(String8Node *map_src_n = map_src_parts.first;
map_src_n != 0 && file_path_part_n != 0;
map_src_n = map_src_n->next, file_path_part_n = file_path_part_n->next)
{
if(!str8_match(map_src_n->string, file_path_part_n->string, 0))
{
matches = 0;
break;
}
match_length += 1;
}
if(matches && match_length < best_map_match_length)
{
best_map_match_length = match_length;
best_map_dst = rd_cfg_child_from_string(n->v, str8_lit("dest"))->first->string;
best_map_remaining_suffix_first = file_path_part_n;
}
}
if(best_map_dst.size != 0)
{
String8 best_map_dst__normalized = path_normalized_from_string(scratch.arena, best_map_dst);
String8List best_map_dst_parts = str8_split_path(scratch.arena, best_map_dst__normalized);
for(String8Node *n = best_map_remaining_suffix_first; n != 0; n = n->next)
{
str8_list_push(scratch.arena, &best_map_dst_parts, n->string);
}
StringJoin join = {.sep = str8_lit("/")};
result = str8_list_join(arena, &best_map_dst_parts, &join);
}
else
{
result = path_normalized_from_string(arena, result);
}
}
scratch_end(scratch);
return result;
}
internal String8List
rd_possible_overrides_from_file_path(Arena *arena, String8 file_path)
{
// NOTE(rjf): This path, given some target file path, scans all file path map
// overrides, and collects the set of file paths which could've redirected
// to the target file path given the set of file path maps.
//
// For example, if I have a rule saying D:/devel/ maps to C:/devel/, and I
// feed in C:/devel/foo/bar.txt, then this path will construct
// D:/devel/foo/bar.txt, as a possible option.
//
// It will also preserve C:/devel/foo/bar.txt in the resultant list, so that
// overrideless files still work through this path, and both redirected
// files and non-redirected files can go through the same path.
//
String8List result = {0};
str8_list_push(arena, &result, file_path);
Temp scratch = scratch_begin(&arena, 1);
PathStyle pth_style = PathStyle_Relative;
String8List pth_parts = path_normalized_list_from_string(scratch.arena, file_path, &pth_style);
{
RD_CfgList links = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("file_path_map"));
for(RD_CfgNode *n = links.first; n != 0; n = n->next)
{
//- rjf: unpack link
RD_Cfg *link = n->v;
RD_Cfg *src = rd_cfg_child_from_string(link, str8_lit("source"));
RD_Cfg *dst = rd_cfg_child_from_string(link, str8_lit("dest"));
PathStyle src_style = PathStyle_Relative;
PathStyle dst_style = PathStyle_Relative;
String8List src_parts = path_normalized_list_from_string(scratch.arena, src->first->string, &src_style);
String8List dst_parts = path_normalized_list_from_string(scratch.arena, dst->first->string, &dst_style);
//- rjf: determine if this link can possibly redirect to the target file path
B32 dst_redirects_to_pth = 0;
String8Node *non_redirected_pth_first = 0;
if(dst_style == pth_style && dst_parts.first != 0 && pth_parts.first != 0)
{
dst_redirects_to_pth = 1;
String8Node *dst_n = dst_parts.first;
String8Node *pth_n = pth_parts.first;
for(;dst_n != 0 && pth_n != 0; dst_n = dst_n->next, pth_n = pth_n->next)
{
if(!str8_match(dst_n->string, pth_n->string, StringMatchFlag_CaseInsensitive))
{
dst_redirects_to_pth = 0;
break;
}
non_redirected_pth_first = pth_n->next;
}
}
//- rjf: if this link can redirect to this path via `src` -> `dst`, compute
// possible full source path, by taking `src` and appending non-redirected
// suffix (which did not show up in `dst`)
if(dst_redirects_to_pth)
{
String8List candidate_parts = src_parts;
for(String8Node *p = non_redirected_pth_first; p != 0; p = p->next)
{
str8_list_push(scratch.arena, &candidate_parts, p->string);
}
StringJoin join = {0};
join.sep = str8_lit("/");
String8 candidate_path = str8_list_join(arena, &candidate_parts, &join);
str8_list_push(arena, &result, candidate_path);
}
}
}
scratch_end(scratch);
return result;
}
////////////////////////////////
//~ rjf: Control Entity Info Extraction
internal Vec4F32
rd_color_from_ctrl_entity(CTRL_Entity *entity)
{
Vec4F32 result = {0};
if(entity->rgba != 0)
{
result = linear_from_srgba(rgba_from_u32(entity->rgba));
}
if(entity->rgba == 0) switch(entity->kind)
{
default:{}break;
case CTRL_EntityKind_Thread:
{
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(entity, CTRL_EntityKind_Process);
CTRL_Entity *main_thread = ctrl_entity_child_from_kind(process, CTRL_EntityKind_Thread);
if(main_thread != entity)
{
result = ui_color_from_name(str8_lit("thread_1"));
}
else
{
result = ui_color_from_name(str8_lit("thread_0"));
}
}break;
}
return result;
}
internal String8
rd_name_from_ctrl_entity(Arena *arena, CTRL_Entity *entity)
{
String8 string = entity->string;
if(string.size == 0)
{
string = str8_lit("unnamed");
}
if(entity->kind == CTRL_EntityKind_Module)
{
string = str8_skip_last_slash(string);
}
return string;
}
////////////////////////////////
//~ rjf: Evaluation Spaces
//- rjf: cfg <-> eval space
internal RD_Cfg *
rd_cfg_from_eval_space(E_Space space)
{
RD_Cfg *cfg = &rd_nil_cfg;
if(space.kind == RD_EvalSpaceKind_MetaCfg)
{
RD_CfgID id = space.u64s[0];
cfg = rd_cfg_from_id(id);
}
return cfg;
}
internal E_Space
rd_eval_space_from_cfg(RD_Cfg *cfg)
{
E_Space space = e_space_make(RD_EvalSpaceKind_MetaCfg);
space.u64s[0] = cfg->id;
return space;
}
//- rjf: ctrl entity <-> eval space
internal CTRL_Entity *
rd_ctrl_entity_from_eval_space(E_Space space)
{
CTRL_Entity *entity = &ctrl_entity_nil;
if(space.kind == RD_EvalSpaceKind_CtrlEntity ||
space.kind == RD_EvalSpaceKind_MetaCtrlEntity ||
space.kind == RD_EvalSpaceKind_MetaUnattachedProcess)
{
CTRL_Handle handle;
handle.machine_id = space.u64s[0];
handle.dmn_handle.u64[0] = space.u64s[1];
entity = ctrl_entity_from_handle(d_state->ctrl_entity_store, handle);
}
return entity;
}
internal E_Space
rd_eval_space_from_ctrl_entity(CTRL_Entity *entity, E_SpaceKind kind)
{
E_Space space = e_space_make(kind);
space.u64s[0] = entity->handle.machine_id;
space.u64s[1] = entity->handle.dmn_handle.u64[0];
return space;
}
//- rjf: command name <-> eval space
internal String8
rd_cmd_name_from_eval(E_Eval eval)
{
String8 result = {0};
if(eval.space.kind == RD_EvalSpaceKind_MetaCmd)
{
result = e_string_from_id(eval.value.u64);
}
return result;
}
//- rjf: eval space reads/writes
internal U64
rd_eval_space_gen(void *u, E_Space space)
{
U64 result = 0;
switch(space.kind)
{
case RD_EvalSpaceKind_MetaCfg:
case RD_EvalSpaceKind_MetaQuery:
{
result = rd_state->cfg_change_gen;
}break;
}
return result;
}
internal B32
rd_eval_space_read(void *u, E_Space space, void *out, Rng1U64 range)
{
Temp scratch = scratch_begin(0, 0);
B32 result = 0;
switch(space.kind)
{
//- rjf: reads from hash store key
case E_SpaceKind_HashStoreKey:
{
U128 key = space.u128;
U128 hash = hs_hash_from_key(key, 0);
HS_Scope *scope = hs_scope_open();
{
String8 data = hs_data_from_hash(scope, hash);
Rng1U64 legal_range = r1u64(0, data.size);
Rng1U64 read_range = intersect_1u64(range, legal_range);
if(read_range.min < read_range.max)
{
result = 1;
MemoryCopy(out, data.str + read_range.min, dim_1u64(read_range));
}
}
hs_scope_close(scope);
}break;
//- rjf: file reads
case E_SpaceKind_File:
{
// rjf: unpack space/path
U64 file_path_string_id = space.u64_0;
String8 file_path = e_string_from_id(file_path_string_id);
// rjf: find containing chunk range
U64 chunk_size = KB(4);
Rng1U64 containing_range = range;
containing_range.min -= containing_range.min%chunk_size;
containing_range.max += chunk_size-1;
containing_range.max -= containing_range.max%chunk_size;
// rjf: map to hash
U128 key = fs_key_from_path_range(file_path, containing_range);
U128 hash = hs_hash_from_key(key, 0);
// rjf: look up from hash store
HS_Scope *scope = hs_scope_open();
{
String8 data = hs_data_from_hash(scope, hash);
Rng1U64 legal_range = r1u64(containing_range.min, containing_range.min + data.size);
Rng1U64 read_range = intersect_1u64(range, legal_range);
if(read_range.min < read_range.max)
{
result = 1;
MemoryCopy(out, data.str + read_range.min - containing_range.min, dim_1u64(read_range));
}
}
hs_scope_close(scope);
}break;
//- rjf: interior control entity reads (inside process address space or thread register block)
case RD_EvalSpaceKind_CtrlEntity:
{
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(space);
switch(entity->kind)
{
default:{}break;
case CTRL_EntityKind_Process:
{
CTRL_ProcessMemorySlice slice = ctrl_query_cached_data_from_process_vaddr_range(scratch.arena, entity->handle, range, rd_state->frame_eval_memread_endt_us);
String8 data = slice.data;
if(data.size == dim_1u64(range))
{
result = 1;
MemoryCopy(out, data.str, data.size);
}
}break;
case CTRL_EntityKind_Thread:
{
CTRL_Unwind unwind = d_query_cached_unwind_from_thread(entity);
U64 frame_idx = e_interpret_ctx->reg_unwind_count;
if(frame_idx < unwind.frames.count)
{
CTRL_UnwindFrame *f = &unwind.frames.v[frame_idx];
U64 regs_size = regs_block_size_from_arch(e_interpret_ctx->reg_arch);
Rng1U64 legal_range = r1u64(0, regs_size);
Rng1U64 read_range = intersect_1u64(legal_range, range);
U64 read_size = dim_1u64(read_range);
MemoryCopy(out, (U8 *)f->regs + read_range.min, read_size);
result = (read_size == dim_1u64(range));
}
}break;
}
}break;
//- rjf: meta-config reads
case RD_EvalSpaceKind_MetaCfg:
{
// rjf: unpack cfg
RD_Cfg *root_cfg = rd_cfg_from_eval_space(space);
String8 child_key = e_string_from_id(space.u64s[1]);
RD_Cfg *cfg = root_cfg;
if(child_key.size != 0)
{
cfg = rd_cfg_child_from_string(root_cfg, child_key);
}
// rjf: determine data to read from, depending on child type in schema
String8 read_data = {0};
if(child_key.size != 0)
{
MD_NodePtrList schemas = rd_schemas_from_name(root_cfg->string);
MD_Node *child_schema = &md_nil_node;
for(MD_NodePtrNode *n = schemas.first; n != 0 && child_schema == &md_nil_node; n = n->next)
{
child_schema = md_child_from_string(n->v, child_key, 0);
}
String8 child_type_name = child_schema->first->string;
if(str8_match(child_type_name, str8_lit("path"), 0) ||
str8_match(child_type_name, str8_lit("code_string"), 0) ||
str8_match(child_type_name, str8_lit("string"), 0))
{
read_data = cfg->first->string;
}
else if(str8_match(child_type_name, str8_lit("bool"), 0))
{
String8 value_string = cfg->first->string;
if(value_string.size == 0)
{
value_string = md_tag_from_string(child_schema, str8_lit("default"), 0)->first->string;
}
String8 value_string_casted = push_str8f(scratch.arena, "(bool)(%S)", value_string);
B32 value = !!e_value_from_string(value_string_casted).u64;
read_data = push_str8_copy(scratch.arena, str8_struct(&value));
}
else if(str8_match(child_type_name, str8_lit("u64"), 0))
{
String8 value_string = cfg->first->string;
if(value_string.size == 0)
{
value_string = md_tag_from_string(child_schema, str8_lit("default"), 0)->first->string;
}
String8 value_string_casted = push_str8f(scratch.arena, "(uint64)(%S)", value_string);
U64 value = e_value_from_string(value_string_casted).u64;
read_data = push_str8_copy(scratch.arena, str8_struct(&value));
}
else if(str8_match(child_type_name, str8_lit("u32"), 0))
{
String8 value_string = cfg->first->string;
if(value_string.size == 0)
{
value_string = md_tag_from_string(child_schema, str8_lit("default"), 0)->first->string;
}
String8 value_string_casted = push_str8f(scratch.arena, "(uint32)(%S)", value_string);
U64 value = e_value_from_string(value_string_casted).u64;
read_data = push_str8_copy(scratch.arena, str8_struct(&value));
}
else if(str8_match(child_type_name, str8_lit("f32"), 0))
{
String8 value_string = cfg->first->string;
if(value_string.size == 0)
{
value_string = md_tag_from_string(child_schema, str8_lit("default"), 0)->first->string;
}
String8 value_string_casted = push_str8f(scratch.arena, "(float32)(%S)", value_string);
F32 value = e_value_from_string(value_string_casted).f32;
read_data = push_str8_copy(scratch.arena, str8_struct(&value));
}
}
// rjf: if no child key? -> just read from this cfg's child string - first 8 bytes -> offset of string (just 8), then string's content
if(child_key.size == 0)
{
read_data = cfg->first->string;
}
// rjf: perform read
Rng1U64 legal_range = r1u64(0, read_data.size);
Rng1U64 read_range = intersect_1u64(range, legal_range);
if(read_range.min < read_range.max)
{
result = 1;
MemoryCopy(out, read_data.str + read_range.min, dim_1u64(read_range));
}
}break;
//- rjf: meta-entity reads
case RD_EvalSpaceKind_MetaCtrlEntity:
{
// rjf: unpack cfg
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(space);
String8 child_key = e_string_from_id(space.u64s[2]);
// rjf: determine data to read from, depending on child name in schema
String8 read_data = {0};
if(child_key.size != 0)
{
MD_NodePtrList schemas = rd_schemas_from_name(ctrl_entity_kind_code_name_table[entity->kind]);
MD_Node *child_schema = &md_nil_node;
for(MD_NodePtrNode *n = schemas.first; n != 0 && child_schema == &md_nil_node; n = n->next)
{
child_schema = md_child_from_string(n->v, child_key, 0);
}
if(str8_match(child_schema->string, str8_lit("exe"), 0) ||
str8_match(child_schema->string, str8_lit("label"), 0))
{
read_data = entity->string;
}
else if(str8_match(child_schema->string, str8_lit("dbg"), 0))
{
read_data = ctrl_entity_child_from_kind(entity, CTRL_EntityKind_DebugInfoPath)->string;
}
else if(str8_match(child_schema->string, str8_lit("vaddr_range"), 0))
{
read_data = str8_struct(&entity->vaddr_range);
}
else if(str8_match(child_schema->string, str8_lit("id"), 0))
{
read_data = str8_struct(&entity->id);
}
else if(str8_match(child_schema->string, str8_lit("active"), 0))
{
B32 is_frozen = ctrl_entity_tree_is_frozen(entity);
B32 is_active = !is_frozen;
read_data = push_str8_copy(scratch.arena, str8_struct(&is_active));
}
}
// rjf: perform read
Rng1U64 legal_range = r1u64(0, read_data.size);
Rng1U64 read_range = intersect_1u64(range, legal_range);
if(read_range.min < read_range.max)
{
result = 1;
MemoryCopy(out, read_data.str + read_range.min, dim_1u64(read_range));
}
}break;
}
scratch_end(scratch);
return result;
}
internal B32
rd_eval_space_write(void *u, E_Space space, void *in, Rng1U64 range)
{
B32 result = 0;
switch(space.kind)
{
default:{}break;
//- rjf: interior control entity writes (inside process address space or
// thread register block)
case RD_EvalSpaceKind_CtrlEntity:
{
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(space);
switch(entity->kind)
{
default:{}break;
case CTRL_EntityKind_Process:
{
result = ctrl_process_write(entity->handle, range, in);
}break;
case CTRL_EntityKind_Thread:
{
Temp scratch = scratch_begin(0, 0);
U64 regs_size = regs_block_size_from_arch(entity->arch);
Rng1U64 legal_range = r1u64(0, regs_size);
Rng1U64 write_range = intersect_1u64(legal_range, range);
U64 write_size = dim_1u64(write_range);
void *new_regs = ctrl_query_cached_reg_block_from_thread(scratch.arena, d_state->ctrl_entity_store, entity->handle);
MemoryCopy((U8 *)new_regs + write_range.min, in, write_size);
result = ctrl_thread_write_reg_block(entity->handle, new_regs);
scratch_end(scratch);
}break;
}
}break;
//- rjf: meta-config writes
case RD_EvalSpaceKind_MetaCfg:
{
result = 1;
// rjf: unpack write info
String8 write_string = str8_cstring_capped(in, (U8 *)in + dim_1u64(range));
// rjf: unpack cfg
RD_Cfg *root_cfg = rd_cfg_from_eval_space(space);
String8 child_key = e_string_from_id(space.u64s[1]);
// rjf: no child key? -> overwrite child string
if(child_key.size == 0)
{
rd_cfg_new_replace(root_cfg, write_string);
}
// rjf: child key -> look up & edit child
else
{
// rjf: modifying a label? -> poison this identifier in the macro map
if(str8_match(child_key, str8_lit("label"), 0))
{
String8 pre_edit_label = rd_label_from_cfg(root_cfg);
if(!str8_match(pre_edit_label, write_string, 0))
{
E_Expr *expr = e_string2expr_map_lookup(e_ir_ctx->macro_map, pre_edit_label);
if(expr != &e_expr_nil)
{
e_string2expr_map_inc_poison(e_ir_ctx->macro_map, pre_edit_label);
e_string2expr_map_insert(e_cache->arena, e_ir_ctx->macro_map, write_string, expr);
}
}
}
// rjf: zero-range? delete child
if(range.min == range.max)
{
rd_cfg_release(rd_cfg_child_from_string(root_cfg, child_key));
}
// rjf: non-zero-range? create child if needed & write value
else
{
RD_Cfg *child_cfg = rd_cfg_child_from_string_or_alloc(root_cfg, child_key);
rd_cfg_new_replace(child_cfg, write_string);
}
}
}break;
case RD_EvalSpaceKind_MetaCtrlEntity:
{
Temp scratch = scratch_begin(0, 0);
// rjf: unpack write info
String8 write_string = str8_cstring_capped(in, (U8 *)in + dim_1u64(range));
// rjf: unpack cfg
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(space);
String8 child_key = e_string_from_id(space.u64s[2]);
// rjf: perform write, based on child name in schema
if(child_key.size != 0)
{
MD_NodePtrList schemas = rd_schemas_from_name(ctrl_entity_kind_code_name_table[entity->kind]);
MD_Node *child_schema = &md_nil_node;
for(MD_NodePtrNode *n = schemas.first; n != 0 && child_schema == &md_nil_node; n = n->next)
{
child_schema = md_child_from_string(n->v, child_key, 0);
}
if(str8_match(child_schema->string, str8_lit("label"), 0))
{
result = 1;
ctrl_entity_equip_string(d_state->ctrl_entity_store, entity, write_string);
rd_cmd(D_CmdKind_SetEntityName, .ctrl_entity = entity->handle, .string = write_string);
}
else if(str8_match(child_schema->string, str8_lit("dbg"), 0))
{
// TODO(rjf)
}
else if(str8_match(child_schema->string, str8_lit("active"), 0))
{
result = 1;
B32 new_active = 0;
MemoryCopy(&new_active, in, dim_1u64(range));
if(!new_active)
{
rd_cmd(D_CmdKind_FreezeEntity, .ctrl_entity = entity->handle);
}
else
{
rd_cmd(D_CmdKind_ThawEntity, .ctrl_entity = entity->handle);
}
}
}
scratch_end(scratch);
}break;
}
return result;
}
//- rjf: asynchronous streamed reads -> hashes from spaces
internal U128
rd_key_from_eval_space_range(E_Space space, Rng1U64 range, B32 zero_terminated)
{
U128 result = {0};
switch(space.kind)
{
case E_SpaceKind_HashStoreKey:
{
result = space.u128;
}break;
case E_SpaceKind_File:
{
U64 file_path_string_id = space.u64_0;
String8 file_path = e_string_from_id(file_path_string_id);
result = fs_key_from_path_range(file_path, range);
}break;
case RD_EvalSpaceKind_CtrlEntity:
{
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(space);
if(entity->kind == CTRL_EntityKind_Process)
{
result = ctrl_hash_store_key_from_process_vaddr_range(entity->handle, range, zero_terminated);
}
}break;
}
return result;
}
//- rjf: space -> entire range
internal Rng1U64
rd_whole_range_from_eval_space(E_Space space)
{
Rng1U64 result = {0};
switch(space.kind)
{
case E_SpaceKind_HashStoreKey:
{
U128 key = space.u128;
U128 hash = hs_hash_from_key(key, 0);
HS_Scope *hs_scope = hs_scope_open();
{
String8 data = hs_data_from_hash(hs_scope, hash);
result = r1u64(0, data.size);
}
hs_scope_close(hs_scope);
}break;
case E_SpaceKind_File:
{
U64 file_path_string_id = space.u64_0;
String8 file_path = e_string_from_id(file_path_string_id);
FileProperties props = os_properties_from_file_path(file_path);
result = r1u64(0, props.size);
}break;
case RD_EvalSpaceKind_CtrlEntity:
{
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(space);
if(entity->kind == CTRL_EntityKind_Process)
{
result = r1u64(0, 0x7FFFFFFFFFFFull);
}
}break;
}
return result;
}
////////////////////////////////
//~ rjf: Evaluation View Visualization & Interaction
//- rjf: writing values back to child processes
internal B32
rd_commit_eval_value_string(E_Eval dst_eval, String8 string)
{
B32 result = 0;
if(dst_eval.irtree.mode == E_Mode_Offset)
{
Temp scratch = scratch_begin(0, 0);
//- rjf: unpack type of destination
E_TypeKey type_key = e_type_key_unwrap(dst_eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative);
E_TypeKind type_kind = e_type_kind_from_key(type_key);
E_TypeKey direct_type_key = e_type_key_unwrap(dst_eval.irtree.type_key, E_TypeUnwrapFlag_All);
E_TypeKind direct_type_kind = e_type_kind_from_key(direct_type_key);
//- rjf: determine data we'll write
B32 got_commit_data = 0;
String8 commit_data = {0};
B32 commit_at_ptr_dest = 0;
if(!e_type_key_match(e_type_key_zero(), type_key))
{
//- rjf: meta evaluations? -> always treat string as textual content, as-is,
// and commit that.
if(!got_commit_data && dst_eval.space.kind == RD_EvalSpaceKind_MetaCfg)
{
got_commit_data = 1;
commit_data = string;
}
//- rjf: basic types or enums? treat string as an expression, cast to the
// destination type, and compute commit data as being the binary representation
// of the new value.
if(!got_commit_data &&
((E_TypeKind_FirstBasic <= type_kind && type_kind <= E_TypeKind_LastBasic) ||
type_kind == E_TypeKind_Enum))
{
got_commit_data = 1;
E_Eval src_eval = e_eval_from_stringf("(%S)(%S)", e_type_string_from_key(scratch.arena, type_key), string);
commit_data = push_str8_copy(scratch.arena, str8_struct(&src_eval.value));
commit_data.size = Min(commit_data.size, e_type_byte_size_from_key(type_key));
}
//- rjf: pointer or array to characters/integers? -> try to treat
// new value string as textual data
if(!got_commit_data &&
((type_kind == E_TypeKind_Ptr || type_kind == E_TypeKind_Array) &&
direct_type_kind == E_TypeKind_Char8 ||
direct_type_kind == E_TypeKind_Char16 ||
direct_type_kind == E_TypeKind_Char32 ||
direct_type_kind == E_TypeKind_UChar8 ||
direct_type_kind == E_TypeKind_UChar16 ||
direct_type_kind == E_TypeKind_UChar32 ||
e_type_kind_is_integer(direct_type_kind)))
{
got_commit_data = 1;
B32 is_quoted = 0;
if(string.size >= 1 && string.str[0] == '"')
{
string = str8_skip(string, 1);
is_quoted = 1;
}
if(string.size >= 1 && string.str[string.size-1] == '"')
{
string = str8_chop(string, 1);
}
if(is_quoted)
{
commit_data = raw_from_escaped_str8(scratch.arena, string);
}
else
{
commit_data = push_str8_copy(scratch.arena, string);
}
commit_data.size += 1;
if(type_kind == E_TypeKind_Ptr)
{
commit_at_ptr_dest = 1;
}
switch(direct_type_kind)
{
default:{}break;
case E_TypeKind_S16:
case E_TypeKind_U16:
case E_TypeKind_Char16:
case E_TypeKind_UChar16:
{
String16 data16 = str16_from_8(scratch.arena, commit_data);
commit_data = str8((U8 *)data16.str, data16.size*sizeof(U16));
}break;
case E_TypeKind_Char32:
case E_TypeKind_UChar32:
case E_TypeKind_S32:
case E_TypeKind_U32:
{
String32 data32 = str32_from_8(scratch.arena, commit_data);
commit_data = str8((U8 *)data32.str, data32.size*sizeof(U32));
}break;
}
}
//- rjf: pointer? -> try to treat new value as numeric value
if(!got_commit_data && type_kind == E_TypeKind_Ptr)
{
E_Eval src_eval = e_eval_from_string(string);
E_Eval src_eval_value = e_value_eval_from_eval(src_eval);
E_TypeKind src_eval_value_type_kind = e_type_kind_from_key(src_eval_value.irtree.type_key);
if((e_type_kind_is_pointer_or_ref(src_eval_value_type_kind) ||
e_type_kind_is_integer(src_eval_value_type_kind)) &&
src_eval_value.irtree.mode == E_Mode_Value)
{
got_commit_data = 1;
commit_data = push_str8_copy(scratch.arena, str8_struct(&src_eval.value));
commit_data.size = Min(commit_data.size, e_type_byte_size_from_key(src_eval.irtree.type_key));
commit_data.size = Min(commit_data.size, e_type_byte_size_from_key(type_key));
}
}
}
//- rjf: determine destination offset we'll write the new data to
U64 dst_offset = dst_eval.value.u64;
if(got_commit_data && commit_at_ptr_dest)
{
E_Eval dst_value_eval = e_value_eval_from_eval(dst_eval);
dst_offset = dst_value_eval.value.u64;
}
//- rjf: if we have commit data, then write that data to the destination offset
if(got_commit_data)
{
result = e_space_write(dst_eval.space, commit_data.str, r1u64(dst_offset, dst_offset + commit_data.size));
}
scratch_end(scratch);
}
return result;
}
//- rjf: eval <-> file path
internal String8
rd_file_path_from_eval(Arena *arena, E_Eval eval)
{
String8 result = {0};
switch(eval.space.kind)
{
default:{}break;
case E_SpaceKind_File:
{
result = push_str8_copy(arena, e_string_from_id(eval.space.u64_0));
}break;
case E_SpaceKind_FileSystem:
{
result = push_str8_copy(arena, e_string_from_id(eval.value.u64));
}break;
}
return result;
}
internal String8
rd_file_path_from_eval_string(Arena *arena, String8 string)
{
String8 result = {0};
{
Temp scratch = scratch_begin(&arena, 1);
E_Eval eval = e_eval_from_string(string);
result = rd_file_path_from_eval(arena, eval);
scratch_end(scratch);
}
return result;
}
internal String8
rd_eval_string_from_file_path(Arena *arena, String8 string)
{
Temp scratch = scratch_begin(&arena, 1);
String8 string_escaped = escaped_from_raw_str8(scratch.arena, string);
String8 result = push_str8f(arena, "file:\"%S\".data", string_escaped);
scratch_end(scratch);
return result;
}
//- rjf: eval -> query
internal String8
rd_query_from_eval_string(Arena *arena, String8 string)
{
String8 result = {0};
{
Temp scratch = scratch_begin(&arena, 1);
E_Expr *expr = e_parse_from_string(string).expr;
if(expr->kind == E_ExprKind_LeafIdentifier &&
str8_match(expr->qualifier, str8_lit("query"), 0))
{
result = expr->string;
}
scratch_end(scratch);
}
return result;
}
////////////////////////////////
//~ rjf: View Functions
internal RD_Cfg *
rd_view_from_eval(RD_Cfg *parent, E_Eval eval)
{
Temp scratch = scratch_begin(0, 0);
E_TypeKey type_key = eval.irtree.type_key;
E_Type *type = e_type_from_key__cached(type_key);
String8 schema_name = str8_lit("watch");
if(type->kind == E_TypeKind_Lens)
{
RD_ViewUIRule *view_ui_rule = rd_view_ui_rule_from_string(type->name);
if(view_ui_rule != &rd_nil_view_ui_rule)
{
schema_name = type->name;
}
}
RD_Cfg *view = rd_cfg_child_from_string_or_alloc(parent, schema_name);
rd_cfg_child_from_string_or_alloc(view, str8_lit("selected"));
{
// rjf: get expression evaluation
E_Eval expr_eval = eval;
if(eval.expr->kind == E_ExprKind_Call)
{
expr_eval = e_eval_from_expr(eval.expr->first->next);
}
// rjf: get arguments to view
E_Expr **args = 0;
U64 args_count = 0;
if(type->args != 0)
{
args = type->args;
args_count = type->count;
}
// rjf: reflect expr & arguments in cfg tree
RD_Cfg *expr_root = rd_cfg_child_from_string_or_alloc(view, str8_lit("expression"));
rd_cfg_new_replace(expr_root, e_full_expr_string_from_key(scratch.arena, expr_eval.key));
{
MD_NodePtrList schemas = rd_schemas_from_name(schema_name);
U64 unnamed_order_idx = 0;
for EachIndex(arg_idx, args_count)
{
E_Expr *arg = args[arg_idx];
String8 param_name = {0};
E_Expr *arg_expr = arg;
if(arg->kind == E_ExprKind_Define)
{
param_name = arg->first->string;
arg_expr = arg->first->next;
}
else if(schemas.last != 0)
{
for MD_EachNode(schema_child, schemas.last->v->first)
{
MD_Node *order_tag = md_tag_from_string(schema_child, str8_lit("order"), 0);
if(order_tag != &md_nil_node)
{
U64 schema_child_order_idx = 0;
try_u64_from_str8_c_rules(order_tag->first->string, &schema_child_order_idx);
if(schema_child_order_idx == unnamed_order_idx)
{
param_name = schema_child->string;
arg_expr = arg;
break;
}
}
}
unnamed_order_idx += 1;
}
RD_Cfg *arg_root = rd_cfg_child_from_string_or_alloc(view, param_name);
rd_cfg_new_replace(arg_root, e_string_from_expr(scratch.arena, arg_expr, str8_zero()));
}
}
}
scratch_end(scratch);
return view;
}
internal RD_ViewState *
rd_view_state_from_cfg(RD_Cfg *cfg)
{
RD_ViewState *view_state = &rd_nil_view_state;
RD_CfgID id = cfg->id;
if(id != 0 &&
id == rd_state->view_state_last_accessed_id &&
id == rd_state->view_state_last_accessed->cfg_id)
{
view_state = rd_state->view_state_last_accessed;
}
else
{
U64 hash = d_hash_from_string(str8_struct(&id));
U64 slot_idx = hash%rd_state->view_state_slots_count;
RD_ViewStateSlot *slot = &rd_state->view_state_slots[slot_idx];
for(RD_ViewState *v = slot->first; v != 0; v = v->hash_next)
{
if(v->cfg_id == id)
{
view_state = v;
break;
}
}
}
if(view_state == &rd_nil_view_state)
{
view_state = rd_state->free_view_state;
if(view_state)
{
SLLStackPop_N(rd_state->free_view_state, hash_next);
}
else
{
view_state = push_array(rd_state->arena, RD_ViewState, 1);
}
MemoryCopyStruct(view_state, &rd_nil_view_state);
U64 hash = d_hash_from_string(str8_struct(&id));
U64 slot_idx = hash%rd_state->view_state_slots_count;
RD_ViewStateSlot *slot = &rd_state->view_state_slots[slot_idx];
DLLPushBack_NP(slot->first, slot->last, view_state, hash_next, hash_prev);
view_state->cfg_id = id;
view_state->arena = arena_alloc();
view_state->arena_reset_pos = arena_pos(view_state->arena);
view_state->ev_view = ev_view_alloc();
}
if(view_state != &rd_nil_view_state)
{
view_state->last_frame_index_touched = rd_state->frame_index;
}
rd_state->view_state_last_accessed = view_state;
rd_state->view_state_last_accessed_id = id;
return view_state;
}
typedef struct RD_WatchRowExtrasDrawData RD_WatchRowExtrasDrawData;
struct RD_WatchRowExtrasDrawData
{
B32 breaks_from_prev;
};
internal UI_BOX_CUSTOM_DRAW(rd_watch_row_extras_custom_draw)
{
RD_WatchRowExtrasDrawData *draw_data = (RD_WatchRowExtrasDrawData *)user_data;
if(draw_data->breaks_from_prev) DR_ClipScope(intersect_2f32(dr_top_clip(), box->rect))
{
Vec4F32 shadow_color = ui_color_from_name(str8_lit("drop_shadow"));
R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x0, box->rect.y0, box->rect.x1, (box->rect.y0+box->rect.y1)/2), shadow_color, 0, 0, 0);
inst->colors[Corner_01] = inst->colors[Corner_11] = v4f32(0, 0, 0, 0);
}
}
internal void
rd_view_ui(Rng2F32 rect)
{
ProfBeginFunction();
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_ViewState *vs = rd_view_state_from_cfg(view);
String8 view_name = view->string;
String8 expr_string = rd_expr_from_cfg(view);
B32 view_is_floating = 0;
for(RD_Cfg *p = view->parent; p != &rd_nil_cfg; p = p->parent)
{
if(str8_match(p->string, str8_lit("immediate"), 0))
{
view_is_floating = 1;
break;
}
}
//////////////////////////////
//- rjf: query extension
//
RD_Cfg *query_root = rd_cfg_child_from_string(view, str8_lit("query"));
RD_Cfg *input_root = rd_cfg_child_from_string(query_root, str8_lit("input"));
RD_Cfg *cmd_root = rd_cfg_child_from_string(query_root, str8_lit("cmd"));
String8 current_input = input_root->first->string;
B32 search_row_is_open = (vs->query_is_selected);
F32 search_row_open_t = ui_anim(ui_key_from_stringf(ui_key_zero(), "search_row_open_%p", view), (F32)!!search_row_is_open, .initial = (F32)!!search_row_is_open, .epsilon = 0.01f);
if(search_row_open_t > 0.001f)
{
String8 cmd_name = cmd_root->first->string;
RD_IconKind icon = rd_icon_kind_from_code_name(cmd_name);
RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name);
//- rjf: store cfg's string into view's
vs->query_string_size = Min(sizeof(vs->query_buffer), current_input.size);
MemoryCopy(vs->query_buffer, current_input.str, vs->query_string_size);
//- rjf: clamp cursor
if(vs->query_cursor.column == 0)
{
vs->query_mark = txt_pt(1, 1);
vs->query_cursor = txt_pt(1, vs->query_string_size+1);
}
//- rjf: determine dimensions
F32 search_row_height_target = ui_top_px_height();
F32 search_row_height = search_row_open_t*search_row_height_target;
search_row_height = Min(search_row_height, dim_2f32(rect).y);
rect.y0 += search_row_height;
rect.y0 = floor_f32(rect.y0);
//- rjf: build container
UI_Box *search_row = &ui_nil_box;
UI_PrefHeight(ui_px(search_row_height, 1.f))
{
search_row = ui_build_box_from_stringf(UI_BoxFlag_DrawSideBottom|UI_BoxFlag_DrawDropShadow, "###search");
}
//- rjf: build contents
UI_Parent(search_row) UI_WidthFill UI_HeightFill UI_Focus(vs->query_is_selected && !vs->contents_are_focused ? UI_FocusKind_On : UI_FocusKind_Off)
RD_Font(cmd_kind_info->query.flags & RD_QueryFlag_CodeInput ? RD_FontSlot_Code : RD_FontSlot_Main)
{
if(cmd_name.size != 0)
{
UI_TextAlignment(UI_TextAlign_Center)
UI_Transparency(1-search_row_open_t)
UI_PrefWidth(ui_em(2.5f, 1.f))
UI_TagF("weak")
RD_Font(RD_FontSlot_Icons)
ui_label(rd_icon_kind_text_table[icon == RD_IconKind_Null ? RD_IconKind_Find : icon]);
UI_Transparency(1-search_row_open_t)
RD_Font(RD_FontSlot_Main) UI_PrefWidth(ui_text_dim(1, 1))
ui_label(rd_display_from_code_name(cmd_name));
ui_spacer(ui_em(0.5f, 1.f));
}
RD_CellParams params = {0};
{
params.flags |= !!(cmd_kind_info->query.flags & RD_QueryFlag_CodeInput) * RD_CellFlag_CodeContents;
params.flags |= RD_CellFlag_Border;
params.cursor = &vs->query_cursor;
params.mark = &vs->query_mark;
params.edit_buffer = vs->query_buffer;
params.edit_string_size_out = &vs->query_string_size;
params.edit_buffer_size = sizeof(vs->query_buffer);
params.pre_edit_value = current_input;
}
UI_Transparency(1-search_row_open_t)
{
UI_Signal sig = rd_cellf(&params, "###search");
// TODO(rjf)
#if 0
if(ui_is_focus_active())
{
rd_set_autocomp_regs(.ui_key = sig.box->key, .string = str8(vs->query_buffer, vs->query_string_size), .cursor = vs->query_cursor);
}
#endif
if(ui_pressed(sig))
{
vs->query_is_selected = 1;
rd_cmd(RD_CmdKind_FocusPanel);
}
}
}
//- rjf: commit string to view
if(input_root == &rd_nil_cfg)
{
input_root = rd_cfg_child_from_string_or_alloc(query_root, str8_lit("input"));
}
rd_cfg_new_replace(input_root, str8(vs->query_buffer, vs->query_string_size));
}
//////////////////////////////
//- rjf: build main view container
//
UI_Box *view_container = &ui_nil_box;
UI_WidthFill UI_HeightFill
{
view_container = ui_build_box_from_key(0, ui_key_zero());
}
//////////////////////////////
//- rjf: fill view container
//
UI_Parent(view_container)
UI_FontSize(rd_setting_f32_from_name(str8_lit("font_size")))
UI_PrefHeight(ui_px(floor_f32(ui_top_font_size()*rd_setting_f32_from_name(str8_lit("row_height"))), 1.f))
{
////////////////////////////
//- rjf: special-case view: "getting started"
//
if(0){}
else if(str8_match(view_name, str8_lit("getting_started"), 0))
{
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_Padding(ui_pct(1, 0)) UI_Focus(UI_FocusKind_Null)
{
RD_CfgList targets = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("target"));
CTRL_EntityArray processes = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Process);
//- rjf: icon & info
UI_Padding(ui_em(2.f, 1.f)) UI_TagF("weak")
{
//- 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 = rd_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)
UI_TagF("pop")
if(ui_clicked(rd_icon_buttonf(RD_IconKind_Add, 0, "Add Target")))
{
rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_AddTarget].string);
}
}break;
//- rjf: user has 1 target. build helper for launching it
case 1:
{
RD_Cfg *target_cfg = rd_cfg_list_first(&targets);
D_Target target = rd_target_from_cfg(scratch.arena, target_cfg);
String8 target_full_path = target.exe;
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)
UI_TagF("good_pop")
{
if(ui_clicked(rd_icon_buttonf(RD_IconKind_Play, 0, "Launch %S", target_name)))
{
rd_cmd(RD_CmdKind_LaunchAndRun, .cfg = target_cfg->id);
}
ui_spacer(ui_em(1.5f, 1));
if(ui_clicked(rd_icon_buttonf(RD_IconKind_StepInto, 0, "Step Into %S", target_name)))
{
rd_cmd(RD_CmdKind_LaunchAndStepInto, .cfg = target_cfg->id);
}
}
}break;
//- rjf: user has N targets.
default:
{
helper_built = 0;
}break;
}
}
//- rjf: or text
if(helper_built)
{
UI_TagF("weak")
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_TagF("weak")
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_TextAlignment(UI_TextAlign_Center) rd_cmd_binding_buttons(rd_cmd_kind_info_table[RD_CmdKind_OpenPalette].string, str8_zero());
ui_labelf("to search for commands and options");
}
}
scratch_end(scratch);
}
////////////////////////////
//- rjf: special-case view: pending
//
else if(str8_match(view_name, str8_lit("pending"), 0))
{
Temp scratch = scratch_begin(0, 0);
typedef struct State State;
struct State
{
Arena *deferred_cmd_arena;
RD_CmdList deferred_cmds;
};
State *state = rd_view_state(State);
if(state->deferred_cmd_arena == 0)
{
state->deferred_cmd_arena = rd_push_view_arena();
}
rd_store_view_loading_info(1, 0, 0);
// rjf: any commands sent to this view need to be deferred until loading is complete
for(RD_Cmd *cmd = 0; rd_next_view_cmd(&cmd);)
{
RD_CmdKind kind = rd_cmd_kind_from_string(cmd->name);
switch(kind)
{
default:{}break;
case RD_CmdKind_GoToLine:
case RD_CmdKind_GoToAddress:
case RD_CmdKind_CenterCursor:
case RD_CmdKind_ContainCursor:
{
rd_cmd_list_push_new(state->deferred_cmd_arena, &state->deferred_cmds, cmd->name, cmd->regs);
}break;
}
}
// rjf: unpack view's target expression & hash
E_Eval eval = e_eval_from_string(expr_string);
Rng1U64 range = r1u64(0, 1024);
U128 key = rd_key_from_eval_space_range(eval.space, range, 0);
U128 hash = hs_hash_from_key(key, 0);
// rjf: determine if hash's blob is ready, and which viewer to use
B32 data_is_ready = 0;
String8 new_view_name = {0};
{
HS_Scope *hs_scope = hs_scope_open();
if(!u128_match(hash, u128_zero()))
{
String8 data = hs_data_from_hash(hs_scope, hash);
U64 num_utf8_bytes = 0;
U64 num_unknown_bytes = 0;
for(U64 idx = 0; idx < data.size && idx < 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;
}
}
data_is_ready = 1;
if(num_utf8_bytes > num_unknown_bytes*4 || num_unknown_bytes == 0)
{
new_view_name = str8_lit("text");
}
else
{
new_view_name = str8_lit("memory");
}
}
hs_scope_close(hs_scope);
}
// rjf: if we don't have a viewer, just use the memory viewer.
if(new_view_name.size == 0)
{
new_view_name = str8_lit("memory");
}
// rjf: if data is ready and we have the name of a new visualizer,
// dispatch deferred commands & change this view's string to be
// that of the new visualizer.
if(data_is_ready && new_view_name.size != 0)
{
for(RD_CmdNode *cmd_node = state->deferred_cmds.first;
cmd_node != 0;
cmd_node = cmd_node->next)
{
RD_Cmd *cmd = &cmd_node->cmd;
rd_push_cmd(cmd->name, cmd->regs);
}
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
rd_cfg_equip_string(view, new_view_name);
RD_ViewState *vs = rd_view_state_from_cfg(view);
for(RD_ArenaExt *ext = vs->first_arena_ext; ext != 0; ext = ext->next)
{
arena_release(ext->arena);
}
arena_pop_to(vs->arena, vs->arena_reset_pos);
vs->user_data = 0;
vs->first_arena_ext = vs->last_arena_ext = 0;
}
// rjf: if we don't have a viewer, for whatever reason, then just
// close the tab.
if(data_is_ready && new_view_name.size == 0)
{
rd_cmd(RD_CmdKind_CloseTab);
}
scratch_end(scratch);
}
////////////////////////////
//- rjf: watch view
//
else if(str8_match(view_name, str8_lit("watch"), 0))
{
Temp scratch = scratch_begin(0, 0);
RD_Font(RD_FontSlot_Code)
{
if(expr_string.size == 0)
{
expr_string = push_str8f(scratch.arena, "query:config.$%I64x.watches", rd_regs()->view);
}
E_Eval eval = e_eval_from_string(expr_string);
RD_WatchViewState *ewv = rd_view_state(RD_WatchViewState);
UI_ScrollPt2 scroll_pos = rd_view_scroll_pos();
F32 entity_hover_t_rate = rd_setting_b32_from_name(str8_lit("hover_animations")) ? (1 - pow_f32(2, (-60.f * rd_state->frame_dt))) : 1.f;
B32 is_first_frame = 0;
if(ewv->initialized == 0)
{
is_first_frame = 1;
ewv->initialized = 1;
ewv->filter_arena = rd_push_view_arena();
ewv->text_edit_arena = rd_push_view_arena();
}
//////////////////////////////
//- rjf: unpack arguments
//
EV_View *eval_view = rd_view_eval_view();
F32 row_height_px = ui_top_px_height();
S64 num_possible_visible_rows = (S64)(dim_2f32(rect).y/row_height_px);
F32 row_string_max_size_px = dim_2f32(rect).x;
EV_StringFlags string_flags = EV_StringFlag_ReadOnlyDisplayRules;
String8 filter = rd_view_query_input();
Vec4F32 pop_background_rgba = {0};
UI_TagF("pop") pop_background_rgba = ui_color_from_name(str8_lit("background"));
//////////////////////////////
//- rjf: whenever the filter changes, we want to reset the cursor/mark state
//
if(!str8_match(filter, ewv->last_filter, 0))
{
MemoryZeroStruct(&ewv->cursor);
MemoryZeroStruct(&ewv->mark);
MemoryZeroStruct(&ewv->next_cursor);
MemoryZeroStruct(&ewv->next_mark);
arena_clear(ewv->filter_arena);
ewv->last_filter = push_str8_copy(ewv->filter_arena, filter);
}
//////////////////////////////
//- rjf: decide if root should be implicit
//
// "implicit" means -> root is automatically expanded, row depth is 1 less than the
// block tree structure would suggest. this would be used if the root is, for instance,
// the "collection of all watches", to build a watch window. but this behavior is not
// as desirable if we are just using some other expression as the root.
//
B32 implicit_root = (rd_cfg_child_from_string(rd_cfg_from_id(rd_regs()->view), str8_lit("explicit_root")) == &rd_nil_cfg);
//////////////////////////////
//- 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: process commands
//
for(RD_Cmd *cmd = 0; rd_next_view_cmd(&cmd);)
{
RD_CmdKind kind = rd_cmd_kind_from_string(cmd->name);
switch(kind)
{
default:{}break;
case RD_CmdKind_Search:
case RD_CmdKind_SearchBackwards:
{
vs->query_is_selected = 0;
}break;
}
}
//////////////////////////////
//- rjf: consume events & perform navigations/edits - calculate state
//
EV_BlockTree block_tree = {0};
EV_BlockRangeList block_ranges = {0};
UI_ScrollListRowBlockArray row_blocks = {0};
Vec2S64 cursor_tbl = {0};
Vec2S64 mark_tbl = {0};
Rng2S64 selection_tbl = {0};
ProfScope("consume events & perform navigations/edits - calculate state") 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) ProfScope("state -> viz blocks")
{
eval = e_eval_from_string(eval.string);
MemoryZeroStruct(&block_tree);
MemoryZeroStruct(&block_ranges);
if(implicit_root || is_first_frame)
{
ev_key_set_expansion(eval_view, ev_key_root(), ev_key_make(ev_hash_from_key(ev_key_root()), 1), 1);
}
block_tree = ev_block_tree_from_eval(scratch.arena, eval_view, filter, eval);
block_ranges = ev_block_range_list_from_tree(scratch.arena, &block_tree);
if(implicit_root && block_ranges.first != 0)
{
block_ranges.count -= 1;
block_ranges.first = block_ranges.first->next;
}
}
//////////////////////////
//- rjf: block ranges -> ui row blocks
//
ProfScope("block ranges -> ui row blocks")
{
UI_ScrollListRowBlockChunkList row_block_chunks = {0};
for(EV_BlockRangeNode *n = block_ranges.first; n != 0; n = n->next)
{
UI_ScrollListRowBlock block = {0};
block.row_count = dim_1u64(n->v.range);
block.item_count = n->v.block->viz_expand_info.single_item ? 1 : dim_1u64(n->v.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
{
RD_WatchPt *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] = rd_watch_pt_from_tbl(&block_ranges, points[point_idx].pt_tbl);
if(ev_key_match(ev_key_zero(), points[point_idx].pt_state->key) && points[point_idx].pt_tbl.y != 0)
{
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
//
EV_WindowedRowList mark_rows = {0};
Rng2S64 cursor_tbl_range = {0};
{
// rjf: compute 2d table coordinates
cursor_tbl = rd_tbl_from_watch_pt(&block_ranges, ewv->cursor);
mark_tbl = rd_tbl_from_watch_pt(&block_ranges, ewv->mark);
// rjf: compute legal coordinate range, given selection-defining row
Rng1S64 cursor_x_range = {0};
{
EV_Row *row = ev_row_from_num(scratch.arena, eval_view, filter, &block_ranges, mark_tbl.y);
RD_WatchRowInfo row_info = rd_watch_row_info_from_row(scratch.arena, row);
cursor_x_range = r1s64(0, (S64)row_info.cells.count-1);
}
cursor_tbl_range = r2s64(v2s64(cursor_x_range.min, 0), v2s64(cursor_x_range.max, block_tree.total_item_count - implicit_root));
// rjf: clamp x positions of cursor/mark tbl
for EachEnumVal(Axis2, axis)
{
cursor_tbl.v[axis] = clamp_1s64(r1s64(cursor_tbl_range.min.v[axis], cursor_tbl_range.max.v[axis]), cursor_tbl.v[axis]);
mark_tbl.v[axis] = clamp_1s64(r1s64(cursor_tbl_range.min.v[axis], cursor_tbl_range.max.v[axis]), mark_tbl.v[axis]);
}
// rjf: form selection range table coordinates
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 global_vnum_range = r1s64(1, block_tree.total_row_count+1);
if(contains_1s64(global_vnum_range, cursor_tbl.y))
{
UI_ScrollPt *scroll_pt = &scroll_pos.y;
//- rjf: compute visible row range
Rng1S64 visible_row_num_range = r1s64(scroll_pt->idx + 1 - !!(scroll_pt->off < 0),
scroll_pt->idx + 1 + num_possible_visible_rows);
//- rjf: compute cursor row range from cursor item
Rng1S64 cursor_visibility_row_num_range = {0};
cursor_visibility_row_num_range.min = ev_vnum_from_num(&block_ranges, cursor_tbl.y) - 1;
cursor_visibility_row_num_range.max = cursor_visibility_row_num_range.min + 3;
//- rjf: compute deltas & apply
S64 min_delta = Min(0, cursor_visibility_row_num_range.min-visible_row_num_range.min);
S64 max_delta = Max(0, cursor_visibility_row_num_range.max-visible_row_num_range.max);
S64 new_num = (S64)scroll_pt->idx + 1 + min_delta + max_delta;
new_num = clamp_1s64(global_vnum_range, new_num);
if(new_num > 0)
{
U64 new_idx = (U64)(new_num - 1);
ui_scroll_pt_target_idx(scroll_pt, new_idx);
}
}
}
//////////////////////////////
//- rjf: apply cursor/mark rugpull change
//
B32 cursor_rugpull = 0;
if(!rd_watch_pt_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: consume query-completion events, if this view is being used as a lister
//
{
if(evt->kind == UI_EventKind_Press &&
evt->slot == UI_EventActionSlot_Accept &&
selection_tbl.min.y == selection_tbl.max.y &&
(rd_cfg_child_from_string(view, str8_lit("lister")) != &rd_nil_cfg))
{
RD_Cfg *query = rd_cfg_child_from_string(view, str8_lit("query"));
RD_Cfg *cmd = rd_cfg_child_from_string(query, str8_lit("cmd"));
String8 cmd_name = cmd->first->string;
// rjf: if we have no selection, just pick the first row
EV_Row *row = 0;
if(selection_tbl.min.y == 0 && selection_tbl.max.y == 0)
{
row = ev_row_from_num(scratch.arena, eval_view, filter, &block_ranges, 1);
}
// rjf: if we do have a selection, compute that row
else
{
row = ev_row_from_num(scratch.arena, eval_view, filter, &block_ranges, selection_tbl.min.y);
}
// rjf: use row to complete query
if(row->eval.expr != &e_expr_nil)
{
taken = 1;
E_Eval eval = row->eval;
// rjf: if we have a specific command we are trying to complete, then
// fill registers based on this row's evaluation.
if(cmd_name.size != 0) switch(eval.space.kind)
{
default:
{
U64 vaddr = eval.value.u64;
CTRL_Entity *process = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->process);
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);
{
DI_Scope *scope = di_scope_open();
RDI_Parsed *rdi = di_rdi_from_key(scope, &dbgi_key, 0);
String8 name = {0};
if(name.size == 0)
{
RDI_Procedure *procedure = rdi_procedure_from_voff(rdi, voff);
name.str = rdi_name_from_procedure(rdi, procedure, &name.size);
}
if(name.size == 0)
{
RDI_GlobalVariable *gvar = rdi_global_variable_from_voff(rdi, voff);
name.str = rdi_string_from_idx(rdi, gvar->name_string_idx, &name.size);
}
if(name.size != 0)
{
rd_cmd(RD_CmdKind_CompleteQuery, .string = name);
}
di_scope_close(scope);
}
}break;
case E_SpaceKind_File:
case E_SpaceKind_FileSystem:
{
E_Type *type = e_type_from_key__cached(eval.irtree.type_key);
String8 file = rd_file_path_from_eval(scratch.arena, eval);
if(str8_match(type->name, str8_lit("folder"), 0))
{
String8 new_input_string = push_str8f(scratch.arena, "%S/", file);
rd_cmd(RD_CmdKind_UpdateQuery, .string = new_input_string);
}
else
{
rd_cmd(RD_CmdKind_CompleteQuery, .file_path = file);
}
}break;
case RD_EvalSpaceKind_MetaCfg:
{
RD_Cfg *cfg = rd_cfg_from_eval_space(eval.space);
rd_cmd(RD_CmdKind_CompleteQuery, .cfg = cfg->id);
}break;
case RD_EvalSpaceKind_MetaCtrlEntity:
{
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(eval.space);
RD_RegsScope(.ctrl_entity = entity->handle)
{
if(0){}
else if(entity->kind == CTRL_EntityKind_Thread) { rd_regs()->thread = entity->handle; }
else if(entity->kind == CTRL_EntityKind_Module) { rd_regs()->module = entity->handle; }
else if(entity->kind == CTRL_EntityKind_Process) { rd_regs()->process = entity->handle; }
else if(entity->kind == CTRL_EntityKind_Machine) { rd_regs()->machine = entity->handle; }
rd_cmd(RD_CmdKind_CompleteQuery);
}
}break;
case RD_EvalSpaceKind_MetaUnattachedProcess:
{
U64 pid = eval.value.u128.u64[0];
rd_cmd(RD_CmdKind_CompleteQuery, .pid = pid);
}break;
case RD_EvalSpaceKind_MetaCmd:
{
String8 cmd_name = rd_cmd_name_from_eval(eval);
rd_cmd(RD_CmdKind_CompleteQuery, .cmd_name = cmd_name);
}break;
}
// rjf: if we do not have a specific command, then we can just
// pick a sensible default based on what was selected.
if(cmd_name.size == 0)
{
B32 did_cmd = 1;
switch(eval.space.kind)
{
default:
{
String8 name = {0};
{
U64 vaddr = eval.value.u64;
CTRL_Entity *process = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->process);
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);
{
DI_Scope *scope = di_scope_open();
RDI_Parsed *rdi = di_rdi_from_key(scope, &dbgi_key, 0);
if(name.size == 0)
{
RDI_Procedure *procedure = rdi_procedure_from_voff(rdi, voff);
name.str = rdi_name_from_procedure(rdi, procedure, &name.size);
}
if(name.size == 0)
{
RDI_GlobalVariable *gvar = rdi_global_variable_from_voff(rdi, voff);
name.str = rdi_string_from_idx(rdi, gvar->name_string_idx, &name.size);
}
di_scope_close(scope);
}
}
if(name.size != 0)
{
rd_cmd(RD_CmdKind_GoToName, .string = name);
}
else
{
rd_cmd(RD_CmdKind_PushQuery, .expr = e_string_from_expr(scratch.arena, eval.expr, str8_zero()));
}
}break;
case E_SpaceKind_File:
case E_SpaceKind_FileSystem:
{
String8 file = rd_file_path_from_eval(scratch.arena, eval);
rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = file, .vaddr = 0);
}break;
case RD_EvalSpaceKind_MetaCfg:
{
RD_Cfg *cfg = rd_cfg_from_eval_space(eval.space);
if(str8_match(cfg->string, str8_lit("recent_file"), 0))
{
rd_cmd(RD_CmdKind_Switch, .cfg = cfg->id);
}
else if(str8_match(cfg->string, str8_lit("recent_project"), 0))
{
rd_cmd(RD_CmdKind_OpenRecentProject, .cfg = cfg->id);
}
else if(e_type_kind_from_key(e_type_key_unwrap(eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative)) == E_TypeKind_Set)
{
rd_cmd(RD_CmdKind_PushQuery, .expr = e_full_expr_string_from_key(scratch.arena, eval.key));
}
else
{
did_cmd = 0;
}
}break;
case RD_EvalSpaceKind_MetaQuery:
{
rd_cmd(RD_CmdKind_PushQuery, .expr = e_full_expr_string_from_key(scratch.arena, eval.key));
}break;
case RD_EvalSpaceKind_MetaUnattachedProcess:
{
U64 pid = eval.value.u128.u64[0];
}break;
case RD_EvalSpaceKind_MetaCmd:
{
String8 cmd_name = rd_cmd_name_from_eval(eval);
rd_cmd(RD_CmdKind_RunCommand, .cmd_name = cmd_name);
}break;
case RD_EvalSpaceKind_MetaCtrlEntity:
{
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(eval.space);
rd_cmd(RD_CmdKind_PushQuery, .expr = push_str8f(scratch.arena, "query:control.%S", ctrl_string_from_handle(scratch.arena, entity->handle)));
}break;
}
if(did_cmd)
{
rd_cmd(RD_CmdKind_CompleteQuery);
}
else
{
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.max.y != 0))
{
Vec2S64 selection_dim = dim_2s64(selection_tbl);
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, RD_WatchViewTextEditState*, ewv->text_edit_state_slots_count);
EV_WindowedRowList rows = ev_rows_from_num_range(scratch.arena, eval_view, filter, &block_ranges, r1u64(selection_tbl.min.y, selection_tbl.max.y+1));
EV_WindowedRowNode *row_node = rows.first;
B32 any_edits_started = 0;
for(S64 y = selection_tbl.min.y; row_node != 0 && y <= selection_tbl.max.y; y += 1, row_node = row_node->next)
{
EV_Row *row = &row_node->row;
RD_WatchRowInfo row_info = rd_watch_row_info_from_row(scratch.arena, row);
S64 cell_x = 0;
for(RD_WatchCell *cell = row_info.cells.first; cell != 0; cell = cell->next, cell_x += 1)
{
if(cell_x < selection_tbl.min.x || selection_tbl.max.x < cell_x)
{
continue;
}
RD_WatchRowCellInfo cell_info = rd_info_from_watch_row_cell(scratch.arena, row, string_flags & (~EV_StringFlag_ReadOnlyDisplayRules), &row_info, cell, ui_top_font(), ui_top_font_size(), row_string_max_size_px);
if(cell_info.flags & RD_WatchCellFlag_CanEdit)
{
any_edits_started = 1;
String8 string = {0};
if(cell_info.flags & RD_WatchCellFlag_NoEval)
{
string = cell->eval.string;
}
else
{
string = dr_string_from_fstrs(scratch.arena, &cell_info.eval_fstrs);
}
string.size = Min(string.size, sizeof(ewv->dummy_text_edit_state.input_buffer));
RD_WatchPt pt = {row->block->key, row->key, rd_id_from_watch_cell(cell)};
U64 hash = ev_hash_from_key(pt.key);
U64 slot_idx = hash%ewv->text_edit_state_slots_count;
RD_WatchViewTextEditState *edit_state = push_array(ewv->text_edit_arena, RD_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);
}
}
}
ewv->text_editing = any_edits_started;
}
//////////////////////////
//- rjf: [table] do cell-granularity multi-cursor 'accept' operations (expansions / etc.); if
// cannot apply to multi-cursor, then just don't take the event
//
if(!ewv->text_editing && evt->slot == UI_EventActionSlot_Accept &&
(selection_tbl.min.y != 0 || selection_tbl.max.y != 0) &&
(selection_tbl.max.y - selection_tbl.min.y > 0))
{
EV_WindowedRowList rows = ev_rows_from_num_range(scratch.arena, eval_view, filter, &block_ranges, r1u64(selection_tbl.min.y, selection_tbl.max.y+1));
EV_WindowedRowNode *row_node = rows.first;
if(row_node != 0)
{
taken = 1;
for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row_node != 0; y += 1, row_node = row_node->next)
{
// rjf: unpack row info
EV_Row *row = &row_node->row;
RD_WatchRowInfo row_info = rd_watch_row_info_from_row(scratch.arena, row);
// rjf: loop through X selections and perform operations for each
for(S64 x = selection_tbl.min.x; x <= selection_tbl.max.x; x += 1)
{
#if 0 // TODO(rjf): @cfg (multicursor watch window press operations)
//- rjf: determine operation for this cell
typedef enum OpKind
{
OpKind_Null,
OpKind_DoExpand,
}
OpKind;
OpKind kind = OpKind_Null;
switch(row_kind)
{
default:{}break;
case RD_WatchViewRowKind_Normal:
{
RD_WatchViewColumn *col = rd_watch_view_column_from_x(ewv, x);
switch(col->kind)
{
default:{}break;
case RD_WatchViewColumnKind_Expr: {kind = OpKind_DoExpand;}break;
}
}break;
case RD_WatchViewRowKind_PrettyEntityControls:
if((!rd_entity_is_nil(row_info.collection_entity) || row_info.collection_ctrl_entity != &ctrl_entity_nil) && selection_tbl.min.x == 1 && selection_tbl.max.x == 1)
{
kind = OpKind_DoExpand;
}break;
}
//- rjf: perform operation
switch(kind)
{
default:{taken = 0;}break;
case OpKind_DoExpand:
if(ev_row_is_expandable(row))
{
B32 is_expanded = ev_expansion_from_key(eval_view, row->key);
ev_key_set_expansion(eval_view, row->block->key, row->key, !is_expanded);
}break;
}
#endif
}
}
}
}
//////////////////////////
//- 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);
rd_state->text_edit_mode = 1;
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;
EV_WindowedRowList rows = ev_rows_from_num_range(scratch.arena, eval_view, filter, &block_ranges, r1u64(selection_tbl.min.y, selection_tbl.max.y+1));
EV_WindowedRowNode *row_node = rows.first;
for(S64 y = selection_tbl.min.y; row_node != 0 && y <= selection_tbl.max.y; y += 1, row_node = row_node->next)
{
EV_Row *row = &row_node->row;
RD_WatchRowInfo row_info = rd_watch_row_info_from_row(scratch.arena, row);
S64 cell_x = 0;
for(RD_WatchCell *cell = row_info.cells.first; cell != 0; cell = cell->next, cell_x += 1)
{
if(cell_x < selection_tbl.min.x || selection_tbl.max.x < cell_x)
{
continue;
}
RD_WatchPt pt = {row->block->key, row->key, rd_id_from_watch_cell(cell)};
RD_WatchViewTextEditState *edit_state = rd_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 = rd_lister_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 - first try to commit eval value, if that path is
// enabled on this cell, next try to commit expression string, if that path is enabled
if(cell->kind == RD_WatchCellKind_Eval)
{
if(cell->flags & RD_WatchCellFlag_Expr && cell->flags & RD_WatchCellFlag_NoEval)
{
RD_Cfg *cfg = row_info.group_cfg_child;
String8 child_key = {0}; // str8_lit("expression");
if(cfg == &rd_nil_cfg && editing_complete && new_string.size != 0)
{
RD_Cfg *new_cfg_parent = row_info.group_cfg_parent;
if(new_cfg_parent != &rd_nil_cfg)
{
child_key = str8_zero();
}
if(new_cfg_parent == &rd_nil_cfg)
{
RD_CfgList all_cfgs = rd_cfg_top_level_list_from_string(scratch.arena, row_info.group_cfg_name);
new_cfg_parent = rd_cfg_list_last(&all_cfgs)->parent;
}
if(new_cfg_parent == &rd_nil_cfg)
{
new_cfg_parent = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("project"));
}
cfg = rd_cfg_new(new_cfg_parent, row_info.group_cfg_name);
state_dirty = 1;
snap_to_cursor = 1;
}
if(cfg != &rd_nil_cfg)
{
RD_Cfg *expr = child_key.size != 0 ? rd_cfg_child_from_string_or_alloc(cfg, child_key) : cfg;
rd_cfg_new_replace(expr, new_string);
}
}
else
{
B32 should_commit_asap = editing_complete;
if(cell->eval.space.kind == RD_EvalSpaceKind_MetaCfg)
{
should_commit_asap = 1;
}
else if(evt->slot != UI_EventActionSlot_Cancel)
{
should_commit_asap = editing_complete;
}
if(should_commit_asap)
{
B32 success = 0;
success = rd_commit_eval_value_string(cell->eval, new_string);
if(!success)
{
log_user_error(str8_lit("Could not commit value successfully."));
}
}
}
}
}
}
}
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_rows_from_num_range(scratch.arena, eval_view, filter, &block_ranges, r1u64(selection_tbl.min.y, selection_tbl.max.y+1));
EV_WindowedRowNode *row_node = rows.first;
for(S64 y = selection_tbl.min.y; y <= selection_tbl.max.y && row_node != 0; y += 1, row_node = row_node->next)
{
EV_Row *row = &row_node->row;
RD_WatchRowInfo row_info = rd_watch_row_info_from_row(scratch.arena, row);
S64 cell_x = 0;
for(RD_WatchCell *cell = row_info.cells.first; cell != 0; cell = cell->next, cell_x += 1)
{
if(cell_x < selection_tbl.min.x || selection_tbl.max.x < cell_x)
{
continue;
}
RD_WatchRowCellInfo cell_info = rd_info_from_watch_row_cell(scratch.arena, row, string_flags, &row_info, cell, ui_top_font(), ui_top_font_size(), row_string_max_size_px);
String8List cell_strings = {0};
str8_list_push(scratch.arena, &cell_strings, dr_string_from_fstrs(scratch.arena, &cell_info.expr_fstrs));
str8_list_push(scratch.arena, &cell_strings, dr_string_from_fstrs(scratch.arena, &cell_info.eval_fstrs));
String8 cell_string = str8_list_join(scratch.arena, &cell_strings, &(StringJoin){.sep = str8_lit(" ")});
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 ? "\"" : "",
cell_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;
RD_CfgList cfgs_to_remove = {0};
RD_WatchPt next_cursor_pt = {0};
B32 next_cursor_set = 0;
EV_WindowedRowList rows = ev_rows_from_num_range(scratch.arena, eval_view, filter, &block_ranges, r1u64(selection_tbl.min.y, selection_tbl.max.y+1));
EV_WindowedRowNode *row_node = rows.first;
for(S64 y = selection_tbl.min.y; row_node != 0 && y <= selection_tbl.max.y; y += 1, row_node = row_node->next)
{
EV_Row *row = &row_node->row;
RD_WatchRowInfo row_info = rd_watch_row_info_from_row(scratch.arena, row);
S64 cell_x = 0;
for(RD_WatchCell *cell = row_info.cells.first; cell != 0; cell = cell->next, cell_x += 1)
{
if(cell_x < selection_tbl.min.x || selection_tbl.max.x < cell_x)
{
continue;
}
RD_WatchPt pt = {row->block->key, row->key, rd_id_from_watch_cell(cell)};
if(cell->flags & RD_WatchCellFlag_Expr && cell->flags & RD_WatchCellFlag_NoEval)
{
RD_Cfg *cfg = row_info.group_cfg_child;
if(cfg != &rd_nil_cfg)
{
rd_cfg_list_push(scratch.arena, &cfgs_to_remove, cfg);
U64 deleted_num = ev_block_num_from_id(row->block, row->key.child_id);
if(deleted_num != 0)
{
EV_Key parent_key = row->block->parent->key;
EV_Key key = row->block->key;
U64 fallback_id_prev = ev_block_id_from_num(row->block, deleted_num-1);
U64 fallback_id_next = ev_block_id_from_num(row->block, deleted_num+1);
if(fallback_id_next != 0)
{
parent_key = row->block->key;
key = ev_key_make(row->key.parent_hash, fallback_id_next);
}
else if(fallback_id_prev != 0)
{
parent_key = row->block->key;
key = ev_key_make(row->key.parent_hash, fallback_id_prev);
}
RD_WatchPt new_pt = {parent_key, key, pt.cell_id};
next_cursor_pt = new_pt;
next_cursor_set = 1;
state_dirty = 1;
}
}
}
else
{
rd_commit_eval_value_string(cell->eval, str8_zero());
}
}
}
for(RD_CfgNode *n = cfgs_to_remove.first; n != 0; n = n->next)
{
rd_cfg_release(n->v);
}
if(next_cursor_set)
{
ewv->cursor = ewv->mark = ewv->next_cursor = ewv->next_mark = next_cursor_pt;
}
}
//////////////////////////
//- 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};
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;
if(filter.size == 0)
{
// rjf: determine blocks of each endpoint of the table selection
EV_Block *selection_endpoint_blocks[2] =
{
ev_block_range_from_num(&block_ranges, selection_tbl.min.y).block,
ev_block_range_from_num(&block_ranges, selection_tbl.max.y).block,
};
// rjf: pick shallowest block within which we can do reordering
U64 selection_depths[2] =
{
ev_depth_from_block(selection_endpoint_blocks[0]),
ev_depth_from_block(selection_endpoint_blocks[1]),
};
EV_Block *selection_block = (selection_depths[1] < selection_depths[0]
? selection_endpoint_blocks[1]
: selection_endpoint_blocks[0]);
// rjf: find selection keys within the block in which we are doing reordering
EV_Key selection_keys_in_block[2] = {0};
{
for EachElement(idx, selection_endpoint_blocks)
{
EV_Block *endpoint_block = selection_endpoint_blocks[idx];
if(endpoint_block == selection_block)
{
selection_keys_in_block[idx] = ev_key_from_num(&block_ranges, selection_tbl.v[idx].y);
}
else
{
for(;endpoint_block->parent != selection_block && endpoint_block != &ev_nil_block;)
{
endpoint_block = endpoint_block->parent;
}
if(endpoint_block->parent == selection_block)
{
selection_keys_in_block[idx] = endpoint_block->key;
}
}
}
EV_Key fallback_key = {0};
for EachElement(idx, selection_endpoint_blocks)
{
if(!ev_key_match(selection_keys_in_block[idx], ev_key_zero()))
{
fallback_key = selection_keys_in_block[idx];
}
}
for EachElement(idx, selection_endpoint_blocks)
{
if(ev_key_match(selection_keys_in_block[idx], ev_key_zero()))
{
selection_keys_in_block[idx] = fallback_key;
}
}
}
// rjf: determine collection info for the block
String8 group_cfg_name = {0};
{
E_IRTreeAndType block_irtree = selection_block->eval.irtree;
E_TypeKey block_type_key = e_type_key_unwrap(block_irtree.type_key, E_TypeUnwrapFlag_AllDecorative);
E_TypeKind block_type_kind = e_type_kind_from_key(block_type_key);
if(block_type_kind == E_TypeKind_Set)
{
E_Type *block_type = e_type_from_key__cached(block_type_key);
group_cfg_name = rd_singular_from_code_name_plural(block_type->name);
if(group_cfg_name.size == 0)
{
group_cfg_name = block_type->name;
}
}
}
// rjf: map selection endpoints to cfgs
RD_Cfg *first_cfg = &rd_nil_cfg;
RD_Cfg *last_cfg = &rd_nil_cfg;
if(group_cfg_name.size != 0)
{
first_cfg = rd_cfg_from_id(selection_keys_in_block[0].child_id);
last_cfg = rd_cfg_from_id(selection_keys_in_block[1].child_id);
}
// rjf: reorder
if(first_cfg != &rd_nil_cfg && last_cfg != &rd_nil_cfg)
{
RD_Cfg *first_cfg_prev = &rd_nil_cfg;
RD_Cfg *last_cfg_next = &rd_nil_cfg;
for(RD_Cfg *prev = first_cfg->prev; prev != &rd_nil_cfg; prev = prev->prev)
{
if(str8_match(prev->string, first_cfg->string, 0))
{
first_cfg_prev = prev;
break;
}
}
for(RD_Cfg *next = last_cfg->next; next != &rd_nil_cfg; next = next->next)
{
if(str8_match(next->string, last_cfg->string, 0))
{
last_cfg_next = next;
break;
}
}
if(evt->delta_2s32.y < 0 && first_cfg != &rd_nil_cfg && first_cfg_prev != &rd_nil_cfg)
{
state_dirty = 1;
snap_to_cursor = 1;
RD_Cfg *parent = first_cfg_prev->parent;
rd_cfg_unhook(parent, first_cfg_prev);
rd_cfg_insert_child(parent, last_cfg, first_cfg_prev);
}
if(evt->delta_2s32.y > 0 && last_cfg != &rd_nil_cfg && last_cfg_next != &rd_nil_cfg)
{
state_dirty = 1;
snap_to_cursor = 1;
RD_Cfg *parent = last_cfg_next->parent;
rd_cfg_unhook(parent, last_cfg_next);
rd_cfg_insert_child(parent, first_cfg_prev, last_cfg_next);
}
}
}
}
//////////////////////////
//- 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: autocomplete watches -> feed autocompletion info forward
//
if(rd_watch_pt_match(ewv->cursor, ewv->mark) &&
rd_cfg_child_from_string(view, str8_lit("autocomplete")) != &rd_nil_cfg)
{
U64 row_num = ev_num_from_key(&block_ranges, ewv->cursor.key);
EV_Row *row = ev_row_from_num(scratch.arena, rd_view_eval_view(), rd_view_query_input(), &block_ranges, row_num);
RD_WatchRowInfo row_info = rd_watch_row_info_from_row(scratch.arena, row);
RD_WatchCell *cell = row_info.cells.first;
if(cell != 0)
{
RD_WatchRowCellInfo cell_info = rd_info_from_watch_row_cell(scratch.arena, row, 0, &row_info, cell, ui_top_font(), ui_top_font_size(), dim_2f32(rect).y);
String8 string = dr_string_from_fstrs(ui_build_arena(), &cell_info.eval_fstrs);
if(string.size != 0)
{
UI_Event evt = zero_struct;
{
evt.kind = UI_EventKind_AutocompleteHint;
evt.string = string;
}
ui_event_list_push(ui_build_arena(), ui_state->events, &evt);
}
}
}
//////////////////////////////
//- rjf: build ui
//
B32 pressed = 0;
ProfScope("build ui")
{
Vec2F32 rect_dim = dim_2f32(rect);
F32 contents_width_px = (rect_dim.x - floor_f32(ui_bottom_font_size()*1.5f));
Rng1S64 visible_row_rng = {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 = rect_dim;
scroll_list_params.cursor_range = r2s64(v2s64(0, 0), v2s64(0, 0));
scroll_list_params.item_range = r1s64(0, block_tree.total_row_count - !!implicit_root);
scroll_list_params.cursor_min_is_empty_selection[Axis2_Y] = 1;
scroll_list_params.row_blocks = row_blocks;
}
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, &scroll_pos.y,
0,
0,
&visible_row_rng,
&scroll_list_sig)
UI_Focus(UI_FocusKind_Null)
{
ui_set_next_pref_height(ui_children_sum(1));
ui_set_next_child_layout_axis(Axis2_Y);
UI_Box *table = ui_build_box_from_string(0, str8_lit("table"));
UI_Parent(table)
{
Vec2F32 scroll_list_view_off_px = ui_top_parent()->parent->view_off;
////////////////////////
//- rjf: viz blocks -> rows
//
EV_WindowedRowList rows = {0};
{
rows = ev_windowed_row_list_from_block_range_list(scratch.arena, eval_view, filter, &block_ranges, r1u64(visible_row_rng.min+1, visible_row_rng.max+2));
}
////////////////////////
//- rjf: rows -> row infos
//
RD_WatchRowInfo *row_infos = push_array(scratch.arena, RD_WatchRowInfo, rows.count);
{
U64 idx = 0;
for(EV_WindowedRowNode *row_node = rows.first; row_node != 0; row_node = row_node->next, idx += 1)
{
EV_Row *row = &row_node->row;
row_infos[idx] = rd_watch_row_info_from_row(scratch.arena, row);
}
}
////////////////////////
//- rjf: build boundaries
//
B32 cell_pcts_are_dirty = 0;
ProfScope("build boundaries")
{
U64 idx = 0;
U64 boundary_start_idx = 0;
EV_Row *last_row = 0;
RD_WatchRowInfo *last_row_info = 0;
for(EV_WindowedRowNode *row_node = rows.first;; row_node = row_node->next, idx += 1)
{
//- rjf: determine if this row breaks the topology
B32 is_new_topology = (row_node == 0);
if(row_node != 0 && last_row_info != 0)
{
EV_Row *row = &row_node->row;
RD_WatchRowInfo *row_info = &row_infos[idx];
for(RD_WatchCell *last_cell = last_row_info->cells.first, *this_cell = row_info->cells.first;;
last_cell = last_cell->next, this_cell = this_cell->next)
{
if(last_cell == 0 && this_cell == 0)
{
break;
}
if((last_cell == 0 && this_cell != 0) || (last_cell != 0 && this_cell == 0))
{
is_new_topology = 1;
break;
}
if(rd_id_from_watch_cell(last_cell) != rd_id_from_watch_cell(this_cell))
{
is_new_topology = 1;
break;
}
}
}
//- rjf: if we reached a new topology, or the end -> build boundaries for all cell separations
if(is_new_topology)
{
EV_Row *row = last_row;
RD_WatchRowInfo *row_info = last_row_info;
F32 row_width_px = contents_width_px;
if(row_info != 0)
{
U64 row_hash = ev_hash_from_key(row->key);
F32 cell_x_px = 0;
U64 cell_idx = 0;
for(RD_WatchCell *cell = row_info->cells.first; cell != 0 && cell->next != 0; cell = cell->next, cell_idx += 1)
{
if(cell->pct == 0 || cell->next->pct == 0)
{
continue;
}
U64 cell_id = rd_id_from_watch_cell(cell);
F32 cell_width_px = cell->px + cell->pct * row_width_px;
F32 next_cell_x_px = cell_x_px + cell_width_px;
{
Rng2F32 rect = r2f32p(next_cell_x_px - ui_top_font_size()*0.4f,
boundary_start_idx*row_height_px,
next_cell_x_px + ui_top_font_size()*0.4f,
idx*row_height_px);
UI_Rect(rect) UI_HoverCursor(OS_Cursor_LeftRight)
{
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|UI_BoxFlag_Floating, "boundary_%I64x_%I64x", row_hash, cell_id);
UI_Signal sig = ui_signal_from_box(box);
if(ui_dragging(sig))
{
typedef struct DragData DragData;
struct DragData
{
F32 min_pct;
F32 max_pct;
};
if(ui_pressed(sig))
{
DragData drag_data = {cell->pct, cell->next->pct};
ui_store_drag_struct(&drag_data);
}
DragData *drag_data = ui_get_drag_struct(DragData);
F32 min_pct__pre = drag_data->min_pct;
F32 max_pct__pre = drag_data->max_pct;
F32 min_px__pre = min_pct__pre*row_width_px;
F32 max_px__pre = max_pct__pre*row_width_px;
F32 min_px__post = min_px__pre + ui_drag_delta().x;
F32 max_px__post = max_px__pre - ui_drag_delta().x;
F32 min_pct__post = min_px__post/row_width_px;
F32 max_pct__post = max_px__post/row_width_px;
if(min_pct__post < 0.05f)
{
min_pct__post = 0.05f;
max_pct__post = (min_pct__pre + max_pct__pre) - min_pct__post;
}
if(max_pct__post < 0.05f)
{
max_pct__post = 0.05f;
min_pct__post = (min_pct__pre + max_pct__pre) - max_pct__post;
}
if(ui_double_clicked(sig))
{
F32 default_sum = cell->default_pct + cell->next->default_pct;
F32 current_sum = min_pct__pre + max_pct__pre;;
min_pct__post = current_sum * (cell->default_pct / default_sum);
max_pct__post = current_sum * (cell->next->default_pct / default_sum);
ui_kill_action();
}
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_Cfg *style = rd_cfg_child_from_string_or_alloc(view, row_info->cell_style_key);
RD_Cfg *min_cfg = &rd_nil_cfg;
RD_Cfg *max_cfg = &rd_nil_cfg;
{
RD_Cfg *pct_child = style->first;
U64 c_idx = 0;
for(RD_WatchCell *c = row_info->cells.first; c != 0; c = c->next, c_idx += 1)
{
if(pct_child == &rd_nil_cfg)
{
pct_child = rd_cfg_newf(style, "%f", c->pct);
}
if(c_idx == cell_idx)
{
min_cfg = pct_child;
}
if(c_idx == cell_idx+1)
{
max_cfg = pct_child;
}
pct_child = pct_child->next;
}
rd_cfg_equip_stringf(min_cfg, "%f", min_pct__post);
rd_cfg_equip_stringf(max_cfg, "%f", max_pct__post);
cell_pcts_are_dirty = 1;
}
}
}
}
cell_x_px = next_cell_x_px;
}
}
boundary_start_idx = idx;
}
//- rjf: advance
if(row_node == 0)
{
break;
}
else
{
last_row = &row_node->row;
last_row_info = &row_infos[idx];
}
}
}
////////////////////////
//- rjf: if cell widths are dirty -> recompute row infos
//
if(cell_pcts_are_dirty)
{
U64 idx = 0;
for(EV_WindowedRowNode *row_node = rows.first; row_node != 0; row_node = row_node->next, idx += 1)
{
EV_Row *row = &row_node->row;
row_infos[idx] = rd_watch_row_info_from_row(scratch.arena, row);
}
}
////////////////////////
//- rjf: do drag/drops
//
if(rd_drag_is_active())
{
Vec2F32 rect_dim = dim_2f32(rect);
ui_set_next_rect(r2f32p(0, 0, rect_dim.x, rect_dim.y));
UI_Box *drop_target = ui_build_box_from_stringf(UI_BoxFlag_DropSite|UI_BoxFlag_Floating, "watch_%I64x_drop", rd_regs()->view);
UI_Signal sig = ui_signal_from_box(drop_target);
if(ui_key_match(ui_drop_hot_key(), drop_target->key))
{
Vec2F32 drag_pos = sub_2f32(ui_mouse(), rect.p0);
RD_RegSlot drag_slot = rd_state->drag_drop_regs_slot;
RD_Regs *drag_regs = rd_state->drag_drop_regs;
//- rjf: obtain best fit for target block & prev-row for this drag
EV_Block *drag_block = &ev_nil_block;
U64 best_prev_row_block_num = 0;
F32 best_prev_row_y = 0;
{
F32 best_prev_row_distance = inf32();
U64 local_row_idx = 0;
F32 row_y = 0;
for(EV_WindowedRowNode *row_node = rows.first; row_node != 0; row_node = row_node->next, local_row_idx += 1)
{
// rjf: unpack row
EV_Row *row = &row_node->row;
F32 row_height = row_height_px*row->visual_size;
RD_WatchRowInfo *row_info = &row_infos[local_row_idx];
E_Type *block_type = e_type_from_key__cached(row->block->eval.irtree.type_key);
// rjf: determine if this row's block is good for the current drag/drop
B32 block_is_good_for_drop = 0;
if(drag_slot == RD_RegSlot_Expr && block_type->expand.id_from_num == E_TYPE_EXPAND_ID_FROM_NUM_FUNCTION_NAME(watches))
{
block_is_good_for_drop = (drag_regs->cfg == 0 || (drag_regs->cfg != row_info->group_cfg_child->id));
}
// rjf: if this block is good, then test this row/block & grab if appropriate
if(block_is_good_for_drop)
{
if(drag_block == &ev_nil_block && row_y <= drag_pos.y && drag_pos.y <= row_y + row_height)
{
drag_block = row->block;
}
F32 row_distance = abs_f32(drag_pos.y - row_y);
if(row_distance <= best_prev_row_distance)
{
U64 row_num = ev_block_num_from_id(row->block, row->key.child_id);
best_prev_row_block_num = row_num-1;
best_prev_row_distance = row_distance;
best_prev_row_y = row_y;
drag_block = row->block;
}
}
row_y += row_height;
}
}
//- rjf: unpack block/previous row info
B32 drag_target_is_good = 0;
RD_Cfg *drag_parent_cfg = &rd_nil_cfg;
RD_Cfg *drag_prev_cfg = &rd_nil_cfg;
if(drag_block != &ev_nil_block)
{
EV_Key prev_row_key = ev_key_make(ev_hash_from_key(drag_block->key), ev_block_id_from_num(drag_block, best_prev_row_block_num));
U64 prev_row_num = ev_num_from_key(&block_ranges, prev_row_key);
EV_Row *prev_row = ev_row_from_num(scratch.arena, eval_view, filter, &block_ranges, prev_row_num);
RD_WatchRowInfo prev_row_info = rd_watch_row_info_from_row(scratch.arena, prev_row);
drag_parent_cfg = rd_cfg_from_eval_space(drag_block->eval.space);
drag_prev_cfg = prev_row_info.group_cfg_child;
if(drag_regs->cfg == 0 || drag_prev_cfg->id != drag_regs->cfg)
{
drag_target_is_good = 1;
}
}
//- rjf: drop
if(drag_target_is_good && rd_drag_drop() && drag_parent_cfg != &rd_nil_cfg)
{
switch(drag_slot)
{
default:{}break;
case RD_RegSlot_Expr:
{
RD_Cfg *cfg = rd_cfg_from_id(drag_regs->cfg);
if(cfg != &rd_nil_cfg)
{
rd_cfg_unhook(cfg->parent, cfg);
}
if(cfg == &rd_nil_cfg)
{
cfg = rd_cfg_alloc();
rd_cfg_equip_stringf(cfg, "watch");
rd_cfg_new(cfg, drag_regs->expr);
}
rd_cfg_insert_child(drag_parent_cfg, drag_prev_cfg, cfg);
}break;
}
}
//- rjf: draw drop position
if(drag_target_is_good)
{
DR_Bucket *bucket = dr_bucket_make();
DR_BucketScope(bucket) UI_TagF("pop")
{
Vec4F32 color = ui_color_from_name(str8_lit("background"));
Rng2F32 drop_line_rect = r2f32p(rect.x0,
rect.y0 + best_prev_row_y - ui_top_font_size()*0.5f,
rect.x1,
rect.y0 + best_prev_row_y + ui_top_font_size()*0.5f);
R_Rect2DInst *inst = dr_rect(drop_line_rect, color, 0, 0, 1.f);
inst->colors[Corner_10] = inst->colors[Corner_11] = v4f32(color.x, color.y, color.z, 0);
}
ui_box_equip_draw_bucket(drop_target, bucket);
}
}
}
////////////////////////
//- rjf: build table
//
ProfScope("build table")
{
U64 local_row_idx = 0;
U64 global_row_idx = rows.count_before_semantic;
RD_WatchRowInfo last_row_info = {0};
for(EV_WindowedRowNode *row_node = rows.first; row_node != 0; row_node = row_node->next, global_row_idx += 1, local_row_idx += 1)
{
////////////////////////
//- rjf: unpack row info
//
ProfBegin("unpack row info");
EV_Row *row = &row_node->row;
RD_WatchRowInfo *row_info = &row_infos[local_row_idx];
U64 row_hash = ev_hash_from_key(row->key);
U64 row_depth = ev_depth_from_block(row->block);
B32 row_selected = (selection_tbl.min.y <= global_row_idx+1 && global_row_idx+1 <= selection_tbl.max.y);
B32 row_expanded = ev_expansion_from_key(eval_view, row->key);
B32 next_row_expanded = row_expanded;
B32 row_is_expandable = row_info->can_expand;
if(implicit_root && row_depth > 0)
{
row_depth -= 1;
}
ProfEnd();
////////////////////////
//- rjf: determine if this row fits the last row's topology
//
B32 row_matches_last_row_topology = 1;
if(row_node != rows.first)
{
for(RD_WatchCell *last_cell = last_row_info.cells.first, *this_cell = row_info->cells.first;;
last_cell = last_cell->next, this_cell = this_cell->next)
{
if(last_cell == 0 && this_cell == 0)
{
break;
}
if((last_cell == 0 && this_cell != 0) || (last_cell != 0 && this_cell == 0))
{
row_matches_last_row_topology = 0;
break;
}
if(rd_id_from_watch_cell(last_cell) != rd_id_from_watch_cell(this_cell))
{
row_matches_last_row_topology = 0;
break;
}
}
}
////////////////////////
//- rjf: store last row's info, for next iteration
//
last_row_info = *row_info;
////////////////////////
//- rjf: determine if row's data is fresh and/or bad
//
ProfBegin("determine if row's data is fresh and/or bad");
B32 row_is_fresh = 0;
B32 row_is_bad = 0;
switch(row->eval.irtree.mode)
{
default:{}break;
case E_Mode_Offset:
{
CTRL_Entity *space_entity = rd_ctrl_entity_from_eval_space(row->eval.space);
if(row->eval.space.kind == RD_EvalSpaceKind_CtrlEntity && space_entity->kind == CTRL_EntityKind_Process)
{
U64 size = e_type_byte_size_from_key(row->eval.irtree.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->handle, vaddr_rng, rd_state->frame_eval_memread_endt_us);
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;
}
ProfEnd();
////////////////////////
//- rjf: determine row's flags & color palette
//
ProfBegin("determine row's flags & color palette");
UI_BoxFlags row_flags = UI_BoxFlag_DisableFocusOverlay;
{
if(row_is_fresh)
{
ui_set_next_tag(str8_lit("fresh"));
row_flags |= UI_BoxFlag_DrawBackground;
}
else if(global_row_idx & 1)
{
ui_set_next_tag(str8_lit("alt"));
row_flags |= UI_BoxFlag_DrawBackground;
}
if(!row_matches_last_row_topology)
{
row_flags |= UI_BoxFlag_DrawSideTop;
}
}
ProfEnd();
////////////////////////
//- rjf: build row box
//
ui_set_next_flags(disabled_flags);
ui_set_next_pref_width(ui_px(contents_width_px, 1.f));
ui_set_next_pref_height(ui_px(row_height_px*row->visual_size, 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|((!row_node->next)*UI_BoxFlag_DrawSideBottom)|UI_BoxFlag_Clickable, "row_%I64x", row_hash);
RD_WatchRowExtrasDrawData *row_draw_data = push_array(ui_build_arena(), RD_WatchRowExtrasDrawData, 1);
row_draw_data->breaks_from_prev = !row_matches_last_row_topology;
ui_box_equip_custom_draw(row_box, rd_watch_row_extras_custom_draw, row_draw_data);
//////////////////////
//- rjf: build row contents
//
RD_RegsScope(.module = row_info->module->handle) UI_Parent(row_box)
{
////////////////////
//- rjf: draw start of cache lines in expansions
//
if(row->eval.space.kind == RD_EvalSpaceKind_CtrlEntity && row_info->view_ui_rule == &rd_nil_view_ui_rule)
{
CTRL_Entity *space_entity = rd_ctrl_entity_from_eval_space(row->eval.space);
if(space_entity->kind == CTRL_EntityKind_Process)
{
U64 row_offset = row->eval.value.u64;
if((row->eval.irtree.mode == E_Mode_Offset || row->eval.irtree.mode == E_Mode_Null) &&
row_offset%64 == 0 && row_depth > 0)
{
ui_set_next_fixed_x(0);
ui_set_next_fixed_y(0);
ui_set_next_fixed_height(ui_top_font_size()*0.2f);
ui_set_next_tag(str8_lit("pop"));
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.space.kind == RD_EvalSpaceKind_CtrlEntity && row_info->view_ui_rule == &rd_nil_view_ui_rule)
{
CTRL_Entity *space_entity = rd_ctrl_entity_from_eval_space(row->eval.space);
if(space_entity->kind == CTRL_EntityKind_Process &&
(row->eval.irtree.mode == E_Mode_Offset || row->eval.irtree.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.irtree.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(row_height_px - ui_top_font_size()*0.5f);
ui_set_next_fixed_height(ui_top_font_size()*1.f);
ui_set_next_tag(str8_lit("pop"));
ui_set_next_transparency(0.5f);
ui_build_box_from_key(UI_BoxFlag_Floating|UI_BoxFlag_DrawBackground, ui_key_zero());
}
}
}
//////////////
//- rjf: build all cells
//
S64 cell_x = 0;
F32 cell_x_px = 0;
for(RD_WatchCell *cell = row_info->cells.first; cell != 0; cell = cell->next, cell_x += 1)
{
if(row_depth > 0) { ui_push_tagf("weak"); }
////////////
//- rjf: unpack cell info
//
F32 cell_width_px = cell->px + cell->pct * (dim_2f32(rect).x - floor_f32(ui_top_font_size()*1.5f));
F32 next_cell_x_px = cell_x_px + cell_width_px;
F32 cell_width_strictness = 0.f;
if(cell->px != 0)
{
cell_width_strictness = 1.f;
}
F32 visual_row_string_max_size_px = cell_width_px * 1.5f;
if(cell->flags & RD_WatchCellFlag_Expr && !(cell->flags & RD_WatchCellFlag_NoEval))
{
visual_row_string_max_size_px /= 2.f;
}
U64 cell_id = rd_id_from_watch_cell(cell);
RD_WatchPt cell_pt = {row->block->key, row->key, cell_id};
RD_WatchViewTextEditState *cell_edit_state = rd_watch_view_text_edit_state_from_pt(ewv, cell_pt);
B32 cell_selected = (row_selected && selection_tbl.min.x <= cell_x && cell_x <= selection_tbl.max.x);
RD_WatchRowCellInfo cell_info = rd_info_from_watch_row_cell(scratch.arena, row, string_flags, row_info, cell, ui_top_font(), ui_top_font_size(), visual_row_string_max_size_px);
E_TypeKey cell_type_key = cell->eval.irtree.type_key;
E_Type *cell_type = e_type_from_key__cached(cell_type_key);
E_Eval cell_value_eval = e_value_eval_from_eval(cell->eval);
B32 cell_toggled = (cell_value_eval.value.u64 != 0);
B32 next_cell_toggled = cell_toggled;
////////////
//- rjf: compute slider parameters
//
E_Value cell_slider_min = zero_struct;
E_Value cell_slider_max = zero_struct;
E_TypeKind slider_value_type_kind = E_TypeKind_Null;
F32 cell_slider_value = 0.f;
if(str8_match(cell_type->name, str8_lit("range1"), 0) && cell_type->args != 0 && cell_type->count >= 2)
{
E_Key min_key = e_key_from_expr(cell_type->args[0]);
E_Key max_key = e_key_from_expr(cell_type->args[1]);
E_ParentKey(cell->eval.key)
{
E_TypeKey slider_value_type = e_type_key_unwrap(cell_type->direct_type_key, E_TypeUnwrapFlag_AllDecorative);
slider_value_type_kind = e_type_kind_from_key(slider_value_type);
String8 slider_type_name = e_type_string_from_key(scratch.arena, slider_value_type);
cell_slider_min = e_value_from_key(e_key_wrapf(min_key, "(%S)$", slider_type_name));
cell_slider_max = e_value_from_key(e_key_wrapf(max_key, "(%S)$", slider_type_name));
}
}
switch(slider_value_type_kind)
{
default:
if(e_type_kind_is_integer(slider_value_type_kind))
{
cell_slider_value = ((F32)(cell_value_eval.value.s64 - cell_slider_min.s64)) / (cell_slider_max.s64 - cell_slider_min.s64);
}break;
case E_TypeKind_F32:
{
cell_slider_value = (cell_value_eval.value.f32 - cell_slider_min.f32) / (cell_slider_max.f32 - cell_slider_min.f32);
}break;
case E_TypeKind_F64:
{
cell_slider_value = (F32)((cell_value_eval.value.f64 - cell_slider_min.f64) / (cell_slider_max.f64 - cell_slider_min.f64));
}break;
}
F32 next_cell_slider_value = cell_slider_value;
////////////
//- rjf: determine cell's palette
//
UI_BoxFlags cell_flags = 0;
Vec4F32 cell_background_color_override = {0};
String8 cell_tag = {0};
{
if(cell_info.cfg->id == rd_get_hover_regs()->cfg &&
rd_state->hover_regs_slot == RD_RegSlot_Cfg)
{
RD_Cfg *cfg = cell_info.cfg;
Vec4F32 rgba = rd_color_from_cfg(cfg);
rgba.w *= 0.05f;
if(rgba.w == 0)
{
rgba = pop_background_rgba;
rgba.w *= 0.5f;
}
rgba.w *= ui_anim(ui_key_from_stringf(ui_key_zero(), "###cfg_hover_t_%p", cfg), 1.f, .rate = entity_hover_t_rate);
cell_background_color_override = rgba;
cell_flags |= UI_BoxFlag_DrawBackground;
}
else if(ctrl_handle_match(cell_info.entity->handle, rd_get_hover_regs()->ctrl_entity) &&
rd_state->hover_regs_slot == RD_RegSlot_CtrlEntity)
{
CTRL_Entity *entity = cell_info.entity;
Vec4F32 rgba = rd_color_from_ctrl_entity(entity);
rgba.w *= 0.05f;
if(rgba.w == 0)
{
rgba = pop_background_rgba;
rgba.w *= 0.5f;
}
rgba.w *= ui_anim(ui_key_from_stringf(ui_key_zero(), "###entity_hover_t_%p", entity), 1.f, .rate = entity_hover_t_rate);
cell_background_color_override = rgba;
cell_flags |= UI_BoxFlag_DrawBackground;
}
}
////////////
//- rjf: build cell container
//
UI_Box *cell_box = &ui_nil_box;
UI_PrefWidth(ui_px(cell_width_px, cell_width_strictness))
{
ui_set_next_fixed_height(floor_f32(row->visual_size * row_height_px));
cell_box = ui_build_box_from_stringf(UI_BoxFlag_DrawSideLeft|cell_flags, "cell_%I64x_%I64x", row_hash, cell_id);
}
////////////
//- rjf: build cell contents
//
UI_Signal sig = {0};
ProfScope("build cell contents")
UI_Parent(cell_box)
UI_FocusHot(cell_selected ? UI_FocusKind_On : UI_FocusKind_Off)
UI_FocusActive((cell_selected && ewv->text_editing) ? UI_FocusKind_On : UI_FocusKind_Off)
RD_Font(RD_FontSlot_Code)
UI_TagF("weak")
UI_Tag(cell_tag)
{
//- rjf: cell has hook? -> build ui by calling hook
if(cell->kind == RD_WatchCellKind_ViewUI && cell_info.view_ui_rule != &rd_nil_view_ui_rule)
{
RD_Cfg *root = rd_immediate_cfg_from_keyf("view%I64x_%I64x", rd_regs()->view, row_hash);
RD_Cfg *view = rd_view_from_eval(root, cell->eval);
Rng2F32 cell_rect = r2f32p(cell_x_px, 0, next_cell_x_px, row_height_px*(row_node->visual_size_skipped + row->visual_size + row_node->visual_size_chopped));
ui_set_next_fixed_y(-1.f * (row_node->visual_size_skipped) * row_height_px);
ui_set_next_fixed_height((row_node->visual_size_skipped + row->visual_size + row_node->visual_size_chopped) * row_height_px);
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clip|UI_BoxFlag_Clickable|UI_BoxFlag_FloatingY, "###val_%I64x", row_hash);
UI_Parent(box)
RD_RegsScope(.view = view->id, .file_path = rd_file_path_from_eval(scratch.arena, cell->eval))
UI_PermissionFlags(UI_PermissionFlag_Clicks|UI_PermissionFlag_ScrollX)
UI_Flags(0)
{
// rjf: 'pull out' button
UI_TagF(".") UI_TagF("tab") UI_Rect(r2f32p(floor_f32(ui_top_font_size()*1.5f),
floor_f32(ui_top_font_size()*1.5f),
floor_f32(ui_top_font_size()*1.5f + ui_top_font_size()*3.f),
floor_f32(ui_top_font_size()*1.5f + ui_top_font_size()*3.f)))
UI_CornerRadius(floor_f32(ui_top_font_size()*1.5f))
UI_TextAlignment(UI_TextAlign_Center)
RD_Font(RD_FontSlot_Icons)
UI_FontSize(floor_f32(ui_top_font_size()*0.9f))
{
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|
UI_BoxFlag_Floating|
UI_BoxFlag_DrawText|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawActiveEffects|
UI_BoxFlag_DrawHotEffects,
"%S###pull_out",
rd_icon_kind_text_table[RD_IconKind_Window]);
UI_Signal sig = ui_signal_from_box(box);
if(ui_dragging(sig) && !contains_2f32(box->rect, ui_mouse()))
{
rd_drag_begin(RD_RegSlot_View);
}
}
// rjf: loading animation container
UI_Box *loading_overlay_container = &ui_nil_box;
UI_Parent(box) UI_WidthFill UI_HeightFill
{
loading_overlay_container = ui_build_box_from_key(UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY, ui_key_zero());
}
// rjf: view ui contents
E_ParentKey(cell->eval.key)
{
cell_info.view_ui_rule->ui(cell->eval, cell_rect);
}
// rjf: loading fill
UI_Parent(loading_overlay_container)
{
RD_ViewState *vs = rd_view_state_from_cfg(view);
rd_loading_overlay(cell_rect, vs->loading_t, vs->loading_progress_v, vs->loading_progress_v_target);
}
}
sig = ui_signal_from_box(box);
}
//- rjf: cell is call stack frame? -> build arrow if this is the selected frame, otherwise leave empty
else if(cell->kind == RD_WatchCellKind_CallStackFrame)
{
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "###%I64x_%I64x", cell_id, row_hash);
sig = ui_signal_from_box(box);
if(ctrl_handle_match(row_info->callstack_thread->handle, rd_base_regs()->thread) &&
row_info->callstack_unwind_index == rd_base_regs()->unwind_count &&
row_info->callstack_inline_depth == rd_base_regs()->inline_depth)
{
UI_Parent(box) UI_Flags(0) UI_TextAlignment(UI_TextAlign_Center)
{
Vec4F32 color = rd_color_from_ctrl_entity(row_info->callstack_thread);
RD_Font(RD_FontSlot_Icons)
UI_Flags(UI_BoxFlag_DisableTextTrunc)
UI_TextColor(color)
ui_label(rd_icon_kind_text_table[RD_IconKind_RightArrow]);
}
}
}
//- rjf: build general cell
else
{
// rjf: compute visual params
ProfBegin("compute visual params");
B32 cell_has_fancy_editors = (!(cell->flags & RD_WatchCellFlag_NoEval));
B32 is_button = !!(cell_info.flags & RD_WatchCellFlag_Button);
B32 has_background = !!(cell_info.flags & RD_WatchCellFlag_Background);
B32 is_toggle_switch = (cell_has_fancy_editors && cell->eval.irtree.mode != E_Mode_Null && e_type_kind_from_key(e_type_key_unwrap(cell->eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative)) == E_TypeKind_Bool);
B32 is_slider = (cell_has_fancy_editors && cell->eval.irtree.mode != E_Mode_Null && cell_type->kind == E_TypeKind_Lens && str8_match(cell_type->name, str8_lit("range1"), 0));
B32 is_activated_on_single_click = !!(cell_info.flags & RD_WatchCellFlag_ActivateWithSingleClick);
B32 is_non_code = !!(cell_info.flags & RD_WatchCellFlag_IsNonCode);
String8 ghost_text = {0};
if(cell_selected && ewv->text_editing && cell->flags & RD_WatchCellFlag_Expr && cell->flags & RD_WatchCellFlag_NoEval)
{
is_non_code = 0;
is_button = 0;
is_activated_on_single_click = 0;
}
ProfEnd();
// rjf: determine query needle
String8 needle = rd_view_query_input();
if(cell->eval.space.kind == E_SpaceKind_FileSystem)
{
needle = str8_skip_last_slash(needle);
}
// rjf: form cell build parameters
RD_CellParams cell_params = {0};
ProfScope("form cell build parameters")
{
// rjf: set up base parameters
cell_params.flags = (RD_CellFlag_KeyboardClickable|RD_CellFlag_NoBackground|RD_CellFlag_CodeContents);
cell_params.depth = (cell->flags & RD_WatchCellFlag_Indented ? row_depth : 0);
cell_params.cursor = &cell_edit_state->cursor;
cell_params.mark = &cell_edit_state->mark;
cell_params.edit_buffer = cell_edit_state->input_buffer;
cell_params.edit_buffer_size = sizeof(cell_edit_state->input_buffer);
cell_params.edit_string_size_out = &cell_edit_state->input_size;
cell_params.expanded_out = &next_row_expanded;
cell_params.search_needle = needle;
cell_params.meta_fstrs = cell_info.expr_fstrs;
cell_params.value_fstrs = cell_info.eval_fstrs;
if(row_height_px > ui_top_font_size()*3.5f)
{
cell_params.description = cell_info.description;
}
if(cell_selected && ewv->text_editing && cell->flags & RD_WatchCellFlag_NoEval)
{
MemoryZeroStruct(&cell_params.meta_fstrs);
MemoryZeroStruct(&cell_params.description);
}
// rjf: apply expander (or substitute space)
if(row_is_expandable && cell == row_info->cells.first)
{
cell_params.flags |= RD_CellFlag_Expander;
}
else if(row_depth == !implicit_root && cell == row_info->cells.first)
{
cell_params.flags |= RD_CellFlag_ExpanderSpace;
}
else if(row_depth != 0 && cell == row_info->cells.first)
{
cell_params.flags |= RD_CellFlag_ExpanderSpace;
}
// rjf: apply single-click-activation
if(is_activated_on_single_click)
{
cell_params.flags |= RD_CellFlag_SingleClickActivate;
}
// rjf: apply code styles
if(is_non_code)
{
cell_params.flags &= ~RD_CellFlag_CodeContents;
}
// rjf: apply button styles
if(is_button)
{
cell_params.flags |= RD_CellFlag_Button;
cell_params.flags &= ~RD_CellFlag_NoBackground;
if(row_depth == 0)
{
cell_params.flags &= ~RD_CellFlag_ExpanderSpace;
}
}
// rjf: apply background
if(has_background)
{
cell_params.flags &= ~RD_CellFlag_NoBackground;
}
// rjf: apply toggle-switch
if(is_toggle_switch)
{
cell_params.flags |= RD_CellFlag_ToggleSwitch;
cell_params.toggled_out = &next_cell_toggled;
}
// rjf: apply slider
if(is_slider)
{
cell_params.flags |= RD_CellFlag_Slider;
cell_params.slider_value_out = &next_cell_slider_value;
}
// rjf: apply bindings
if(cell->px == 0 && cell->eval.space.kind == RD_EvalSpaceKind_MetaCmd)
{
cell_params.flags |= RD_CellFlag_Bindings;
cell_params.bindings_name = rd_cmd_name_from_eval(cell->eval);
}
}
// rjf: build
if(cell_background_color_override.w != 0)
{
ui_push_background_color(cell_background_color_override);
}
UI_TextAlignment(cell->px != 0 ? UI_TextAlign_Center : UI_TextAlign_Left)
RD_Font(is_non_code ? RD_FontSlot_Main : RD_FontSlot_Code)
{
sig = rd_cellf(&cell_params, "%S###%I64x_row_%I64x", ghost_text, cell_x, row_hash);
}
if(cell_background_color_override.w != 0)
{
ui_pop_background_color();
}
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);
rd_set_autocomp_regs(.ui_key = sig.box->key, .string = input, .cursor = cell_edit_state->cursor);
}
}
}
////////////
//- rjf: handle interactions
//
{
// rjf: hover -> rich hover cfgs
if(ui_hovering(sig) && cell_info.cfg != &rd_nil_cfg)
{
RD_RegsScope(.cfg = cell_info.cfg->id, .no_rich_tooltip = 1) rd_set_hover_regs(RD_RegSlot_Cfg);
}
// rjf: hover -> rich hover entities
if(ui_hovering(sig) && cell_info.entity != &ctrl_entity_nil)
{
RD_RegsScope(.ctrl_entity = cell_info.entity->handle, .no_rich_tooltip = 1) rd_set_hover_regs(RD_RegSlot_CtrlEntity);
}
// rjf: dragging -> drag/drop
if(ui_dragging(sig) && !contains_2f32(sig.box->rect, ui_mouse()) &&
(!cell_selected || !ewv->text_editing))
{
if(cell->eval.space.kind == E_SpaceKind_FileSystem)
{
String8 file_path = rd_file_path_from_eval(scratch.arena, cell->eval);
RD_RegsScope(.file_path = file_path) rd_drag_begin(RD_RegSlot_FilePath);
}
else if(cell_info.cfg != &rd_nil_cfg)
{
RD_RegsScope(.cfg = cell_info.cfg->id) rd_drag_begin(RD_RegSlot_Cfg);
}
else if(cell_info.entity != &ctrl_entity_nil)
{
RD_RegsScope(.ctrl_entity = cell_info.entity->handle) switch(cell_info.entity->kind)
{
default:{rd_drag_begin(RD_RegSlot_CtrlEntity);}break;
case CTRL_EntityKind_Machine:{RD_RegsScope(.machine = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Machine);}break;
case CTRL_EntityKind_Process:{RD_RegsScope(.process = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Process);}break;
case CTRL_EntityKind_Module:{RD_RegsScope(.module = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Module);}break;
case CTRL_EntityKind_Thread:{RD_RegsScope(.thread = cell_info.entity->handle) rd_drag_begin(RD_RegSlot_Thread);}break;
}
}
else if(cell->eval.space.kind == RD_EvalSpaceKind_CtrlEntity ||
cell->eval.space.kind == E_SpaceKind_FileSystem ||
cell->eval.space.kind == E_SpaceKind_File ||
cell->eval.space.kind == E_SpaceKind_Null)
{
RD_RegsScope(.expr = e_full_expr_string_from_key(scratch.arena, cell->eval.key))
{
if(cell->flags & RD_WatchCellFlag_Expr)
{
rd_regs()->cfg = row_info->group_cfg_child->id;
}
rd_drag_begin(RD_RegSlot_Expr);
}
}
}
// rjf: (normally) single-click -> move selection here
if(!(cell_info.flags & RD_WatchCellFlag_ActivateWithSingleClick) && ui_pressed(sig))
{
ewv->next_cursor = ewv->next_mark = cell_pt;
pressed = 1;
}
// rjf: activation (double-click normally, or single-clicks with special buttons)
if((!(cell_info.flags & RD_WatchCellFlag_ActivateWithSingleClick) && ui_double_clicked(sig)) ||
((cell_info.flags & RD_WatchCellFlag_ActivateWithSingleClick) && ui_clicked(sig)) ||
sig.f & UI_SignalFlag_KeyboardPressed)
{
// rjf: kill if a double-clickable cell
if(!(cell_info.flags & RD_WatchCellFlag_ActivateWithSingleClick))
{
ui_kill_action();
}
// rjf: this watch window is a lister? -> move cursor & accept
if(rd_cfg_child_from_string(view, str8_lit("lister")) != &rd_nil_cfg ||
rd_cfg_child_from_string(view, str8_lit("autocomplete")) != &rd_nil_cfg)
{
ewv->next_cursor = ewv->next_mark = cell_pt;
rd_cmd(RD_CmdKind_Accept);
}
// rjf: has a command name? -> push command
else if(cell_info.cmd_name.size != 0)
{
String8 cmd_name = cell_info.cmd_name;
RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name);
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(row->eval.space);
RD_Cfg *cfg = rd_cfg_from_eval_space(row->eval.space);
if(cfg == &rd_nil_cfg)
{
cfg = rd_cfg_from_eval_space(row->block->eval.space);
}
if(entity == &ctrl_entity_nil)
{
entity = rd_ctrl_entity_from_eval_space(row->eval.space);
}
RD_RegsScope(.cfg = cfg->id, .ctrl_entity = entity->handle)
{
if(!(cmd_kind_info->query.flags & RD_QueryFlag_Required) ||
(cmd_kind_info->query.slot == RD_RegSlot_Cfg && cfg != &rd_nil_cfg) ||
(cmd_kind_info->query.slot == RD_RegSlot_CtrlEntity && entity != &ctrl_entity_nil))
{
rd_push_cmd(cell_info.cmd_name, rd_regs());
}
else
{
rd_cmd(RD_CmdKind_RunCommand, .cmd_name = cmd_name);
}
}
}
// rjf: row has callstack info? -> select unwind
else if(row_info->callstack_thread != &ctrl_entity_nil)
{
rd_cmd(RD_CmdKind_SelectThread, .thread = row_info->callstack_thread->handle);
rd_cmd(RD_CmdKind_SelectUnwind,
.unwind_count = row_info->callstack_unwind_index,
.inline_depth = row_info->callstack_inline_depth);
}
// rjf: can edit? -> begin editing
else if(!(sig.f & UI_SignalFlag_KeyboardPressed) && cell_info.flags & RD_WatchCellFlag_CanEdit)
{
ewv->next_cursor = ewv->next_mark = cell_pt;
rd_cmd(RD_CmdKind_Edit);
}
// rjf: can expand? -> expand
else if(sig.f & UI_SignalFlag_KeyboardPressed && row_is_expandable)
{
next_row_expanded = !row_expanded;
}
// rjf: can't edit, but has address info? -> go to address
else if(cell->eval.space.kind == RD_EvalSpaceKind_CtrlEntity)
{
CTRL_Entity *entity = rd_ctrl_entity_from_eval_space(cell->eval.space);
CTRL_Entity *process = ctrl_process_from_entity(entity);
if(process != &ctrl_entity_nil)
{
U64 vaddr = cell->eval.value.u64;
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;
rd_cmd(RD_CmdKind_FindCodeLocation,
.process = process->handle,
.vaddr = vaddr,
.file_path = file_path,
.cursor = pt);
}
}
}
// rjf: can't edit, but has cfg? -> find or select
else if(cell_info.cfg != &rd_nil_cfg)
{
RD_Cfg *cfg = cell_info.cfg;
RD_Location loc = rd_location_from_cfg(cfg);
if(loc.file_path.size != 0)
{
rd_cmd(RD_CmdKind_FindCodeLocation, .vaddr = 0, .file_path = loc.file_path, .cursor = loc.pt);
}
else if(loc.expr.size != 0)
{
U64 value = e_value_from_string(loc.expr).u64;
rd_cmd(RD_CmdKind_FindCodeLocation, .vaddr = value);
}
else if(str8_match(cfg->string, str8_lit("target"), 0) && sig.event_flags & OS_Modifier_Ctrl)
{
rd_cmd(RD_CmdKind_EnableCfg, .cfg = cfg->id);
}
else if(str8_match(cfg->string, str8_lit("target"), 0))
{
rd_cmd(RD_CmdKind_SelectCfg, .cfg = cfg->id);
}
}
// rjf: can't edit, but has thread? -> select
else if(cell_info.entity->kind == CTRL_EntityKind_Thread)
{
rd_cmd(RD_CmdKind_SelectThread, .thread = cell_info.entity->handle);
}
// rjf: other cases, but this watch window is floating? -> push query
else if(view_is_floating)
{
rd_cmd(RD_CmdKind_PushQuery, .expr = cell->eval.string);
}
}
// rjf: hovering with inheritance string -> show tooltip
if(ui_hovering(sig) && cell_info.inheritance_tooltip.size != 0) UI_Tooltip
{
UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(1, 1)) UI_TextPadding(0)
{
ui_labelf("Inherited from ");
RD_Font(RD_FontSlot_Code) rd_code_label(1.f, 0, ui_color_from_name(str8_lit("code_default")), cell_info.inheritance_tooltip);
}
}
// rjf: hovering with error tooltip -> show tooltip
if(ui_hovering(sig) && cell_info.error_tooltip.size != 0) UI_Tooltip
{
UI_PrefWidth(ui_children_sum(1)) rd_error_label(cell_info.error_tooltip);
}
}
////////////
//- rjf: commit toggle changes
//
if(next_cell_toggled != cell_toggled)
{
rd_commit_eval_value_string(cell->eval, next_cell_toggled ? str8_lit("1") : str8_lit("0"));
}
////////////
//- rjf: commit slider changes
//
if(next_cell_slider_value != cell_slider_value)
{
String8 new_value_string = {0};
switch(slider_value_type_kind)
{
default:
if(e_type_kind_is_integer(slider_value_type_kind))
{
S64 new_value = (S64)((next_cell_slider_value * (cell_slider_max.s64 - cell_slider_min.s64)) + cell_slider_min.s64);
new_value = Clamp(cell_slider_min.s64, new_value, cell_slider_max.s64);
new_value_string = push_str8f(scratch.arena, "%I64d", new_value);
}break;
case E_TypeKind_F32:
{
F32 new_value = (next_cell_slider_value * (cell_slider_max.f32 - cell_slider_min.f32)) + cell_slider_min.f32;
new_value = Clamp(cell_slider_min.f32, new_value, cell_slider_max.f32);
new_value_string = push_str8f(scratch.arena, "%f", new_value);
}break;
case E_TypeKind_F64:
{
F64 new_value = (F64)((next_cell_slider_value * (cell_slider_max.f64 - cell_slider_min.f64)) + cell_slider_min.f64);
new_value = Clamp(cell_slider_min.f64, new_value, cell_slider_max.f64);
new_value_string = push_str8f(scratch.arena, "%f", new_value);
}break;
}
rd_commit_eval_value_string(cell->eval, new_value_string);
}
////////////
//- rjf: bump x pixel coordinate
//
cell_x_px = next_cell_x_px;
if(row_depth > 0) { ui_pop_tag(); }
}
}
//////////////////////
//- rjf: commit expansion state changes
//
if(next_row_expanded != row_expanded)
{
if(!ev_key_match(ev_key_root(), row->key))
{
ev_key_set_expansion(eval_view, row->block->key, row->key, next_row_expanded);
}
}
}
}
}
}
}
//////////////////////////////
//- rjf: general table-wide press logic
//
if(pressed)
{
rd_cmd(RD_CmdKind_FocusPanel);
}
//////////////////////////////
//- rjf: disable query if text editing is occurring
//
vs->contents_are_focused = ewv->text_editing;
rd_store_view_scroll_pos(scroll_pos);
}
scratch_end(scratch);
}
////////////////////////////
//- rjf: visualizer hook
//
else
{
Temp scratch = scratch_begin(0, 0);
RD_ViewUIRule *view_ui_rule = rd_view_ui_rule_from_string(view_name);
E_Eval expr_eval = e_eval_from_string(expr_string);
E_ParentKey(expr_eval.key)
{
view_ui_rule->ui(expr_eval, rect);
}
scratch_end(scratch);
}
}
////////////////////////////
//- rjf: catchall completion controls
//
if(vs->query_is_selected) UI_Focus(UI_FocusKind_On)
{
if(ui_is_focus_active() && ui_slot_press(UI_EventActionSlot_Cancel))
{
vs->query_is_selected = 0;
vs->query_string_size = 0;
}
if(ui_is_focus_active() && ui_slot_press(UI_EventActionSlot_Accept))
{
String8 cmd_name = rd_view_query_cmd();
String8 input = rd_view_query_input();
RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name);
RD_RegsScope()
{
rd_regs_fill_slot_from_string(cmd_kind_info->query.slot, input);
rd_cmd(RD_CmdKind_CompleteQuery);
}
}
}
vs->last_frame_index_built = rd_state->frame_index;
ProfEnd();
}
////////////////////////////////
//~ rjf: View Building API
//- rjf: view info extraction
internal Arena *
rd_view_arena(void)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_ViewState *view_state = rd_view_state_from_cfg(view);
return view_state->arena;
}
internal UI_ScrollPt2
rd_view_scroll_pos(void)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_ViewState *view_state = rd_view_state_from_cfg(view);
return view_state->scroll_pos;
}
internal EV_View *
rd_view_eval_view(void)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_ViewState *view_state = rd_view_state_from_cfg(view);
return view_state->ev_view;
}
internal String8
rd_view_query_cmd(void)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_Cfg *query = rd_cfg_child_from_string(view, str8_lit("query"));
RD_Cfg *cmd = rd_cfg_child_from_string(query, str8_lit("cmd"));
String8 string = cmd->first->string;
return string;
}
internal String8
rd_view_query_input(void)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_Cfg *query = rd_cfg_child_from_string(view, str8_lit("query"));
RD_Cfg *input = rd_cfg_child_from_string(query, str8_lit("input"));
String8 string = input->first->string;
return string;
}
internal RD_Cfg *
rd_view_cfg_from_string(String8 string)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_Cfg *cfg = rd_cfg_child_from_string(view, string);
return cfg;
}
internal E_Value
rd_view_cfg_value_from_string(String8 string)
{
RD_Cfg *root = rd_view_cfg_from_string(string);
String8 expr = root->first->string;
E_Eval eval = e_eval_from_string(expr);
E_Value result = e_value_eval_from_eval(eval).value;
return result;
}
internal B32
rd_view_cfg_b32_from_string(String8 string)
{
RD_Cfg *root = rd_view_cfg_from_string(string);
B32 result = !!e_value_from_stringf("(bool)(%S)", root->first->string).u64;
return result;
}
internal U64
rd_view_cfg_u64_from_string(String8 string)
{
RD_Cfg *root = rd_view_cfg_from_string(string);
U64 result = e_value_from_stringf("(uint64)(%S)", root->first->string).u64;
return result;
}
internal F32
rd_view_cfg_f32_from_string(String8 string)
{
RD_Cfg *root = rd_view_cfg_from_string(string);
F32 result = e_value_from_stringf("(float32)(%S)", root->first->string).f32;
return result;
}
//- rjf: evaluation & tag (a view's 'call') parameter extraction
internal TXT_LangKind
rd_lang_kind_from_eval(E_Eval eval)
{
TXT_LangKind lang_kind = TXT_LangKind_Null;
Temp scratch = scratch_begin(0, 0);
String8 file_path = rd_file_path_from_eval(scratch.arena, eval);
if(file_path.size != 0)
{
lang_kind = txt_lang_kind_from_extension(str8_skip_last_dot(file_path));
}
if(lang_kind == TXT_LangKind_Null)
{
E_Type *type = e_type_from_key__cached(eval.irtree.type_key);
if(type->kind == E_TypeKind_Lens)
{
for EachIndex(idx, type->count)
{
E_Expr *arg = type->args[idx];
if(arg->kind == E_ExprKind_Define && str8_match(arg->first->string, str8_lit("lang"), 0))
{
lang_kind = txt_lang_kind_from_extension(arg->first->next->string);
break;
}
}
}
}
scratch_end(scratch);
return lang_kind;
}
internal Arch
rd_arch_from_eval(E_Eval eval)
{
// rjf: try implicitly from either `eval` itself, or from context
CTRL_Entity *ctrl_entity = rd_ctrl_entity_from_eval_space(eval.space);
CTRL_Entity *process = ctrl_process_from_entity(ctrl_entity);
if(process == &ctrl_entity_nil)
{
process = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->process);
}
Arch arch = process->arch;
if(arch == Arch_Null)
{
arch = arch_from_context();
}
// rjf: try arch arguments
E_Type *type = e_type_from_key__cached(eval.irtree.type_key);
if(type->kind == E_TypeKind_Lens)
{
for EachIndex(idx, type->count)
{
E_Expr *arg = type->args[idx];
{
String8 arg_arch_string = arg->string;
if(arg->kind == E_ExprKind_Define && str8_match(arg->first->string, str8_lit("arch"), 0))
{
arg_arch_string = arg->first->next->string;
}
if(str8_match(arg->first->next->string, str8_lit("x64"), 0))
{
arch = Arch_x64;
break;
}
}
}
}
return arch;
}
internal R_Tex2DFormat
rd_tex2dformat_from_eval(E_Eval eval)
{
R_Tex2DFormat fmt = R_Tex2DFormat_RGBA8;
B32 got_fmt = 0;
// rjf: try explicitly passed formats
E_Type *type = e_type_from_key__cached(eval.irtree.type_key);
if(type->kind == E_TypeKind_Lens)
{
for EachIndex(idx, type->count)
{
E_Expr *arg = type->args[idx];
if(arg->kind == E_ExprKind_Define && str8_match(arg->first->string, str8_lit("fmt"), 0))
{
got_fmt = 1;
for EachEnumVal(R_Tex2DFormat, f)
{
if(str8_match(arg->first->next->string, r_tex2d_format_display_string_table[f], StringMatchFlag_CaseInsensitive))
{
fmt = f;
break;
}
}
}
}
}
// rjf: try implicit non-define arguments
if(type->kind == E_TypeKind_Lens)
{
for EachIndex(idx, type->count)
{
E_Expr *arg = type->args[idx];
if(arg->kind == E_ExprKind_LeafIdentifier)
{
for EachEnumVal(R_Tex2DFormat, f)
{
if(str8_match(arg->string, r_tex2d_format_display_string_table[f], StringMatchFlag_CaseInsensitive))
{
fmt = f;
break;
}
}
}
}
}
return fmt;
}
//- rjf: pushing/attaching view resources
internal void *
rd_view_state_by_size(U64 size)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_ViewState *view_state = rd_view_state_from_cfg(view);
if(view_state->user_data == 0)
{
view_state->user_data = push_array(view_state->arena, U8, size);
}
return view_state->user_data;
}
internal Arena *
rd_push_view_arena(void)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_ViewState *view_state = rd_view_state_from_cfg(view);
RD_ArenaExt *ext = push_array(view_state->arena, RD_ArenaExt, 1);
ext->arena = arena_alloc();
SLLQueuePush(view_state->first_arena_ext, view_state->last_arena_ext, ext);
return ext->arena;
}
//- rjf: storing view-attached state
internal void
rd_store_view_expr_string(String8 string)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_Cfg *expr = rd_cfg_child_from_string_or_alloc(view, str8_lit("expression"));
rd_cfg_new_replace(expr, string);
}
internal void
rd_store_view_loading_info(B32 is_loading, U64 progress_u64, U64 progress_u64_target)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_ViewState *view_state = rd_view_state_from_cfg(view);
view_state->loading_t_target = (F32)!!is_loading;
view_state->loading_progress_v = progress_u64;
view_state->loading_progress_v_target = progress_u64_target;
if(view_state->last_frame_index_built+1 < rd_state->frame_index)
{
view_state->loading_t = view_state->loading_t_target;
}
}
internal void
rd_store_view_scroll_pos(UI_ScrollPt2 pos)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_ViewState *view_state = rd_view_state_from_cfg(view);
view_state->scroll_pos = pos;
}
internal void
rd_store_view_param(String8 key, String8 value)
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_Cfg *child = rd_cfg_child_from_string_or_alloc(view, key);
rd_cfg_new_replace(child, value);
}
internal void
rd_store_view_paramf(String8 key, char *fmt, ...)
{
Temp scratch = scratch_begin(0, 0);
va_list args;
va_start(args, fmt);
String8 string = push_str8fv(scratch.arena, fmt, args);
rd_store_view_param(key, string);
va_end(args);
scratch_end(scratch);
}
////////////////////////////////
//~ rjf: Window Functions
internal RD_Cfg *
rd_window_from_cfg(RD_Cfg *cfg)
{
RD_Cfg *result = &rd_nil_cfg;
for(RD_Cfg *c = cfg; c != &rd_nil_cfg; c = c->parent)
{
if(c->parent->parent == rd_state->root_cfg && str8_match(c->string, str8_lit("window"), 0))
{
result = c;
break;
}
}
return result;
}
internal RD_WindowState *
rd_window_state_from_cfg(RD_Cfg *cfg)
{
//- rjf: unpack
RD_Cfg *window_cfg = rd_window_from_cfg(cfg);
RD_CfgID id = window_cfg->id;
//- rjf: scan for existing window
RD_WindowState *ws = &rd_nil_window_state;
if(id != 0 &&
id == rd_state->window_state_last_accessed_id &&
id == rd_state->window_state_last_accessed->cfg_id)
{
ws = rd_state->window_state_last_accessed;
}
else
{
U64 hash = d_hash_from_string(str8_struct(&id));
U64 slot_idx = hash%rd_state->window_state_slots_count;
RD_WindowStateSlot *slot = &rd_state->window_state_slots[slot_idx];
for(RD_WindowState *w = slot->first; w != 0; w = w->hash_next)
{
if(w->cfg_id == id)
{
ws = w;
break;
}
}
}
//- rjf: allocate/open new window if one was not found
if(window_cfg != &rd_nil_cfg && ws == &rd_nil_window_state)
{
Temp scratch = scratch_begin(0, 0);
rd_state->frame_depth += 1;
// rjf: unpack configuration options
B32 has_pos = 0;
Vec2F32 pos = {0};
Vec2F32 size = {0};
OS_Handle preferred_monitor = {0};
{
RD_Cfg *pos_cfg = rd_cfg_child_from_string(window_cfg, str8_lit("pos"));
has_pos = (pos_cfg != &rd_nil_cfg);
RD_Cfg *size_cfg = rd_cfg_child_from_string(window_cfg, str8_lit("size"));
RD_Cfg *monitor_cfg = rd_cfg_child_from_string(window_cfg, str8_lit("monitor"));
pos.x = (F32)f64_from_str8(pos_cfg->first->string);
pos.y = (F32)f64_from_str8(pos_cfg->first->next->string);
size.x = (F32)f64_from_str8(size_cfg->first->string);
size.y = (F32)f64_from_str8(size_cfg->first->next->string);
OS_HandleArray monitors = os_push_monitors_array(scratch.arena);
for EachIndex(idx, monitors.count)
{
String8 monitor_name = os_name_from_monitor(scratch.arena, monitors.v[idx]);
if(str8_match(monitor_name, monitor_cfg->first->string, StringMatchFlag_CaseInsensitive))
{
preferred_monitor = monitors.v[idx];
break;
}
}
}
// rjf: allocate window
ws = rd_state->free_window_state;
if(ws != 0)
{
SLLStackPop_N(rd_state->free_window_state, order_next);
}
else
{
ws = push_array_no_zero(rd_state->arena, RD_WindowState, 1);
}
MemoryZeroStruct(ws);
// rjf: fill out window
ws->cfg_id = id;
ws->arena = arena_alloc();
{
String8 title = str8_lit_comp(BUILD_TITLE_STRING_LITERAL);
ws->os = os_window_open(r2f32p(pos.x, pos.y, pos.x+size.x, pos.y+size.y), (!has_pos*OS_WindowFlag_UseDefaultPosition)|OS_WindowFlag_CustomBorder, title);
}
ws->r = r_window_equip(ws->os);
ws->ui = ui_state_alloc();
ws->drop_completion_arena = arena_alloc();
ws->query_arena = arena_alloc();
ws->hover_eval_arena = arena_alloc();
ws->autocomp_arena = arena_alloc();
ws->last_dpi = os_dpi_from_window(ws->os);
OS_Handle zero_monitor = {0};
if(!os_handle_match(zero_monitor, preferred_monitor))
{
os_window_set_monitor(ws->os, preferred_monitor);
}
if(rd_cfg_child_from_string(window_cfg, str8_lit("fullscreen")) != &rd_nil_cfg)
{
os_window_set_fullscreen(ws->os, 1);
}
if(rd_cfg_child_from_string(window_cfg, str8_lit("maximized")) != &rd_nil_cfg)
{
os_window_set_maximized(ws->os, 1);
}
// rjf: hook up window links
U64 hash = d_hash_from_string(str8_struct(&id));
U64 slot_idx = hash%rd_state->window_state_slots_count;
RD_WindowStateSlot *slot = &rd_state->window_state_slots[slot_idx];
DLLPushBack_NPZ(&rd_nil_window_state, rd_state->first_window_state, rd_state->last_window_state, ws, order_next, order_prev);
DLLPushBack_NP(slot->first, slot->last, ws, hash_next, hash_prev);
rd_state->frame_depth -= 1;
scratch_end(scratch);
}
//- rjf: touch window for this frame
if(ws != &rd_nil_window_state)
{
ws->last_frame_index_touched = rd_state->frame_index;
}
rd_state->window_state_last_accessed_id = ws->cfg_id;
rd_state->window_state_last_accessed = ws;
return ws;
}
internal RD_WindowState *
rd_window_state_from_os_handle(OS_Handle os)
{
RD_WindowState *ws = &rd_nil_window_state;
{
for(RD_WindowState *w = rd_state->first_window_state;
w != &rd_nil_window_state;
w = w->order_next)
{
if(os_handle_match(w->os, os))
{
ws = w;
break;
}
}
}
return ws;
}
#if COMPILER_MSVC && !BUILD_DEBUG
#pragma optimize("", off)
#endif
internal void
rd_window_frame(void)
{
Temp scratch = scratch_begin(0, 0);
ProfBeginFunction();
//////////////////////////////
//- rjf: @window_frame_part unpack context
//
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(rd_cfg_from_id(rd_regs()->window));
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
B32 window_is_focused = (os_window_is_focused(ws->os) || ws->window_temporarily_focused_ipc);
B32 popup_is_open = (rd_state->popup_active);
B32 query_is_open = (ws->query_is_active);
U64 hover_eval_open_delay_us = 400000;
B32 hover_eval_is_open = (!popup_is_open &&
!query_is_open &&
ws->hover_eval_string.size != 0 &&
ws->hover_eval_firstt_us+hover_eval_open_delay_us < ws->hover_eval_lastt_us &&
rd_state->time_in_us - ws->hover_eval_lastt_us < hover_eval_open_delay_us);
if(!window_is_focused || popup_is_open)
{
ws->menu_bar_key_held = 0;
}
ws->window_temporarily_focused_ipc = 0;
ui_select_state(ws->ui);
//////////////////////////////
//- rjf: @window_frame_part compute window's theme
//
{
//- rjf: for this window, scan upwards, and then try the project, then the user, until we
// find explicit preset / colors trees in the config. we will prefer the tightest ones, so
// that windows can have their own colors, and have those override higher-up settings.
RD_Cfg *preset_cfg = &rd_nil_cfg;
RD_CfgList colors_cfgs = {0};
RD_Cfg *scan_parents[] = {window, rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("project")), rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"))};
for EachElement(idx, scan_parents)
{
RD_Cfg *parent_cfg = scan_parents[idx];
if(preset_cfg != &rd_nil_cfg)
{
RD_Cfg *possible_preset_cfg = rd_cfg_child_from_string(parent_cfg, str8_lit("color_preset"));
if(possible_preset_cfg != &rd_nil_cfg)
{
preset_cfg = possible_preset_cfg;
}
}
for(RD_Cfg *child = parent_cfg->first; child != &rd_nil_cfg; child = child->next)
{
if(str8_match(child->string, str8_lit("theme_color"), 0))
{
rd_cfg_list_push_front(scratch.arena, &colors_cfgs, child);
}
}
}
//- rjf: map the preset config to the associated preset tree
MD_Node *preset_tree = rd_state->theme_preset_trees[RD_ThemePreset_DefaultDark];
if(preset_cfg != &rd_nil_cfg)
{
String8 preset_name = preset_cfg->first->string;
for EachEnumVal(RD_ThemePreset, p)
{
if(str8_match(preset_name, rd_theme_preset_code_string_table[p], 0))
{
preset_tree = rd_state->theme_preset_trees[p];
break;
}
}
}
//- rjf: build tasks for color applications - each task comprises of a metadesk
// tree, describing the color patterns
typedef struct ThemeTask ThemeTask;
struct ThemeTask
{
ThemeTask *next;
MD_Node *tree;
};
ThemeTask start_task = {0, preset_tree};
ThemeTask *first_task = &start_task;
ThemeTask *last_task = first_task;
{
for(RD_CfgNode *n = colors_cfgs.first; n != 0; n = n->next)
{
ThemeTask *t = push_array(scratch.arena, ThemeTask, 1);
SLLQueuePushFront(first_task, last_task, t);
t->tree = md_tree_from_string(scratch.arena, rd_string_from_cfg_tree(scratch.arena, str8_zero(), n->v));
}
}
//- rjf: apply theme tasks, build each color pattern for this window's
// structured theme
typedef struct ThemePatternNode ThemePatternNode;
struct ThemePatternNode
{
ThemePatternNode *next;
UI_ThemePattern pattern;
};
ThemePatternNode *first_pattern = 0;
ThemePatternNode *last_pattern = 0;
U64 pattern_count = 0;
for(ThemeTask *t = first_task; t != 0; t = t->next)
{
MD_Node *tree_root = t->tree;
for(MD_Node *n = tree_root; !md_node_is_nil(n); n = md_node_rec_depth_first_pre(n, tree_root).next)
{
if(str8_match(n->string, str8_lit("theme_color"), 0))
{
MD_Node *tags_child = md_child_from_string(n, str8_lit("tags"), 0);
MD_Node *value_child = md_child_from_string(n, str8_lit("value"), 0);
U8 split_char = ' ';
String8List tags = str8_split(scratch.arena, tags_child->first->string, &split_char, 1, 0);
U32 color_u32 = e_value_from_string(value_child->first->string).u32;
Vec4F32 color_linear = linear_from_srgba(rgba_from_u32(color_u32));
ThemePatternNode *node = push_array(scratch.arena, ThemePatternNode, 1);
node->pattern.tags = str8_array_from_list(rd_frame_arena(), &tags);
node->pattern.linear = color_linear;
SLLQueuePush(first_pattern, last_pattern, node);
pattern_count += 1;
}
}
}
//- rjf: convert to final pattern array
ws->theme = push_array(rd_frame_arena(), UI_Theme, 1);
ws->theme->patterns_count = pattern_count;
ws->theme->patterns = push_array(rd_frame_arena(), UI_ThemePattern, ws->theme->patterns_count);
{
U64 idx = 0;
for(ThemePatternNode *n = first_pattern; n != 0; n = n->next, idx += 1)
{
ws->theme->patterns[idx] = n->pattern;
}
}
}
//////////////////////////////
//- rjf: @window_frame_part compute window's font raster flags
//
{
ws->font_slot_raster_flags[RD_FontSlot_Icons] = FNT_RasterFlag_Smooth;
ws->font_slot_raster_flags[RD_FontSlot_Main] = (rd_setting_b32_from_name(str8_lit("smooth_ui_text"))*FNT_RasterFlag_Smooth)|(rd_setting_b32_from_name(str8_lit("hint_ui_text"))*FNT_RasterFlag_Hinted);
ws->font_slot_raster_flags[RD_FontSlot_Code] = (rd_setting_b32_from_name(str8_lit("smooth_code_text"))*FNT_RasterFlag_Smooth)|(rd_setting_b32_from_name(str8_lit("hint_code_text"))*FNT_RasterFlag_Hinted);
}
//////////////////////////////
//- rjf: @window_frame_part pre-emptively rasterize common glyphs on the first frame
//
if(rd_state->first_window_state == ws && rd_state->last_window_state == ws && ws->frames_alive == 0)
{
F32 font_size = rd_setting_f32_from_name(str8_lit("font_size"));
RD_FontSlot english_font_slots[] = {RD_FontSlot_Main, RD_FontSlot_Code};
RD_FontSlot icon_font_slot = RD_FontSlot_Icons;
for(U64 idx = 0; idx < ArrayCount(english_font_slots); idx += 1)
{
Temp scratch = scratch_begin(0, 0);
RD_FontSlot slot = english_font_slots[idx];
String8 sample_text = str8_lit("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890~!@#$%^&*()-_+=[{]}\\|;:'\",<.>/?");
fnt_run_from_string(rd_font_from_slot(slot),
font_size,
0, 0, 0,
sample_text);
fnt_run_from_string(rd_font_from_slot(slot),
font_size,
0, 0, 0,
sample_text);
scratch_end(scratch);
}
for(RD_IconKind icon_kind = RD_IconKind_Null; icon_kind < RD_IconKind_COUNT; icon_kind = (RD_IconKind)(icon_kind+1))
{
Temp scratch = scratch_begin(0, 0);
fnt_run_from_string(rd_font_from_slot(icon_font_slot),
font_size,
0, 0, FNT_RasterFlag_Smooth,
rd_icon_kind_text_table[icon_kind]);
fnt_run_from_string(rd_font_from_slot(icon_font_slot),
font_size,
0, 0, FNT_RasterFlag_Smooth,
rd_icon_kind_text_table[icon_kind]);
fnt_run_from_string(rd_font_from_slot(icon_font_slot),
font_size,
0, 0, FNT_RasterFlag_Smooth,
rd_icon_kind_text_table[icon_kind]);
scratch_end(scratch);
}
}
//////////////////////////////
//- rjf: @window_frame_part commit window's position/status to underlying cfg tree
//
{
Temp scratch = scratch_begin(0, 0);
B32 is_fullscreen = os_window_is_fullscreen(ws->os);
B32 is_maximized = os_window_is_maximized(ws->os);
B32 is_minimized = os_window_is_minimized(ws->os);
if(is_fullscreen)
{
rd_cfg_child_from_string_or_alloc(window, str8_lit("fullscreen"));
}
else
{
rd_cfg_release(rd_cfg_child_from_string(window, str8_lit("fullscreen")));
}
if(is_maximized)
{
rd_cfg_child_from_string_or_alloc(window, str8_lit("maximized"));
}
else
{
rd_cfg_release(rd_cfg_child_from_string(window, str8_lit("maximized")));
}
//- rjf: commit position
Rng2F32 window_rect = os_rect_from_window(ws->os);
if(!is_fullscreen && !is_maximized && !is_minimized)
{
Vec2F32 pos = window_rect.p0;
RD_Cfg *pos_root = rd_cfg_child_from_string_or_alloc(window, str8_lit("pos"));
if((S32)pos.x != (S32)f64_from_str8(pos_root->first->string) ||
(S32)pos.y != (S32)f64_from_str8(pos_root->last->string))
{
RD_Cfg *x = pos_root->first;
if(x == &rd_nil_cfg)
{
x= rd_cfg_alloc();
rd_cfg_insert_child(pos_root, &rd_nil_cfg, x);
}
RD_Cfg *y = x->next;
if(y == &rd_nil_cfg)
{
y = rd_cfg_alloc();
rd_cfg_insert_child(pos_root, x, y);
}
rd_cfg_equip_stringf(x, "%i", (S32)pos.x);
rd_cfg_equip_stringf(y, "%i", (S32)pos.y);
}
}
//- rjf: commit size
if(!is_fullscreen && !is_maximized && !is_minimized)
{
Vec2F32 size = dim_2f32(window_rect);
RD_Cfg *size_root = rd_cfg_child_from_string_or_alloc(window, str8_lit("size"));
if((S32)size.x != (S32)f64_from_str8(size_root->first->string) ||
(S32)size.y != (S32)f64_from_str8(size_root->last->string))
{
RD_Cfg *width = size_root->first;
if(width == &rd_nil_cfg)
{
width = rd_cfg_alloc();
rd_cfg_insert_child(size_root, &rd_nil_cfg, width);
}
RD_Cfg *height = width->next;
if(height == &rd_nil_cfg)
{
height = rd_cfg_alloc();
rd_cfg_insert_child(size_root, width, height);
}
rd_cfg_equip_stringf(width, "%i", (S32)size.x);
rd_cfg_equip_stringf(height, "%i", (S32)size.y);
}
}
//- rjf: commit monitor
if(!is_minimized)
{
OS_Handle monitor = os_monitor_from_window(ws->os);
String8 monitor_name = os_name_from_monitor(scratch.arena, monitor);
RD_Cfg *monitor_root = rd_cfg_child_from_string_or_alloc(window, str8_lit("monitor"));
if(!str8_match(monitor_root->first->string, monitor_name, 0))
{
rd_cfg_new_replace(monitor_root, monitor_name);
}
}
scratch_end(scratch);
}
//////////////////////////////
//- rjf: @window_frame_part fill panel/view interaction registers
//
rd_regs()->panel = panel_tree.focused->cfg->id;
rd_regs()->tab = panel_tree.focused->selected_tab->id;
rd_regs()->view = panel_tree.focused->selected_tab->id;
//////////////////////////////
//- rjf: @window_frame_part build UI
//
UI_Box *lister_box = &ui_nil_box;
ProfScope("build UI")
{
////////////////////////////
//- rjf: @window_ui_part set up
//
{
// rjf: get top-level font size info
F32 top_level_font_size = 0;
RD_RegsScope(.view = 0, .tab = 0) top_level_font_size = rd_setting_f32_from_name(str8_lit("font_size"));
// rjf: build icon info
UI_IconInfo icon_info = {0};
{
icon_info.icon_font = rd_font_from_slot(RD_FontSlot_Icons);
icon_info.icon_kind_text_map[UI_IconKind_RightArrow] = rd_icon_kind_text_table[RD_IconKind_RightScroll];
icon_info.icon_kind_text_map[UI_IconKind_DownArrow] = rd_icon_kind_text_table[RD_IconKind_DownScroll];
icon_info.icon_kind_text_map[UI_IconKind_LeftArrow] = rd_icon_kind_text_table[RD_IconKind_LeftScroll];
icon_info.icon_kind_text_map[UI_IconKind_UpArrow] = rd_icon_kind_text_table[RD_IconKind_UpScroll];
icon_info.icon_kind_text_map[UI_IconKind_RightCaret] = rd_icon_kind_text_table[RD_IconKind_RightCaret];
icon_info.icon_kind_text_map[UI_IconKind_DownCaret] = rd_icon_kind_text_table[RD_IconKind_DownCaret];
icon_info.icon_kind_text_map[UI_IconKind_LeftCaret] = rd_icon_kind_text_table[RD_IconKind_LeftCaret];
icon_info.icon_kind_text_map[UI_IconKind_UpCaret] = rd_icon_kind_text_table[RD_IconKind_UpCaret];
icon_info.icon_kind_text_map[UI_IconKind_CheckHollow] = rd_icon_kind_text_table[RD_IconKind_CheckHollow];
icon_info.icon_kind_text_map[UI_IconKind_CheckFilled] = rd_icon_kind_text_table[RD_IconKind_CheckFilled];
}
// rjf: build animation info
UI_AnimationInfo animation_info = {0};
{
if(rd_setting_b32_from_name(str8_lit("hover_animations"))) {animation_info.flags |= UI_AnimationInfoFlag_HotAnimations;}
if(rd_setting_b32_from_name(str8_lit("press_animations"))) {animation_info.flags |= UI_AnimationInfoFlag_ActiveAnimations;}
if(rd_setting_b32_from_name(str8_lit("focus_animations"))) {animation_info.flags |= UI_AnimationInfoFlag_FocusAnimations;}
if(rd_setting_b32_from_name(str8_lit("tooltip_animations"))) {animation_info.flags |= UI_AnimationInfoFlag_TooltipAnimations;}
if(rd_setting_b32_from_name(str8_lit("menu_animations"))) {animation_info.flags |= UI_AnimationInfoFlag_ContextMenuAnimations;}
if(rd_setting_b32_from_name(str8_lit("scrolling_animations"))) {animation_info.flags |= UI_AnimationInfoFlag_ScrollingAnimations;}
}
// rjf: begin & push initial stack values
ui_begin_build(ws->os, &ws->ui_events, &icon_info, ws->theme, &animation_info, rd_state->frame_dt, rd_state->frame_dt);
ui_push_font(rd_font_from_slot(RD_FontSlot_Main));
ui_push_font_size(top_level_font_size);
ui_push_text_padding(floor_f32(ui_top_font_size()*0.3f));
ui_push_pref_width(ui_px(floor_f32(ui_top_font_size()*20.f), 1.f));
ui_push_pref_height(ui_px(floor_f32(ui_top_font_size()*3.f), 1.f));
ui_push_blur_size(10.f);
FNT_RasterFlags text_raster_flags = 0;
if(rd_setting_b32_from_name(str8_lit("smooth_ui_text"))) {text_raster_flags |= FNT_RasterFlag_Smooth;}
if(rd_setting_b32_from_name(str8_lit("hint_ui_text"))) {text_raster_flags |= FNT_RasterFlag_Hinted;}
ui_push_text_raster_flags(text_raster_flags);
}
////////////////////////////
//- rjf: @window_ui_part calculate top-level rectangles/sizes
//
Rng2F32 window_rect = os_client_rect_from_window(ws->os);
Vec2F32 window_rect_dim = dim_2f32(window_rect);
F32 top_bar_dim_px = floor_f32(ui_top_font_size()*3.f);
Rng2F32 top_bar_rect = r2f32p(window_rect.x0, window_rect.y0, window_rect.x0+window_rect_dim.x+1, window_rect.y0+top_bar_dim_px);
Rng2F32 bottom_bar_rect = r2f32p(window_rect.x0, window_rect_dim.y - top_bar_dim_px, window_rect.x0+window_rect_dim.x, window_rect.y0+window_rect_dim.y);
Rng2F32 content_rect = r2f32p(window_rect.x0, top_bar_rect.y1, window_rect.x0+window_rect_dim.x, bottom_bar_rect.y0);
F32 window_edge_px = os_dpi_from_window(ws->os)*0.035f;
content_rect = pad_2f32(content_rect, -window_edge_px);
////////////////////////////
//- rjf: @window_ui_part truncated string hover
//
if(ui_string_hover_active()) UI_Tooltip
{
Temp scratch = scratch_begin(0, 0);
DR_FStrList fstrs = ui_string_hover_fstrs(scratch.arena);
UI_Box *box = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero());
ui_box_equip_display_fstrs(box, &fstrs);
scratch_end(scratch);
}
////////////////////////////
//- rjf: @window_ui_part rich hover / drag/drop tooltips
//
if((rd_state->hover_regs_slot != RD_RegSlot_Null && !rd_state->hover_regs->no_rich_tooltip) || (rd_state->drag_drop_regs_slot != RD_RegSlot_Null && rd_drag_is_active()))
{
Temp scratch = scratch_begin(0, 0);
RD_RegSlot slot = ((rd_state->drag_drop_regs_slot != RD_RegSlot_Null && rd_drag_is_active()) ? rd_state->drag_drop_regs_slot : rd_state->hover_regs_slot);
RD_Regs *regs = (((rd_state->drag_drop_regs_slot != RD_RegSlot_Null && rd_drag_is_active()) ? rd_state->drag_drop_regs : rd_state->hover_regs));
CTRL_Entity *ctrl_entity = &ctrl_entity_nil;
switch(slot)
{
default:{}break;
////////////////////////
//- rjf: file path tooltips
//
case RD_RegSlot_FilePath:
UI_Tooltip
{
FileProperties props = os_properties_from_file_path(regs->file_path);
ui_set_next_pref_width(ui_children_sum(1));
UI_Row
{
RD_Font(RD_FontSlot_Icons) ui_label(rd_icon_kind_text_table[props.flags & FilePropertyFlag_IsFolder ? RD_IconKind_FolderClosedFilled : RD_IconKind_FileOutline]);
ui_label(regs->file_path);
}
}break;
////////////////////////
//- rjf: cfg tooltips
//
case RD_RegSlot_Cfg:
UI_Tooltip
{
// rjf: unpack
RD_Cfg *cfg = rd_cfg_from_id(regs->cfg);
DR_FStrList fstrs = rd_title_fstrs_from_cfg(scratch.arena, cfg);
// rjf: title
UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(5, 1))
{
UI_Box *box = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero());
ui_box_equip_display_fstrs(box, &fstrs);
}
}break;
////////////////////////
//- rjf: control entity tooltips
//
case RD_RegSlot_Machine: {ctrl_entity = ctrl_entity_from_handle(d_state->ctrl_entity_store, regs->machine); }goto ctrl_entity_tooltip;
case RD_RegSlot_Process: {ctrl_entity = ctrl_entity_from_handle(d_state->ctrl_entity_store, regs->process); }goto ctrl_entity_tooltip;
case RD_RegSlot_Module: {ctrl_entity = ctrl_entity_from_handle(d_state->ctrl_entity_store, regs->module); }goto ctrl_entity_tooltip;
case RD_RegSlot_Thread: {ctrl_entity = ctrl_entity_from_handle(d_state->ctrl_entity_store, regs->thread); }goto ctrl_entity_tooltip;
case RD_RegSlot_CtrlEntity:{ctrl_entity = ctrl_entity_from_handle(d_state->ctrl_entity_store, regs->ctrl_entity); }goto ctrl_entity_tooltip;
ctrl_entity_tooltip:;
UI_Tooltip
{
// rjf: unpack
DI_Scope *di_scope = di_scope_open();
Arch arch = ctrl_entity->arch;
String8 arch_str = string_from_arch(arch);
DR_FStrList fstrs = rd_title_fstrs_from_ctrl_entity(scratch.arena, ctrl_entity, 0);
// rjf: title
UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(5, 1))
{
UI_Box *box = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero());
ui_box_equip_display_fstrs(box, &fstrs);
ui_spacer(ui_em(0.5f, 1.f));
UI_FontSize(ui_top_font_size() - 1.f)
UI_CornerRadius(ui_top_font_size()*0.5f)
{
UI_TagF("weak") UI_FlagsAdd(UI_BoxFlag_DrawBorder) ui_label(arch_str);
ui_spacer(ui_em(0.5f, 1.f));
if(ctrl_entity->kind == CTRL_EntityKind_Thread ||
ctrl_entity->kind == CTRL_EntityKind_Process)
{
UI_TagF("weak") UI_FlagsAdd(UI_BoxFlag_DrawBorder) ui_labelf("ID: %i", (U32)ctrl_entity->id);
}
}
}
// rjf: debug info status
if(ctrl_entity->kind == CTRL_EntityKind_Module) UI_TagF("weak")
{
DI_Scope *di_scope = di_scope_open();
DI_Key dbgi_key = ctrl_dbgi_key_from_module(ctrl_entity);
RDI_Parsed *rdi = di_rdi_from_key(di_scope, &dbgi_key, 0);
if(rdi->raw_data_size != 0)
{
ui_labelf("Symbols successfully loaded from %S", dbgi_key.path);
}
else if(dbgi_key.path.size != 0)
{
ui_labelf("Symbols not found at %S", dbgi_key.path);
}
else if(dbgi_key.path.size == 0)
{
ui_labelf("Symbol information not found in module file");
}
di_scope_close(di_scope);
}
// rjf: unwind
if(ctrl_entity->kind == CTRL_EntityKind_Thread)
{
Vec4F32 symbol_color = ui_color_from_name(str8_lit("code_symbol"));
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(ctrl_entity, CTRL_EntityKind_Process);
CTRL_Unwind base_unwind = d_query_cached_unwind_from_thread(ctrl_entity);
CTRL_CallStack call_stack = ctrl_call_stack_from_unwind(scratch.arena, di_scope, process, &base_unwind);
if(call_stack.count != 0)
{
ui_spacer(ui_em(1.5f, 1.f));
}
for(U64 idx = 0; idx < call_stack.count; idx += 1)
{
CTRL_CallStackFrame *f = &call_stack.frames[idx];
RDI_Parsed *rdi = f->rdi;
RDI_Procedure *procedure = f->procedure;
U64 rip_vaddr = regs_rip_from_arch_block(arch, f->regs);
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr);
String8 module_name = module == &ctrl_entity_nil ? str8_lit("???") : str8_skip_last_slash(module->string);
UI_PrefWidth(ui_children_sum(1)) UI_Row
{
String8 name = {0};
if(f->inline_site != 0)
{
name.str = rdi_string_from_idx(rdi, f->inline_site->name_string_idx, &name.size);
name.size = Min(512, name.size);
}
else if(f->procedure != 0)
{
name.str = rdi_name_from_procedure(rdi, procedure, &name.size);
name.size = Min(512, name.size);
}
UI_TextAlignment(UI_TextAlign_Left) RD_Font(RD_FontSlot_Code) UI_TagF("weak") UI_PrefWidth(ui_em(12.f, 1)) ui_labelf("0x%I64x", rip_vaddr);
if(f->parent_num != 0)
{
RD_Font(RD_FontSlot_Code) UI_TagF("weak") UI_PrefWidth(ui_text_dim(10, 1)) ui_label(str8_lit("[inlined]"));
}
if(name.size != 0)
{
RD_Font(RD_FontSlot_Code) UI_PrefWidth(ui_text_dim(10, 1))
{
rd_code_label(1.f, 0, symbol_color, name);
}
}
else
{
RD_Font(RD_FontSlot_Code) UI_TagF("weak") UI_PrefWidth(ui_text_dim(10, 1)) ui_labelf("[??? in %S]", module_name);
}
}
}
}
di_scope_close(di_scope);
}break;
////////////////////////
//- rjf: expression tooltips
//
case RD_RegSlot_Expr:
UI_Tooltip RD_Font(RD_FontSlot_Code)
{
ui_set_next_pref_width(ui_children_sum(1));
UI_Row
{
rd_code_label(1.f, 0, ui_color_from_name(str8_lit("text")), rd_state->drag_drop_regs->expr);
ui_spacer(ui_em(2.f, 1.f));
E_Eval eval = e_eval_from_string(rd_state->drag_drop_regs->expr);
if(eval.irtree.mode != E_Mode_Null)
{
EV_StringParams string_params = {.flags = EV_StringFlag_ReadOnlyDisplayRules, .radix = 10};
String8 value_string = rd_value_string_from_eval(scratch.arena, str8_zero(), &string_params, ui_top_font(), ui_top_font_size(), ui_top_font_size()*20.f, eval);
rd_code_label(1.f, 0, ui_color_from_name(str8_lit("text")), value_string);
}
}
}break;
}
scratch_end(scratch);
}
////////////////////////////
//- rjf: @window_ui_part drag/drop visualization tooltips
//
if(rd_drag_is_active() && window_is_focused)
RD_RegsScope(.window = rd_state->drag_drop_regs->window,
.panel = rd_state->drag_drop_regs->panel,
.tab = 0,
.view = rd_state->drag_drop_regs->view)
{
Temp scratch = scratch_begin(0, 0);
RD_Cfg *view = rd_cfg_from_id(rd_state->drag_drop_regs->view);
{
//- rjf: tab dragging
if(rd_state->drag_drop_regs_slot == RD_RegSlot_View && view != &rd_nil_cfg)
{
RD_Cfg *immediate_parent = &rd_nil_cfg;
for(RD_Cfg *p = view->parent; p != &rd_nil_cfg; p = p->parent)
{
if(str8_match(p->parent->string, str8_lit("immediate"), 0))
{
immediate_parent = p->parent;
break;
}
}
if(immediate_parent != &rd_nil_cfg)
{
rd_cfg_child_from_string_or_alloc(immediate_parent, str8_lit("hot"));
}
UI_Size main_width = ui_top_pref_width();
UI_Size main_height = ui_top_pref_height();
UI_TextAlign main_text_align = ui_top_text_alignment();
UI_Tooltip
UI_PrefWidth(main_width)
UI_PrefHeight(main_height)
UI_TextAlignment(main_text_align)
{
ui_state->tooltip_can_overflow_window = 1;
ui_set_next_pref_width(ui_em(60.f, 1.f));
ui_set_next_pref_height(ui_em(40.f, 1.f));
ui_set_next_child_layout_axis(Axis2_Y);
UI_Box *container = ui_build_box_from_key(0, ui_key_zero());
UI_Parent(container)
{
UI_Row UI_PrefWidth(ui_text_dim(10, 1))
{
DR_FStrList fstrs = rd_title_fstrs_from_cfg(scratch.arena, view);
UI_Box *name_box = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero());
ui_box_equip_display_fstrs(name_box, &fstrs);
}
ui_set_next_pref_width(ui_pct(1, 0));
ui_set_next_pref_height(ui_pct(1, 0));
ui_set_next_child_layout_axis(Axis2_Y);
UI_Box *view_preview_container = ui_build_box_from_stringf(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground|UI_BoxFlag_Clip, "###view_preview_container");
UI_Parent(view_preview_container) UI_Focus(UI_FocusKind_Off) UI_WidthFill
{
rd_view_ui(view_preview_container->rect);
}
}
}
}
}
scratch_end(scratch);
}
////////////////////////////
//- rjf: @window_ui_part developer menu
//
if(ws->dev_menu_is_open) RD_Font(RD_FontSlot_Code)
{
ui_set_next_flags(UI_BoxFlag_ViewScrollY|UI_BoxFlag_AllowOverflowY|UI_BoxFlag_ViewClamp);
UI_PaneF(r2f32p(30, 30, 30+ui_top_font_size()*100, ui_top_font_size()*150), "###dev_ctx_menu")
{
//- rjf: capture
if(!ProfIsCapturing() && ui_clicked(ui_buttonf("Begin Profiler Capture###prof_cap")))
{
ProfBeginCapture("raddbg");
}
else if(ProfIsCapturing() && ui_clicked(ui_buttonf("End Profiler Capture###prof_cap")))
{
ProfEndCapture();
}
//- rjf: toggles
for(U64 idx = 0; idx < ArrayCount(DEV_toggle_table); idx += 1)
{
if(ui_clicked(rd_icon_button(*DEV_toggle_table[idx].value_ptr ? RD_IconKind_CheckFilled : RD_IconKind_CheckHollow, 0, DEV_toggle_table[idx].name)))
{
*DEV_toggle_table[idx].value_ptr ^= 1;
}
}
ui_divider(ui_em(1.f, 1.f));
//- rjf: draw match store stats
ui_labelf("name match nodes: %I64u", rd_state->match_store->active_match_name_nodes_count);
ui_divider(ui_em(1.f, 1.f));
//- rjf: draw registers
ui_labelf("hover_reg_slot: %i", rd_state->hover_regs_slot);
struct
{
String8 name;
RD_Regs *regs;
}
regs_info[] =
{
{str8_lit("regs"), rd_regs()},
{str8_lit("hover_regs"), rd_state->hover_regs},
};
for EachElement(idx, regs_info)
{
ui_divider(ui_em(1.f, 1.f));
ui_label(regs_info[idx].name);
RD_Regs *regs = regs_info[idx].regs;
#define ID(name) ui_labelf("%s: $0x%I64x", #name, (regs->name))
ID(window);
ID(panel);
ID(view);
#undef ID
#define Handle(name) ui_labelf("%s: [0x%I64x, 0x%I64x]", #name, (regs->name).machine_id, (regs->name).dmn_handle.u64[0])
Handle(machine);
Handle(process);
Handle(module);
Handle(thread);
#undef Handle
ui_labelf("file_path: \"%S\"", regs->file_path);
ui_labelf("cursor: (L:%I64d, C:%I64d)", regs->cursor.line, regs->cursor.column);
ui_labelf("mark: (L:%I64d, C:%I64d)", regs->mark.line, regs->mark.column);
ui_labelf("unwind_count: %I64u", regs->unwind_count);
ui_labelf("inline_depth: %I64u", regs->inline_depth);
ui_labelf("text_key: [0x%I64x, 0x%I64x]", regs->text_key.u64[0], regs->text_key.u64[1]);
ui_labelf("lang_kind: '%S'", txt_extension_from_lang_kind(regs->lang_kind));
ui_labelf("vaddr_range: [0x%I64x, 0x%I64x)", regs->vaddr_range.min, regs->vaddr_range.max);
ui_labelf("voff_range: [0x%I64x, 0x%I64x)", regs->voff_range.min, regs->voff_range.max);
}
ui_divider(ui_em(1.f, 1.f));
//- rjf: draw per-window stats
for(RD_WindowState *w = rd_state->first_window_state; w != &rd_nil_window_state; w = w->order_next)
{
// rjf: calc ui hash chain length
F64 avg_ui_hash_chain_length = 0;
{
F64 chain_count = 0;
F64 chain_length_sum = 0;
for(U64 idx = 0; idx < w->ui->box_table_size; idx += 1)
{
F64 chain_length = 0;
for(UI_Box *b = w->ui->box_table[idx].hash_first; !ui_box_is_nil(b); b = b->hash_next)
{
chain_length += 1;
}
if(chain_length > 0)
{
chain_length_sum += chain_length;
chain_count += 1;
}
}
avg_ui_hash_chain_length = chain_length_sum / chain_count;
}
ui_labelf("Target Hz: %.2f", 1.f/rd_state->frame_dt);
ui_labelf("Ctrl Run Index: %I64u", ctrl_run_gen());
ui_labelf("Ctrl Mem Gen Index: %I64u", ctrl_mem_gen());
ui_labelf("Window %p", w);
ui_set_next_pref_width(ui_children_sum(1));
ui_set_next_pref_height(ui_children_sum(1));
UI_Row
{
ui_spacer(ui_em(2.f, 1.f));
ui_labelf("Box Count: %I64u", w->ui->last_build_box_count);
}
ui_set_next_pref_width(ui_children_sum(1));
ui_set_next_pref_height(ui_children_sum(1));
UI_Row
{
ui_spacer(ui_em(2.f, 1.f));
ui_labelf("Average UI Hash Chain Length: %f", avg_ui_hash_chain_length);
}
}
ui_divider(ui_em(1.f, 1.f));
}
}
////////////////////////////
//- rjf: @window_ui_part drop-completion context menu
//
if(ws->drop_completion_paths.node_count != 0)
{
UI_CtxMenu(rd_state->drop_completion_key) UI_PrefWidth(ui_em(40.f, 1.f)) UI_TagF("implicit")
{
UI_TagF("weak")
for(String8Node *n = ws->drop_completion_paths.first; n != 0; n = n->next)
{
UI_Row UI_Padding(ui_em(1.f, 1.f))
{
UI_PrefWidth(ui_em(2.f, 1.f)) RD_Font(RD_FontSlot_Icons) ui_label(rd_icon_kind_text_table[RD_IconKind_FileOutline]);
UI_PrefWidth(ui_text_dim(10, 1)) ui_label(n->string);
}
}
ui_divider(ui_em(1.f, 1.f));
if(ui_clicked(rd_icon_buttonf(RD_IconKind_Target, 0, "Add File%s As Target%s",
(ws->drop_completion_paths.node_count > 1) ? "s" : "",
(ws->drop_completion_paths.node_count > 1) ? "s" : "")))
{
for(String8Node *n = ws->drop_completion_paths.first; n != 0; n = n->next)
{
rd_cmd(RD_CmdKind_AddTarget, .file_path = n->string);
}
ui_ctx_menu_close();
}
if(ws->drop_completion_paths.node_count == 1)
{
if(ui_clicked(rd_icon_buttonf(RD_IconKind_Play, 0, "Add File%s As Target%s And Run",
(ws->drop_completion_paths.node_count > 1) ? "s" : "",
(ws->drop_completion_paths.node_count > 1) ? "s" : "")))
{
for(String8Node *n = ws->drop_completion_paths.first; n != 0; n = n->next)
{
rd_cmd(RD_CmdKind_AddTarget, .file_path = n->string);
}
CTRL_EntityArray processes = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Process);
if(processes.count != 0)
{
rd_cmd(RD_CmdKind_KillAll);
}
rd_cmd(RD_CmdKind_Run);
ui_ctx_menu_close();
}
}
if(ws->drop_completion_paths.node_count == 1)
{
if(ui_clicked(rd_icon_buttonf(RD_IconKind_StepInto, 0, "Add File%s As Target%s And Step Into",
(ws->drop_completion_paths.node_count > 1) ? "s" : "",
(ws->drop_completion_paths.node_count > 1) ? "s" : "")))
{
for(String8Node *n = ws->drop_completion_paths.first; n != 0; n = n->next)
{
rd_cmd(RD_CmdKind_AddTarget, .file_path = n->string);
}
CTRL_EntityArray processes = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Process);
if(processes.count != 0)
{
rd_cmd(RD_CmdKind_KillAll);
}
rd_cmd(RD_CmdKind_StepInto);
ui_ctx_menu_close();
}
}
if(ui_clicked(rd_icon_buttonf(RD_IconKind_Target, 0, "View File%s",
(ws->drop_completion_paths.node_count > 1) ? "s" : "")))
{
for(String8Node *n = ws->drop_completion_paths.first; n != 0; n = n->next)
{
rd_cmd(RD_CmdKind_Open, .file_path = n->string);
}
ui_ctx_menu_close();
}
}
}
////////////////////////////
//- rjf: @window_ui_part popup
//
{
if(rd_state->popup_t > 0.005f) UI_TextAlignment(UI_TextAlign_Center) UI_Focus(rd_state->popup_active ? UI_FocusKind_Root : UI_FocusKind_Off)
{
Vec2F32 window_dim = dim_2f32(window_rect);
UI_Box *bg_box = &ui_nil_box;
UI_Rect(window_rect)
UI_ChildLayoutAxis(Axis2_X)
UI_Focus(UI_FocusKind_On)
UI_BlurSize(10*rd_state->popup_t)
UI_Transparency(1-rd_state->popup_t)
{
bg_box = ui_build_box_from_stringf(UI_BoxFlag_FixedSize|
UI_BoxFlag_Floating|
UI_BoxFlag_Clickable|
UI_BoxFlag_Scroll|
UI_BoxFlag_DefaultFocusNav|
UI_BoxFlag_DisableFocusOverlay|
UI_BoxFlag_DrawBackgroundBlur, "###popup_%p", ws);
}
if(rd_state->popup_active) UI_Parent(bg_box) UI_Transparency(1-rd_state->popup_t)
{
ui_ctx_menu_close();
UI_WidthFill UI_PrefHeight(ui_children_sum(1.f)) UI_Column UI_Padding(ui_pct(1, 0))
{
UI_TextRasterFlags(rd_raster_flags_from_slot(RD_FontSlot_Main)) UI_FontSize(ui_top_font_size()*2.f) UI_PrefHeight(ui_em(3.f, 1.f)) ui_label(rd_state->popup_title);
UI_PrefHeight(ui_em(3.f, 1.f)) UI_TagF("weak") ui_label(rd_state->popup_desc);
ui_spacer(ui_em(1.5f, 1.f));
UI_Row UI_Padding(ui_pct(1.f, 0.f)) UI_PrefWidth(ui_em(16.f, 1.f)) UI_PrefHeight(ui_em(3.5f, 1.f)) UI_CornerRadius(ui_top_font_size()*0.5f)
{
UI_TagF("pop")
if(ui_clicked(ui_buttonf("OK")) || (ui_key_match(bg_box->default_nav_focus_hot_key, ui_key_zero()) && ui_slot_press(UI_EventActionSlot_Accept)))
{
rd_cmd(RD_CmdKind_PopupAccept);
}
ui_spacer(ui_em(1.f, 1.f));
if(ui_clicked(ui_buttonf("Cancel")) || ui_slot_press(UI_EventActionSlot_Cancel))
{
rd_cmd(RD_CmdKind_PopupCancel);
}
}
ui_spacer(ui_em(3.f, 1.f));
}
}
ui_signal_from_box(bg_box);
}
}
////////////////////////////
//- rjf: @window_ui_part gather all tasks to build floating views
//
typedef struct FloatingViewTask FloatingViewTask;
struct FloatingViewTask
{
FloatingViewTask *next;
RD_Cfg *view;
Rng2F32 rect;
B32 is_focused;
B32 is_anchored;
B32 only_secondary_navigation;
B32 reset_open;
UI_Signal signal; // NOTE(rjf): output, from build
B32 pressed;
B32 pressed_outside;
};
FloatingViewTask *hover_eval_floating_view_task = 0;
FloatingViewTask *query_floating_view_task = 0;
FloatingViewTask *first_floating_view_task = 0;
FloatingViewTask *last_floating_view_task = 0;
RD_Font(RD_FontSlot_Code)
{
//- rjf: add autocompletion view task
if(ws->autocomp_regs != 0 && ws->autocomp_last_frame_index+1 >= rd_state->frame_index)
{
// rjf: build view
RD_Cfg *root = rd_immediate_cfg_from_keyf("autocomp_view_%I64x", window->id);
RD_Cfg *view = rd_cfg_child_from_string_or_alloc(root, str8_lit("watch"));
rd_cfg_child_from_string_or_alloc(view, str8_lit("autocomplete"));
RD_Cfg *query = rd_cfg_child_from_string_or_alloc(view, str8_lit("query"));
RD_Cfg *input = rd_cfg_child_from_string_or_alloc(query, str8_lit("input"));
rd_cfg_new_replace(input, ws->autocomp_regs->string);
RD_Cfg *expr = rd_cfg_child_from_string_or_alloc(view, str8_lit("expression"));
rd_cfg_new_replacef(expr, "query:locals, query:globals, query:thread_locals, query:procedures, query:types");
// rjf: determine container size
EV_BlockTree predicted_block_tree = {0};
RD_RegsScope(.view = view->id, .tab = 0)
{
String8 expr = rd_expr_from_cfg(view);
E_Eval list_eval = e_eval_from_string(expr);
ev_key_set_expansion(rd_view_eval_view(), ev_key_root(), ev_key_make(ev_hash_from_key(ev_key_root()), 1), 1);
predicted_block_tree = ev_block_tree_from_eval(scratch.arena, rd_view_eval_view(), rd_view_query_input(), list_eval);
}
F32 row_height_px = ui_top_px_height();
U64 max_row_count = (U64)floor_f32(ui_top_font_size()*30.f / row_height_px);
U64 needed_row_count = Min(max_row_count, predicted_block_tree.total_row_count - 1);
F32 width_px = floor_f32(40.f*ui_top_font_size());
F32 height_px = needed_row_count*row_height_px;
// rjf: determine list top-level rect
Rng2F32 rect = r2f32p(0, 0, 0, 0);
if(!ui_key_match(ui_key_zero(), ws->autocomp_regs->ui_key))
{
UI_Box *anchor_box = ui_box_from_key(ws->autocomp_regs->ui_key);
rect.x0 = anchor_box->rect.x0;
rect.y0 = anchor_box->rect.y1;
rect.x1 = rect.x0 + width_px;
rect.y1 = rect.y0 + height_px;
}
// rjf: push task
if(predicted_block_tree.total_row_count > 1)
{
FloatingViewTask *t = push_array(scratch.arena, FloatingViewTask, 1);
SLLQueuePush(first_floating_view_task, last_floating_view_task, t);
t->view = view;
t->rect = rect;
t->is_focused = 1;
t->is_anchored = 1;
t->only_secondary_navigation = 1;
}
}
//- rjf: try to add hover eval
{
B32 build_hover_eval = (hover_eval_is_open && !rd_drag_is_active());
// rjf: disable hover eval if hovered view is actively scrolling
if(hover_eval_is_open)
{
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
if(panel->first != &rd_nil_panel_node) { continue; }
RD_Cfg *tab = panel->selected_tab;
if(tab != &rd_nil_cfg)
{
RD_ViewState *vs = rd_view_state_from_cfg(tab);
Rng2F32 panel_rect = rd_target_rect_from_panel_node(content_rect, panel_tree.root, panel);
if(contains_2f32(panel_rect, ui_mouse()) &&
(abs_f32(vs->scroll_pos.x.off) > 0.01f ||
abs_f32(vs->scroll_pos.y.off) > 0.01f))
{
build_hover_eval = 0;
ws->hover_eval_firstt_us = rd_state->time_in_us;
}
}
}
}
// rjf: choose hover evaluation expression
String8 hover_eval_expr = ws->hover_eval_string;
// rjf: evaluate hover evaluation expression, & determine if it evaluates
// such that we want to build a hover eval.
E_Eval hover_eval = e_eval_from_string(hover_eval_expr);
{
if(hover_eval.msgs.max_kind > E_MsgKind_Null)
{
build_hover_eval = 0;
}
else if(hover_eval.space.kind == RD_EvalSpaceKind_MetaCfg &&
rd_cfg_from_eval_space(hover_eval.space) == &rd_nil_cfg)
{
build_hover_eval = 0;
}
else if((hover_eval.space.kind == RD_EvalSpaceKind_MetaCtrlEntity ||
hover_eval.space.kind == RD_EvalSpaceKind_CtrlEntity) &&
rd_ctrl_entity_from_eval_space(hover_eval.space) == &ctrl_entity_nil)
{
build_hover_eval = 0;
}
}
// rjf: request frames if we're waiting to open
if(ws->hover_eval_string.size != 0 &&
!hover_eval_is_open &&
ws->hover_eval_lastt_us < ws->hover_eval_firstt_us+hover_eval_open_delay_us &&
rd_state->time_in_us - ws->hover_eval_lastt_us < hover_eval_open_delay_us*2)
{
rd_request_frame();
}
// rjf: build hover eval task
if(build_hover_eval)
{
// rjf: determine if we have a top-level visualizer
EV_ExpandRule *expand_rule = ev_expand_rule_from_type_key(hover_eval.irtree.type_key);
RD_ViewUIRule *view_ui_rule = rd_view_ui_rule_from_string(expand_rule->string);
// rjf: build view
RD_Cfg *root = rd_immediate_cfg_from_keyf("hover_eval_view_%I64x", ws->cfg_id);
RD_Cfg *view = rd_view_from_eval(root, hover_eval);
rd_cfg_child_from_string_or_alloc(view, str8_lit("explicit_root"));
// rjf: determine size of hover evaluation container
EV_BlockTree predicted_block_tree = {0};
RD_RegsScope(.view = view->id, .tab = 0)
{
ev_key_set_expansion(rd_view_eval_view(), ev_key_root(), ev_key_make(ev_hash_from_key(ev_key_root()), 1), 1);
predicted_block_tree = ev_block_tree_from_eval(scratch.arena, rd_view_eval_view(), str8_zero(), hover_eval);
}
F32 row_height_px = ui_top_px_height();
U64 max_row_count = (U64)floor_f32(ui_top_font_size()*10.f / row_height_px);
if(ws->hover_eval_focused)
{
max_row_count *= 3;
}
U64 needed_row_count = Min(max_row_count, predicted_block_tree.total_row_count);
F32 width_px = floor_f32(70.f*ui_top_font_size());
F32 height_px = needed_row_count*row_height_px;
// rjf: if arbitrary visualizer, pick catchall size
if(view_ui_rule != &rd_nil_view_ui_rule)
{
height_px = floor_f32(40.f*ui_top_font_size());
}
// rjf: determine hover eval top-level rect
Rng2F32 rect = r2f32p(ws->hover_eval_spawn_pos.x,
ws->hover_eval_spawn_pos.y,
ws->hover_eval_spawn_pos.x + width_px,
ws->hover_eval_spawn_pos.y + height_px);
// rjf: push hover eval task
{
FloatingViewTask *t = push_array(scratch.arena, FloatingViewTask, 1);
SLLQueuePush(first_floating_view_task, last_floating_view_task, t);
hover_eval_floating_view_task = t;
t->view = view;
t->rect = rect;
t->is_focused = ws->hover_eval_focused;
t->is_anchored = 1;
}
}
// rjf: reset focus state if hover eval is not being built
if(!build_hover_eval || ws->hover_eval_string.size == 0 || !hover_eval_is_open)
{
ws->hover_eval_focused = 0;
}
}
//- rjf: force-close query, if it's anchored, but box is gone
if(query_is_open)
{
UI_Box *box = ui_box_from_key(ws->query_regs->ui_key);
if(!ui_key_match(ui_key_zero(), ws->query_regs->ui_key) && ui_box_is_nil(box))
{
query_is_open = 0;
rd_cmd(RD_CmdKind_CancelQuery);
}
}
//- rjf: force-close query, if it has an expression, but that expression does not evaluate
if(query_is_open)
{
String8 expr = ws->query_regs->expr;
E_Eval eval = e_eval_from_string(expr);
if(eval.msgs.max_kind > E_MsgKind_Null)
{
query_is_open = 0;
rd_cmd(RD_CmdKind_CancelQuery);
}
else if(eval.space.kind == RD_EvalSpaceKind_MetaCfg &&
rd_cfg_from_eval_space(eval.space) == &rd_nil_cfg)
{
query_is_open = 0;
rd_cmd(RD_CmdKind_CancelQuery);
}
else if((eval.space.kind == RD_EvalSpaceKind_MetaCtrlEntity ||
eval.space.kind == RD_EvalSpaceKind_CtrlEntity) &&
rd_ctrl_entity_from_eval_space(eval.space) == &ctrl_entity_nil)
{
query_is_open = 0;
rd_cmd(RD_CmdKind_CancelQuery);
}
}
//- rjf: try to add opened query
if(query_is_open)
{
// rjf: unpack view for query
RD_Cfg *root = rd_immediate_cfg_from_keyf("window_query_%p", window);
RD_Cfg *view = rd_cfg_child_from_string_or_alloc(root, str8_lit("watch"));
RD_Cfg *query = rd_cfg_child_from_string_or_alloc(view, str8_lit("query"));
B32 is_lister = (rd_cfg_child_from_string(view, str8_lit("lister")) != &rd_nil_cfg);
B32 root_is_explicit = (rd_cfg_child_from_string(view, str8_lit("explicit_root")) != &rd_nil_cfg);
RD_ViewState *vs = rd_view_state_from_cfg(view);
// rjf: did this view ID change? -> reset open animation
B32 reset_open = 0;
if(view->id != ws->query_last_view_id)
{
ws->query_last_view_id = view->id;
reset_open = 1;
}
// rjf: unpack query info
String8 cmd_name = ws->query_regs->cmd_name;
RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name);
String8 query_expr = ws->query_regs->expr;
if(query_expr.size == 0 && cmd_name.size != 0)
{
query_expr = cmd_kind_info->query.expr;
}
B32 query_is_anchored = (!ui_box_is_nil(ui_box_from_key(ws->query_regs->ui_key)));
B32 size_query_by_expr_eval = (query_is_anchored || query_expr.size == 0);
// rjf: compute query expression
if(query_expr.size == 0)
{
query_expr = str8(vs->query_buffer, vs->query_string_size);
}
else
{
U64 input_insertion_pos = str8_find_needle(query_expr, 0, str8_lit("$input"), 0);
if(input_insertion_pos < query_expr.size)
{
String8 pre_insertion = str8_prefix(query_expr, input_insertion_pos);
String8 post_insertion = str8_skip(query_expr, input_insertion_pos + 6);
query_expr = push_str8f(scratch.arena, "%S%S%S", pre_insertion, str8(vs->query_buffer, vs->query_string_size), post_insertion);
}
}
// rjf: store expression
RD_Cfg *expr = rd_cfg_child_from_string_or_alloc(view, str8_lit("expression"));
rd_cfg_new_replace(expr, query_expr);
// rjf: evaluate query expression
E_Eval query_eval = e_eval_from_string(query_expr);
// rjf: determine & store row-height setting
if(ws->query_regs->do_big_rows)
{
F32 row_height = 5.f;
F32 row_height_px = row_height * ui_top_font_size();
RD_Cfg *row_height_root = rd_cfg_child_from_string_or_alloc(view, str8_lit("row_height"));
rd_cfg_new_replacef(row_height_root, "%f", row_height);
}
// rjf: compute query view's top-level rectangle
Rng2F32 rect = {0};
RD_RegsScope(.view = view->id, .tab = 0)
{
F32 row_height_px = ui_top_font_size() * rd_setting_f32_from_name(str8_lit("row_height"));
Vec2F32 content_rect_center = center_2f32(content_rect);
Vec2F32 content_rect_dim = dim_2f32(content_rect);
ev_key_set_expansion(rd_view_eval_view(), ev_key_root(), ev_key_make(ev_hash_from_key(ev_key_root()), 1), 1);
EV_BlockTree predicted_block_tree = ev_block_tree_from_eval(scratch.arena, rd_view_eval_view(), rd_view_query_input(), query_eval);
F32 query_width_px = floor_f32(content_rect_dim.x * 0.35f);
F32 max_query_height_px = content_rect_dim.y*0.8f;
F32 query_height_px = max_query_height_px;
if(size_query_by_expr_eval)
{
F32 search_row_open_t = ui_anim(ui_key_from_stringf(ui_key_zero(), "search_row_open_%p", view), (F32)!!vs->query_is_selected, .initial = (F32)!!vs->query_is_selected, .epsilon = 0.01f);
query_height_px = row_height_px * (predicted_block_tree.total_row_count - !root_is_explicit) + ui_top_px_height()*search_row_open_t;
query_height_px = Min(query_height_px, max_query_height_px);
}
rect = r2f32p(content_rect_center.x - query_width_px/2,
content_rect_center.y - max_query_height_px/2.f,
content_rect_center.x + query_width_px/2,
content_rect_center.y - max_query_height_px/2.f + query_height_px);
if(!ui_key_match(ui_key_zero(), ws->query_regs->ui_key))
{
UI_Box *anchor_box = ui_box_from_key(ws->query_regs->ui_key);
if(anchor_box != &ui_nil_box)
{
rect.x0 = anchor_box->rect.x0 + ws->query_regs->off_px.x;
rect.y0 = anchor_box->rect.y1 + ws->query_regs->off_px.y;
rect.x1 = rect.x0 + ui_top_font_size()*60.f;
rect.y1 = rect.y0 + query_height_px;
}
}
}
// rjf: push query task
{
FloatingViewTask *t = push_array(scratch.arena, FloatingViewTask, 1);
SLLQueuePush(first_floating_view_task, last_floating_view_task, t);
query_floating_view_task = t;
t->view = view;
t->rect = rect;
t->is_focused = 1;
t->is_anchored = query_is_anchored;
t->reset_open = reset_open;
}
}
}
////////////////////////////
//- rjf: @window_ui_part build all floating views
//
ProfScope("build all floating views")
RD_Font(RD_FontSlot_Code)
UI_TagF("floating")
UI_Focus(ui_any_ctx_menu_is_open() || ws->menu_bar_focused ? UI_FocusKind_Off : UI_FocusKind_Null)
{
F32 fast_open_rate = 1 - pow_f32(2, (-70.f * ui_state->animation_dt));
F32 slow_open_rate = 1 - pow_f32(2, (-50.f * ui_state->animation_dt));
for(FloatingViewTask *t = first_floating_view_task; t != 0; t = t->next)
{
// rjf: unpack
RD_Cfg *view = t->view;
Rng2F32 rect = t->rect;
B32 is_focused = t->is_focused;
B32 is_anchored = t->is_anchored;
B32 only_secondary_navigation = t->only_secondary_navigation;
F32 open_t = ui_anim(ui_key_from_stringf(ui_key_zero(), "floating_view_open_%p", view), 1.f, .rate = is_anchored ? fast_open_rate : slow_open_rate, .reset = t->reset_open, .initial = 0.f);
// rjf: push view regs
rd_push_regs(.view = view->id);
{
String8 view_expr = rd_expr_from_cfg(view);
String8 view_file_path = rd_file_path_from_eval_string(rd_frame_arena(), view_expr);
// NOTE(rjf): we want to only fill out this view's file path slot if it
// evaluates one - this way, a view can use the slot to know the selected
// file path (if there is one). this is useful when pushing commandas which
// apply to a cursor, for example.
if(view_file_path.size != 0)
{
rd_regs()->file_path = view_file_path;
}
}
// rjf: build
UI_Focus(is_focused ? UI_FocusKind_On : UI_FocusKind_Off)
UI_PermissionFlags(only_secondary_navigation ?
UI_PermissionFlag_KeyboardSecondary|UI_PermissionFlag_Clicks|UI_PermissionFlag_ScrollX|UI_PermissionFlag_ScrollY :
UI_PermissionFlag_All)
{
// rjf: build top-level container box
UI_Box *container = &ui_nil_box;
UI_Rect(rect) UI_ChildLayoutAxis(Axis2_Y) UI_Squish(0.1f-0.1f*open_t) UI_Transparency(1.f-open_t)
UI_CornerRadius(ui_top_font_size()*0.25f)
{
container = ui_build_box_from_stringf(UI_BoxFlag_Clickable|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawBackgroundBlur|
UI_BoxFlag_RoundChildrenByParent|
UI_BoxFlag_DisableFocusOverlay|
UI_BoxFlag_DrawDropShadow|
(UI_BoxFlag_SquishAnchored*!!is_anchored),
"floating_view_container_%p", view);
}
// rjf: peek press inside/outside events
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->kind == UI_EventKind_Press &&
evt->key == OS_Key_LeftMouseButton)
{
if(contains_2f32(container->rect, evt->pos))
{
t->pressed = 1;
}
else
{
t->pressed_outside = 1;
}
}
}
}
// rjf: build overlay container for loading animation
UI_Box *loading_overlay_container = &ui_nil_box;
UI_Parent(container) UI_WidthFill UI_HeightFill
{
loading_overlay_container = ui_build_box_from_key(UI_BoxFlag_Floating, ui_key_zero());
}
// rjf: build contents
UI_Parent(container) UI_Focus(is_focused ? UI_FocusKind_Null : UI_FocusKind_Off)
{
ui_set_next_pref_width(ui_pct(1, 0));
ui_set_next_pref_height(ui_pct(1, 0));
ui_set_next_child_layout_axis(Axis2_Y);
UI_Box *view_contents_container = ui_build_box_from_stringf(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground|UI_BoxFlag_Clip, "###view_contents_container");
UI_Parent(view_contents_container) UI_WidthFill
{
rd_view_ui(rect);
}
}
// rjf: build loading overlay
{
RD_ViewState *vs = rd_view_state_from_cfg(view);
F32 loading_t = vs->loading_t;
if(loading_t > 0.01f) UI_Parent(loading_overlay_container)
{
rd_loading_overlay(rect, loading_t, vs->loading_progress_v, vs->loading_progress_v_target);
}
}
// rjf: interact with container
UI_Signal sig = ui_signal_from_box(container);
t->signal = sig;
}
// rjf: pop interaction registers; commit if this is focused
RD_Regs *view_regs = rd_pop_regs();
if(is_focused)
{
MemoryCopyStruct(rd_regs(), view_regs);
}
// rjf: is not anchored? -> darken rest of screen
if(!is_anchored)
{
UI_TagF("inactive") UI_Transparency(1-open_t) UI_Rect(content_rect) ui_build_box_from_key(UI_BoxFlag_DrawBackground|UI_BoxFlag_Floating, ui_key_zero());
}
}
}
////////////////////////////
//- rjf: @window_ui_part do special handling of floating view interactions
//
{
//- rjf: hover eval focus rules
if(hover_eval_floating_view_task)
{
UI_Signal sig = hover_eval_floating_view_task->signal;
if(ui_pressed(sig) || hover_eval_floating_view_task->pressed)
{
ws->hover_eval_focused = 1;
}
if(ui_mouse_over(sig) || ws->hover_eval_focused)
{
ws->hover_eval_lastt_us = rd_state->time_in_us;
}
else if(ws->hover_eval_lastt_us+1000000 < rd_state->time_in_us)
{
rd_request_frame();
}
if(hover_eval_floating_view_task->pressed_outside || ui_slot_press(UI_EventActionSlot_Cancel))
{
ws->hover_eval_focused = 0;
MemoryZeroStruct(&ws->hover_eval_string);
arena_clear(ws->hover_eval_arena);
rd_request_frame();
}
}
//- rjf: query interactions
if(query_floating_view_task)
{
RD_Cfg *view = query_floating_view_task->view;
RD_ViewState *vs = rd_view_state_from_cfg(query_floating_view_task->view);
String8 cmd_name = ws->query_regs->cmd_name;
RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name);
// rjf: close queries
if(query_floating_view_task->pressed_outside ||
(rd_cfg_child_from_string(view, str8_lit("lister")) != &rd_nil_cfg && !vs->query_is_selected) ||
(cmd_name.size != 0 && !vs->query_is_selected) ||
ui_slot_press(UI_EventActionSlot_Cancel))
{
rd_cmd(RD_CmdKind_CancelQuery);
}
// rjf: any queries which take a file path mutate the debugger's "current path"
if(cmd_kind_info->query.slot == RD_RegSlot_FilePath)
{
RD_Cfg *query = rd_cfg_child_from_string(view, str8_lit("query"));
RD_Cfg *input = rd_cfg_child_from_string(query, str8_lit("input"));
if(input != &rd_nil_cfg)
{
String8 path_chopped = str8_chop_last_slash(input->first->string);
RD_Cfg *user = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
RD_Cfg *current_path = rd_cfg_child_from_string_or_alloc(user, str8_lit("current_path"));
if(!str8_match(current_path->first->string, path_chopped, 0))
{
rd_cmd(RD_CmdKind_SetCurrentPath, .file_path = path_chopped);
}
}
}
}
}
////////////////////////////
//- rjf: @window_ui_part top bar
//
ProfScope("build top bar")
{
os_window_clear_custom_border_data(ws->os);
os_window_push_custom_edges(ws->os, window_edge_px);
os_window_push_custom_title_bar(ws->os, dim_2f32(top_bar_rect).y);
ui_set_next_flags(UI_BoxFlag_DefaultFocusNav|UI_BoxFlag_DisableFocusOverlay);
UI_Focus((ws->menu_bar_focused && window_is_focused && !ui_any_ctx_menu_is_open()) ? UI_FocusKind_On : UI_FocusKind_Null)
UI_TagF("menu_bar")
UI_Pane(top_bar_rect, str8_lit("###top_bar"))
UI_WidthFill UI_Row
UI_Focus(UI_FocusKind_Null)
{
UI_Key menu_bar_group_key = ui_key_from_string(ui_key_zero(), str8_lit("###top_bar_group"));
MemoryZeroArray(ui_top_parent()->parent->corner_radii);
//- rjf: left column
{
ui_set_next_flags(UI_BoxFlag_Clip|UI_BoxFlag_ViewScrollX|UI_BoxFlag_ViewClamp);
UI_WidthFill UI_NamedRow(str8_lit("###menu_bar"))
{
//- rjf: icon
UI_Padding(ui_em(0.5f, 1.f))
{
UI_PrefWidth(ui_px(dim_2f32(top_bar_rect).y - ui_top_font_size()*0.8f, 1.f))
UI_Column
UI_Padding(ui_em(0.4f, 1.f))
UI_HeightFill
{
R_Handle texture = rd_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: menu items
if(dim_2f32(top_bar_rect).x > ui_top_font_size()*60)
{
ui_set_next_flags(UI_BoxFlag_DrawBackground);
UI_PrefWidth(ui_children_sum(1)) UI_Row UI_PrefWidth(ui_text_dim(20, 1)) UI_GroupKey(menu_bar_group_key)
{
// rjf: file menu
UI_Key file_menu_key = ui_key_from_string(ui_key_zero(), str8_lit("_file_menu_key_"));
UI_CtxMenu(file_menu_key) UI_PrefWidth(ui_em(50.f, 1.f)) UI_TagF("implicit")
{
String8 cmds[] =
{
rd_cmd_kind_info_table[RD_CmdKind_Open].string,
rd_cmd_kind_info_table[RD_CmdKind_OpenUser].string,
rd_cmd_kind_info_table[RD_CmdKind_OpenProject].string,
rd_cmd_kind_info_table[RD_CmdKind_OpenRecentProject].string,
rd_cmd_kind_info_table[RD_CmdKind_OpenPalette].string,
rd_cmd_kind_info_table[RD_CmdKind_UserSettings].string,
rd_cmd_kind_info_table[RD_CmdKind_ProjectSettings].string,
rd_cmd_kind_info_table[RD_CmdKind_Exit].string,
};
U32 codepoints[] =
{
'o',
'u',
'p',
'r',
'n',
'e',
't',
'x',
};
Assert(ArrayCount(codepoints) == ArrayCount(cmds));
rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints);
}
// rjf: window menu
UI_Key window_menu_key = ui_key_from_string(ui_key_zero(), str8_lit("_window_menu_key_"));
UI_CtxMenu(window_menu_key) UI_PrefWidth(ui_em(50.f, 1.f)) UI_TagF("implicit")
{
String8 cmds[] =
{
rd_cmd_kind_info_table[RD_CmdKind_OpenWindow].string,
rd_cmd_kind_info_table[RD_CmdKind_CloseWindow].string,
rd_cmd_kind_info_table[RD_CmdKind_ToggleFullscreen].string,
rd_cmd_kind_info_table[RD_CmdKind_WindowSettings].string,
};
U32 codepoints[] =
{
'w',
'c',
'f',
's',
};
Assert(ArrayCount(codepoints) == ArrayCount(cmds));
rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints);
}
// rjf: panel menu
UI_Key panel_menu_key = ui_key_from_string(ui_key_zero(), str8_lit("_panel_menu_key_"));
UI_CtxMenu(panel_menu_key) UI_PrefWidth(ui_em(50.f, 1.f)) UI_TagF("implicit")
{
String8 cmds[] =
{
rd_cmd_kind_info_table[RD_CmdKind_NewPanelUp].string,
rd_cmd_kind_info_table[RD_CmdKind_NewPanelDown].string,
rd_cmd_kind_info_table[RD_CmdKind_NewPanelRight].string,
rd_cmd_kind_info_table[RD_CmdKind_NewPanelLeft].string,
rd_cmd_kind_info_table[RD_CmdKind_ClosePanel].string,
rd_cmd_kind_info_table[RD_CmdKind_RotatePanelColumns].string,
rd_cmd_kind_info_table[RD_CmdKind_NextPanel].string,
rd_cmd_kind_info_table[RD_CmdKind_PrevPanel].string,
rd_cmd_kind_info_table[RD_CmdKind_TabBarTop].string,
rd_cmd_kind_info_table[RD_CmdKind_TabBarBottom].string,
rd_cmd_kind_info_table[RD_CmdKind_ResetToDefaultPanels].string,
rd_cmd_kind_info_table[RD_CmdKind_ResetToCompactPanels].string,
rd_cmd_kind_info_table[RD_CmdKind_ResetToSimplePanels].string,
};
U32 codepoints[] =
{
'u',
'd',
'r',
'l',
'x',
'c',
'n',
'p',
0,
0,
0,
0,
0,
};
Assert(ArrayCount(codepoints) == ArrayCount(cmds));
rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints);
}
// rjf: view menu
UI_Key tab_menu_key = ui_key_from_string(ui_key_zero(), str8_lit("_tab_menu_key_"));
UI_CtxMenu(tab_menu_key) UI_PrefWidth(ui_em(50.f, 1.f)) UI_TagF("implicit")
{
String8 cmds[] =
{
rd_cmd_kind_info_table[RD_CmdKind_OpenTab].string,
rd_cmd_kind_info_table[RD_CmdKind_CloseTab].string,
rd_cmd_kind_info_table[RD_CmdKind_DuplicateTab].string,
rd_cmd_kind_info_table[RD_CmdKind_MoveTabLeft].string,
rd_cmd_kind_info_table[RD_CmdKind_MoveTabRight].string,
rd_cmd_kind_info_table[RD_CmdKind_NextTab].string,
rd_cmd_kind_info_table[RD_CmdKind_PrevTab].string,
rd_cmd_kind_info_table[RD_CmdKind_TabSettings].string,
};
U32 codepoints[] =
{
'o',
'c',
'd',
'l',
'r',
'n',
'p',
's',
};
Assert(ArrayCount(codepoints) == ArrayCount(cmds));
rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints);
}
// rjf: targets menu
UI_Key targets_menu_key = ui_key_from_string(ui_key_zero(), str8_lit("_targets_menu_key_"));
UI_CtxMenu(targets_menu_key) UI_PrefWidth(ui_em(50.f, 1.f)) UI_TagF("implicit")
{
Temp scratch = scratch_begin(0, 0);
String8 cmds[] =
{
rd_cmd_kind_info_table[RD_CmdKind_AddTarget].string,
rd_cmd_kind_info_table[RD_CmdKind_LaunchAndRun].string,
rd_cmd_kind_info_table[RD_CmdKind_LaunchAndStepInto].string,
};
U32 codepoints[] =
{
'a',
'r',
's',
};
Assert(ArrayCount(codepoints) == ArrayCount(cmds));
rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints);
scratch_end(scratch);
}
// rjf: ctrl menu
UI_Key ctrl_menu_key = ui_key_from_string(ui_key_zero(), str8_lit("_ctrl_menu_key_"));
UI_CtxMenu(ctrl_menu_key) UI_PrefWidth(ui_em(50.f, 1.f)) UI_TagF("implicit")
{
String8 cmds[] =
{
rd_cmd_kind_info_table[D_CmdKind_Run].string,
rd_cmd_kind_info_table[D_CmdKind_KillAll].string,
rd_cmd_kind_info_table[D_CmdKind_Restart].string,
rd_cmd_kind_info_table[D_CmdKind_Halt].string,
rd_cmd_kind_info_table[D_CmdKind_StepInto].string,
rd_cmd_kind_info_table[D_CmdKind_StepOver].string,
rd_cmd_kind_info_table[D_CmdKind_StepOut].string,
rd_cmd_kind_info_table[D_CmdKind_Attach].string,
};
U32 codepoints[] =
{
'r',
'k',
's',
'h',
'i',
'o',
't',
'a',
};
Assert(ArrayCount(codepoints) == ArrayCount(cmds));
rd_cmd_list_menu_buttons(ArrayCount(cmds), cmds, codepoints);
}
// rjf: help menu
UI_Key help_menu_key = ui_key_from_string(ui_key_zero(), str8_lit("_help_menu_key_"));
UI_CtxMenu(help_menu_key) UI_PrefWidth(ui_em(50.f, 1.f)) UI_TagF("implicit")
{
UI_Row UI_TextAlignment(UI_TextAlign_Center) UI_TagF("weak")
ui_label(str8_lit(BUILD_TITLE_STRING_LITERAL));
ui_spacer(ui_em(1.f, 1.f));
UI_PrefHeight(ui_children_sum(1)) UI_Row UI_Padding(ui_pct(1, 0))
{
R_Handle texture = rd_state->icon_texture;
Vec2S32 texture_dim = r_size_from_tex2d(texture);
UI_PrefWidth(ui_px(ui_top_font_size()*10.f, 1.f))
UI_PrefHeight(ui_px(ui_top_font_size()*10.f, 1.f))
ui_image(texture, R_Tex2DSampleKind_Linear, r2f32p(0, 0, texture_dim.x, texture_dim.y), v4f32(1, 1, 1, 1), 0, str8_lit(""));
}
ui_spacer(ui_em(1.f, 1.f));
UI_Row
UI_PrefWidth(ui_text_dim(10, 1))
UI_TextAlignment(UI_TextAlign_Center)
UI_Padding(ui_pct(1, 0))
{
ui_labelf("Search for commands and options by pressing ");
UI_Flags(UI_BoxFlag_DrawBorder)
UI_TextAlignment(UI_TextAlign_Center)
rd_cmd_binding_buttons(rd_cmd_kind_info_table[RD_CmdKind_OpenPalette].string, str8_zero());
}
ui_spacer(ui_em(1.f, 1.f));
UI_TagF("pop")
UI_Row UI_Padding(ui_pct(1, 0)) UI_TextAlignment(UI_TextAlign_Center) UI_PrefWidth(ui_text_dim(10, 1))
UI_CornerRadius(ui_top_font_size()*0.5f)
{
String8 url = str8_lit("https://github.com/EpicGamesExt/raddebugger/issues");
UI_Signal sig = ui_button(str8_lit("Submit Request, Issue, or Bug Report"));
if(ui_clicked(sig))
{
os_open_in_browser(url);
}
}
ui_spacer(ui_em(0.5f, 1.f));
}
// rjf: buttons
UI_TextAlignment(UI_TextAlign_Center) UI_HeightFill
{
// rjf: set up table
struct
{
String8 name;
U32 codepoint;
OS_Key key;
UI_Key menu_key;
}
items[] =
{
{str8_lit("File"), 'f', OS_Key_F, file_menu_key},
{str8_lit("Window"), 'w', OS_Key_W, window_menu_key},
{str8_lit("Panel"), 'p', OS_Key_P, panel_menu_key},
{str8_lit("Tab"), 'b', OS_Key_V, tab_menu_key},
{str8_lit("Targets"), 't', OS_Key_T, targets_menu_key},
{str8_lit("Control"), 'c', OS_Key_C, ctrl_menu_key},
{str8_lit("Help"), 'h', OS_Key_H, help_menu_key},
};
// rjf: determine if one of the menus is already open
B32 menu_open = 0;
U64 open_menu_idx = 0;
for(U64 idx = 0; idx < ArrayCount(items); idx += 1)
{
if(ui_ctx_menu_is_open(items[idx].menu_key))
{
menu_open = 1;
open_menu_idx = idx;
break;
}
}
// rjf: navigate between menus
U64 open_menu_idx_prime = open_menu_idx;
if(menu_open && ws->menu_bar_focused && window_is_focused)
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
B32 taken = 0;
if(evt->delta_2s32.x > 0)
{
taken = 1;
open_menu_idx_prime += 1;
open_menu_idx_prime = open_menu_idx_prime%ArrayCount(items);
}
if(evt->delta_2s32.x < 0)
{
taken = 1;
open_menu_idx_prime = open_menu_idx_prime > 0 ? open_menu_idx_prime-1 : (ArrayCount(items)-1);
}
if(taken)
{
ui_eat_event(evt);
}
}
}
// rjf: make ui
for(U64 idx = 0; idx < ArrayCount(items); idx += 1)
{
ui_set_next_fastpath_codepoint(items[idx].codepoint);
B32 alt_fastpath_key = 0;
if(rd_setting_b32_from_name(str8_lit("focus_menu_bar_with_alt")) && ui_key_press(OS_Modifier_Alt, items[idx].key))
{
alt_fastpath_key = 1;
}
if((ws->menu_bar_key_held || ws->menu_bar_focused) && !ui_any_ctx_menu_is_open())
{
ui_set_next_flags(UI_BoxFlag_DrawTextFastpathCodepoint);
}
UI_Signal sig = rd_menu_bar_button(items[idx].name);
os_window_push_custom_title_bar_client_area(ws->os, sig.box->rect);
if(menu_open)
{
if((ui_hovering(sig) && !ui_ctx_menu_is_open(items[idx].menu_key)) || (open_menu_idx_prime == idx && open_menu_idx_prime != open_menu_idx))
{
ui_ctx_menu_open(items[idx].menu_key, sig.box->key, v2f32(0, sig.box->rect.y1-sig.box->rect.y0));
}
}
else if(ui_pressed(sig) || alt_fastpath_key)
{
if(ui_ctx_menu_is_open(items[idx].menu_key))
{
ui_ctx_menu_close();
}
else
{
ui_ctx_menu_open(items[idx].menu_key, sig.box->key, v2f32(0, sig.box->rect.y1-sig.box->rect.y0));
}
}
}
}
}
ui_spacer(ui_em(0.75f, 1));
// rjf: conversion task visualization
UI_PrefWidth(ui_text_dim(10, 1)) UI_HeightFill UI_TagF("pop")
{
Temp scratch = scratch_begin(0, 0);
RD_CfgList tasks = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("conversion_task"));
for(RD_CfgNode *n = tasks.first; n != 0; n = n->next)
{
RD_Cfg *task = n->v;
F32 task_t = ui_anim(ui_key_from_stringf(ui_key_zero(), "task_anim_%I64u", task->id), 1.f);
if(task_t > 0.5f)
{
String8 rdi_path = task->first->string;
String8 rdi_name = str8_skip_last_slash(rdi_path);
String8 task_text = push_str8f(scratch.arena, "Creating %S...", rdi_name);
UI_Key key = ui_key_from_stringf(ui_key_zero(), "task_%p", task);
UI_Box *box = ui_build_box_from_key(UI_BoxFlag_DrawHotEffects|UI_BoxFlag_DrawText|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground|UI_BoxFlag_Clickable, key);
os_window_push_custom_title_bar_client_area(ws->os, box->rect);
UI_Signal sig = ui_signal_from_box(box);
if(ui_hovering(sig)) UI_Tooltip
{
ui_label(rdi_path);
}
ui_box_equip_display_string(box, task_text);
}
}
scratch_end(scratch);
}
}
}
}
//- rjf: center column
if(dim_2f32(top_bar_rect).x > ui_top_font_size()*60)
UI_PrefWidth(ui_children_sum(1.f)) UI_Row
UI_PrefWidth(ui_px(dim_2f32(top_bar_rect).y, 1))
RD_Font(RD_FontSlot_Icons)
UI_FontSize(ui_top_font_size()*0.85f)
{
Temp scratch = scratch_begin(0, 0);
RD_CfgList targets = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("target"));
CTRL_EntityArray processes = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Process);
B32 have_targets = (targets.count != 0);
B32 can_send_signal = !d_ctrl_targets_running();
B32 can_play = (have_targets && (can_send_signal || d_ctrl_last_run_frame_idx()+4 > d_frame_index()));
B32 can_pause = (!can_send_signal);
B32 can_stop = (processes.count != 0);
B32 can_step = (processes.count != 0 && can_send_signal);
//- rjf: play button
if(can_play || !have_targets || processes.count == 0)
UI_TextAlignment(UI_TextAlign_Center)
UI_Flags((can_play ? 0 : UI_BoxFlag_Disabled))
UI_TagF(can_play ? "good" : "weak")
{
UI_Signal sig = ui_button(rd_icon_kind_text_table[RD_IconKind_Play]);
os_window_push_custom_title_bar_client_area(ws->os, sig.box->rect);
if(ui_hovering(sig) && !can_play)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Disabled: %s", have_targets ? "Targets are currently running" : "No active targets exist");
}
if(ui_hovering(sig) && can_play)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
{
if(can_stop)
{
ui_labelf("Resume all processes");
}
else
{
ui_labelf("Launch all active targets:");
for(RD_CfgNode *n = targets.first; n != 0; n = n->next)
{
RD_Cfg *target = n->v;
B32 target_is_enabled = !rd_disabled_from_cfg(target);
if(target_is_enabled)
{
DR_FStrList title_fstrs = rd_title_fstrs_from_cfg(ui_build_arena(), target);
UI_Box *box = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero());
ui_box_equip_display_fstrs(box, &title_fstrs);
}
}
}
}
}
if(ui_clicked(sig))
{
rd_cmd(RD_CmdKind_Run);
}
}
//- rjf: restart button
else UI_TextAlignment(UI_TextAlign_Center)
UI_TagF("good")
{
UI_Signal sig = ui_button(rd_icon_kind_text_table[RD_IconKind_Redo]);
os_window_push_custom_title_bar_client_area(ws->os, sig.box->rect);
if(ui_hovering(sig))
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
{
ui_labelf("Restart");
}
}
if(ui_clicked(sig))
{
rd_cmd(RD_CmdKind_Restart);
}
}
//- rjf: pause button
UI_TextAlignment(UI_TextAlign_Center) UI_Flags(can_pause ? 0 : UI_BoxFlag_Disabled)
UI_TagF("weak")
{
UI_Signal sig = ui_button(rd_icon_kind_text_table[RD_IconKind_Pause]);
os_window_push_custom_title_bar_client_area(ws->os, sig.box->rect);
if(ui_hovering(sig) && !can_pause)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Disabled: Already halted");
}
if(ui_hovering(sig) && can_pause)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Halt all attached processes");
}
if(ui_clicked(sig))
{
rd_cmd(RD_CmdKind_Halt);
}
}
//- rjf: stop button
UI_TextAlignment(UI_TextAlign_Center) UI_Flags(can_stop ? 0 : UI_BoxFlag_Disabled)
UI_TagF(can_stop ? "bad" : "weak")
{
UI_Signal sig = {0};
{
sig = ui_button(rd_icon_kind_text_table[RD_IconKind_Stop]);
os_window_push_custom_title_bar_client_area(ws->os, sig.box->rect);
}
if(ui_hovering(sig) && !can_stop)
{
UI_Tooltip RD_Font(RD_FontSlot_Main) ui_labelf("Disabled: No processes are running");
}
if(ui_hovering(sig) && can_stop)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Kill all attached processes");
}
if(ui_clicked(sig))
{
rd_cmd(RD_CmdKind_KillAll);
}
}
//- rjf: step over button
UI_TextAlignment(UI_TextAlign_Center) UI_Flags((can_play ? 0 : UI_BoxFlag_Disabled))
UI_TagF("weak")
{
UI_Signal sig = ui_button(rd_icon_kind_text_table[RD_IconKind_StepOver]);
os_window_push_custom_title_bar_client_area(ws->os, sig.box->rect);
if(ui_hovering(sig))
{
if(can_play)
{
UI_Tooltip RD_Font(RD_FontSlot_Main) ui_labelf("Step Over");
}
else
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Disabled: %s", have_targets ? "Targets are currently running" : "No active targets exist");
}
}
if(ui_clicked(sig))
{
rd_cmd(RD_CmdKind_StepOver);
}
}
//- rjf: step into button
UI_TextAlignment(UI_TextAlign_Center) UI_Flags((can_play ? 0 : UI_BoxFlag_Disabled))
UI_TagF("weak")
{
UI_Signal sig = ui_button(rd_icon_kind_text_table[RD_IconKind_StepInto]);
os_window_push_custom_title_bar_client_area(ws->os, sig.box->rect);
if(ui_hovering(sig))
{
if(can_play)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Step Into");
}
else
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Disabled: %s", have_targets ? "Targets are currently running" : "No active targets exist");
}
}
if(ui_clicked(sig))
{
rd_cmd(RD_CmdKind_StepInto);
}
}
//- rjf: step out button
UI_TextAlignment(UI_TextAlign_Center) UI_Flags(can_step ? 0 : UI_BoxFlag_Disabled)
UI_TagF("weak")
{
UI_Signal sig = ui_button(rd_icon_kind_text_table[RD_IconKind_StepOut]);
os_window_push_custom_title_bar_client_area(ws->os, sig.box->rect);
if(ui_hovering(sig) && !can_step && can_pause)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Disabled: Running");
}
if(ui_hovering(sig) && !can_step && !can_stop)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Disabled: No processes are running");
}
if(ui_hovering(sig) && can_step)
{
UI_Tooltip RD_Font(RD_FontSlot_Main)
ui_labelf("Step Out");
}
if(ui_clicked(sig))
{
rd_cmd(RD_CmdKind_StepOut);
}
}
scratch_end(scratch);
}
//- rjf: right column
UI_WidthFill UI_Row
{
B32 do_user_prof = (dim_2f32(top_bar_rect).x > ui_top_font_size()*80);
ui_spacer(ui_pct(1, 0));
// rjf: loaded user viz
if(do_user_prof) UI_TagF("pop")
{
ui_set_next_pref_width(ui_children_sum(1));
ui_set_next_child_layout_axis(Axis2_X);
UI_Box *user_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawActiveEffects,
"###loaded_user_button");
os_window_push_custom_title_bar_client_area(ws->os, user_box->rect);
UI_Parent(user_box) UI_PrefWidth(ui_text_dim(10, 0)) UI_TextAlignment(UI_TextAlign_Center)
{
String8 user_path = rd_state->user_path;
user_path = str8_chop_last_dot(user_path);
RD_Font(RD_FontSlot_Icons)
UI_TextRasterFlags(rd_raster_flags_from_slot(RD_FontSlot_Icons))
ui_label(rd_icon_kind_text_table[RD_IconKind_Person]);
ui_label(str8_skip_last_slash(user_path));
}
UI_Signal user_sig = ui_signal_from_box(user_box);
if(ui_clicked(user_sig))
{
rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_OpenUser].string);
}
}
if(do_user_prof)
{
ui_spacer(ui_em(0.75f, 0));
}
// rjf: loaded project viz
if(do_user_prof) UI_TagF("pop")
{
ui_set_next_pref_width(ui_children_sum(1));
ui_set_next_child_layout_axis(Axis2_X);
UI_Box *prof_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawActiveEffects,
"###loaded_project_button");
os_window_push_custom_title_bar_client_area(ws->os, prof_box->rect);
UI_Parent(prof_box) UI_PrefWidth(ui_text_dim(10, 0)) UI_TextAlignment(UI_TextAlign_Center)
{
String8 prof_path = rd_state->project_path;
prof_path = str8_chop_last_dot(prof_path);
RD_Font(RD_FontSlot_Icons)
ui_label(rd_icon_kind_text_table[RD_IconKind_Briefcase]);
ui_label(str8_skip_last_slash(prof_path));
}
UI_Signal prof_sig = ui_signal_from_box(prof_box);
if(ui_clicked(prof_sig))
{
rd_cmd(RD_CmdKind_RunCommand, .cmd_name = rd_cmd_kind_info_table[RD_CmdKind_OpenProject].string);
}
}
if(do_user_prof)
{
ui_spacer(ui_em(0.75f, 0));
}
// rjf: close dropdown
UI_Key close_ctx_menu_key = ui_key_from_stringf(ui_key_zero(), "###close_ctx_menu");
UI_CtxMenu(close_ctx_menu_key) UI_TagF("implicit")
{
if(ui_clicked(rd_icon_buttonf(RD_IconKind_Window, 0, "Close Window")))
{
rd_cmd(RD_CmdKind_CloseWindow);
}
if(ui_clicked(rd_icon_buttonf(RD_IconKind_X, 0, "Exit")))
{
rd_cmd(RD_CmdKind_Exit);
}
}
// rjf: min/max/close buttons
{
UI_Signal min_sig = {0};
UI_Signal max_sig = {0};
UI_Signal cls_sig = {0};
Vec2F32 bar_dim = dim_2f32(top_bar_rect);
F32 button_dim = floor_f32(bar_dim.y);
UI_PrefWidth(ui_px(button_dim, 1.f))
UI_FontSize(ui_top_font_size()*0.75f)
{
min_sig = rd_icon_buttonf(RD_IconKind_WindowMinimize, 0, "##minimize");
max_sig = rd_icon_buttonf(os_window_is_maximized(ws->os) ? RD_IconKind_WindowRestore : RD_IconKind_Window, 0, "##maximize");
}
UI_PrefWidth(ui_px(button_dim, 1.f))
UI_TagF("bad_pop")
{
cls_sig = rd_icon_buttonf(RD_IconKind_X, 0, "##close");
}
if(ui_clicked(min_sig))
{
os_window_set_minimized(ws->os, 1);
}
if(ui_clicked(max_sig))
{
os_window_set_maximized(ws->os, !os_window_is_maximized(ws->os));
}
if(ui_clicked(cls_sig))
{
if(ws->order_next != &rd_nil_window_state ||
ws->order_prev != &rd_nil_window_state)
{
ui_ctx_menu_open(close_ctx_menu_key, cls_sig.box->key, v2f32(0, dim_2f32(cls_sig.box->rect).y));
}
else
{
rd_cmd(RD_CmdKind_Exit);
}
}
os_window_push_custom_title_bar_client_area(ws->os, min_sig.box->rect);
os_window_push_custom_title_bar_client_area(ws->os, max_sig.box->rect);
os_window_push_custom_title_bar_client_area(ws->os, pad_2f32(cls_sig.box->rect, 2.f));
}
}
}
}
////////////////////////////
//- rjf: @window_ui_part bottom bar
//
ProfScope("build bottom bar")
{
B32 is_running = d_ctrl_targets_running() && d_ctrl_last_run_frame_idx() < d_frame_index();
CTRL_Event stop_event = d_ctrl_last_stop_event();
String8 tag = str8_lit("pop");
if(!is_running)
{
switch(stop_event.cause)
{
default:
case CTRL_EventCause_Finished:
{
tag = str8_lit("good_pop");
}break;
case CTRL_EventCause_UserBreakpoint:
case CTRL_EventCause_InterruptedByException:
case CTRL_EventCause_InterruptedByTrap:
case CTRL_EventCause_InterruptedByHalt:
{
tag = str8_lit("bad_pop");
}break;
}
}
UI_Flags(UI_BoxFlag_DrawBackground) UI_CornerRadius(0)
UI_Tag(tag)
UI_Pane(bottom_bar_rect, str8_lit("###bottom_bar")) UI_WidthFill UI_Row
UI_Flags(0)
{
// rjf: developer frame-time indicator
if(DEV_updating_indicator)
{
F32 animation_t = pow_f32(sin_f32(rd_state->time_in_seconds/2.f), 2.f);
ui_spacer(ui_em(0.3f, 1.f));
ui_spacer(ui_em(1.5f*animation_t, 1.f));
UI_PrefWidth(ui_text_dim(10, 1)) ui_labelf("*");
ui_spacer(ui_em(1.5f*(1-animation_t), 1.f));
}
// rjf: status
{
ui_spacer(ui_em(1.f, 1.f));
if(is_running)
{
ui_label(str8_lit("Running"));
}
else
{
Temp scratch = scratch_begin(0, 0);
DR_FStrList explanation_fstrs = rd_stop_explanation_fstrs_from_ctrl_event(scratch.arena, &stop_event);
UI_Box *box = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero());
ui_box_equip_display_fstrs(box, &explanation_fstrs);
scratch_end(scratch);
}
}
ui_spacer(ui_pct(1, 0));
// rjf: bind change visualization
if(rd_state->bind_change_active)
{
RD_CmdKindInfo *info = rd_cmd_kind_info_from_string(rd_state->bind_change_cmd_name);
String8 display_name = rd_display_from_code_name(info->string);
UI_PrefWidth(ui_text_dim(10, 1))
UI_Flags(UI_BoxFlag_DrawBackground)
UI_TextAlignment(UI_TextAlign_Center)
UI_CornerRadius(4)
UI_TagF("pop")
ui_labelf("Currently rebinding \"%S\" hotkey", display_name);
}
// rjf: error visualization
else if(ws->error_t >= 0.01f)
{
ws->error_t -= rd_state->frame_dt/8.f;
rd_request_frame();
String8 error_string = str8(ws->error_buffer, ws->error_string_size);
if(error_string.size != 0)
{
ui_set_next_pref_width(ui_children_sum(1));
UI_CornerRadius(4)
UI_Row
UI_PrefWidth(ui_text_dim(10, 1))
UI_TextAlignment(UI_TextAlign_Center)
{
RD_Font(RD_FontSlot_Icons) ui_label(rd_icon_kind_text_table[RD_IconKind_WarningBig]);
rd_label(error_string);
}
}
}
}
}
////////////////////////////
//- rjf: @window_ui_part panel non-leaf UI (drag boundaries, drag/drop sites)
//
B32 is_changing_panel_boundaries = 0;
ProfScope("non-leaf panel UI")
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
//////////////////////////
//- rjf: continue on leaf panels
//
if(panel->first == &rd_nil_panel_node)
{
continue;
}
//////////////////////////
//- rjf: grab info
//
Axis2 split_axis = panel->split_axis;
Rng2F32 panel_rect = rd_target_rect_from_panel_node(content_rect, panel_tree.root, panel);
//////////////////////////
//- rjf: boundary tab-drag/drop sites
//
{
RD_Cfg *drag_view = rd_cfg_from_id(rd_state->drag_drop_regs->view);
if(rd_drag_is_active() && rd_state->drag_drop_regs_slot == RD_RegSlot_View && drag_view != &rd_nil_cfg)
{
//- rjf: params
F32 drop_site_major_dim_px = ceil_f32(ui_top_font_size()*7.f);
F32 drop_site_minor_dim_px = ceil_f32(ui_top_font_size()*5.f);
F32 corner_radius = ui_top_font_size()*0.5f;
F32 padding = ceil_f32(ui_top_font_size()*0.5f);
//- rjf: special case - build Y boundary drop sites on root panel
//
// (this does not naturally follow from the below algorithm, since the
// root level panel only splits on X)
if(panel == panel_tree.root) UI_CornerRadius(corner_radius)
{
Vec2F32 panel_rect_center = center_2f32(panel_rect);
Axis2 axis = axis2_flip(panel_tree.root->split_axis);
for EachEnumVal(Side, side)
{
UI_Key key = ui_key_from_stringf(ui_key_zero(), "root_extra_split_%i", side);
Rng2F32 site_rect = panel_rect;
site_rect.p0.v[axis2_flip(axis)] = panel_rect_center.v[axis2_flip(axis)] - drop_site_major_dim_px/2;
site_rect.p1.v[axis2_flip(axis)] = panel_rect_center.v[axis2_flip(axis)] + drop_site_major_dim_px/2;
site_rect.p0.v[axis] = panel_rect.v[side].v[axis] - drop_site_minor_dim_px/2;
site_rect.p1.v[axis] = panel_rect.v[side].v[axis] + drop_site_minor_dim_px/2;
// rjf: build
UI_Box *site_box = &ui_nil_box;
{
F32 site_open_t = ui_anim(ui_key_from_stringf(key, "open_t"), 1.f);
UI_Rect(site_rect) UI_Squish(0.1f-0.1f*site_open_t) UI_Transparency(1-site_open_t)
{
site_box = ui_build_box_from_key(UI_BoxFlag_DropSite|UI_BoxFlag_DrawHotEffects, key);
ui_signal_from_box(site_box);
}
UI_Box *site_box_viz = &ui_nil_box;
UI_Parent(site_box) UI_WidthFill UI_HeightFill
UI_Padding(ui_px(padding, 1.f))
UI_Column
UI_Padding(ui_px(padding, 1.f))
UI_GroupKey(key)
{
ui_set_next_child_layout_axis(axis2_flip(axis));
site_box_viz = ui_build_box_from_key(UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawDropShadow|
UI_BoxFlag_DrawBackgroundBlur|
UI_BoxFlag_DrawHotEffects, ui_key_zero());
}
UI_Parent(site_box_viz) UI_WidthFill UI_HeightFill UI_Padding(ui_px(padding, 1.f))
{
ui_set_next_child_layout_axis(axis);
UI_Box *row_or_column = ui_build_box_from_key(0, ui_key_zero()); UI_Parent(row_or_column) UI_Padding(ui_px(padding, 1.f))
{
ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero());
ui_spacer(ui_px(padding, 1.f));
ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero());
}
}
}
// rjf: viz
if(ui_key_match(site_box->key, ui_drop_hot_key()))
{
Rng2F32 future_split_rect_target = site_rect;
future_split_rect_target.p0.v[axis] -= drop_site_major_dim_px;
future_split_rect_target.p1.v[axis] += drop_site_major_dim_px;
future_split_rect_target.p0.v[axis2_flip(axis)] = panel_rect.p0.v[axis2_flip(axis)];
future_split_rect_target.p1.v[axis2_flip(axis)] = panel_rect.p1.v[axis2_flip(axis)];
future_split_rect_target = pad_2f32(future_split_rect_target, -ui_top_font_size()*2.f);
Vec2F32 future_split_rect_target_center = center_2f32(future_split_rect_target);
Rng2F32 future_split_rect =
{
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v0"), future_split_rect_target.x0, .initial = future_split_rect_target_center.x),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v1"), future_split_rect_target.y0, .initial = future_split_rect_target_center.y),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v2"), future_split_rect_target.x1, .initial = future_split_rect_target_center.x),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v3"), future_split_rect_target.y1, .initial = future_split_rect_target_center.y),
};
UI_Rect(future_split_rect) UI_TagF("drop_site") UI_CornerRadius(ui_top_font_size()*2.f)
{
ui_build_box_from_key(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder, ui_key_zero());
}
}
// rjf: drop
if(ui_key_match(site_box->key, ui_drop_hot_key()) && rd_drag_drop())
{
Dir2 dir = (axis == Axis2_Y ? (side == Side_Min ? Dir2_Up : Dir2_Down) :
axis == Axis2_X ? (side == Side_Min ? Dir2_Left : Dir2_Right) :
Dir2_Invalid);
if(dir != Dir2_Invalid)
{
RD_PanelNode *split_panel = panel;
rd_cmd(RD_CmdKind_SplitPanel,
.dst_panel = split_panel->cfg->id,
.panel = rd_state->drag_drop_regs->panel,
.view = rd_state->drag_drop_regs->view,
.dir2 = dir);
}
}
}
}
//- rjf: iterate all children, build boundary drop sites
Axis2 split_axis = panel->split_axis;
UI_CornerRadius(corner_radius) for(RD_PanelNode *child = panel->first;; child = child->next)
{
// rjf: form rect
Rng2F32 child_rect = rd_target_rect_from_panel_node_child(panel_rect, panel, child);
Vec2F32 child_rect_center = center_2f32(child_rect);
UI_Key key = ui_key_from_stringf(ui_key_zero(), "drop_boundary_%p_%p", panel->cfg, child->cfg);
Rng2F32 site_rect = r2f32(child_rect_center, child_rect_center);
site_rect.p0.v[split_axis] = child_rect.p0.v[split_axis] - drop_site_minor_dim_px/2;
site_rect.p1.v[split_axis] = child_rect.p0.v[split_axis] + drop_site_minor_dim_px/2;
site_rect.p0.v[axis2_flip(split_axis)] -= drop_site_major_dim_px/2;
site_rect.p1.v[axis2_flip(split_axis)] += drop_site_major_dim_px/2;
// rjf: build
UI_Box *site_box = &ui_nil_box;
{
F32 site_open_t = ui_anim(ui_key_from_stringf(key, "open_t"), 1.f);
UI_Rect(site_rect) UI_Squish(0.1f-0.1f*site_open_t) UI_Transparency(1-site_open_t)
{
site_box = ui_build_box_from_key(UI_BoxFlag_DropSite|UI_BoxFlag_DrawHotEffects, key);
ui_signal_from_box(site_box);
}
UI_Box *site_box_viz = &ui_nil_box;
UI_Parent(site_box) UI_WidthFill UI_HeightFill
UI_Padding(ui_px(padding, 1.f))
UI_Column
UI_Padding(ui_px(padding, 1.f))
UI_GroupKey(key)
{
ui_set_next_child_layout_axis(axis2_flip(split_axis));
site_box_viz = ui_build_box_from_key(UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawDropShadow|
UI_BoxFlag_DrawBackgroundBlur|
UI_BoxFlag_DrawHotEffects, ui_key_zero());
}
UI_Parent(site_box_viz) UI_WidthFill UI_HeightFill UI_Padding(ui_px(padding, 1.f))
{
ui_set_next_child_layout_axis(split_axis);
UI_Box *row_or_column = ui_build_box_from_key(0, ui_key_zero()); UI_Parent(row_or_column) UI_Padding(ui_px(padding, 1.f))
{
ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero());
ui_spacer(ui_px(padding, 1.f));
ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero());
}
}
}
// rjf: viz
if(ui_key_match(site_box->key, ui_drop_hot_key()))
{
Rng2F32 future_split_rect_target = site_rect;
future_split_rect_target.p0.v[split_axis] -= drop_site_major_dim_px;
future_split_rect_target.p1.v[split_axis] += drop_site_major_dim_px;
future_split_rect_target.p0.v[axis2_flip(split_axis)] = child_rect.p0.v[axis2_flip(split_axis)];
future_split_rect_target.p1.v[axis2_flip(split_axis)] = child_rect.p1.v[axis2_flip(split_axis)];
future_split_rect_target = pad_2f32(future_split_rect_target, -ui_top_font_size()*2.f);
Vec2F32 future_split_rect_target_center = center_2f32(future_split_rect_target);
Rng2F32 future_split_rect =
{
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v0"), future_split_rect_target.x0, .initial = future_split_rect_target_center.x),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v1"), future_split_rect_target.y0, .initial = future_split_rect_target_center.y),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v2"), future_split_rect_target.x1, .initial = future_split_rect_target_center.x),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v3"), future_split_rect_target.y1, .initial = future_split_rect_target_center.y),
};
UI_Rect(future_split_rect) UI_TagF("drop_site") UI_CornerRadius(ui_top_font_size()*2.f)
{
ui_build_box_from_key(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder, ui_key_zero());
}
}
// rjf: drop
if(ui_key_match(site_box->key, ui_drop_hot_key()) && rd_drag_drop())
{
Dir2 dir = (panel->split_axis == Axis2_X ? Dir2_Left : Dir2_Up);
RD_PanelNode *split_panel = child;
if(split_panel == &rd_nil_panel_node)
{
split_panel = panel->last;
dir = (panel->split_axis == Axis2_X ? Dir2_Right : Dir2_Down);
}
rd_cmd(RD_CmdKind_SplitPanel,
.dst_panel = split_panel->cfg->id,
.panel = rd_state->drag_drop_regs->panel,
.view = rd_state->drag_drop_regs->view,
.dir2 = dir);
}
// rjf: exit on opl child
if(child == &rd_nil_panel_node)
{
break;
}
}
}
}
//////////////////////////
//- rjf: do UI for drag boundaries between all children
//
for(RD_PanelNode *child = panel->first;
child != &rd_nil_panel_node && child->next != &rd_nil_panel_node;
child = child->next)
{
RD_PanelNode *min_child = child;
RD_PanelNode *max_child = min_child->next;
Rng2F32 min_child_rect = rd_target_rect_from_panel_node_child(panel_rect, panel, min_child);
Rng2F32 max_child_rect = rd_target_rect_from_panel_node_child(panel_rect, panel, max_child);
Rng2F32 boundary_rect = {0};
{
boundary_rect.p0.v[split_axis] = min_child_rect.p1.v[split_axis] - ui_top_font_size()/3;
boundary_rect.p1.v[split_axis] = max_child_rect.p0.v[split_axis] + ui_top_font_size()/3;
boundary_rect.p0.v[axis2_flip(split_axis)] = panel_rect.p0.v[axis2_flip(split_axis)];
boundary_rect.p1.v[axis2_flip(split_axis)] = panel_rect.p1.v[axis2_flip(split_axis)];
}
UI_Rect(boundary_rect)
{
ui_set_next_hover_cursor(split_axis == Axis2_X ? OS_Cursor_LeftRight : OS_Cursor_UpDown);
UI_Box *box = ui_build_box_from_stringf(UI_BoxFlag_Clickable, "###%p_%p", min_child->cfg, max_child->cfg);
UI_Signal sig = ui_signal_from_box(box);
if(ui_double_clicked(sig))
{
ui_kill_action();
F32 sum_pct = min_child->pct_of_parent + max_child->pct_of_parent;
min_child->pct_of_parent = 0.5f * sum_pct;
max_child->pct_of_parent = 0.5f * sum_pct;
rd_cfg_equip_stringf(min_child->cfg, "%f", min_child->pct_of_parent);
rd_cfg_equip_stringf(max_child->cfg, "%f", max_child->pct_of_parent);
}
else if(ui_pressed(sig))
{
Vec2F32 v = {min_child->pct_of_parent, max_child->pct_of_parent};
ui_store_drag_struct(&v);
}
else if(ui_dragging(sig))
{
Vec2F32 v = *ui_get_drag_struct(Vec2F32);
Vec2F32 mouse_delta = ui_drag_delta();
F32 total_size = dim_2f32(panel_rect).v[split_axis];
F32 min_pct__before = v.v[0];
F32 min_pixels__before = min_pct__before * total_size;
F32 min_pixels__after = min_pixels__before + mouse_delta.v[split_axis];
if(min_pixels__after < 50.f)
{
min_pixels__after = 50.f;
}
F32 min_pct__after = min_pixels__after / total_size;
F32 pct_delta = min_pct__after - min_pct__before;
F32 max_pct__before = v.v[1];
F32 max_pct__after = max_pct__before - pct_delta;
F32 max_pixels__after = max_pct__after * total_size;
if(max_pixels__after < 50.f)
{
max_pixels__after = 50.f;
max_pct__after = max_pixels__after / total_size;
pct_delta = -(max_pct__after - max_pct__before);
min_pct__after = min_pct__before + pct_delta;
}
min_child->pct_of_parent = min_pct__after;
max_child->pct_of_parent = max_pct__after;
rd_cfg_equip_stringf(min_child->cfg, "%f", min_pct__after);
rd_cfg_equip_stringf(max_child->cfg, "%f", max_pct__after);
is_changing_panel_boundaries = 1;
}
}
}
}
////////////////////////////
//- rjf: @window_ui_part animate panels
//
{
B32 window_is_resizing = (ws->last_window_rect.x1 != window_rect.x1 ||
ws->last_window_rect.y1 != window_rect.y1);
Vec2F32 content_rect_dim = dim_2f32(content_rect);
if(content_rect_dim.x > 0 && content_rect_dim.y > 0)
{
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
Rng2F32 target_rect_px = rd_target_rect_from_panel_node(content_rect, panel_tree.root, panel);
Rng2F32 target_rect_pct = r2f32p(target_rect_px.x0/content_rect_dim.x,
target_rect_px.y0/content_rect_dim.y,
target_rect_px.x1/content_rect_dim.x,
target_rect_px.y1/content_rect_dim.y);
B32 reset = (window_is_resizing || ws->window_layout_reset || ws->frames_alive < 5 || is_changing_panel_boundaries);
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x0", panel->cfg), target_rect_pct.x0, .initial = target_rect_pct.x0, .reset = reset);
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y0", panel->cfg), target_rect_pct.y0, .initial = target_rect_pct.y0, .reset = reset);
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x1", panel->cfg), target_rect_pct.x1, .initial = target_rect_pct.x1, .reset = reset);
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y1", panel->cfg), target_rect_pct.y1, .initial = target_rect_pct.y1, .reset = reset);
}
}
ws->window_layout_reset = 0;
}
////////////////////////////
//- rjf: @window_ui_part panel leaf UI
//
if(content_rect.x1 > content_rect.x0 && content_rect.y1 > content_rect.y0)
{
ProfScope("leaf panel UI")
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
if(panel->first != &rd_nil_panel_node) {continue;}
B32 panel_is_focused = (window_is_focused &&
!ws->menu_bar_focused &&
!query_is_open &&
!ui_any_ctx_menu_is_open() &&
!ws->hover_eval_focused &&
panel_tree.focused == panel);
RD_Cfg *selected_tab = panel->selected_tab;
RD_ViewState *selected_tab_view_state = rd_view_state_from_cfg(selected_tab);
ProfScope("leaf panel UI work - %.*s: %.*s", str8_varg(selected_tab->string), str8_varg(rd_expr_from_cfg(selected_tab)))
UI_Focus(panel_is_focused ? UI_FocusKind_Null : UI_FocusKind_Off)
{
//////////////////////////
//- rjf: calculate UI rectangles
//
Vec2F32 content_rect_dim = dim_2f32(content_rect);
Rng2F32 target_rect_px = rd_target_rect_from_panel_node(content_rect, panel_tree.root, panel);
Rng2F32 target_rect_pct = r2f32p(target_rect_px.x0 / content_rect_dim.x,
target_rect_px.y0 / content_rect_dim.y,
target_rect_px.x1 / content_rect_dim.x,
target_rect_px.y1 / content_rect_dim.y);
Rng2F32 panel_rect_pct = r2f32p(ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x0", panel->cfg), target_rect_pct.x0, .initial = target_rect_pct.x0),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y0", panel->cfg), target_rect_pct.y0, .initial = target_rect_pct.y0),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x1", panel->cfg), target_rect_pct.x1, .initial = target_rect_pct.x1),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y1", panel->cfg), target_rect_pct.y1, .initial = target_rect_pct.y1));
Rng2F32 panel_rect = r2f32p(panel_rect_pct.x0*content_rect_dim.x,
panel_rect_pct.y0*content_rect_dim.y,
panel_rect_pct.x1*content_rect_dim.x,
panel_rect_pct.y1*content_rect_dim.y);
panel_rect = pad_2f32(panel_rect, floor_f32(-ui_top_font_size()*0.15f));
F32 tab_bar_rheight = floor_f32(ui_top_font_size()*3.5f);
F32 tab_bar_vheight = floor_f32(ui_top_font_size()*rd_setting_f32_from_name(str8_lit("tab_height")));
F32 tab_bar_rv_diff = tab_bar_rheight - tab_bar_vheight;
F32 tab_spacing = floor_f32(ui_top_font_size()*0.4f);
Rng2F32 tab_bar_rect = r2f32p(panel_rect.x0, panel_rect.y0, panel_rect.x1, panel_rect.y0 + tab_bar_vheight);
Rng2F32 content_rect = r2f32p(panel_rect.x0, panel_rect.y0+tab_bar_vheight, panel_rect.x1, panel_rect.y1);
if(panel->tab_side == Side_Max)
{
tab_bar_rect.y0 = panel_rect.y1 - tab_bar_vheight;
tab_bar_rect.y1 = panel_rect.y1;
content_rect.y0 = panel_rect.y0;
content_rect.y1 = panel_rect.y1 - tab_bar_vheight;
}
tab_bar_rect = intersect_2f32(tab_bar_rect, panel_rect);
content_rect = intersect_2f32(content_rect, panel_rect);
//////////////////////////
//- rjf: decide to skip this panel (e.g. if it is too small
//
B32 build_panel = (content_rect.x1 > content_rect.x0 && content_rect.y1 > content_rect.y0);
//////////////////////////
//- rjf: build combined split+movetab drag/drop sites
//
if(build_panel)
{
RD_Cfg *view = rd_cfg_from_id(rd_state->drag_drop_regs->view);
if(rd_drag_is_active() && rd_state->drag_drop_regs_slot == RD_RegSlot_View && view != &rd_nil_cfg && contains_2f32(panel_rect, ui_mouse()) && ui_key_match(ui_drop_hot_key(), ui_key_zero()))
{
F32 drop_site_dim_px = ceil_f32(ui_top_font_size()*7.f);
drop_site_dim_px = Min(drop_site_dim_px, dim_2f32(panel_rect).v[panel->split_axis]/4.f);
drop_site_dim_px = Max(drop_site_dim_px, ceil_f32(ui_top_font_size()*3.f));
Vec2F32 drop_site_half_dim = v2f32(drop_site_dim_px/2, drop_site_dim_px/2);
Vec2F32 panel_center = center_2f32(panel_rect);
F32 corner_radius = ui_top_font_size()*0.5f;
F32 padding = ceil_f32(ui_top_font_size()*0.5f);
struct
{
UI_Key key;
Dir2 split_dir;
Rng2F32 rect;
}
sites[] =
{
{
ui_key_from_stringf(ui_key_zero(), "drop_split_center_%p", panel->cfg),
Dir2_Invalid,
r2f32(sub_2f32(panel_center, drop_site_half_dim),
add_2f32(panel_center, drop_site_half_dim))
},
{
ui_key_from_stringf(ui_key_zero(), "drop_split_up_%p", panel->cfg),
Dir2_Up,
r2f32p(panel_center.x-drop_site_half_dim.x,
panel_center.y-drop_site_half_dim.y - drop_site_half_dim.y*2,
panel_center.x+drop_site_half_dim.x,
panel_center.y+drop_site_half_dim.y - drop_site_half_dim.y*2),
},
{
ui_key_from_stringf(ui_key_zero(), "drop_split_down_%p", panel->cfg),
Dir2_Down,
r2f32p(panel_center.x-drop_site_half_dim.x,
panel_center.y-drop_site_half_dim.y + drop_site_half_dim.y*2,
panel_center.x+drop_site_half_dim.x,
panel_center.y+drop_site_half_dim.y + drop_site_half_dim.y*2),
},
{
ui_key_from_stringf(ui_key_zero(), "drop_split_left_%p", panel->cfg),
Dir2_Left,
r2f32p(panel_center.x-drop_site_half_dim.x - drop_site_half_dim.x*2,
panel_center.y-drop_site_half_dim.y,
panel_center.x+drop_site_half_dim.x - drop_site_half_dim.x*2,
panel_center.y+drop_site_half_dim.y),
},
{
ui_key_from_stringf(ui_key_zero(), "drop_split_right_%p", panel->cfg),
Dir2_Right,
r2f32p(panel_center.x-drop_site_half_dim.x + drop_site_half_dim.x*2,
panel_center.y-drop_site_half_dim.y,
panel_center.x+drop_site_half_dim.x + drop_site_half_dim.x*2,
panel_center.y+drop_site_half_dim.y),
},
};
UI_CornerRadius(corner_radius)
for(U64 idx = 0; idx < ArrayCount(sites); idx += 1)
{
UI_Key key = sites[idx].key;
Dir2 dir = sites[idx].split_dir;
Rng2F32 rect = sites[idx].rect;
Axis2 split_axis = axis2_from_dir2(dir);
Side split_side = side_from_dir2(dir);
if(dir != Dir2_Invalid && split_axis == panel->parent->split_axis)
{
continue;
}
UI_Box *site_box = &ui_nil_box;
{
F32 site_open_t = ui_anim(ui_key_from_stringf(key, "open_t"), 1.f);
UI_Rect(rect) UI_Squish(0.1f-0.1f*site_open_t) UI_Transparency(1-site_open_t)
{
site_box = ui_build_box_from_key(UI_BoxFlag_DropSite|UI_BoxFlag_DrawHotEffects, key);
ui_signal_from_box(site_box);
}
UI_Box *site_box_viz = &ui_nil_box;
UI_GroupKey(key)
UI_Parent(site_box) UI_WidthFill UI_HeightFill
UI_Padding(ui_px(padding, 1.f))
UI_Column
UI_Padding(ui_px(padding, 1.f))
{
ui_set_next_child_layout_axis(axis2_flip(split_axis));
site_box_viz = ui_build_box_from_key(UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawDropShadow|
UI_BoxFlag_DrawBackgroundBlur|
UI_BoxFlag_DrawHotEffects, ui_key_zero());
}
if(dir != Dir2_Invalid)
{
UI_Parent(site_box_viz) UI_WidthFill UI_HeightFill UI_Padding(ui_px(padding, 1.f))
{
ui_set_next_child_layout_axis(split_axis);
UI_Box *row_or_column = ui_build_box_from_key(0, ui_key_zero());
UI_Parent(row_or_column) UI_Padding(ui_px(padding, 1.f)) UI_TagF("drop_site")
{
if(split_side == Side_Min) { ui_set_next_flags(UI_BoxFlag_DrawBackground); }
ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero());
ui_spacer(ui_px(padding, 1.f));
if(split_side == Side_Max) { ui_set_next_flags(UI_BoxFlag_DrawBackground); }
ui_build_box_from_key(UI_BoxFlag_DrawBorder, ui_key_zero());
}
}
}
else
{
UI_Parent(site_box_viz) UI_WidthFill UI_HeightFill UI_Padding(ui_px(padding, 1.f))
{
ui_set_next_child_layout_axis(split_axis);
UI_Box *row_or_column = ui_build_box_from_key(0, ui_key_zero());
UI_Parent(row_or_column) UI_Padding(ui_px(padding, 1.f)) UI_TagF("drop_site")
{
ui_build_box_from_key(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground, ui_key_zero());
}
}
}
}
if(ui_key_match(site_box->key, ui_drop_hot_key()) && rd_drag_drop())
{
if(dir != Dir2_Invalid)
{
rd_cmd(RD_CmdKind_SplitPanel,
.dst_panel = panel->cfg->id,
.panel = rd_state->drag_drop_regs->panel,
.view = rd_state->drag_drop_regs->view,
.dir2 = dir);
}
else
{
rd_cmd(RD_CmdKind_MoveView,
.dst_panel = panel->cfg->id,
.panel = rd_state->drag_drop_regs->panel,
.view = rd_state->drag_drop_regs->view,
.prev_tab = rd_cfg_list_last(&panel->tabs)->id);
}
}
}
for(U64 idx = 0; idx < ArrayCount(sites); idx += 1)
{
B32 is_drop_hot = ui_key_match(ui_drop_hot_key(), sites[idx].key);
if(is_drop_hot)
{
Axis2 split_axis = axis2_from_dir2(sites[idx].split_dir);
Side split_side = side_from_dir2(sites[idx].split_dir);
Rng2F32 future_split_rect_target = panel_rect;
if(sites[idx].split_dir != Dir2_Invalid)
{
Vec2F32 panel_center = center_2f32(panel_rect);
future_split_rect_target.v[side_flip(split_side)].v[split_axis] = panel_center.v[split_axis];
}
future_split_rect_target = pad_2f32(future_split_rect_target, -ui_top_font_size()*2.f);
Vec2F32 future_split_rect_target_center = center_2f32(future_split_rect_target);
Rng2F32 future_split_rect =
{
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v0"), future_split_rect_target.x0, .initial = future_split_rect_target_center.x),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v1"), future_split_rect_target.y0, .initial = future_split_rect_target_center.y),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v2"), future_split_rect_target.x1, .initial = future_split_rect_target_center.x),
ui_anim(ui_key_from_stringf(ui_key_zero(), "drop_site_v3"), future_split_rect_target.y1, .initial = future_split_rect_target_center.y),
};
UI_Rect(future_split_rect) UI_TagF("drop_site") UI_CornerRadius(ui_top_font_size()*2.f)
{
ui_build_box_from_key(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder, ui_key_zero());
}
}
}
}
}
//////////////////////////
//- rjf: build catch-all panel drop-site
//
UI_Key catchall_drop_site_key = ui_key_from_stringf(ui_key_zero(), "catchall_drop_site_%p", panel->cfg);
if(build_panel && rd_drag_is_active() && rd_state->drag_drop_regs_slot == RD_RegSlot_View) UI_Rect(panel_rect)
{
UI_Box *catchall_drop_site = ui_build_box_from_key(UI_BoxFlag_DropSite, catchall_drop_site_key);
ui_signal_from_box(catchall_drop_site);
}
//////////////////////////
//- rjf: panel not selected? -> darken
//
if(build_panel) if(panel != panel_tree.focused)
{
UI_Rect(content_rect) UI_TagF("inactive")
ui_build_box_from_key(UI_BoxFlag_DrawBackground, ui_key_zero());
}
//////////////////////////
//- rjf: build panel container box
//
UI_Box *panel_box = &ui_nil_box;
if(build_panel) UI_Rect(content_rect) UI_ChildLayoutAxis(Axis2_Y) UI_CornerRadius(0) UI_Focus(UI_FocusKind_On)
{
UI_Key panel_key = ui_key_from_stringf(ui_key_zero(), "panel_box_%p", panel->cfg);
panel_box = ui_build_box_from_key(UI_BoxFlag_MouseClickable|
UI_BoxFlag_Clip|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DisableFocusOverlay|
((panel_tree.focused != panel)*UI_BoxFlag_DisableFocusBorder),
panel_key);
}
//////////////////////////
//- rjf: loading animation for stable view
//
UI_Box *loading_overlay_container = &ui_nil_box;
if(build_panel) UI_Parent(panel_box) UI_WidthFill UI_HeightFill
{
loading_overlay_container = ui_build_box_from_key(UI_BoxFlag_Floating, ui_key_zero());
}
//////////////////////////
//- rjf: build selected tab view
//
if(build_panel)
UI_Parent(panel_box)
UI_Focus(panel_is_focused ? UI_FocusKind_Null : UI_FocusKind_Off)
UI_WidthFill
{
//- rjf: push interaction registers, fill with per-view states
rd_push_regs(.panel = panel->cfg->id,
.tab = selected_tab->id,
.view = selected_tab->id);
{
String8 view_expr = rd_expr_from_cfg(selected_tab);
String8 view_file_path = rd_file_path_from_eval_string(rd_frame_arena(), view_expr);
// NOTE(rjf): we want to only fill out this view's file path slot if it
// evaluates one - this way, a view can use the slot to know the selected
// file path (if there is one). this is useful when pushing commandas which
// apply to a cursor, for example.
if(view_file_path.size != 0)
{
rd_regs()->file_path = view_file_path;
}
}
//- rjf: build view container
UI_Box *view_container_box = &ui_nil_box;
UI_FixedWidth(dim_2f32(content_rect).x)
UI_FixedHeight(dim_2f32(content_rect).y)
UI_ChildLayoutAxis(Axis2_Y)
{
view_container_box = ui_build_box_from_key(0, ui_key_zero());
}
//- rjf: build empty view
UI_Parent(view_container_box) if(selected_tab == &rd_nil_cfg && panel->parent != &rd_nil_panel_node)
{
ui_set_next_flags(UI_BoxFlag_DefaultFocusNav);
UI_Focus(UI_FocusKind_On) UI_WidthFill UI_HeightFill UI_NamedColumn(str8_lit("empty_view")) UI_TagF("weak")
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)
UI_TagF("bad_pop")
{
if(ui_clicked(rd_icon_buttonf(RD_IconKind_X, 0, "Close Panel")))
{
rd_cmd(RD_CmdKind_ClosePanel);
}
}
}
}
//- rjf: build tab view
UI_Parent(view_container_box) if(selected_tab != &rd_nil_cfg) ProfScope("build tab view")
{
rd_view_ui(content_rect);
}
//- rjf: pop interaction registers; commit if this is the selected view
RD_Regs *view_regs = rd_pop_regs();
if(panel_is_focused)
{
MemoryCopyStruct(rd_regs(), view_regs);
}
}
////////////////////////
//- rjf: loading? -> fill loading overlay container
//
if(build_panel)
{
F32 selected_tab_loading_t = selected_tab_view_state->loading_t;
if(selected_tab_loading_t > 0.01f) UI_Parent(loading_overlay_container)
{
rd_loading_overlay(panel_rect, selected_tab_loading_t, selected_tab_view_state->loading_progress_v, selected_tab_view_state->loading_progress_v_target);
}
}
//////////////////////////
//- rjf: consume panel fallthrough interaction events
//
if(build_panel)
{
UI_Signal panel_sig = ui_signal_from_box(panel_box);
if(ui_pressed(panel_sig))
{
rd_cmd(RD_CmdKind_FocusPanel, .panel = panel->cfg->id);
}
}
//////////////////////////
//- rjf: compute tab build tasks
//
typedef struct TabTask TabTask;
struct TabTask
{
TabTask *next;
RD_Cfg *tab;
DR_FStrList fstrs;
F32 tab_width;
};
TabTask *first_tab_task = 0;
TabTask *last_tab_task = 0;
U64 tab_task_count = 0;
F32 tab_close_width_px = ui_top_font_size()*2.5f;
F32 max_tab_width_px = ui_top_font_size()*20.f;
if(build_panel)
{
B32 reset = (ws->window_layout_reset || ws->frames_alive < 5 || is_changing_panel_boundaries);
for(RD_CfgNode *n = panel->tabs.first; n != 0; n = n->next)
{
RD_Cfg *tab = n->v;
if(rd_cfg_is_project_filtered(tab))
{
continue;
}
TabTask *t = push_array(scratch.arena, TabTask, 1);
t->tab = tab;
t->fstrs = rd_title_fstrs_from_cfg(scratch.arena, tab);
F32 tab_width_target = dr_dim_from_fstrs(&t->fstrs).x + tab_close_width_px + ui_top_font_size()*1.f;
tab_width_target = Min(max_tab_width_px, tab_width_target);
t->tab_width = floor_f32(ui_anim(ui_key_from_stringf(ui_key_zero(), "tab_width_%p", tab), tab_width_target, .initial = reset ? tab_width_target : 0));
SLLQueuePush(first_tab_task, last_tab_task, t);
tab_task_count += 1;
}
}
//////////////////////////
//- rjf: build tab bar container
//
UI_Box *tab_bar_box = &ui_nil_box;
if(build_panel) UI_CornerRadius(0) UI_Rect(tab_bar_rect)
{
tab_bar_box = ui_build_box_from_stringf(UI_BoxFlag_Clip|
UI_BoxFlag_AllowOverflowY|
UI_BoxFlag_ViewClampX|
UI_BoxFlag_ViewScrollX|
UI_BoxFlag_Clickable,
"tab_bar_%p", panel->cfg);
if(panel->tab_side == Side_Max)
{
tab_bar_box->view_off.y = tab_bar_box->view_off_target.y = (tab_bar_rheight - tab_bar_vheight);
}
else
{
tab_bar_box->view_off.y = tab_bar_box->view_off_target.y = 0;
}
}
//////////////////////////
//- rjf: determine tab drop site
//
B32 tab_drop_is_active = rd_drag_is_active() && ui_key_match(ui_drop_hot_key(), catchall_drop_site_key);
RD_Cfg *tab_drop_prev = &rd_nil_cfg;
if(build_panel)
{
F32 best_prev_distance_px = 1000000.f;
TabTask start_boundary_tab_task = {first_tab_task, &rd_nil_cfg};
F32 off = 0;
for(TabTask *task = &start_boundary_tab_task; task != 0; task = task->next)
{
off += task->tab_width;
Vec2F32 anchor_pt = v2f32(tab_bar_box->rect.x0 + off, tab_bar_box->rect.y1);
F32 distance = length_2f32(sub_2f32(ui_mouse(), anchor_pt));
if(distance < best_prev_distance_px)
{
best_prev_distance_px = distance;
tab_drop_prev = task->tab;
}
}
}
//////////////////////////
//- rjf: turn off drop visualization if this drag would be a no-op
//
if(tab_drop_is_active && rd_state->drag_drop_regs->panel == panel->cfg->id)
{
TabTask start_boundary_tab_task = {first_tab_task, &rd_nil_cfg};
if(tab_drop_prev->id == rd_state->drag_drop_regs->view)
{
tab_drop_is_active = 0;
}
if(tab_drop_is_active) for(TabTask *t = &start_boundary_tab_task; t != 0; t = t->next)
{
if(t->tab == tab_drop_prev && t->next != 0 && t->next->tab->id == rd_state->drag_drop_regs->view)
{
tab_drop_is_active = 0;
break;
}
}
}
//////////////////////////
//- rjf: build tab bar contents
//
if(build_panel) UI_Focus(UI_FocusKind_Off) UI_Parent(tab_bar_box) UI_Padding(ui_em(0.5f, 1.f)) UI_PrefHeight(ui_pct(1, 0)) UI_TagF("tab")
{
F32 corner_radius = ui_top_font_size()*0.6f;
TabTask start_boundary_tab_task = {first_tab_task, &rd_nil_cfg};
UI_CornerRadius00(panel->tab_side == Side_Min ? corner_radius : 0)
UI_CornerRadius01(panel->tab_side == Side_Min ? 0 : corner_radius)
UI_CornerRadius10(panel->tab_side == Side_Min ? corner_radius : 0)
UI_CornerRadius11(panel->tab_side == Side_Min ? 0 : corner_radius)
for(TabTask *tab_task = &start_boundary_tab_task; tab_task != 0; tab_task = tab_task->next)
{
RD_Cfg *tab = tab_task->tab;
//- rjf: build tab
DR_FStrList tab_fstrs = tab_task->fstrs;
F32 tab_width_px = tab_task->tab_width;
if(tab != &rd_nil_cfg) RD_RegsScope(.panel = panel->cfg->id, .view = tab->id, .tab = tab->id)
{
// rjf: gather info for this tab
B32 tab_is_selected = (tab == panel->selected_tab);
B32 tab_is_auto = rd_view_cfg_b32_from_string(str8_lit("auto"));
// rjf: begin vertical region for this tab
ui_set_next_child_layout_axis(Axis2_Y);
ui_set_next_pref_width(ui_px(tab_width_px, 1));
UI_Box *tab_column_box = ui_build_box_from_stringf(!is_changing_panel_boundaries*UI_BoxFlag_AnimatePosX, "tab_column_%p", tab);
// rjf: choose palette
B32 omit_name = 0;
if(rd_drag_is_active() && rd_state->drag_drop_regs->view == tab->id && rd_state->drag_drop_regs_slot == RD_RegSlot_View)
{
omit_name = 1;
}
// rjf: build tab container box
UI_Parent(tab_column_box)
UI_PrefHeight(ui_px(tab_bar_vheight, 1))
UI_TagF(omit_name ? "hollow" : "")
UI_TagF(!omit_name && !tab_is_selected ? "inactive" : "")
UI_TagF(!omit_name && tab_is_auto ? "auto" : "")
{
if(panel->tab_side == Side_Max)
{
ui_spacer(ui_px(tab_bar_rv_diff-1.f, 1.f));
}
else
{
ui_spacer(ui_px(1.f, 1.f));
}
UI_Box *tab_box = ui_build_box_from_stringf(UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawBorder|
(UI_BoxFlag_DrawDropShadow*tab_is_selected)|
UI_BoxFlag_Clickable,
"tab_%p", tab);
// rjf: build tab contents
if(!omit_name) UI_Parent(tab_box)
{
UI_WidthFill UI_Row
{
ui_spacer(ui_em(0.5f, 1.f));
UI_PrefWidth(ui_text_dim(10, 0))
{
UI_Box *name_box = ui_build_box_from_key(UI_BoxFlag_DrawText, ui_key_zero());
ui_box_equip_display_fstrs(name_box, &tab_fstrs);
}
}
UI_PrefWidth(ui_px(tab_close_width_px, 1.f)) UI_TextAlignment(UI_TextAlign_Center)
RD_Font(RD_FontSlot_Icons)
UI_FontSize(ui_top_font_size()*0.75f)
UI_TagF(".") UI_TagF("weak") UI_TagF("implicit")
UI_CornerRadius00(0)
UI_CornerRadius01(0)
{
UI_Box *close_box = ui_build_box_from_stringf(UI_BoxFlag_Clickable|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawText|
UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawActiveEffects,
"%S###close_view_%p", rd_icon_kind_text_table[RD_IconKind_X], tab);
UI_Signal sig = ui_signal_from_box(close_box);
if(ui_clicked(sig) || ui_middle_clicked(sig))
{
rd_cmd(RD_CmdKind_CloseTab);
}
}
}
// rjf: consume events for tab clicking
{
UI_Signal sig = ui_signal_from_box(tab_box);
if(ui_pressed(sig))
{
rd_cmd(RD_CmdKind_FocusTab);
rd_cmd(RD_CmdKind_FocusPanel);
}
else if(ui_dragging(sig) && !rd_drag_is_active() && length_2f32(ui_drag_delta()) > 10.f)
{
rd_drag_begin(RD_RegSlot_View);
}
else if(ui_right_clicked(sig))
{
rd_cmd(RD_CmdKind_PushQuery,
.ui_key = sig.box->key,
.expr = push_str8f(scratch.arena, "query:config.$%I64x", tab->id));
}
else if(ui_middle_clicked(sig))
{
rd_cmd(RD_CmdKind_CloseTab);
}
}
}
// rjf: space for next tab
{
ui_spacer(ui_px(floor_f32(ui_top_font_size()*0.4f), 1.f));
}
}
//- rjf: if this is the currently active drop site's previous tab, then build empty space
// to visualize where tab will be moved once dropped
if(tab_drop_is_active &&
rd_drag_is_active() &&
rd_state->drag_drop_regs_slot == RD_RegSlot_View &&
tab == tab_drop_prev)
{
// rjf: begin vertical region for this spot
ui_set_next_child_layout_axis(Axis2_Y);
ui_set_next_pref_width(ui_px(ui_top_font_size()*4.f, 1));
UI_Box *tab_column_box = ui_build_box_from_stringf(!is_changing_panel_boundaries*UI_BoxFlag_AnimatePosX, "tab_column_%p", tab);
// rjf: build spot container box
UI_Parent(tab_column_box)
UI_PrefHeight(ui_px(tab_bar_vheight, 1))
UI_TagF("hollow")
{
if(panel->tab_side == Side_Max)
{
ui_spacer(ui_px(tab_bar_rv_diff-1.f, 1.f));
}
else
{
ui_spacer(ui_px(1.f, 1.f));
}
ui_set_next_group_key(catchall_drop_site_key);
UI_Box *tab_box = ui_build_box_from_key(UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_Clickable,
ui_key_zero());
}
// rjf: space for next tab
{
ui_spacer(ui_px(floor_f32(ui_top_font_size()*0.4f), 1.f));
}
}
}
// rjf: build add-new-tab button
UI_TextAlignment(UI_TextAlign_Center)
UI_PrefWidth(ui_px(tab_bar_vheight, 1.f))
UI_PrefHeight(ui_px(tab_bar_vheight, 1.f))
{
ui_set_next_child_layout_axis(Axis2_Y);
UI_Box *container = ui_build_box_from_stringf(!is_changing_panel_boundaries*UI_BoxFlag_AnimatePosX, "###add_new_tab");
UI_Parent(container)
{
if(panel->tab_side == Side_Max)
{
ui_spacer(ui_px(tab_bar_rv_diff-1.f, 1.f));
}
else
{
ui_spacer(ui_px(1.f, 1.f));
}
UI_CornerRadius00(panel->tab_side == Side_Min ? corner_radius : 0)
UI_CornerRadius10(panel->tab_side == Side_Min ? corner_radius : 0)
UI_CornerRadius01(panel->tab_side == Side_Max ? corner_radius : 0)
UI_CornerRadius11(panel->tab_side == Side_Max ? corner_radius : 0)
RD_Font(RD_FontSlot_Icons)
UI_FontSize(ui_top_font_size())
UI_TagF("implicit")
UI_TagF("weak")
{
UI_Box *add_new_box = ui_build_box_from_stringf(UI_BoxFlag_DrawText|
UI_BoxFlag_DrawBorder|
UI_BoxFlag_DrawBackground|
UI_BoxFlag_DrawHotEffects|
UI_BoxFlag_DrawActiveEffects|
UI_BoxFlag_Clickable|
UI_BoxFlag_DisableTextTrunc,
"%S##add_new_tab_button_%p",
rd_icon_kind_text_table[RD_IconKind_Add],
panel->cfg);
UI_Signal sig = ui_signal_from_box(add_new_box);
if(ui_pressed(sig))
{
rd_cmd(RD_CmdKind_FocusPanel, .panel = panel->cfg->id);
if(ws->query_is_active &&
ui_key_match(add_new_box->key, ws->query_regs->ui_key))
{
rd_cmd(RD_CmdKind_CancelQuery);
}
else
{
rd_cmd(RD_CmdKind_PushQuery,
.expr = str8_lit("query:tab_commands"),
.panel = panel->cfg->id,
.do_implicit_root = 1,
.do_lister = 1,
.ui_key = add_new_box->key);
}
}
}
}
}
// rjf: interact with tab bar
ui_signal_from_box(tab_bar_box);
}
//////////////////////////
//- rjf: accept tab drops
//
if(tab_drop_is_active && rd_drag_drop() && rd_state->drag_drop_regs_slot == RD_RegSlot_View)
{
rd_cmd(RD_CmdKind_MoveView,
.dst_panel = panel->cfg->id,
.panel = rd_state->drag_drop_regs->panel,
.view = rd_state->drag_drop_regs->view,
.prev_tab = tab_drop_prev->id);
}
//////////////////////////
//- rjf: accept file drops
//
{
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->kind == UI_EventKind_FileDrop && contains_2f32(content_rect, evt->pos))
{
B32 need_drop_completion = 0;
arena_clear(ws->drop_completion_arena);
MemoryZeroStruct(&ws->drop_completion_paths);
for(String8Node *n = evt->paths.first; n != 0; n = n->next)
{
Temp scratch = scratch_begin(0, 0);
String8 path = path_normalized_from_string(scratch.arena, n->string);
if(str8_match(str8_skip_last_dot(path), str8_lit("exe"), StringMatchFlag_CaseInsensitive))
{
str8_list_push(ws->drop_completion_arena, &ws->drop_completion_paths, push_str8_copy(ws->drop_completion_arena, path));
need_drop_completion = 1;
}
else
{
rd_cmd(RD_CmdKind_Open, .file_path = path);
}
scratch_end(scratch);
}
if(need_drop_completion)
{
ui_ctx_menu_open(rd_state->drop_completion_key, ui_key_zero(), evt->pos);
}
ui_eat_event(evt);
}
}
}
}
}
}
////////////////////////////
//- rjf: @window_ui_part drag/drop cancelling
//
if(rd_drag_is_active() && ui_slot_press(UI_EventActionSlot_Cancel))
{
rd_drag_kill();
ui_kill_action();
}
////////////////////////////
//- rjf: @window_ui_part top-level font size changing
//
for(UI_Event *evt = 0; ui_next_event(&evt);)
{
if(evt->kind == UI_EventKind_Scroll && evt->modifiers & OS_Modifier_Ctrl)
{
ui_eat_event(evt);
if(evt->delta_2f32.y < 0)
{
rd_cmd(RD_CmdKind_IncWindowFontSize);
}
else if(evt->delta_2f32.y > 0)
{
rd_cmd(RD_CmdKind_DecWindowFontSize);
}
}
}
ui_end_build();
}
//////////////////////////////
//- rjf: @window_frame_part hover eval cancelling
//
if(ws->hover_eval_string.size != 0 && ui_slot_press(UI_EventActionSlot_Cancel))
{
MemoryZeroStruct(&ws->hover_eval_string);
arena_clear(ws->hover_eval_arena);
ws->hover_eval_focused = 0;
rd_request_frame();
}
//////////////////////////////
//- rjf: @window_frame_part animate
//
if(ui_animating_from_state(ws->ui))
{
rd_request_frame();
}
//////////////////////////////
//- rjf: @window_frame_part draw UI
//
ws->draw_bucket = dr_bucket_make();
DR_BucketScope(ws->draw_bucket)
ProfScope("draw UI")
{
Temp scratch = scratch_begin(0, 0);
F32 box_squish_epsilon = 0.001f;
Rng2F32 window_rect = os_client_rect_from_window(ws->os);
//- rjf: unpack settings
F32 rounded_corner_amount = rd_setting_f32_from_name(str8_lit("rounded_corner_amount"));
F32 border_softness = 1.f;
B32 do_background_blur = rd_setting_b32_from_name(str8_lit("background_blur"));
B32 do_drop_shadows =
rd_setting_b32_from_name(str8_lit("drop_shadows"));
Vec4F32 base_background_color = ui_color_from_name(str8_lit("background"));
Vec4F32 base_border_color = ui_color_from_name(str8_lit("border"));
Vec4F32 drop_shadow_color = ui_color_from_name(str8_lit("drop_shadow"));
//- rjf: set up heatmap buckets
F32 heatmap_bucket_size = 32.f;
U64 *heatmap_buckets = 0;
U64 heatmap_bucket_pitch = 0;
U64 heatmap_bucket_count = 0;
if(DEV_draw_ui_box_heatmap)
{
Rng2F32 rect = os_client_rect_from_window(ws->os);
Vec2F32 size = dim_2f32(rect);
Vec2S32 buckets_dim = {(S32)(size.x/heatmap_bucket_size), (S32)(size.y/heatmap_bucket_size)};
heatmap_bucket_pitch = buckets_dim.x;
heatmap_bucket_count = buckets_dim.x*buckets_dim.y;
heatmap_buckets = push_array(scratch.arena, U64, heatmap_bucket_count);
}
//- rjf: draw background color
{
dr_rect(os_client_rect_from_window(ws->os), base_background_color, 0, 0, 0);
}
//- rjf: draw window border
{
dr_rect(os_client_rect_from_window(ws->os), base_border_color, 0, 1.f, border_softness*0.5f);
}
//- rjf: recurse & draw
U64 total_heatmap_sum_count = 0;
UI_Box *hover_debug_box = &ui_nil_box;
for(UI_Box *box = ui_root_from_state(ws->ui); !ui_box_is_nil(box);)
{
// rjf: get corner radii
F32 box_corner_radii[Corner_COUNT] =
{
box->corner_radii[Corner_00]*rounded_corner_amount,
box->corner_radii[Corner_01]*rounded_corner_amount,
box->corner_radii[Corner_10]*rounded_corner_amount,
box->corner_radii[Corner_11]*rounded_corner_amount,
};
// rjf: get recursion
UI_BoxRec rec = ui_box_rec_df_post(box, &ui_nil_box);
// rjf: sum to box heatmap
if(DEV_draw_ui_box_heatmap)
{
Vec2F32 center = center_2f32(box->rect);
Vec2S32 p = v2s32(center.x / heatmap_bucket_size, center.y / heatmap_bucket_size);
U64 bucket_idx = p.y * heatmap_bucket_pitch + p.x;
if(bucket_idx < heatmap_bucket_count)
{
heatmap_buckets[bucket_idx] += 1;
total_heatmap_sum_count += 1;
}
}
// rjf: grab if debug
if(box->flags & UI_BoxFlag_Debug && contains_2f32(box->rect, ui_mouse()))
{
hover_debug_box = box;
}
// rjf: push transparency
if(box->transparency != 0)
{
dr_push_transparency(box->transparency);
}
// rjf: push squish
if(box->squish > box_squish_epsilon)
{
Vec2F32 box_dim = dim_2f32(box->rect);
Vec2F32 anchor_off = {0};
if(box->flags & UI_BoxFlag_SquishAnchored)
{
anchor_off.x = box_dim.x/2.f;
}
else
{
anchor_off.y = -box_dim.y/8.f;
}
Mat3x3F32 box2origin_xform = make_translate_3x3f32(v2f32(-box->rect.x0 - box_dim.x/2 + anchor_off.x, -box->rect.y0 + anchor_off.y));
Mat3x3F32 scale_xform = make_scale_3x3f32(v2f32(1-box->squish, 1-box->squish));
Mat3x3F32 origin2box_xform = make_translate_3x3f32(v2f32(box->rect.x0 + box_dim.x/2 - anchor_off.x, box->rect.y0 - anchor_off.y));
Mat3x3F32 xform = mul_3x3f32(origin2box_xform, mul_3x3f32(scale_xform, box2origin_xform));
dr_push_xform2d(xform);
dr_push_tex2d_sample_kind(R_Tex2DSampleKind_Linear);
}
// rjf: draw drop shadow
if(do_drop_shadows && box->flags & UI_BoxFlag_DrawDropShadow)
{
Rng2F32 drop_shadow_rect = shift_2f32(pad_2f32(box->rect, 8), v2f32(4, 4));
R_Rect2DInst *inst = dr_rect(drop_shadow_rect, drop_shadow_color, 0.8f, 0, 8.f);
MemoryCopyArray(inst->corner_radii, box_corner_radii);
}
// rjf: blur background
if(do_background_blur && box->flags & UI_BoxFlag_DrawBackgroundBlur)
{
R_PassParams_Blur *params = dr_blur(pad_2f32(box->rect, 1.f), box->blur_size*(1-box->transparency), 0);
MemoryCopyArray(params->corner_radii, box_corner_radii);
}
// rjf: compute effective active t
F32 effective_active_t = box->active_t;
if(!(box->flags & UI_BoxFlag_DrawActiveEffects))
{
effective_active_t = 0;
}
F32 t = box->hot_t*(1-effective_active_t);
// rjf: compute background color
Vec4F32 box_background_color = box->background_color;
// rjf: draw background
if(box->flags & UI_BoxFlag_DrawBackground)
{
// rjf: hot effect extension (drop shadow)
if(box->flags & UI_BoxFlag_DrawHotEffects)
{
Rng2F32 drop_shadow_rect = shift_2f32(pad_2f32(box->rect, 8), v2f32(4, 4));
Vec4F32 color = drop_shadow_color;
color.w *= t*box_background_color.w;
dr_rect(drop_shadow_rect, color, 0.8f, 0, 8.f);
}
// rjf: draw background
R_Rect2DInst *inst = dr_rect(pad_2f32(box->rect, 1.f), box_background_color, 0, 0, border_softness*1.f);
MemoryCopyArray(inst->corner_radii, box_corner_radii);
// rjf: hot effect extension
if(box->flags & UI_BoxFlag_DrawHotEffects)
{
B32 is_hot = !ui_key_match(box->key, ui_key_zero()) && ui_key_match(box->key, ui_hot_key());
Vec4F32 hover_color = ui_color_from_tags_key_name(box->tags_key, str8_lit("hover"));
// rjf: brighten
{
Vec4F32 color = hover_color;
color.w *= 0.05f;
if(!is_hot)
{
color.w *= t;
}
R_Rect2DInst *inst = dr_rect(pad_2f32(box->rect, 1.f), v4f32(0, 0, 0, 0), 0, 0, border_softness*1.f);
inst->colors[Corner_00] = color;
inst->colors[Corner_10] = color;
MemoryCopyArray(inst->corner_radii, box_corner_radii);
}
// rjf: soft circle around mouse
if(box->hot_t > 0.01f) DR_ClipScope(box->rect)
{
Vec4F32 color = hover_color;
color.w *= 0.02f;
if(!is_hot)
{
color.w *= t;
}
Vec2F32 center = ui_mouse();
Vec2F32 box_dim = dim_2f32(box->rect);
F32 max_dim = Max(box_dim.x, box_dim.y);
F32 radius = box->font_size*12.f;
radius = Min(max_dim, radius);
dr_rect(pad_2f32(r2f32(center, center), radius), color, radius, 0, radius/3.f);
}
}
// rjf: active effect extension
if(box->flags & UI_BoxFlag_DrawActiveEffects)
{
Vec4F32 shadow_color = drop_shadow_color;
shadow_color.w *= 0.5f*box->active_t;
Vec2F32 shadow_size =
{
(box->rect.x1 - box->rect.x0)*0.60f*box->active_t,
(box->rect.y1 - box->rect.y0)*0.60f*box->active_t,
};
shadow_size.x = Clamp(0, shadow_size.x, box->font_size*2.f);
shadow_size.y = Clamp(0, shadow_size.y, box->font_size*2.f);
// rjf: top -> bottom dark effect
{
R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x0, box->rect.y0, box->rect.x1, box->rect.y0 + shadow_size.y), v4f32(0, 0, 0, 0), 0, 0, 1.f);
inst->colors[Corner_00] = inst->colors[Corner_10] = shadow_color;
inst->colors[Corner_01] = inst->colors[Corner_11] = v4f32(0.f, 0.f, 0.f, 0.0f);
MemoryCopyArray(inst->corner_radii, box_corner_radii);
}
// rjf: bottom -> top light effect
{
R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x0, box->rect.y1 - shadow_size.y, box->rect.x1, box->rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f);
inst->colors[Corner_00] = inst->colors[Corner_10] = v4f32(0, 0, 0, 0);
inst->colors[Corner_01] = inst->colors[Corner_11] = v4f32(1.0f, 1.0f, 1.0f, 0.08f*box->active_t);
MemoryCopyArray(inst->corner_radii, box_corner_radii);
}
// rjf: left -> right dark effect
{
R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x0, box->rect.y0, box->rect.x0 + shadow_size.x, box->rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f);
inst->colors[Corner_10] = inst->colors[Corner_11] = v4f32(0.f, 0.f, 0.f, 0.f);
inst->colors[Corner_00] = shadow_color;
inst->colors[Corner_01] = shadow_color;
MemoryCopyArray(inst->corner_radii, box_corner_radii);
}
// rjf: right -> left dark effect
{
R_Rect2DInst *inst = dr_rect(r2f32p(box->rect.x1 - shadow_size.x, box->rect.y0, box->rect.x1, box->rect.y1), v4f32(0, 0, 0, 0), 0, 0, 1.f);
inst->colors[Corner_00] = inst->colors[Corner_01] = v4f32(0.f, 0.f, 0.f, 0.f);
inst->colors[Corner_10] = shadow_color;
inst->colors[Corner_11] = shadow_color;
MemoryCopyArray(inst->corner_radii, box_corner_radii);
}
}
}
// rjf: draw string
if(box->flags & UI_BoxFlag_DrawText)
{
Vec2F32 text_position = ui_box_text_position(box);
if(DEV_draw_ui_text_pos)
{
dr_rect(r2f32p(text_position.x-4, text_position.y-4, text_position.x+4, text_position.y+4),
v4f32(1, 0, 1, 1), 1, 0, 1);
}
F32 max_x = 100000.f;
FNT_Run ellipses_run = {0};
if(!(box->flags & UI_BoxFlag_DisableTextTrunc))
{
FNT_Tag ellipses_font = box->font;
F32 ellipses_size = box->font_size;
FNT_RasterFlags ellipses_raster_flags = box->text_raster_flags;
if(box->display_fstrs.last)
{
ellipses_font = box->display_fstrs.last->v.params.font;
ellipses_size = box->display_fstrs.last->v.params.size;
ellipses_raster_flags = box->display_fstrs.last->v.params.raster_flags;
}
max_x = (box->rect.x1-text_position.x);
ellipses_run = fnt_run_from_string(ellipses_font, ellipses_size, 0, box->tab_size, ellipses_raster_flags, str8_lit("..."));
}
if(box->flags & UI_BoxFlag_HasFuzzyMatchRanges) UI_TagF("match")
{
Vec4F32 match_color = ui_color_from_tags_key_name(ui_top_tags_key(), str8_lit("background"));
dr_truncated_fancy_run_fuzzy_matches(text_position, &box->display_fruns, max_x, &box->fuzzy_match_ranges, match_color);
}
dr_truncated_fancy_run_list(text_position, &box->display_fruns, max_x, ellipses_run);
}
// rjf: draw focus viz
if(DEV_draw_ui_focus_debug)
{
B32 focused = (box->flags & (UI_BoxFlag_FocusHot|UI_BoxFlag_FocusActive) &&
box->flags & UI_BoxFlag_Clickable);
B32 disabled = 0;
for(UI_Box *p = box; !ui_box_is_nil(p); p = p->parent)
{
if(p->flags & (UI_BoxFlag_FocusHotDisabled|UI_BoxFlag_FocusActiveDisabled))
{
disabled = 1;
break;
}
}
if(focused)
{
Vec4F32 color = v4f32(0.3f, 0.8f, 0.3f, 1.f);
if(disabled)
{
color = v4f32(0.8f, 0.3f, 0.3f, 1.f);
}
dr_rect(r2f32p(box->rect.x0-6, box->rect.y0-6, box->rect.x0+6, box->rect.y0+6), color, 2, 0, 1);
dr_rect(box->rect, color, 2, 2, 1);
}
if(box->flags & (UI_BoxFlag_FocusHot|UI_BoxFlag_FocusActive))
{
if(box->flags & (UI_BoxFlag_FocusHotDisabled|UI_BoxFlag_FocusActiveDisabled))
{
dr_rect(r2f32p(box->rect.x0-6, box->rect.y0-6, box->rect.x0+6, box->rect.y0+6), v4f32(1, 0, 0, 0.2f), 2, 0, 1);
}
else
{
dr_rect(r2f32p(box->rect.x0-6, box->rect.y0-6, box->rect.x0+6, box->rect.y0+6), v4f32(0, 1, 0, 0.2f), 2, 0, 1);
}
}
}
// rjf: push clip
if(box->flags & UI_BoxFlag_Clip)
{
Rng2F32 top_clip = dr_top_clip();
Rng2F32 new_clip = pad_2f32(box->rect, -1);
if(top_clip.x1 != 0 || top_clip.y1 != 0)
{
new_clip = intersect_2f32(new_clip, top_clip);
}
dr_push_clip(new_clip);
}
// rjf: custom draw list
if(box->flags & UI_BoxFlag_DrawBucket)
{
Mat3x3F32 xform = make_translate_3x3f32(box->position_delta);
DR_XForm2DScope(xform)
{
dr_sub_bucket(box->draw_bucket);
}
}
// rjf: call custom draw callback
if(box->custom_draw != 0)
{
box->custom_draw(box, box->custom_draw_user_data);
}
// rjf: pop
{
S32 pop_idx = 0;
for(UI_Box *b = box; !ui_box_is_nil(b) && pop_idx <= rec.pop_count; b = b->parent)
{
pop_idx += 1;
if(b == box && rec.push_count != 0)
{
continue;
}
// rjf: pop clips
if(b->flags & UI_BoxFlag_Clip)
{
dr_pop_clip();
}
// rjf: get corner radii
F32 b_corner_radii[Corner_COUNT] =
{
b->corner_radii[Corner_00]*rounded_corner_amount,
b->corner_radii[Corner_01]*rounded_corner_amount,
b->corner_radii[Corner_10]*rounded_corner_amount,
b->corner_radii[Corner_11]*rounded_corner_amount,
};
// rjf: draw border
if(b->flags & UI_BoxFlag_DrawBorder)
{
Vec4F32 border_color = ui_color_from_tags_key_name(box->tags_key, str8_lit("border"));
Rng2F32 b_border_rect = pad_2f32(b->rect, 1.f);
R_Rect2DInst *inst = dr_rect(b_border_rect, border_color, 0, 1.f, border_softness*1.f);
MemoryCopyArray(inst->corner_radii, b_corner_radii);
// rjf: hover effect
if(b->flags & UI_BoxFlag_DrawHotEffects)
{
Vec4F32 color = ui_color_from_tags_key_name(box->tags_key, str8_lit("hover"));
if(ui_key_match(b->key, ui_key_zero()) || !ui_key_match(b->key, ui_hot_key()))
{
color.w *= b->hot_t;
}
R_Rect2DInst *inst = dr_rect(b_border_rect, color, 0, 1.f, 1.f);
inst->colors[Corner_01].w *= 0.2f;
inst->colors[Corner_11].w *= 0.2f;
MemoryCopyArray(inst->corner_radii, b_corner_radii);
}
}
// rjf: debug border rendering
if(b->flags & UI_BoxFlag_Debug)
{
R_Rect2DInst *inst = dr_rect(b->rect, v4f32(1*box->pref_size[Axis2_X].strictness, 0, 1, 0.25f), 0, 1.f, 0);
MemoryCopyArray(inst->corner_radii, b_corner_radii);
}
// rjf: draw sides
if(b->flags & (UI_BoxFlag_DrawSideTop|UI_BoxFlag_DrawSideBottom|UI_BoxFlag_DrawSideLeft|UI_BoxFlag_DrawSideRight))
{
Vec4F32 border_color = ui_color_from_tags_key_name(box->tags_key, str8_lit("border"));
Rng2F32 r = b->rect;
F32 half_thickness = 1.f;
F32 softness = 0.f;
if(b->flags & UI_BoxFlag_DrawSideTop)
{
dr_rect(r2f32p(r.x0, r.y0, r.x1, r.y0+2*half_thickness), border_color, 0, 0, softness);
}
if(b->flags & UI_BoxFlag_DrawSideBottom)
{
dr_rect(r2f32p(r.x0, r.y1-2*half_thickness, r.x1, r.y1), border_color, 0, 0, softness);
}
if(b->flags & UI_BoxFlag_DrawSideLeft)
{
dr_rect(r2f32p(r.x0, r.y0, r.x0+2*half_thickness, r.y1), border_color, 0, 0, softness);
}
if(b->flags & UI_BoxFlag_DrawSideRight)
{
dr_rect(r2f32p(r.x1-2*half_thickness, r.y0, r.x1, r.y1), border_color, 0, 0, softness);
}
}
// rjf: draw focus overlay
if(b->flags & UI_BoxFlag_Clickable && !(b->flags & UI_BoxFlag_DisableFocusOverlay) && b->focus_hot_t > 0.01f)
{
Vec4F32 color = ui_color_from_tags_key_name(box->tags_key, str8_lit("focus"));
color.w *= 0.09f*b->focus_hot_t;
R_Rect2DInst *inst = dr_rect(b->rect, color, 0, 0, 0.f);
MemoryCopyArray(inst->corner_radii, b_corner_radii);
}
// rjf: draw focus border
if(b->flags & UI_BoxFlag_Clickable && !(b->flags & UI_BoxFlag_DisableFocusBorder) && b->focus_active_t > 0.01f)
{
Rng2F32 rect = b->rect;
if(b->flags & UI_BoxFlag_Floating)
{
rect = pad_2f32(rect, 1.f);
rect = intersect_2f32(window_rect, rect);
}
Vec4F32 color = ui_color_from_tags_key_name(box->tags_key, str8_lit("focus"));
color.w *= b->focus_active_t;
R_Rect2DInst *inst = dr_rect(rect, color, 0, 1.f, border_softness*1.f);
MemoryCopyArray(inst->corner_radii, b_corner_radii);
}
// rjf: disabled overlay
if(b->disabled_t >= 0.005f)
{
Vec4F32 disabled_overlay_color = v4f32(base_background_color.x, base_background_color.y, base_background_color.z, b->disabled_t*0.3f);
R_Rect2DInst *inst = dr_rect(b->rect, disabled_overlay_color, 0, 0, 1);
MemoryCopyArray(inst->corner_radii, b_corner_radii);
}
// rjf: pop squish
if(b->squish > box_squish_epsilon)
{
dr_pop_xform2d();
dr_pop_tex2d_sample_kind();
}
// rjf: pop transparency
if(b->transparency != 0)
{
dr_pop_transparency();
}
}
}
// rjf: next
box = rec.next;
}
//- rjf: draw heatmap
if(DEV_draw_ui_box_heatmap)
{
U64 uniform_dist_count = total_heatmap_sum_count / heatmap_bucket_count;
uniform_dist_count = ClampBot(uniform_dist_count, 10);
for(U64 bucket_idx = 0; bucket_idx < heatmap_bucket_count; bucket_idx += 1)
{
U64 x = bucket_idx % heatmap_bucket_pitch;
U64 y = bucket_idx / heatmap_bucket_pitch;
U64 bucket = heatmap_buckets[bucket_idx];
F32 pct = (F32)bucket / uniform_dist_count;
pct = Clamp(0, pct, 1);
Vec3F32 hsv = v3f32((1-pct) * 0.9411f, 1, 0.5f);
Vec3F32 rgb = rgb_from_hsv(hsv);
Rng2F32 rect = r2f32p(x*heatmap_bucket_size, y*heatmap_bucket_size, (x+1)*heatmap_bucket_size, (y+1)*heatmap_bucket_size);
dr_rect(rect, v4f32(rgb.x, rgb.y, rgb.z, 0.3f), 0, 0, 0);
}
}
//- rjf: draw hover debug box
if(hover_debug_box != &ui_nil_box)
{
FNT_Tag font = rd_font_from_slot(RD_FontSlot_Code);
Vec2F32 p = ui_mouse();
dr_rect(hover_debug_box->rect, v4f32(1, 1, 1, 0.2f), 0, 0, 0);
dr_text(font, 12.f, 0, 0, FNT_RasterFlag_Hinted, p, v4f32(1, 1, 1, 1), push_str8f(scratch.arena, "key: 0x%I64x", hover_debug_box->key.u64[0]));
p.y += 20.f;
dr_text(font, 12.f, 0, 0, FNT_RasterFlag_Hinted, p, v4f32(1, 1, 1, 1), push_str8f(scratch.arena, "string: '%S'", hover_debug_box->string));
p.y += 20.f;
}
//- rjf: draw border/overlay color to signify error
if(ws->error_t > 0.01f) UI_TagF("bad")
{
Vec4F32 color = ui_color_from_name(str8_lit("text"));
color.w *= ws->error_t;
Rng2F32 rect = os_client_rect_from_window(ws->os);
dr_rect(pad_2f32(rect, 24.f), color, 0, 16.f, 12.f);
dr_rect(rect, v4f32(color.x, color.y, color.z, color.w*0.025f), 0, 0, 0);
}
scratch_end(scratch);
}
//////////////////////////////
//- rjf: @window_frame_part update per-window frame counters/info
//
ws->frames_alive += 1;
ws->last_window_rect = os_client_rect_from_window(ws->os);
ProfEnd();
scratch_end(scratch);
}
#if COMPILER_MSVC && !BUILD_DEBUG
#pragma optimize("", on)
#endif
////////////////////////////////
//~ rjf: Eval Visualization
internal String8
rd_value_string_from_eval(Arena *arena, String8 filter, EV_StringParams *params, FNT_Tag font, F32 font_size, F32 max_size, E_Eval eval)
{
Temp scratch = scratch_begin(&arena, 1);
String8List strs = {0};
{
EV_StringIter *iter = ev_string_iter_begin(scratch.arena, eval, params);
F32 space_taken_px = 0;
for(String8 string = {0}; ev_string_iter_next(scratch.arena, iter, &string);)
{
if(space_taken_px > max_size)
{
str8_list_push(scratch.arena, &strs, str8_lit("..."));
break;
}
else
{
str8_list_push(scratch.arena, &strs, string);
space_taken_px += fnt_dim_from_tag_size_string(font, font_size, 0, 0, string).x;
}
}
}
String8 result = str8_list_join(arena, &strs, 0);
scratch_end(scratch);
return result;
}
////////////////////////////////
//~ rjf: Hover Eval
internal void
rd_set_hover_eval(Vec2F32 pos, String8 string)
{
RD_Cfg *window_cfg = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window_cfg);
if(ws->hover_eval_lastt_us < rd_state->time_in_us &&
ui_key_match(ui_active_key(UI_MouseButtonKind_Left), ui_key_zero()) &&
ui_key_match(ui_active_key(UI_MouseButtonKind_Middle), ui_key_zero()) &&
ui_key_match(ui_active_key(UI_MouseButtonKind_Right), ui_key_zero()))
{
B32 is_new_string = (!str8_match(ws->hover_eval_string, string, 0));
if(is_new_string)
{
ws->hover_eval_firstt_us = ws->hover_eval_lastt_us = rd_state->time_in_us;
arena_clear(ws->hover_eval_arena);
ws->hover_eval_string = push_str8_copy(ws->hover_eval_arena, string);
ws->hover_eval_focused = 0;
}
ws->hover_eval_spawn_pos = pos;
ws->hover_eval_lastt_us = rd_state->time_in_us;
}
}
////////////////////////////////
//~ rjf: Autocompletion Lister
internal String8
rd_lister_query_word_from_input_string_off(String8 input, U64 cursor_off)
{
U64 word_start_off = 0;
for(U64 off = 0; off < input.size && off < cursor_off; off += 1)
{
if(!char_is_alpha(input.str[off]) && !char_is_digit(input.str[off], 10) && input.str[off] != '_')
{
word_start_off = off+1;
}
}
String8 query = str8_skip(str8_prefix(input, cursor_off), word_start_off);
return query;
}
internal void
rd_set_autocomp_regs_(RD_Regs *regs)
{
RD_Cfg *window_cfg = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window_cfg);
if(ws->autocomp_last_frame_index < rd_state->frame_index)
{
ws->autocomp_last_frame_index = rd_state->frame_index;
arena_clear(ws->autocomp_arena);
ws->autocomp_regs = rd_regs_copy(ws->autocomp_arena, regs);
}
}
////////////////////////////////
//~ rjf: Colors, Fonts, Config
//- rjf: colors
internal Vec4F32
rd_rgba_from_theme_color(RD_ThemeColor color)
{
return rd_state->theme->colors[color];
}
internal RD_ThemeColor
rd_theme_color_from_txt_token_kind(TXT_TokenKind kind)
{
RD_ThemeColor color = RD_ThemeColor_CodeDefault;
switch(kind)
{
default:break;
case TXT_TokenKind_Keyword:{color = RD_ThemeColor_CodeKeyword;}break;
case TXT_TokenKind_Numeric:{color = RD_ThemeColor_CodeNumeric;}break;
case TXT_TokenKind_String: {color = RD_ThemeColor_CodeString;}break;
case TXT_TokenKind_Meta: {color = RD_ThemeColor_CodeMeta;}break;
case TXT_TokenKind_Comment:{color = RD_ThemeColor_CodeComment;}break;
case TXT_TokenKind_Symbol: {color = RD_ThemeColor_CodeDelimiterOperator;}break;
}
return color;
}
internal RD_ThemeColor
rd_theme_color_from_txt_token_kind_lookup_string(TXT_TokenKind kind, String8 string)
{
RD_ThemeColor color = RD_ThemeColor_CodeDefault;
if(kind == TXT_TokenKind_Identifier || kind == TXT_TokenKind_Keyword)
{
CTRL_Entity *module = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->module);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
B32 mapped = 0;
// rjf: try to map as local
if(!mapped && kind == TXT_TokenKind_Identifier)
{
U64 local_num = e_num_from_string(e_ir_ctx->locals_map, string);
if(local_num != 0)
{
mapped = 1;
color = RD_ThemeColor_CodeLocal;
}
}
// rjf: try to map as member
if(!mapped && kind == TXT_TokenKind_Identifier)
{
U64 member_num = e_num_from_string(e_ir_ctx->member_map, string);
if(member_num != 0)
{
mapped = 1;
color = RD_ThemeColor_CodeLocal;
}
}
// rjf: try to map as register
if(!mapped)
{
U64 reg_num = e_num_from_string(e_ir_ctx->regs_map, string);
if(reg_num != 0)
{
mapped = 1;
color = RD_ThemeColor_CodeRegister;
}
}
// rjf: try to map as register alias
if(!mapped)
{
U64 alias_num = e_num_from_string(e_ir_ctx->reg_alias_map, string);
if(alias_num != 0)
{
mapped = 1;
color = RD_ThemeColor_CodeRegister;
}
}
// rjf: try to map using asynchronous matching system
if(!mapped && kind == TXT_TokenKind_Identifier)
{
RDI_SectionKind section_kind = di_match_store_section_kind_from_name(rd_state->match_store, string, 0);
mapped = 1;
switch(section_kind)
{
default:{mapped = 0;}break;
case RDI_SectionKind_Procedures:
case RDI_SectionKind_GlobalVariables:
case RDI_SectionKind_ThreadVariables:
{
color = RD_ThemeColor_CodeSymbol;
}break;
case RDI_SectionKind_TypeNodes:
{
color = RD_ThemeColor_CodeType;
}break;
}
}
}
return color;
}
//- rjf: fonts/sizes
internal FNT_Tag
rd_font_from_slot(RD_FontSlot slot)
{
FNT_Tag tag = rd_state->font_slot_table[slot];
return tag;
}
internal FNT_RasterFlags
rd_raster_flags_from_slot(RD_FontSlot slot)
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
FNT_RasterFlags flags = ws->font_slot_raster_flags[slot];
return flags;
}
////////////////////////////////
//~ rjf: Process Control Info Stringification
internal String8
rd_string_from_exception_code(U32 code)
{
String8 string = {0};
for EachNonZeroEnumVal(CTRL_ExceptionCodeKind, k)
{
if(code == ctrl_exception_code_kind_code_table[k])
{
string = ctrl_exception_code_kind_display_string_table[k];
break;
}
}
return string;
}
internal DR_FStrList
rd_stop_explanation_fstrs_from_ctrl_event(Arena *arena, CTRL_Event *event)
{
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, event->entity);
DR_FStrList thread_fstrs = rd_title_fstrs_from_ctrl_entity(arena, thread, 0);
DR_FStrList fstrs = {0};
DR_FStrParams params = {ui_top_font(), ui_top_text_raster_flags(), ui_color_from_name(str8_lit("text")), ui_top_font_size()};
switch(event->cause)
{
default:{}break;
//- rjf: finished operation; if active thread, completed thread, otherwise we're just stopped
case CTRL_EventCause_Finished:
{
if(thread != &ctrl_entity_nil)
{
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" completed step"));
}
else
{
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit("Stopped"));
}
}break;
//- rjf: stopped at entry point
case CTRL_EventCause_EntryPoint:
{
if(thread != &ctrl_entity_nil)
{
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" stopped at entry point"));
}
else
{
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit("Stopped at entry point"));
}
}break;
//- rjf: user breakpoint
case CTRL_EventCause_UserBreakpoint:
{
if(thread != &ctrl_entity_nil)
{
dr_fstrs_push_new(arena, &fstrs, &params, rd_icon_kind_text_table[RD_IconKind_CircleFilled], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" "));
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" hit a breakpoint"));
}
}break;
//- rjf: exception
case CTRL_EventCause_InterruptedByException:
{
if(thread != &ctrl_entity_nil)
{
dr_fstrs_push_new(arena, &fstrs, &params, rd_icon_kind_text_table[RD_IconKind_WarningBig], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" "));
switch(event->exception_kind)
{
default:
{
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" "));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" hit an exception: "));
String8 exception_code_string = str8_from_u64(arena, event->exception_code, 16, 0, 0);
String8 exception_explanation_string = rd_string_from_exception_code(event->exception_code);
String8 exception_info_string = push_str8f(arena, "%S%s%S%s",
exception_code_string,
exception_explanation_string.size != 0 ? " (" : "",
exception_explanation_string,
exception_explanation_string.size != 0 ? ")" : "");
dr_fstrs_push_new(arena, &fstrs, &params, exception_info_string);
}break;
case CTRL_ExceptionKind_CppThrow:
{
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" "));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" hit a C++ exception: "));
String8 exception_code_string = str8_from_u64(arena, event->exception_code, 16, 0, 0);
dr_fstrs_push_new(arena, &fstrs, &params, exception_code_string);
}break;
case CTRL_ExceptionKind_MemoryRead:
{
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" "));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" hit an exception: "));
String8 exception_info_string = push_str8f(arena, "Access violation reading from address 0x%I64x", event->vaddr_rng.min);
dr_fstrs_push_new(arena, &fstrs, &params, exception_info_string);
}break;
case CTRL_ExceptionKind_MemoryWrite:
{
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" "));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" hit an exception: "));
String8 exception_info_string = push_str8f(arena, "Access violation writing to address 0x%I64x", event->vaddr_rng.min);
dr_fstrs_push_new(arena, &fstrs, &params, exception_info_string);
}break;
case CTRL_ExceptionKind_MemoryExecute:
{
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" "));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" hit an exception: "));
String8 exception_code_string = str8_from_u64(arena, event->exception_code, 16, 0, 0);
String8 exception_info_string = push_str8f(arena, "Access violation executing at address 0x%I64x", event->vaddr_rng.min);
dr_fstrs_push_new(arena, &fstrs, &params, exception_info_string);
}break;
}
}
else
{
dr_fstrs_push_new(arena, &fstrs, &params, rd_icon_kind_text_table[RD_IconKind_WarningBig], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit("Hit an exception: "));
String8 exception_code_string = str8_from_u64(arena, event->exception_code, 16, 0, 0);
String8 exception_explanation_string = rd_string_from_exception_code(event->exception_code);
String8 exception_info_string = push_str8f(arena, "%S%s%S%s",
exception_code_string,
exception_explanation_string.size != 0 ? " (" : "",
exception_explanation_string,
exception_explanation_string.size != 0 ? ")" : "");
dr_fstrs_push_new(arena, &fstrs, &params, exception_info_string);
}
}break;
//- rjf: trap
case CTRL_EventCause_InterruptedByTrap:
{
dr_fstrs_push_new(arena, &fstrs, &params, rd_icon_kind_text_table[RD_IconKind_WarningBig], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" "));
dr_fstrs_concat_in_place(&fstrs, &thread_fstrs);
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit(" hit a trap"));
}break;
//- rjf: halt
case CTRL_EventCause_InterruptedByHalt:
{
dr_fstrs_push_new(arena, &fstrs, &params, rd_icon_kind_text_table[RD_IconKind_Pause], .font = rd_font_from_slot(RD_FontSlot_Icons), .raster_flags = rd_raster_flags_from_slot(RD_FontSlot_Icons));
dr_fstrs_push_new(arena, &fstrs, &params, str8_lit("Halted"));
}break;
}
return fstrs;
}
////////////////////////////////
//~ rjf: Vocab Info Lookups
internal RD_VocabInfo *
rd_vocab_info_from_code_name(String8 code_name)
{
RD_VocabInfo *result = &rd_nil_vocab_info;
if(code_name.size != 0)
{
U64 hash = d_hash_from_string(code_name);
U64 slot_idx = hash%rd_state->vocab_info_map.single_slots_count;
for(RD_VocabInfoMapNode *n = rd_state->vocab_info_map.single_slots[slot_idx].first;
n != 0;
n = n->single_next)
{
if(str8_match(n->v.code_name, code_name, 0))
{
result = &n->v;
break;
}
}
}
return result;
}
internal RD_VocabInfo *
rd_vocab_info_from_code_name_plural(String8 code_name_plural)
{
RD_VocabInfo *result = &rd_nil_vocab_info;
if(code_name_plural.size != 0)
{
U64 hash = d_hash_from_string(code_name_plural);
U64 slot_idx = hash%rd_state->vocab_info_map.plural_slots_count;
for(RD_VocabInfoMapNode *n = rd_state->vocab_info_map.plural_slots[slot_idx].first;
n != 0;
n = n->plural_next)
{
if(str8_match(n->v.code_name_plural, code_name_plural, 0))
{
result = &n->v;
break;
}
}
}
return result;
}
////////////////////////////////
//~ rjf: Continuous Frame Requests
internal void
rd_request_frame(void)
{
rd_state->num_frames_requested = 4;
}
////////////////////////////////
//~ rjf: Main State Accessors
//- rjf: per-frame arena
internal Arena *
rd_frame_arena(void)
{
return rd_state->frame_arenas[rd_state->frame_index%ArrayCount(rd_state->frame_arenas)];
}
////////////////////////////////
//~ rjf: Registers
internal RD_Regs *
rd_regs(void)
{
RD_Regs *regs = &rd_state->top_regs->v;
return regs;
}
internal RD_Regs *
rd_base_regs(void)
{
RD_Regs *regs = &rd_state->base_regs.v;
return regs;
}
internal RD_Regs *
rd_push_regs_(RD_Regs *regs)
{
RD_RegsNode *n = push_array(rd_frame_arena(), RD_RegsNode, 1);
rd_regs_copy_contents(rd_frame_arena(), &n->v, regs);
SLLStackPush(rd_state->top_regs, n);
return &n->v;
}
internal RD_Regs *
rd_pop_regs(void)
{
RD_Regs *regs = &rd_state->top_regs->v;
SLLStackPop(rd_state->top_regs);
if(rd_state->top_regs == 0)
{
rd_state->top_regs = &rd_state->base_regs;
}
return regs;
}
internal void
rd_regs_fill_slot_from_string(RD_RegSlot slot, String8 string)
{
String8 error = {0};
switch(slot)
{
default:
case RD_RegSlot_String:
case RD_RegSlot_FilePath:
{
String8TxtPtPair pair = str8_txt_pt_pair_from_string(string);
if(pair.pt.line == 0 || slot == RD_RegSlot_String)
{
rd_regs()->string = push_str8_copy(rd_frame_arena(), string);
}
else
{
rd_regs()->file_path = push_str8_copy(rd_frame_arena(), pair.string);
rd_regs()->cursor = pair.pt;
}
}break;
case RD_RegSlot_Expr:
{
rd_regs()->expr = push_str8_copy(rd_frame_arena(), string);
}break;
case RD_RegSlot_Cursor:
{
U64 v = 0;
if(try_u64_from_str8_c_rules(string, &v))
{
rd_regs()->cursor.column = 1;
rd_regs()->cursor.line = v;
}
else
{
log_user_error(str8_lit("Couldn't interpret as a line number."));
}
}break;
case RD_RegSlot_Vaddr: goto use_numeric_eval;
case RD_RegSlot_Voff: goto use_numeric_eval;
case RD_RegSlot_UnwindCount: goto use_numeric_eval;
case RD_RegSlot_InlineDepth: goto use_numeric_eval;
case RD_RegSlot_PID: goto use_numeric_eval;
use_numeric_eval:
{
E_Eval eval = e_eval_from_string(string);
if(eval.msgs.max_kind == E_MsgKind_Null)
{
E_TypeKind eval_type_kind = e_type_kind_from_key(e_type_key_unwrap(eval.irtree.type_key, E_TypeUnwrapFlag_AllDecorative));
if(eval_type_kind == E_TypeKind_Ptr ||
eval_type_kind == E_TypeKind_LRef ||
eval_type_kind == E_TypeKind_RRef)
{
eval = e_value_eval_from_eval(eval);
}
U64 u64 = eval.value.u64;
switch(slot)
{
default:{}break;
case RD_RegSlot_Vaddr:
{
rd_regs()->vaddr = u64;
}break;
case RD_RegSlot_Voff:
{
rd_regs()->voff = u64;
}break;
case RD_RegSlot_UnwindCount:
{
rd_regs()->unwind_count = u64;
}break;
case RD_RegSlot_InlineDepth:
{
rd_regs()->inline_depth = u64;
}break;
case RD_RegSlot_PID:
{
rd_regs()->pid = u64;
}break;
}
}
else
{
log_user_errorf("Couldn't evaluate \"%S\" as an address.", string);
}
}break;
}
}
////////////////////////////////
//~ rjf: Commands
//- rjf: name -> info
internal RD_CmdKind
rd_cmd_kind_from_string(String8 string)
{
RD_CmdKind result = RD_CmdKind_Null;
for(U64 idx = 0; idx < ArrayCount(rd_cmd_kind_info_table); idx += 1)
{
if(str8_match(string, rd_cmd_kind_info_table[idx].string, 0))
{
result = (RD_CmdKind)idx;
break;
}
}
return result;
}
internal RD_CmdKindInfo *
rd_cmd_kind_info_from_string(String8 string)
{
RD_CmdKindInfo *info = &rd_nil_cmd_kind_info;
{
// TODO(rjf): @dynamic_cmds extend this by looking up into dynamically-registered commands by views
RD_CmdKind kind = rd_cmd_kind_from_string(string);
if(kind != RD_CmdKind_Null)
{
info = &rd_cmd_kind_info_table[kind];
}
}
return info;
}
//- rjf: pushing
internal void
rd_push_cmd(String8 name, RD_Regs *regs)
{
rd_cmd_list_push_new(rd_state->cmds_arenas[0], &rd_state->cmds[0], name, regs);
}
//- rjf: iterating
internal B32
rd_next_cmd(RD_Cmd **cmd)
{
U64 slot = rd_state->cmds_gen%ArrayCount(rd_state->cmds);
RD_CmdNode *start_node = rd_state->cmds[slot].first;
if(cmd[0] != 0)
{
start_node = CastFromMember(RD_CmdNode, cmd, cmd[0]);
start_node = start_node->next;
}
cmd[0] = 0;
if(start_node != 0)
{
cmd[0] = &start_node->cmd;
}
return !!cmd[0];
}
internal B32
rd_next_view_cmd(RD_Cmd **cmd)
{
for(;rd_next_cmd(cmd);)
{
if(rd_regs()->view == cmd[0]->regs->view)
{
break;
}
}
B32 result = !!cmd[0];
return result;
}
////////////////////////////////
//~ rjf: Main Layer Top-Level Calls
#if !defined(STBI_INCLUDE_STB_IMAGE_H)
# define STB_IMAGE_IMPLEMENTATION
# define STBI_ONLY_PNG
# define STBI_ONLY_BMP
# include "third_party/stb/stb_image.h"
#endif
internal void
rd_init(CmdLine *cmdln)
{
ProfBeginFunction();
Arena *arena = arena_alloc();
rd_state = push_array(arena, RD_State, 1);
rd_state->arena = arena;
rd_state->quit_after_success = (cmd_line_has_flag(cmdln, str8_lit("quit_after_success")) ||
cmd_line_has_flag(cmdln, str8_lit("q")));
rd_state->user_path_arena = arena_alloc();
rd_state->project_path_arena = arena_alloc();
rd_state->theme_path_arena = arena_alloc();
rd_state->user_cfg_string_key = hs_hash_from_data(str8_lit("raddbg_user_data_string_key"));
rd_state->project_cfg_string_key = hs_hash_from_data(str8_lit("raddbg_project_data_string_key"));
rd_state->cmdln_cfg_string_key = hs_hash_from_data(str8_lit("raddbg_cmdln_data_string_key"));
rd_state->transient_cfg_string_key = hs_hash_from_data(str8_lit("raddbg_transient_data_string_key"));
for(U64 idx = 0; idx < ArrayCount(rd_state->frame_arenas); idx += 1)
{
rd_state->frame_arenas[idx] = arena_alloc();
}
rd_state->log = log_alloc();
log_select(rd_state->log);
{
Temp scratch = scratch_begin(0, 0);
String8 user_program_data_path = os_get_process_info()->user_program_data_path;
String8 user_data_folder = push_str8f(scratch.arena, "%S/raddbg/logs", user_program_data_path);
rd_state->log_path = push_str8f(rd_state->arena, "%S/ui_thread.raddbg_log", user_data_folder);
os_make_directory(user_data_folder);
os_write_data_to_file_path(rd_state->log_path, str8_zero());
scratch_end(scratch);
}
rd_state->num_frames_requested = 2;
rd_state->seconds_until_autosave = 0.5f;
rd_state->match_store = di_match_store_alloc();
rd_state->eval_cache = e_cache_alloc();
for(U64 idx = 0; idx < ArrayCount(rd_state->cmds_arenas); idx += 1)
{
rd_state->cmds_arenas[idx] = arena_alloc();
}
rd_state->popup_arena = arena_alloc();
rd_state->ctx_menu_key = ui_key_from_string(ui_key_zero(), str8_lit("top_level_ctx_menu"));
rd_state->drop_completion_key = ui_key_from_string(ui_key_zero(), str8_lit("drop_completion_ctx_menu"));
rd_state->bind_change_arena = arena_alloc();
rd_state->drag_drop_arena = arena_alloc();
rd_state->drag_drop_regs = push_array(rd_state->drag_drop_arena, RD_Regs, 1);
rd_state->top_regs = &rd_state->base_regs;
// rjf: set up schemas
{
U64 schemas_count = ArrayCount(rd_name_schema_info_table);
rd_state->schemas = push_array(rd_state->arena, MD_NodePtrList, schemas_count);
for EachIndex(idx, schemas_count)
{
Temp scratch = scratch_begin(0, 0);
typedef struct SchemaParseTask SchemaParseTask;
struct SchemaParseTask
{
SchemaParseTask *next;
String8 schema_text;
};
SchemaParseTask start_task = {0, rd_name_schema_info_table[idx].schema};
SchemaParseTask *first_task = &start_task;
SchemaParseTask *last_task = first_task;
for(SchemaParseTask *t = first_task; t != 0; t = t->next)
{
MD_Node *schema = md_tree_from_string(rd_state->arena, t->schema_text)->first;
md_node_ptr_list_push_front(rd_state->arena, &rd_state->schemas[idx], schema);
for MD_EachNode(tag, schema->first_tag)
{
if(str8_match(tag->string, str8_lit("inherit"), 0))
{
for EachIndex(idx2, schemas_count)
{
if(str8_match(rd_name_schema_info_table[idx2].name, tag->first->string, 0))
{
SchemaParseTask *new_task = push_array(scratch.arena, SchemaParseTask, 1);
SLLQueuePush(first_task, last_task, new_task);
new_task->schema_text = rd_name_schema_info_table[idx2].schema;
break;
}
}
}
}
}
scratch_end(scratch);
}
}
// rjf: set up theme presets
{
for EachEnumVal(RD_ThemePreset, p)
{
rd_state->theme_preset_trees[p] = md_tree_from_string(rd_state->arena, rd_theme_preset_cfg_string_table[p])->first;
}
}
// rjf: set up vocab info map
{
rd_state->vocab_info_map.single_slots_count = 1024;
rd_state->vocab_info_map.single_slots = push_array(rd_state->arena, RD_VocabInfoMapSlot, rd_state->vocab_info_map.single_slots_count);
rd_state->vocab_info_map.plural_slots_count = 1024;
rd_state->vocab_info_map.plural_slots = push_array(rd_state->arena, RD_VocabInfoMapSlot, rd_state->vocab_info_map.plural_slots_count);
for EachElement(idx, rd_vocab_info_table)
{
RD_VocabInfoMapNode *n = push_array(rd_state->arena, RD_VocabInfoMapNode, 1);
MemoryCopyStruct(&n->v, &rd_vocab_info_table[idx]);
U64 single_hash = d_hash_from_string(n->v.code_name);
U64 plural_hash = d_hash_from_string(n->v.code_name_plural);
U64 single_slot_idx = single_hash%rd_state->vocab_info_map.single_slots_count;
U64 plural_slot_idx = plural_hash%rd_state->vocab_info_map.plural_slots_count;
if(n->v.code_name.size != 0)
{
SLLQueuePush_N(rd_state->vocab_info_map.single_slots[single_slot_idx].first, rd_state->vocab_info_map.single_slots[single_slot_idx].last, n, single_next);
}
if(n->v.code_name_plural.size != 0)
{
SLLQueuePush_N(rd_state->vocab_info_map.plural_slots[plural_slot_idx].first, rd_state->vocab_info_map.plural_slots[plural_slot_idx].last, n, plural_next);
}
}
}
// rjf: set up top-level config entity trees & tables
{
rd_state->cfg_id_slots_count = 1024;
rd_state->cfg_id_slots = push_array(arena, RD_CfgSlot, rd_state->cfg_id_slots_count);
rd_state->root_cfg = rd_cfg_alloc();
RD_Cfg *user_tree = rd_cfg_new(rd_state->root_cfg, str8_lit("user"));
RD_Cfg *project_tree = rd_cfg_new(rd_state->root_cfg, str8_lit("project"));
RD_Cfg *command_line_tree = rd_cfg_new(rd_state->root_cfg, str8_lit("command_line"));
RD_Cfg *transient = rd_cfg_new(rd_state->root_cfg, str8_lit("transient"));
}
// rjf: set up window cache
{
rd_state->window_state_slots_count = 64;
rd_state->window_state_slots = push_array(arena, RD_WindowStateSlot, rd_state->window_state_slots_count);
rd_state->first_window_state = rd_state->last_window_state = &rd_nil_window_state;
}
// rjf: set up view cache
{
rd_state->view_state_slots_count = 4096;
rd_state->view_state_slots = push_array(arena, RD_ViewStateSlot, rd_state->view_state_slots_count);
}
// rjf: set up user / project paths
{
Temp scratch = scratch_begin(0, 0);
// rjf: unpack command line arguments
String8 user_path = cmd_line_string(cmdln, str8_lit("user"));
String8 project_path = cmd_line_string(cmdln, str8_lit("project"));
{
String8 user_program_data_path = os_get_process_info()->user_program_data_path;
String8 user_data_folder = push_str8f(scratch.arena, "%S/%S", user_program_data_path, str8_lit("raddbg"));
os_make_directory(user_data_folder);
if(user_path.size == 0)
{
user_path = push_str8f(scratch.arena, "%S/default.raddbg_user", user_data_folder);
}
if(project_path.size == 0)
{
project_path = push_str8f(scratch.arena, "%S/default.raddbg_project", user_data_folder);
}
}
// rjf: do initial load
rd_cmd(RD_CmdKind_OpenUser, .file_path = user_path);
rd_cmd(RD_CmdKind_OpenProject, .file_path = project_path);
scratch_end(scratch);
}
// rjf: unpack icon image data
{
Temp scratch = scratch_begin(0, 0);
String8 data = rd_icon_file_bytes;
U8 *ptr = data.str;
U8 *opl = ptr+data.size;
// rjf: read header
#pragma pack(push, 1)
typedef struct ICO_Header ICO_Header;
struct ICO_Header
{
U16 reserved_padding; // must be 0
U16 image_type; // if 1 -> ICO, if 2 -> CUR
U16 num_images;
};
typedef struct ICO_Entry ICO_Entry;
struct ICO_Entry
{
U8 image_width_px;
U8 image_height_px;
U8 num_colors;
U8 reserved_padding; // should be 0
union
{
U16 ico_color_planes; // in ICO
U16 cur_hotspot_x_px; // in CUR
};
union
{
U16 ico_bits_per_pixel; // in ICO
U16 cur_hotspot_y_px; // in CUR
};
U32 image_data_size;
U32 image_data_off;
};
#pragma pack(pop)
ICO_Header hdr = {0};
if(ptr+sizeof(hdr) < opl)
{
MemoryCopy(&hdr, ptr, sizeof(hdr));
ptr += sizeof(hdr);
}
// rjf: read image entries
U64 entries_count = hdr.num_images;
ICO_Entry *entries = push_array(scratch.arena, ICO_Entry, hdr.num_images);
{
U64 bytes_to_read = sizeof(ICO_Entry)*entries_count;
bytes_to_read = Min(bytes_to_read, opl-ptr);
MemoryCopy(entries, ptr, bytes_to_read);
ptr += bytes_to_read;
}
// rjf: find largest image
ICO_Entry *best_entry = 0;
U64 best_entry_area = 0;
for(U64 idx = 0; idx < entries_count; idx += 1)
{
ICO_Entry *entry = &entries[idx];
U64 width = entry->image_width_px;
if(width == 0) { width = 256; }
U64 height = entry->image_height_px;
if(height == 0) { height = 256; }
U64 entry_area = width*height;
if(entry_area > best_entry_area)
{
best_entry = entry;
best_entry_area = entry_area;
}
}
// rjf: deserialize raw image data from best entry's offset
U8 *image_data = 0;
Vec2S32 image_dim = {0};
if(best_entry != 0)
{
U8 *file_data_ptr = data.str + best_entry->image_data_off;
U64 file_data_size = best_entry->image_data_size;
int width = 0;
int height = 0;
int components = 0;
image_data = stbi_load_from_memory(file_data_ptr, file_data_size, &width, &height, &components, 4);
image_dim.x = width;
image_dim.y = height;
}
// rjf: upload to gpu texture
rd_state->icon_texture = r_tex2d_alloc(R_ResourceKind_Static, image_dim, R_Tex2DFormat_RGBA8, image_data);
// rjf: release
stbi_image_free(image_data);
scratch_end(scratch);
}
ProfEnd();
}
internal void
rd_frame(void)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
log_scope_begin();
//////////////////////////////
//- rjf: (DEBUG) take top-level cfg roots, stringize them, and store them to hash store
//
#if 0
{
struct
{
U128 key;
String8 name;
}
table[] =
{
{rd_state->user_cfg_string_key, str8_lit("user")},
{rd_state->project_cfg_string_key, str8_lit("project")},
{rd_state->cmdln_cfg_string_key, str8_lit("command_line")},
{rd_state->transient_cfg_string_key, str8_lit("transient")},
};
for EachElement(idx, table)
{
Arena *arena = arena_alloc();
String8 data = rd_string_from_cfg_tree(arena, rd_cfg_child_from_string(rd_state->root_cfg, table[idx].name));
hs_submit_data(table[idx].key, &arena, data);
}
}
#endif
//////////////////////////////
//- rjf: do per-frame resets
//
arena_clear(rd_frame_arena());
rd_state->top_regs = &rd_state->base_regs;
rd_regs_copy_contents(rd_frame_arena(), &rd_state->top_regs->v, &rd_state->top_regs->v);
if(rd_state->next_hover_regs != 0)
{
rd_state->hover_regs = rd_regs_copy(rd_frame_arena(), rd_state->next_hover_regs);
rd_state->hover_regs_slot = rd_state->next_hover_regs_slot;
rd_state->next_hover_regs = 0;
}
else
{
rd_state->hover_regs = push_array(rd_frame_arena(), RD_Regs, 1);
rd_state->hover_regs_slot = RD_RegSlot_Null;
}
B32 allow_text_hotkeys = !rd_state->text_edit_mode;
rd_state->text_edit_mode = 0;
//////////////////////////////
//- rjf: iterate all tabs, touch their view-states
//
if(rd_state->frame_depth == 0)
{
Temp scratch = scratch_begin(0, 0);
RD_CfgList windows = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("window"));
for(RD_CfgNode *n = windows.first; n != 0; n = n->next)
{
RD_Cfg *window = n->v;
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
for(RD_PanelNode *p = panel_tree.root; p != &rd_nil_panel_node; p = rd_panel_node_rec__depth_first_pre(panel_tree.root, p).next)
{
for(RD_CfgNode *n = p->tabs.first; n != 0; n = n->next)
{
RD_Cfg *tab = n->v;
if(rd_cfg_is_project_filtered(tab))
{
continue;
}
rd_view_state_from_cfg(tab);
}
}
}
scratch_end(scratch);
}
//////////////////////////////
//- rjf: garbage collect untouched immediate cfg trees
//
if(rd_state->frame_depth == 0)
{
RD_Cfg *transient = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("transient"));
for(RD_Cfg *tln = transient->first, *next = &rd_nil_cfg; tln != &rd_nil_cfg; tln = next)
{
next = tln->next;
if(str8_match(tln->string, str8_lit("immediate"), 0))
{
if(rd_cfg_child_from_string(tln, str8_lit("hot")) == &rd_nil_cfg)
{
rd_cfg_release(tln);
}
}
}
for(RD_Cfg *tln = transient->first; tln != &rd_nil_cfg; tln = tln->next)
{
if(str8_match(tln->string, str8_lit("immediate"), 0))
{
for(RD_Cfg *child = tln->first, *next = &rd_nil_cfg; child != &rd_nil_cfg; child = next)
{
next = child->next;
if(str8_match(child->string, str8_lit("hot"), 0))
{
rd_cfg_release(child);
}
}
}
}
}
//////////////////////////////
//- rjf: garbage collect untouched window states
//
if(rd_state->frame_depth == 0) DeferLoop(rd_state->frame_depth += 1, rd_state->frame_depth -= 1)
{
for EachIndex(slot_idx, rd_state->window_state_slots_count)
{
for(RD_WindowState *ws = rd_state->window_state_slots[slot_idx].first, *next; ws != 0; ws = next)
{
next = ws->hash_next;
if(ws->last_frame_index_touched+2 < rd_state->frame_index)
{
ui_state_release(ws->ui);
r_window_unequip(ws->os, ws->r);
os_window_close(ws->os);
arena_release(ws->drop_completion_arena);
arena_release(ws->query_arena);
arena_release(ws->hover_eval_arena);
arena_release(ws->autocomp_arena);
arena_release(ws->arena);
DLLRemove_NPZ(&rd_nil_window_state, rd_state->first_window_state, rd_state->last_window_state, ws, order_next, order_prev);
DLLRemove_NP(rd_state->window_state_slots[slot_idx].first, rd_state->window_state_slots[slot_idx].last, ws, hash_next, hash_prev);
SLLStackPush_N(rd_state->free_window_state, ws, order_next);
}
}
}
}
//////////////////////////////
//- rjf: garbage collect untouched view states
//
if(rd_state->frame_depth == 0)
{
for EachIndex(slot_idx, rd_state->view_state_slots_count)
{
for(RD_ViewState *vs = rd_state->view_state_slots[slot_idx].first, *next; vs != 0; vs = next)
{
next = vs->hash_next;
if(vs->last_frame_index_touched+2 < rd_state->frame_index)
{
ev_view_release(vs->ev_view);
for(RD_ArenaExt *ext = vs->first_arena_ext; ext != 0; ext = ext->next)
{
arena_release(ext->arena);
}
arena_release(vs->arena);
DLLRemove_NP(rd_state->view_state_slots[slot_idx].first, rd_state->view_state_slots[slot_idx].last, vs, hash_next, hash_prev);
SLLStackPush_N(rd_state->free_view_state, vs, hash_next);
}
}
}
}
//////////////////////////////
//- rjf: sync with di parsers
//
ProfScope("sync with di parsers")
{
DI_EventList events = di_p2u_pop_events(scratch.arena, 0);
for(DI_EventNode *n = events.first; n != 0; n = n->next)
{
DI_Event *event = &n->v;
switch(event->kind)
{
default:{}break;
case DI_EventKind_ConversionStarted:
{
RD_Cfg *root = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("transient"));
RD_Cfg *task = rd_cfg_new(root, str8_lit("conversion_task"));
rd_cfg_new(task, event->string);
}break;
case DI_EventKind_ConversionEnded:
{
RD_Cfg *root = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("transient"));
for(RD_Cfg *tln = root->first; tln != &rd_nil_cfg; tln = tln->next)
{
if(str8_match(tln->string, str8_lit("conversion_task"), 0) && str8_match(tln->first->string, event->string, 0))
{
rd_cfg_release(tln);
break;
}
}
}break;
}
}
}
//////////////////////////////
//- rjf: animate all views
//
if(rd_state->frame_depth == 0)
{
F32 slow_rate = 1 - pow_f32(2, (-10.f * rd_state->frame_dt));
F32 fast_rate = 1 - pow_f32(2, (-40.f * rd_state->frame_dt));
for EachIndex(slot_idx, rd_state->view_state_slots_count)
{
for(RD_ViewState *vs = rd_state->view_state_slots[slot_idx].first;
vs != 0;
vs = vs->hash_next)
{
F32 scroll_x_diff = (-vs->scroll_pos.x.off);
F32 scroll_y_diff = (-vs->scroll_pos.y.off);
F32 loading_t_diff = (vs->loading_t_target - vs->loading_t);
vs->scroll_pos.x.off += scroll_x_diff*fast_rate;
vs->scroll_pos.y.off += scroll_y_diff*fast_rate;
vs->loading_t += loading_t_diff * slow_rate;
if(abs_f32(loading_t_diff) > 0.01f ||
abs_f32(scroll_x_diff) > 0.01f ||
abs_f32(scroll_y_diff) > 0.01f)
{
rd_request_frame();
}
if(abs_f32(scroll_x_diff) <= 0.01f)
{
vs->scroll_pos.x.off = 0;
}
if(abs_f32(scroll_y_diff) <= 0.01f)
{
vs->scroll_pos.y.off = 0;
}
RD_Cfg *vcfg = rd_cfg_from_id(vs->cfg_id);
if(rd_cfg_child_from_string(vcfg, str8_lit("selected")) != &rd_nil_cfg)
{
if(vs->loading_t_target > 0.5f)
{
rd_request_frame();
}
vs->loading_t_target = 0;
}
}
}
}
//////////////////////////////
//- rjf: get events from the OS
//
OS_EventList events = {0};
if(rd_state->frame_depth == 0) DeferLoop(rd_state->frame_depth += 1, rd_state->frame_depth -= 1)
{
events = os_get_events(scratch.arena, rd_state->num_frames_requested == 0);
}
//////////////////////////////
//- rjf: open frame debug info scope
//
{
rd_state->frame_di_scope = di_scope_open();
}
//////////////////////////////
//- rjf: calculate avg length in us of last many frames
//
U64 frame_time_history_avg_us = 0;
{
U64 num_frames_in_history = Min(ArrayCount(rd_state->frame_time_us_history), rd_state->frame_index);
U64 frame_time_history_sum_us = 0;
if(num_frames_in_history > 0)
{
for(U64 idx = 0; idx < num_frames_in_history; idx += 1)
{
frame_time_history_sum_us += rd_state->frame_time_us_history[idx];
}
frame_time_history_avg_us = frame_time_history_sum_us/num_frames_in_history;
}
}
//////////////////////////////
//- rjf: pick target hz
//
// pick among a number of sensible targets to snap to, given how well
// we've been performing
//
// TODO(rjf): maximize target, given all windows and their monitors
//
F32 target_hz = os_get_gfx_info()->default_refresh_rate;
if(rd_state->frame_index > 32)
{
F32 possible_alternate_hz_targets[] = {target_hz, 60.f, 120.f, 144.f, 240.f};
F32 best_target_hz = target_hz;
S64 best_target_hz_frame_time_us_diff = max_S64;
for(U64 idx = 0; idx < ArrayCount(possible_alternate_hz_targets); idx += 1)
{
F32 candidate = possible_alternate_hz_targets[idx];
if(candidate <= target_hz)
{
U64 candidate_frame_time_us = 1000000/(U64)candidate;
S64 frame_time_us_diff = (S64)frame_time_history_avg_us - (S64)candidate_frame_time_us;
if(abs_s64(frame_time_us_diff) < best_target_hz_frame_time_us_diff)
{
best_target_hz = candidate;
best_target_hz_frame_time_us_diff = frame_time_us_diff;
}
}
}
target_hz = best_target_hz;
}
//////////////////////////////
//- rjf: given frame time history, decide on amount of time we're willing to wait for memory read results
// for evaluations
//
{
rd_state->frame_eval_memread_endt_us = 0;
U64 frame_time_target_cap_us = (U64)(1000000/target_hz);
if(frame_time_history_avg_us < frame_time_target_cap_us)
{
U64 spare_time = (frame_time_target_cap_us - frame_time_history_avg_us) + 4000;
rd_state->frame_eval_memread_endt_us = os_now_microseconds() + spare_time;
}
}
//////////////////////////////
//- rjf: target Hz -> delta time
//
rd_state->frame_dt = 1.f/target_hz;
//////////////////////////////
//- rjf: begin measuring actual per-frame work
//
U64 begin_time_us = os_now_microseconds();
//////////////////////////////
//- rjf: bind change
//
if(!rd_state->popup_active && rd_state->bind_change_active)
{
if(os_key_press(&events, os_handle_zero(), 0, OS_Key_Esc))
{
rd_request_frame();
rd_state->bind_change_active = 0;
}
if(os_key_press(&events, os_handle_zero(), 0, OS_Key_Delete))
{
rd_request_frame();
rd_cfg_release(rd_cfg_from_id(rd_state->bind_change_binding_id));
rd_state->bind_change_active = 0;
}
for(OS_Event *event = events.first, *next = 0; event != 0; event = next)
{
if(event->kind == OS_EventKind_Press &&
event->key != OS_Key_Esc &&
event->key != OS_Key_Return &&
event->key != OS_Key_Backspace &&
event->key != OS_Key_Delete &&
event->key != OS_Key_LeftMouseButton &&
event->key != OS_Key_RightMouseButton &&
event->key != OS_Key_MiddleMouseButton &&
event->key != OS_Key_Ctrl &&
event->key != OS_Key_Alt &&
event->key != OS_Key_Shift)
{
rd_state->bind_change_active = 0;
RD_Cfg *binding = rd_cfg_from_id(rd_state->bind_change_binding_id);
if(binding == &rd_nil_cfg)
{
RD_Cfg *user = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
RD_Cfg *keybindings = rd_cfg_child_from_string_or_alloc(user, str8_lit("keybindings"));
binding = rd_cfg_new(keybindings, str8_lit(""));
}
rd_cfg_release_all_children(binding);
rd_cfg_new(binding, rd_state->bind_change_cmd_name);
rd_cfg_new(binding, os_g_key_cfg_string_table[event->key]);
if(event->modifiers & OS_Modifier_Ctrl) { rd_cfg_new(binding, str8_lit("ctrl")); }
if(event->modifiers & OS_Modifier_Shift) { rd_cfg_new(binding, str8_lit("shift")); }
if(event->modifiers & OS_Modifier_Alt) { rd_cfg_new(binding, str8_lit("alt")); }
U32 codepoint = os_codepoint_from_modifiers_and_key(event->modifiers, event->key);
os_text(&events, event->window, codepoint);
os_eat_event(&events, event);
rd_request_frame();
break;
}
}
}
//////////////////////////////
//- rjf: build key map from config
//
ProfScope("build key map from config")
{
//- rjf: set up table
rd_state->key_map = push_array(rd_frame_arena(), RD_KeyMap, 1);
RD_KeyMap *key_map = rd_state->key_map;
key_map->name_slots_count = 4096;
key_map->name_slots = push_array(rd_frame_arena(), RD_KeyMapSlot, key_map->name_slots_count);
key_map->binding_slots_count = 4096;
key_map->binding_slots = push_array(rd_frame_arena(), RD_KeyMapSlot, key_map->binding_slots_count);
//- rjf: gather & parse all explicitly stored keybinding sets
RD_CfgList keybindings_cfg_list = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("keybindings"));
for(RD_CfgNode *n = keybindings_cfg_list.first; n != 0; n = n->next)
{
RD_Cfg *keybindings_root = n->v;
for(RD_Cfg *keybinding = keybindings_root->first; keybinding != &rd_nil_cfg; keybinding = keybinding->next)
{
String8 name = {0};
RD_Binding binding = {0};
for(RD_Cfg *child = keybinding->first; child != &rd_nil_cfg; child = child->next)
{
if(0){}
else if(str8_match(child->string, str8_lit("ctrl"), 0)) { binding.modifiers |= OS_Modifier_Ctrl; }
else if(str8_match(child->string, str8_lit("alt"), 0)) { binding.modifiers |= OS_Modifier_Alt; }
else if(str8_match(child->string, str8_lit("shift"), 0)) { binding.modifiers |= OS_Modifier_Shift; }
else
{
OS_Key key = OS_Key_Null;
for EachEnumVal(OS_Key, k)
{
if(str8_match(child->string, os_g_key_cfg_string_table[k], StringMatchFlag_CaseInsensitive))
{
key = k;
break;
}
}
if(key != OS_Key_Null)
{
binding.key = key;
}
else
{
name = child->string;
for(U64 idx = 0; idx < ArrayCount(rd_binding_version_remap_old_name_table); idx += 1)
{
if(str8_match(rd_binding_version_remap_old_name_table[idx], name, StringMatchFlag_CaseInsensitive))
{
name = rd_binding_version_remap_new_name_table[idx];
}
}
}
}
}
if(name.size != 0)
{
U64 name_hash = d_hash_from_string(name);
U64 binding_hash = d_hash_from_string(str8_struct(&binding));
U64 name_slot_idx = name_hash%key_map->name_slots_count;
U64 binding_slot_idx = binding_hash%key_map->binding_slots_count;
RD_KeyMapNode *n = push_array(rd_frame_arena(), RD_KeyMapNode, 1);
n->cfg_id = keybinding->id;
n->name = push_str8_copy(rd_frame_arena(), name);
n->binding = binding;
SLLQueuePush_N(key_map->name_slots[name_slot_idx].first, key_map->name_slots[name_slot_idx].last, n, name_hash_next);
SLLQueuePush_N(key_map->binding_slots[binding_slot_idx].first, key_map->binding_slots[binding_slot_idx].last, n, binding_hash_next);
}
}
}
}
//////////////////////////////
//- rjf: get fonts from config
//
ProfScope("get fonts from config")
{
String8 main_font_name = rd_setting_from_name(str8_lit("main_font"));
String8 code_font_name = rd_setting_from_name(str8_lit("code_font"));
rd_state->font_slot_table[RD_FontSlot_Main] = fnt_tag_from_path(main_font_name);
rd_state->font_slot_table[RD_FontSlot_Code] = fnt_tag_from_path(code_font_name);
if(fnt_tag_match(rd_state->font_slot_table[RD_FontSlot_Main], fnt_tag_zero()))
{
rd_state->font_slot_table[RD_FontSlot_Main] = fnt_tag_from_static_data_string(&rd_default_main_font_bytes);
}
if(fnt_tag_match(rd_state->font_slot_table[RD_FontSlot_Code], fnt_tag_zero()))
{
rd_state->font_slot_table[RD_FontSlot_Code] = fnt_tag_from_static_data_string(&rd_default_code_font_bytes);
}
rd_state->font_slot_table[RD_FontSlot_Icons] = fnt_tag_from_static_data_string(&rd_icon_font_bytes);
}
//////////////////////////////
//- rjf: build theme from config
//
ProfScope("build theme from config")
{
RD_Theme *theme_srgba = push_array(scratch.arena, RD_Theme, 1);
//- rjf: gather globally-applying config options
RD_CfgList preset_roots = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("theme_preset"));
RD_CfgList colors_roots = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("theme_colors"));
//- rjf: assume default-dark
MemoryCopy(theme_srgba->colors, rd_theme_preset_colors_table[RD_ThemePreset_DefaultDark], sizeof(rd_theme_preset_colors__default_dark));
//- rjf: apply explicitly-specified presets
for(RD_CfgNode *n = preset_roots.first; n != 0; n = n->next)
{
RD_Cfg *preset = n->v;
String8 preset_name = preset->first->string;
RD_ThemePreset preset_kind = (RD_ThemePreset)0;
B32 found_preset_kind = 0;
for(RD_ThemePreset p = (RD_ThemePreset)0; p < RD_ThemePreset_COUNT; p = (RD_ThemePreset)(p+1))
{
if(str8_match(preset_name, rd_theme_preset_code_string_table[p], StringMatchFlag_CaseInsensitive))
{
found_preset_kind = 1;
preset_kind = p;
break;
}
}
if(found_preset_kind)
{
MemoryCopy(theme_srgba->colors, rd_theme_preset_colors_table[preset_kind], sizeof(rd_theme_preset_colors__default_dark));
}
}
//- rjf: apply explicitly-specified color codes
for(RD_CfgNode *n = colors_roots.first; n != 0; n = n->next)
{
RD_Cfg *colors = n->v;
for(RD_Cfg *color = colors->first; color != &rd_nil_cfg; color = color->next)
{
String8 name = color->string;
RD_ThemeColor color_code = RD_ThemeColor_Null;
for(RD_ThemeColor c = RD_ThemeColor_Null; c < RD_ThemeColor_COUNT; c = (RD_ThemeColor)(c+1))
{
if(str8_match(rd_theme_color_cfg_string_table[c], name, StringMatchFlag_CaseInsensitive))
{
color_code = c;
break;
}
}
if(color_code != RD_ThemeColor_Null)
{
U64 color_val = 0;
if(try_u64_from_str8_c_rules(color->first->string, &color_val))
{
Vec4F32 color_rgba = rgba_from_u32((U32)color_val);
theme_srgba->colors[color_code] = color_rgba;
}
}
}
}
//- rjf: srgba -> linear, compute final theme
rd_state->theme_target = push_array(rd_frame_arena(), RD_Theme, 1);
RD_Theme *theme = rd_state->theme_target;
for EachEnumVal(RD_ThemeColor, c)
{
theme->colors[c] = linear_from_srgba(theme_srgba->colors[c]);
}
}
//////////////////////////////
//- rjf: animate theme
//
{
Temp scratch = scratch_begin(0, 0);
RD_Theme *last = push_array(scratch.arena, RD_Theme, 1);
if(rd_state->theme != 0)
{
MemoryCopyStruct(last, rd_state->theme);
}
rd_state->theme = push_array(rd_frame_arena(), RD_Theme, 1);
RD_Theme *current = rd_state->theme;
RD_Theme *target = rd_state->theme_target;
if(last)
{
MemoryCopyStruct(current, last);
}
if(rd_state->frame_index <= 2)
{
MemoryCopyStruct(current, target);
}
else
{
F32 rate = 1 - pow_f32(2, (-50.f * rd_state->frame_dt));
for(RD_ThemeColor color = RD_ThemeColor_Null;
color < RD_ThemeColor_COUNT;
color = (RD_ThemeColor)(color+1))
{
if(abs_f32(target->colors[color].x - current->colors[color].x) > 0.01f ||
abs_f32(target->colors[color].y - current->colors[color].y) > 0.01f ||
abs_f32(target->colors[color].z - current->colors[color].z) > 0.01f ||
abs_f32(target->colors[color].w - current->colors[color].w) > 0.01f)
{
rd_request_frame();
}
current->colors[color].x += (target->colors[color].x - current->colors[color].x) * rate;
current->colors[color].y += (target->colors[color].y - current->colors[color].y) * rate;
current->colors[color].z += (target->colors[color].z - current->colors[color].z) * rate;
current->colors[color].w += (target->colors[color].w - current->colors[color].w) * rate;
}
}
scratch_end(scratch);
}
//////////////////////////////
//- rjf: consume events
//
ProfScope("consume events")
{
for(OS_Event *event = events.first, *next = 0;
event != 0;
event = next)
RD_RegsScope()
{
next = event->next;
RD_WindowState *ws = rd_window_state_from_os_handle(event->window);
if(ws != 0 && ws != rd_window_state_from_cfg(rd_cfg_from_id(rd_regs()->window)))
{
Temp scratch = scratch_begin(0, 0);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, rd_cfg_from_id(ws->cfg_id));
rd_regs()->window = ws->cfg_id;
rd_regs()->panel = panel_tree.focused->cfg->id;
rd_regs()->tab = panel_tree.focused->selected_tab->id;
rd_regs()->view = panel_tree.focused->selected_tab->id;
scratch_end(scratch);
}
B32 take = 0;
//- rjf: try drag/drop drop-kickoff
if(rd_drag_is_active() && event->kind == OS_EventKind_Release && event->key == OS_Key_LeftMouseButton)
{
rd_state->drag_drop_state = RD_DragDropState_Dropping;
}
//- rjf: try window close
if(!take && event->kind == OS_EventKind_WindowClose && ws != 0)
{
take = 1;
rd_cmd(RD_CmdKind_Exit);
}
//- rjf: try menu bar operations
if(rd_state->alt_menu_bar_enabled)
{
if(!take && event->kind == OS_EventKind_Press && event->key == OS_Key_Alt && event->modifiers == 0 && event->is_repeat == 0)
{
take = 1;
rd_request_frame();
ws->menu_bar_focused_on_press = ws->menu_bar_focused;
ws->menu_bar_key_held = 1;
ws->menu_bar_focus_press_started = 1;
}
if(!take && event->kind == OS_EventKind_Release && event->key == OS_Key_Alt && event->modifiers == 0 && event->is_repeat == 0)
{
take = 1;
rd_request_frame();
ws->menu_bar_key_held = 0;
}
if(ws->menu_bar_focused && event->kind == OS_EventKind_Press && event->key == OS_Key_Alt && event->modifiers == 0 && event->is_repeat == 0)
{
take = 1;
rd_request_frame();
ws->menu_bar_focused = 0;
}
else if(ws->menu_bar_focus_press_started && !ws->menu_bar_focused && event->kind == OS_EventKind_Release && event->modifiers == 0 && event->key == OS_Key_Alt && event->is_repeat == 0)
{
take = 1;
rd_request_frame();
ws->menu_bar_focused = !ws->menu_bar_focused_on_press;
ws->menu_bar_focus_press_started = 0;
}
else if(event->kind == OS_EventKind_Press && event->key == OS_Key_Esc && ws->menu_bar_focused && !ui_any_ctx_menu_is_open())
{
take = 1;
rd_request_frame();
ws->menu_bar_focused = 0;
}
}
//- rjf: try hotkey presses
if(!take && event->kind == OS_EventKind_Press)
{
RD_Binding binding = {event->key, event->modifiers};
RD_KeyMapNodePtrList key_map_nodes = rd_key_map_node_ptr_list_from_binding(scratch.arena, binding);
if(key_map_nodes.first != 0)
{
U32 hit_char = os_codepoint_from_modifiers_and_key(event->modifiers, event->key);
if(hit_char == 0 || allow_text_hotkeys)
{
rd_cmd(RD_CmdKind_RunCommand, .cmd_name = key_map_nodes.first->v->name);
if(allow_text_hotkeys)
{
os_text(&events, event->window, hit_char);
next = event->next;
}
take = 1;
if(event->modifiers & OS_Modifier_Alt)
{
ws->menu_bar_focus_press_started = 0;
}
}
}
else if(OS_Key_F1 <= event->key && event->key <= OS_Key_F19)
{
ws->menu_bar_focus_press_started = 0;
}
rd_request_frame();
}
//- rjf: try text events
if(!take && event->kind == OS_EventKind_Text)
{
String32 insertion32 = str32(&event->character, 1);
String8 insertion8 = str8_from_32(scratch.arena, insertion32);
rd_cmd(RD_CmdKind_InsertText, .string = insertion8);
rd_request_frame();
take = 1;
if(event->modifiers & OS_Modifier_Alt)
{
ws->menu_bar_focus_press_started = 0;
}
}
//- rjf: do fall-through
if(!take)
{
take = 1;
rd_cmd(RD_CmdKind_OSEvent, .os_event = event);
}
//- rjf: take
if(take)
{
os_eat_event(&events, event);
}
}
}
//////////////////////////////
//- rjf: loop - consume events in core, tick engine, and repeat
//
CTRL_Handle find_thread_retry = {0};
RD_Cmd *cmd = 0;
ProfScope("loop - consume events in core, tick engine, and repeat") for(U64 cmd_process_loop_idx = 0; cmd_process_loop_idx < 3; cmd_process_loop_idx += 1)
{
////////////////////////////
//- rjf: unpack eval-dependent info
//
ProfBegin("unpack eval-dependent info");
CTRL_Entity *process = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->process);
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->thread);
Arch arch = thread->arch;
U64 unwind_count = rd_regs()->unwind_count;
U64 rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, unwind_count);
CTRL_Unwind unwind = d_query_cached_unwind_from_thread(thread);
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr);
U64 rip_voff = ctrl_voff_from_vaddr(module, rip_vaddr);
U64 tls_root_vaddr = ctrl_query_cached_tls_root_vaddr_from_thread(d_state->ctrl_entity_store, thread->handle);
CTRL_EntityArray all_modules = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Module);
U64 eval_modules_count = Max(1, all_modules.count);
E_Module *eval_modules = push_array(scratch.arena, E_Module, eval_modules_count);
E_Module *eval_modules_primary = &eval_modules[0];
eval_modules_primary->rdi = &rdi_parsed_nil;
eval_modules_primary->vaddr_range = r1u64(0, max_U64);
DI_Key primary_dbgi_key = {0};
ProfScope("produce all eval modules")
{
for EachIndex(eval_module_idx, all_modules.count)
{
CTRL_Entity *m = all_modules.v[eval_module_idx];
DI_Key dbgi_key = ctrl_dbgi_key_from_module(m);
eval_modules[eval_module_idx].arch = m->arch;
eval_modules[eval_module_idx].rdi = di_rdi_from_key(rd_state->frame_di_scope, &dbgi_key, 0);
eval_modules[eval_module_idx].vaddr_range = m->vaddr_range;
eval_modules[eval_module_idx].space = rd_eval_space_from_ctrl_entity(ctrl_entity_ancestor_from_kind(m, CTRL_EntityKind_Process), RD_EvalSpaceKind_CtrlEntity);
if(module == m)
{
eval_modules_primary = &eval_modules[eval_module_idx];
primary_dbgi_key = dbgi_key;
}
}
}
ProfEnd();
////////////////////////////
//- rjf: begin evaluation
//
e_select_cache(rd_state->eval_cache);
////////////////////////////
//- rjf: build base evaluation context
//
E_BaseCtx *eval_base_ctx = push_array(scratch.arena, E_BaseCtx, 1);
{
E_BaseCtx *ctx = eval_base_ctx;
//- rjf: fill instruction pointer info
ctx->thread_ip_vaddr = rip_vaddr;
ctx->thread_ip_voff = rip_voff;
ctx->thread_reg_space = rd_eval_space_from_ctrl_entity(thread, RD_EvalSpaceKind_CtrlEntity);
ctx->thread_arch = thread->arch;
ctx->thread_unwind_count = unwind_count;
//- rjf: fill modules
ctx->modules = eval_modules;
ctx->modules_count = eval_modules_count;
ctx->primary_module = eval_modules_primary;
//- rjf: fill space hooks
ctx->space_gen = rd_eval_space_gen;
ctx->space_read = rd_eval_space_read;
ctx->space_write = rd_eval_space_write;
}
e_select_base_ctx(eval_base_ctx);
////////////////////////////
//- rjf: build extra types & maps
//
E_String2ExprMap *macro_map = push_array(scratch.arena, E_String2ExprMap, 1);
macro_map[0] = e_string2expr_map_make(scratch.arena, 512);
E_AutoHookMap *auto_hook_map = push_array(scratch.arena, E_AutoHookMap, 1);
auto_hook_map[0] = e_auto_hook_map_make(scratch.arena, 512);
rd_state->meta_name2type_map = push_array(rd_frame_arena(), E_String2TypeKeyMap, 1);
rd_state->meta_name2type_map[0] = e_string2typekey_map_make(rd_frame_arena(), 256);
EV_ExpandRuleTable *expand_rule_table = push_array(scratch.arena, EV_ExpandRuleTable, 1);
rd_state->view_ui_rule_map = rd_view_ui_rule_map_make(scratch.arena, 512);
{
//- rjf: add macros for command groups
{
String8 names[] =
{
str8_lit("commands"),
str8_lit("tab_commands"),
str8_lit("text_pt_commands"),
str8_lit("text_range_commands"),
};
for EachElement(idx, names)
{
String8 name = names[idx];
E_TypeKey type_key = e_type_key_cons(.kind = E_TypeKind_Set,
.flags = E_TypeFlag_StubSingleLineExpansion,
.name = name,
.access = E_TYPE_ACCESS_FUNCTION_NAME(commands),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(commands),
.range = E_TYPE_EXPAND_RANGE_FUNCTION_NAME(commands),
});
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->type_key = type_key;
expr->space = e_space_make(RD_EvalSpaceKind_MetaQuery);
e_string2expr_map_insert(scratch.arena, macro_map, name, expr);
}
}
//- rjf: build schema types & cache (name -> type) mapping
for EachElement(idx, rd_name_schema_info_table)
{
String8 name = rd_name_schema_info_table[idx].name;
E_TypeKey type_key = e_type_key_cons(.name = name,
.kind = E_TypeKind_Set,
.irext = E_TYPE_IREXT_FUNCTION_NAME(schema),
.access = E_TYPE_ACCESS_FUNCTION_NAME(schema),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(schema),
.range = E_TYPE_EXPAND_RANGE_FUNCTION_NAME(schema),
});
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, name, type_key);
}
//- rjf: add macro for top-level config root
{
String8 name = str8_lit("config");
E_TypeKey type_key = e_type_key_cons(.name = name,
.kind = E_TypeKind_Set,
.access = E_TYPE_ACCESS_FUNCTION_NAME(cfgs));
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->type_key = type_key;
expr->space = e_space_make(RD_EvalSpaceKind_MetaQuery);
e_string2expr_map_insert(scratch.arena, macro_map, name, expr);
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, name, type_key);
}
//- rjf: add macro for top-level control root
{
String8 name = str8_lit("control");
E_TypeKey type_key = e_type_key_cons(.name = name,
.kind = E_TypeKind_Set,
.access = E_TYPE_ACCESS_FUNCTION_NAME(control));
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->type_key = type_key;
expr->space = e_space_make(RD_EvalSpaceKind_MetaQuery);
e_string2expr_map_insert(scratch.arena, macro_map, name, expr);
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, name, type_key);
}
//- rjf: add macros for config "slice" collections (targets, breakpoints, etc.)
String8 evallable_cfg_names[] =
{
str8_lit("breakpoint"),
str8_lit("watch_pin"),
str8_lit("target"),
str8_lit("file_path_map"),
str8_lit("auto_view_rule"),
str8_lit("recent_project"),
str8_lit("recent_file"),
};
for EachElement(cfg_name_idx, evallable_cfg_names)
{
String8 cfg_name = evallable_cfg_names[cfg_name_idx];
String8 collection_name = rd_plural_from_code_name(cfg_name);
E_TypeKey collection_type_key = e_type_key_cons(.kind = E_TypeKind_Set, .name = collection_name,
.irext = E_TYPE_IREXT_FUNCTION_NAME(cfgs_slice),
.access = E_TYPE_ACCESS_FUNCTION_NAME(cfgs_slice),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(cfgs_slice),
.range= E_TYPE_EXPAND_RANGE_FUNCTION_NAME(cfgs_slice),
.id_from_num = E_TYPE_EXPAND_ID_FROM_NUM_FUNCTION_NAME(cfgs_slice),
.num_from_id = E_TYPE_EXPAND_NUM_FROM_ID_FUNCTION_NAME(cfgs_slice),
});
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->type_key = collection_type_key;
expr->space = e_space_make(RD_EvalSpaceKind_MetaQuery);
e_string2expr_map_insert(scratch.arena, macro_map, collection_name, expr);
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, collection_name, collection_type_key);
}
//- rjf: add macros for evallable top-level individual config entity trees -
// things with names either explicitly attached, or that we can infer
for EachElement(idx, rd_name_schema_info_table)
{
String8 name = rd_name_schema_info_table[idx].name;
MD_NodePtrList schemas = rd_schemas_from_name(name);
B32 is_individually_evallable = 0;
for(MD_NodePtrNode *n = schemas.first; n != 0; n = n->next)
{
if(md_node_has_child(n->v, str8_lit("label"), 0) ||
md_node_has_child(n->v, str8_lit("executable"), 0))
{
is_individually_evallable = 1;
break;
}
}
if(is_individually_evallable)
{
E_TypeKey type_key = e_string2typekey_map_lookup(rd_state->meta_name2type_map, name);
RD_CfgList cfgs = rd_cfg_top_level_list_from_string(scratch.arena, name);
for(RD_CfgNode *n = cfgs.first; n != 0; n = n->next)
{
RD_Cfg *cfg = n->v;
String8 label = rd_label_from_cfg(cfg);
if(label.size != 0)
{
E_Space space = rd_eval_space_from_cfg(cfg);
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->space = space;
expr->mode = E_Mode_Offset;
expr->type_key = type_key;
e_string2expr_map_insert(scratch.arena, macro_map, label, expr);
}
}
}
}
//- rjf: add macros for windows/tabs
RD_CfgList watch_tabs = {0};
{
RD_CfgList windows = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("window"));
for(RD_CfgNode *n = windows.first; n != 0; n = n->next)
{
RD_Cfg *window = n->v;
{
E_TypeKey type_key = e_string2typekey_map_lookup(rd_state->meta_name2type_map, window->string);
E_Space space = rd_eval_space_from_cfg(window);
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->space = space;
expr->mode = E_Mode_Offset;
expr->type_key = type_key;
e_string2expr_map_insert(scratch.arena, macro_map, push_str8f(scratch.arena, "query:config.$%I64x", window->id), expr);
}
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
for(RD_PanelNode *p = panel_tree.root;
p != &rd_nil_panel_node;
p = rd_panel_node_rec__depth_first_pre(panel_tree.root, p).next)
{
for(RD_CfgNode *tab_n = p->tabs.first; tab_n != 0; tab_n = tab_n->next)
{
RD_Cfg *tab = tab_n->v;
E_TypeKey type_key = e_string2typekey_map_lookup(rd_state->meta_name2type_map, tab->string);
E_Space space = rd_eval_space_from_cfg(tab);
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->space = space;
expr->mode = E_Mode_Offset;
expr->type_key = type_key;
e_string2expr_map_insert(scratch.arena, macro_map, push_str8f(scratch.arena, "query:config.$%I64x", tab->id), expr);
if(str8_match(tab->string, str8_lit("watch"), 0))
{
rd_cfg_list_push(scratch.arena, &watch_tabs, tab);
}
}
}
}
}
//- rjf: add macros for all watches in all watch tabs which define identifiers
for(RD_CfgNode *n = watch_tabs.first; n != 0; n = n->next)
{
RD_Cfg *watch_tab = n->v;
for(RD_Cfg *child = watch_tab->first; child != &rd_nil_cfg; child = child->next)
{
if(str8_match(child->string, str8_lit("watch"), 0))
{
RD_Cfg *watch = child;
String8 expr = watch->first->string;
E_Parse parse = e_parse_from_string(expr);
if(parse.msgs.max_kind == E_MsgKind_Null)
{
for(E_Expr *expr = parse.expr; expr != &e_expr_nil; expr = expr->next)
{
typedef struct ExprWalkTask ExprWalkTask;
struct ExprWalkTask
{
ExprWalkTask *next;
E_Expr *expr;
};
ExprWalkTask start_task = {0, expr};
ExprWalkTask *first_task = &start_task;
ExprWalkTask *last_task = first_task;
for(ExprWalkTask *t = first_task; t != 0; t = t->next)
{
switch(t->expr->kind)
{
case E_ExprKind_Call:{}break;
case E_ExprKind_Define:
{
E_Expr *lhs = t->expr->first;
E_Expr *rhs = lhs->next;
if(lhs->kind == E_ExprKind_LeafIdentifier)
{
e_string2expr_map_insert(scratch.arena, macro_map, lhs->string, rhs);
}
}break;
default:
{
for(E_Expr *child = t->expr->first; child != &e_expr_nil; child = child->next)
{
ExprWalkTask *task = push_array(scratch.arena, ExprWalkTask, 1);
SLLQueuePush(first_task, last_task, task);
task->expr = child;
}
}break;
}
}
}
}
}
}
}
//- rjf: add macros for user/project
{
E_TypeKey type_key = e_string2typekey_map_lookup(rd_state->meta_name2type_map, str8_lit("user"));
E_Space space = rd_eval_space_from_cfg(rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user")));
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->space = space;
expr->mode = E_Mode_Offset;
expr->type_key = type_key;
e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("user_settings"), expr);
}
{
E_TypeKey type_key = e_string2typekey_map_lookup(rd_state->meta_name2type_map, str8_lit("project"));
E_Space space = rd_eval_space_from_cfg(rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("project")));
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->space = space;
expr->mode = E_Mode_Offset;
expr->type_key = type_key;
e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("project_settings"), expr);
}
//- rjf: add macros for evallable control entities
String8 evallable_ctrl_names[] =
{
str8_lit("machine"),
str8_lit("process"),
str8_lit("thread"),
str8_lit("module"),
};
for EachElement(idx, evallable_ctrl_names)
{
String8 name = evallable_ctrl_names[idx];
CTRL_EntityKind kind = ctrl_entity_kind_from_string(name);
CTRL_EntityArray array = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, kind);
E_TypeKey type_key = e_string2typekey_map_lookup(rd_state->meta_name2type_map, name);
for EachIndex(idx, array.count)
{
CTRL_Entity *entity = array.v[idx];
E_Space space = rd_eval_space_from_ctrl_entity(entity, RD_EvalSpaceKind_MetaCtrlEntity);
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->space = space;
expr->mode = E_Mode_Offset;
expr->type_key = type_key;
if(entity->string.size != 0)
{
e_string2expr_map_insert(scratch.arena, macro_map, entity->string, expr);
}
if(kind == CTRL_EntityKind_Machine && entity->handle.machine_id == CTRL_MachineID_Local)
{
e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("local_machine"), expr);
}
if(kind == CTRL_EntityKind_Thread && ctrl_handle_match(rd_base_regs()->thread, entity->handle))
{
e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("current_thread"), expr);
}
if(kind == CTRL_EntityKind_Process && ctrl_handle_match(rd_base_regs()->process, entity->handle))
{
e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("current_process"), expr);
}
if(kind == CTRL_EntityKind_Module && ctrl_handle_match(rd_base_regs()->module, entity->handle))
{
e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("current_module"), expr);
}
}
}
//- rjf: add macros for all ctrl entity collections
for EachElement(ctrl_name_idx, evallable_ctrl_names)
{
String8 kind_name = evallable_ctrl_names[ctrl_name_idx];
String8 collection_name = rd_plural_from_code_name(kind_name);
E_TypeKey collection_type_key = e_type_key_cons(.kind = E_TypeKind_Set,
.name = collection_name,
.access = E_TYPE_ACCESS_FUNCTION_NAME(ctrl_entities),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(ctrl_entities),
.range = E_TYPE_EXPAND_RANGE_FUNCTION_NAME(ctrl_entities)
});
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->type_key = collection_type_key;
expr->space = e_space_make(RD_EvalSpaceKind_MetaQuery);
e_string2expr_map_insert(scratch.arena, macro_map, collection_name, expr);
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, collection_name, collection_type_key);
}
//- rjf: add macro / lookup rules for unattached processes
{
String8 collection_name = str8_lit("unattached_processes");
E_TypeKey collection_type_key = e_type_key_cons(.kind = E_TypeKind_Set,
.name = collection_name,
.flags = E_TypeFlag_StubSingleLineExpansion,
.access = E_TYPE_ACCESS_FUNCTION_NAME(unattached_processes),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(unattached_processes),
.range = E_TYPE_EXPAND_RANGE_FUNCTION_NAME(unattached_processes)
});
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->type_key = collection_type_key;
expr->space = e_space_make(RD_EvalSpaceKind_MetaCtrlEntity);
e_string2expr_map_insert(scratch.arena, macro_map, collection_name, expr);
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, collection_name, collection_type_key);
}
//- rjf: add macro for 'call_stack' -> 'query:current_thread.callstack'
{
E_Expr *expr = e_parse_from_string(str8_lit("query:current_thread.call_stack")).expr;
e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("call_stack"), expr);
}
//- rjf: add types for queries
{
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, str8_lit("environment"),
e_type_key_cons(.kind = E_TypeKind_Set,
.name = str8_lit("environment"),
.irext = E_TYPE_IREXT_FUNCTION_NAME(environment),
.access = E_TYPE_ACCESS_FUNCTION_NAME(environment),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(environment),
.range = E_TYPE_EXPAND_RANGE_FUNCTION_NAME(environment),
.id_from_num = E_TYPE_EXPAND_ID_FROM_NUM_FUNCTION_NAME(environment),
.num_from_id = E_TYPE_EXPAND_NUM_FROM_ID_FUNCTION_NAME(environment),
}));
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, str8_lit("watches"),
e_type_key_cons(.kind = E_TypeKind_Set,
.flags = E_TypeFlag_EditableChildren|E_TypeFlag_StubSingleLineExpansion,
.name = str8_lit("watches"),
.irext = E_TYPE_IREXT_FUNCTION_NAME(watches),
.access = E_TYPE_ACCESS_FUNCTION_NAME(watches),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(watches),
.range = E_TYPE_EXPAND_RANGE_FUNCTION_NAME(watches),
.id_from_num = E_TYPE_EXPAND_ID_FROM_NUM_FUNCTION_NAME(watches),
.num_from_id = E_TYPE_EXPAND_NUM_FROM_ID_FUNCTION_NAME(watches),
}));
e_string2typekey_map_insert(rd_frame_arena(),
rd_state->meta_name2type_map,
str8_lit("call_stack"),
e_type_key_cons(.kind = E_TypeKind_Set,
.name = str8_lit("call_stack"),
.irext = E_TYPE_IREXT_FUNCTION_NAME(call_stack),
.access = E_TYPE_ACCESS_FUNCTION_NAME(call_stack),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(call_stack),
}));
e_string2typekey_map_insert(rd_frame_arena(), rd_state->meta_name2type_map, str8_lit("theme_colors"),
e_type_key_cons(.kind = E_TypeKind_Set,
.flags = E_TypeFlag_StubSingleLineExpansion,
.name = str8_lit("theme_colors"),
.irext = E_TYPE_IREXT_FUNCTION_NAME(cfgs_slice),
.access = E_TYPE_ACCESS_FUNCTION_NAME(cfgs_slice),
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(cfgs_query),
.range = E_TYPE_EXPAND_RANGE_FUNCTION_NAME(cfgs_slice),
.id_from_num = E_TYPE_EXPAND_ID_FROM_NUM_FUNCTION_NAME(cfgs_slice),
.num_from_id = E_TYPE_EXPAND_NUM_FROM_ID_FUNCTION_NAME(cfgs_slice),
}));
}
//- rjf: add macro for collections with specific lookup rules (but no unique id rules)
{
struct
{
String8 name;
E_TypeExpandInfoFunctionType *info;
E_TypeExpandRangeFunctionType *range;
}
collection_infos[] =
{
#define Collection(name) {str8_lit_comp(#name), E_TYPE_EXPAND_INFO_FUNCTION_NAME(name), E_TYPE_EXPAND_RANGE_FUNCTION_NAME(name)}
Collection(locals),
Collection(registers),
#undef Collection
};
for EachElement(idx, collection_infos)
{
String8 collection_name = collection_infos[idx].name;
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->type_key = e_type_key_cons(.kind = E_TypeKind_Set,
.name = collection_name,
.expand =
{
.info = collection_infos[idx].info,
.range = collection_infos[idx].range,
});
expr->space = e_space_make(RD_EvalSpaceKind_MetaQuery);
e_string2expr_map_insert(scratch.arena, macro_map, collection_name, expr);
}
}
//- rjf: add macros for debug info table collections
String8 debug_info_table_collection_names[] =
{
str8_lit_comp("procedures"),
str8_lit_comp("thread_locals"),
str8_lit_comp("globals"),
str8_lit_comp("types"),
};
for EachElement(idx, debug_info_table_collection_names)
{
String8 name = debug_info_table_collection_names[idx];
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->space = e_space_make(RD_EvalSpaceKind_MetaQuery);
expr->type_key = e_type_key_cons(.kind = E_TypeKind_Set,
.flags = E_TypeFlag_StubSingleLineExpansion,
.name = name,
.expand =
{
.info = E_TYPE_EXPAND_INFO_FUNCTION_NAME(debug_info_table),
.range = E_TYPE_EXPAND_RANGE_FUNCTION_NAME(debug_info_table),
.id_from_num = E_TYPE_EXPAND_ID_FROM_NUM_FUNCTION_NAME(debug_info_table),
.num_from_id = E_TYPE_EXPAND_NUM_FROM_ID_FUNCTION_NAME(debug_info_table)
});
e_string2expr_map_insert(scratch.arena, macro_map, name, expr);
}
//- rjf: add macro for output log
{
HS_Scope *hs_scope = hs_scope_open();
U128 key = d_state->output_log_key;
U128 hash = hs_hash_from_key(key, 0);
String8 data = hs_data_from_hash(hs_scope, hash);
E_Space space = e_space_make(E_SpaceKind_HashStoreKey);
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
space.u128 = key;
expr->space = space;
expr->mode = E_Mode_Offset;
expr->type_key = e_type_key_cons_array(e_type_key_basic(E_TypeKind_U8), data.size, 0);
e_string2expr_map_insert(scratch.arena, macro_map, str8_lit("output"), expr);
hs_scope_close(hs_scope);
}
//- rjf: (DEBUG) add macro for cfg strings
#if 0
{
struct
{
U128 key;
String8 name;
}
table[] =
{
{rd_state->user_cfg_string_key, str8_lit("raddbg_user_data")},
{rd_state->project_cfg_string_key, str8_lit("raddbg_project_data")},
{rd_state->cmdln_cfg_string_key, str8_lit("raddbg_command_line_data")},
{rd_state->transient_cfg_string_key, str8_lit("raddbg_transient_data")},
};
for EachElement(idx, table)
{
HS_Scope *hs_scope = hs_scope_open();
U128 key = table[idx].key;
U128 hash = hs_hash_from_key(key, 0);
String8 data = hs_data_from_hash(hs_scope, hash);
E_Space space = e_space_make(E_SpaceKind_HashStoreKey);
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
space.u128 = key;
expr->space = space;
expr->mode = E_Mode_Offset;
expr->type_key = e_type_key_cons_array(e_type_key_basic(E_TypeKind_U8), data.size, 0);
e_string2expr_map_insert(scratch.arena, macro_map, table[idx].name, expr);
hs_scope_close(hs_scope);
}
}
#endif
//- rjf: choose set of lenses
// TODO(rjf): generate via metaprogram
struct
{
String8 name;
B32 inherited_by_members;
B32 inherited_by_elements;
B32 array_like;
E_TypeIRExtFunctionType *irext;
E_TypeAccessFunctionType *access;
E_TypeExpandRule expand;
RD_ViewUIFunctionType *ui;
EV_ExpandRuleInfoHookFunctionType *ev_expand;
}
lens_table[] =
{
{str8_lit("raw"), 0, 0, 0, 0, 0, {0}},
{str8_lit("bin"), 1, 1, 0, 0, 0, {0}},
{str8_lit("oct"), 1, 1, 0, 0, 0, {0}},
{str8_lit("dec"), 1, 1, 0, 0, 0, {0}},
{str8_lit("hex"), 1, 1, 0, 0, 0, {0}},
{str8_lit("digits"), 1, 1, 0, 0, 0, {0}},
{str8_lit("no_string"), 1, 1, 0, 0, 0, {0}},
{str8_lit("no_char"), 1, 1, 0, 0, 0, {0}},
{str8_lit("no_addr"), 1, 1, 0, 0, 0, {0}},
{str8_lit("sequence"), 0, 0, 1, 0, 0, {E_TYPE_EXPAND_INFO_FUNCTION_NAME(sequence), E_TYPE_EXPAND_RANGE_FUNCTION_NAME(sequence)}},
{str8_lit("only"), 0, 0, 0, 0, 0, {E_TYPE_EXPAND_INFO_FUNCTION_NAME(only), E_TYPE_EXPAND_RANGE_FUNCTION_NAME(only)}},
{str8_lit("omit"), 0, 0, 0, 0, 0, {E_TYPE_EXPAND_INFO_FUNCTION_NAME(omit), E_TYPE_EXPAND_RANGE_FUNCTION_NAME(omit)}},
{str8_lit("range1"), 0, 0, 0, 0, 0, {0}},
{str8_lit("array"), 0, 0, 1, 0, 0, {E_TYPE_EXPAND_INFO_FUNCTION_NAME(array), E_TYPE_EXPAND_RANGE_FUNCTION_NAME(array)}},
{str8_lit("slice"), 0, 0, 1, E_TYPE_IREXT_FUNCTION_NAME(slice), E_TYPE_ACCESS_FUNCTION_NAME(slice), {E_TYPE_EXPAND_INFO_FUNCTION_NAME(slice), E_TYPE_EXPAND_RANGE_FUNCTION_NAME(slice)}},
{str8_lit("table"), 0, 0, 0, 0, 0, {0}},
{str8_lit("text"), 0, 0, 0, 0, 0, {0}, RD_VIEW_UI_FUNCTION_NAME(text), EV_EXPAND_RULE_INFO_FUNCTION_NAME(text)},
{str8_lit("disasm"), 0, 0, 0, 0, 0, {0}, RD_VIEW_UI_FUNCTION_NAME(disasm), EV_EXPAND_RULE_INFO_FUNCTION_NAME(disasm)},
{str8_lit("memory"), 0, 0, 0, 0, 0, {0}, RD_VIEW_UI_FUNCTION_NAME(memory), EV_EXPAND_RULE_INFO_FUNCTION_NAME(memory)},
{str8_lit("bitmap"), 0, 0, 0, 0, 0, {0}, RD_VIEW_UI_FUNCTION_NAME(bitmap), EV_EXPAND_RULE_INFO_FUNCTION_NAME(bitmap)},
{str8_lit("color"), 0, 0, 0, 0, 0, {0}, RD_VIEW_UI_FUNCTION_NAME(color), EV_EXPAND_RULE_INFO_FUNCTION_NAME(color)},
{str8_lit("geo3d"), 0, 0, 0, 0, 0, {0}, RD_VIEW_UI_FUNCTION_NAME(geo3d), EV_EXPAND_RULE_INFO_FUNCTION_NAME(geo3d)},
};
//- rjf: fill lenses in ev expand rule map, rd view ui rule map
{
for EachElement(idx, lens_table)
{
if(lens_table[idx].ui != 0)
{
rd_view_ui_rule_map_insert(scratch.arena, rd_state->view_ui_rule_map, lens_table[idx].name, lens_table[idx].ui);
}
if(lens_table[idx].ev_expand != 0)
{
ev_expand_rule_table_push_new(scratch.arena, expand_rule_table, lens_table[idx].name, lens_table[idx].ev_expand);
}
}
}
//- rjf: fill macros w/ types for lenses
for EachElement(idx, lens_table)
{
E_TypeFlags type_flags = 0;
if(lens_table[idx].inherited_by_members)
{
type_flags |= E_TypeFlag_InheritedByMembers;
}
if(lens_table[idx].inherited_by_elements)
{
type_flags |= E_TypeFlag_InheritedByElements;
}
if(lens_table[idx].array_like)
{
type_flags |= E_TypeFlag_ArrayLikeExpansion;
}
E_Expr *expr = e_push_expr(scratch.arena, E_ExprKind_LeafOffset, 0);
expr->type_key = e_type_key_cons(.kind = E_TypeKind_LensSpec,
.flags = type_flags,
.name = lens_table[idx].name,
.irext = lens_table[idx].irext,
.access = lens_table[idx].access,
.expand = lens_table[idx].expand);
e_string2expr_map_insert(scratch.arena, macro_map, lens_table[idx].name, expr);
}
}
ev_select_expand_rule_table(expand_rule_table);
////////////////////////////
//- rjf: gather auto-view-rules from loaded modules
//
RD_CfgList immediate_auto_view_rules = {0};
CTRL_EntityArray modules = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Module);
for EachIndex(idx, modules.count)
{
CTRL_Entity *module = modules.v[idx];
String8 raddbg_data = ctrl_raddbg_data_from_module(scratch.arena, module->handle);
U8 split_char = 0;
String8List raddbg_data_text_parts = str8_split(scratch.arena, raddbg_data, &split_char, 1, 0);
U64 cfg_idx = 0;
for(String8Node *text_n = raddbg_data_text_parts.first; text_n != 0; text_n = text_n->next)
{
String8 text = text_n->string;
RD_CfgList cfgs = rd_cfg_tree_list_from_string(scratch.arena, str8_zero(), text);
String8 module_name = ctrl_string_from_handle(scratch.arena, module->handle);
for(RD_CfgNode *n = cfgs.first; n != 0; n = n->next, cfg_idx += 1)
{
RD_Cfg *immediate_root = rd_immediate_cfg_from_keyf("module_%S_cfg_%I64x", module_name, cfg_idx);
rd_cfg_release_all_children(immediate_root);
rd_cfg_insert_child(immediate_root, immediate_root->last, n->v);
rd_cfg_list_push(scratch.arena, &immediate_auto_view_rules, n->v);
}
}
}
////////////////////////////
//- rjf: add auto-hook rules for auto-view-rules
//
{
RD_CfgList auto_view_rules = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("auto_view_rule"));
RD_CfgList rules_lists[] =
{
auto_view_rules,
immediate_auto_view_rules,
};
for EachElement(list_idx, rules_lists)
{
RD_CfgList list = rules_lists[list_idx];
for(RD_CfgNode *n = list.first; n != 0; n = n->next)
{
RD_Cfg *rule = n->v;
String8 type_string = rd_cfg_child_from_string(rule, str8_lit("type"))->first->string;
String8 view_rule_string = rd_cfg_child_from_string(rule, str8_lit("view_rule"))->first->string;
e_auto_hook_map_insert_new(scratch.arena, auto_hook_map, .type_pattern = type_string, .tag_expr_string = view_rule_string);
}
}
}
////////////////////////////
//- rjf: build IR evaluation context
//
E_IRCtx *ir_ctx = push_array(scratch.arena, E_IRCtx, 1);
{
E_IRCtx *ctx = ir_ctx;
ctx->regs_map = ctrl_string2reg_from_arch(eval_base_ctx->primary_module->arch);
ctx->reg_alias_map = ctrl_string2alias_from_arch(eval_base_ctx->primary_module->arch);
ctx->locals_map = d_query_cached_locals_map_from_dbgi_key_voff(&primary_dbgi_key, rip_voff);
ctx->member_map = d_query_cached_member_map_from_dbgi_key_voff(&primary_dbgi_key, rip_voff);
ctx->macro_map = macro_map;
ctx->auto_hook_map = auto_hook_map;
}
e_select_ir_ctx(ir_ctx);
////////////////////////////
//- rjf: build eval interpretation context
//
E_InterpretCtx *interpret_ctx = push_array(scratch.arena, E_InterpretCtx, 1);
{
E_InterpretCtx *ctx = interpret_ctx;
ctx->space_read = rd_eval_space_read;
ctx->space_write = rd_eval_space_write;
ctx->primary_space = eval_modules_primary->space;
ctx->reg_arch = eval_modules_primary->arch;
ctx->reg_space = rd_eval_space_from_ctrl_entity(thread, RD_EvalSpaceKind_CtrlEntity);
ctx->reg_unwind_count = unwind_count;
ctx->module_base = push_array(scratch.arena, U64, 1);
ctx->module_base[0] = module->vaddr_range.min;
ctx->tls_base = push_array(scratch.arena, U64, 1);
ctx->tls_base[0] = d_query_cached_tls_base_vaddr_from_process_root_rip(process, tls_root_vaddr, rip_vaddr);
}
e_select_interpret_ctx(interpret_ctx, eval_modules_primary->rdi, rip_voff);
////////////////////////////
//- rjf: autosave if needed
//
{
rd_state->seconds_until_autosave -= rd_state->frame_dt;
if(rd_state->seconds_until_autosave <= 0.f)
{
rd_cmd(RD_CmdKind_WriteUserData);
rd_cmd(RD_CmdKind_WriteProjectData);
rd_state->seconds_until_autosave = 5.f;
}
}
////////////////////////////
//- rjf: process top-level graphical commands
//
rd_state->alt_menu_bar_enabled = rd_setting_b32_from_name(str8_lit("focus_menu_bar_with_alt"));
if(rd_state->frame_depth == 0)
{
for(;rd_next_cmd(&cmd);) RD_RegsScope()
{
// rjf: unpack command
RD_CmdKind kind = rd_cmd_kind_from_string(cmd->name);
rd_regs_copy_contents(rd_frame_arena(), rd_regs(), cmd->regs);
// rjf: request frame
rd_request_frame();
// rjf: process command
RD_Cfg *cfg = &rd_nil_cfg;
String8 dst_path = {0};
String8 bucket_name = {0};
Dir2 split_dir = Dir2_Invalid;
RD_Cfg *split_panel = &rd_nil_cfg;
U64 panel_sib_off = 0;
U64 panel_child_off = 0;
Vec2S32 panel_change_dir = {0};
switch(kind)
{
//- rjf: default cases
case RD_CmdKind_Run:
case RD_CmdKind_LaunchAndRun:
case RD_CmdKind_LaunchAndStepInto:
case RD_CmdKind_StepInto:
case RD_CmdKind_StepOver:
case RD_CmdKind_Restart:
{
CTRL_EntityArray processes = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Process);
if(processes.count == 0 || kind == RD_CmdKind_Restart)
{
RD_CfgList bps = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("breakpoint"));
for(RD_CfgNode *n = bps.first; n != 0; n = n->next)
{
RD_Cfg *hit_count = rd_cfg_child_from_string_or_alloc(n->v, str8_lit("hit_count"));
rd_cfg_new_replace(hit_count, str8_lit("0"));
}
}
} // fallthrough
default:
{
// rjf: try to run engine command
if(D_CmdKind_Null < (D_CmdKind)kind && (D_CmdKind)kind < D_CmdKind_COUNT)
{
D_CmdParams params = {0};
params.machine = rd_regs()->machine;
params.process = rd_regs()->process;
params.thread = rd_regs()->thread;
params.entity = rd_regs()->ctrl_entity;
params.string = rd_regs()->string;
params.file_path = rd_regs()->file_path;
params.cursor = rd_regs()->cursor;
params.vaddr = rd_regs()->vaddr;
params.prefer_disasm = rd_regs()->prefer_disasm;
params.pid = rd_regs()->pid;
params.targets.count = 1;
params.targets.v = push_array(scratch.arena, D_Target, params.targets.count);
params.targets.v[0] = rd_target_from_cfg(scratch.arena, rd_cfg_from_id(rd_regs()->cfg));
d_push_cmd((D_CmdKind)kind, &params);
}
// rjf: try to open tabs, if this is a tab-fastpath-opener
if(kind >= RD_CmdKind_FirstTabFastPathCmd)
{
U64 fast_path_idx = (kind - RD_CmdKind_FirstTabFastPathCmd);
String8 view_name = rd_tab_fast_path_view_name_table[fast_path_idx];
String8 query_name = rd_tab_fast_path_query_name_table[fast_path_idx];
rd_cmd(RD_CmdKind_BuildTab, .string = view_name, .expr = query_name);
}
}break;
//- rjf: open palette
case RD_CmdKind_OpenPalette:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_Cfg *tab = panel_tree.focused->selected_tab;
String8 expr = push_str8f(scratch.arena,
"query:commands, "
"query:config.$%I64x, "
"query:config.$%I64x, "
"query:targets, "
"query:breakpoints, "
"query:recent_files, "
"query:recent_projects, "
"query:machines, "
"query:processes, "
"query:threads, "
"query:modules, "
"query:user_settings, "
"query:project_settings, "
"query:procedures, "
"query:types, "
"query:globals, "
"query:thread_locals, "
,
tab->id, window->id);
rd_cmd(RD_CmdKind_PushQuery, .expr = expr, .do_implicit_root = 1, .do_lister = 1, .do_big_rows = 1, .view = tab->id, .tab = tab->id);
}break;
//- rjf: command fast paths
case RD_CmdKind_RunCommand:
case RD_CmdKind_OpenTab:
{
RD_CmdKindInfo *info = rd_cmd_kind_info_from_string(cmd->regs->cmd_name);
// rjf: command does not have a query - simply execute with the current registers
if(!(info->query.flags & RD_QueryFlag_Required))
{
RD_RegsScope(.cmd_name = str8_zero()) rd_push_cmd(cmd->regs->cmd_name, rd_regs());
}
// rjf: command has required query -> prep query
else
{
rd_cmd(RD_CmdKind_PushQuery,
.do_implicit_root = 1,
.do_lister = info->query.expr.size != 0);
}
}break;
//- rjf: exiting
case RD_CmdKind_Exit:
{
// rjf: if control processes are live, but this is not force-confirmed, then
// get confirmation from user
CTRL_EntityArray processes = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Process);
UI_Key key = ui_key_from_string(ui_key_zero(), str8_lit("lossy_exit_confirmation"));
if(processes.count != 0 && !rd_regs()->force_confirm && !ui_key_match(rd_state->popup_key, key))
{
rd_state->popup_key = key;
rd_state->popup_active = 1;
arena_clear(rd_state->popup_arena);
MemoryZeroStruct(&rd_state->popup_cmds);
rd_state->popup_title = push_str8f(rd_state->popup_arena, "Are you sure you want to exit?");
rd_state->popup_desc = push_str8f(rd_state->popup_arena, "The debugger is still attached to %slive process%s.",
processes.count == 1 ? "a " : "",
processes.count == 1 ? "" : "es");
RD_Regs *regs = rd_regs_copy(rd_frame_arena(), rd_regs());
regs->force_confirm = 1;
rd_cmd_list_push_new(rd_state->popup_arena, &rd_state->popup_cmds, rd_cmd_kind_info_table[RD_CmdKind_Exit].string, regs);
}
// rjf: otherwise, actually exit
else
{
rd_cmd(RD_CmdKind_WriteUserData);
rd_cmd(RD_CmdKind_WriteProjectData);
rd_state->quit = 1;
}
}break;
//- rjf: windows
case RD_CmdKind_OpenWindow:
{
RD_Cfg *old_window = rd_cfg_from_id(rd_regs()->window);
RD_Cfg *bucket = old_window->parent;
if(bucket == &rd_nil_cfg)
{
bucket = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
}
RD_Cfg *new_window = rd_cfg_new(bucket, str8_lit("window"));
RD_Cfg *size = rd_cfg_new(new_window, str8_lit("size"));
rd_cfg_newf(size, "1280");
rd_cfg_newf(size, "720");
for(RD_Cfg *old_child = old_window->first; old_child != &rd_nil_cfg; old_child = old_child->next)
{
if(!str8_match(old_child->string, str8_lit("panels"), 0) &&
!str8_match(old_child->string, str8_lit("size"), 0) &&
!str8_match(old_child->string, str8_lit("pos"), 0) &&
!str8_match(old_child->string, str8_lit("monitor"), 0) &&
!str8_match(old_child->string, str8_lit("fullscreen"), 0) &&
!str8_match(old_child->string, str8_lit("maximized"), 0))
{
RD_Cfg *new_child = rd_cfg_deep_copy(old_child);
rd_cfg_insert_child(new_window, new_window->last, new_child);
}
}
RD_Cfg *panels = rd_cfg_new(new_window, str8_lit("panels"));
rd_cfg_child_from_string_or_alloc(panels, str8_lit("selected"));
}break;
case RD_CmdKind_WindowSettings:
{
String8 expr = push_str8f(scratch.arena, "query:config.$%I64x", rd_regs()->window);
rd_cmd(RD_CmdKind_PushQuery, .expr = expr, .do_implicit_root = 1, .do_big_rows = 1, .do_lister = 1);
}break;
case RD_CmdKind_CloseWindow:
{
RD_CfgList all_windows = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("window"));
RD_Cfg *wcfg = rd_cfg_from_id(rd_regs()->window);
if(all_windows.count == 1 && all_windows.first->v == wcfg)
{
rd_cmd(RD_CmdKind_Exit);
}
else
{
rd_cfg_release(wcfg);
}
}break;
case RD_CmdKind_ToggleFullscreen:
{
RD_Cfg *wcfg = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(wcfg);
if(ws != &rd_nil_window_state)
{
os_window_set_fullscreen(ws->os, !os_window_is_fullscreen(ws->os));
}
}break;
case RD_CmdKind_BringToFront:
{
RD_Cfg *last_focused_wcfg = rd_cfg_from_id(rd_state->last_focused_window);
RD_WindowState *last_focused_ws = rd_window_state_from_cfg(last_focused_wcfg);
if(last_focused_ws == &rd_nil_window_state)
{
last_focused_ws = rd_state->first_window_state;
}
if(last_focused_ws != &rd_nil_window_state)
{
os_window_set_minimized(last_focused_ws->os, 0);
os_window_focus(last_focused_ws->os);
}
}break;
//- rjf: confirmations
case RD_CmdKind_PopupAccept:
{
rd_state->popup_active = 0;
rd_state->popup_key = ui_key_zero();
for(RD_CmdNode *n = rd_state->popup_cmds.first; n != 0; n = n->next)
{
rd_push_cmd(n->cmd.name, n->cmd.regs);
}
}break;
case RD_CmdKind_PopupCancel:
{
rd_state->popup_active = 0;
rd_state->popup_key = ui_key_zero();
}break;
//- rjf: config path saving/loading/applying
case RD_CmdKind_OpenRecentProject:
{
RD_Cfg *cfg = rd_cfg_from_id(rd_regs()->cfg);
RD_Cfg *path = rd_cfg_child_from_string(cfg, str8_lit("path"));
if(str8_match(cfg->string, str8_lit("recent_project"), 0) &&
path->first->string.size != 0)
{
rd_cmd(RD_CmdKind_OpenProject, .file_path = path->first->string);
}
}break;
case RD_CmdKind_OpenUser:
case RD_CmdKind_OpenProject:
{
String8 file_root_key = (kind == RD_CmdKind_OpenUser ? str8_lit("user") :
kind == RD_CmdKind_OpenProject ? str8_lit("project") :
str8_lit("other"));
RD_Cfg *file_root = rd_cfg_child_from_string(rd_state->root_cfg, file_root_key);
//- rjf: load the new file's data
String8 file_path = rd_regs()->file_path;
String8 file_data = os_data_from_file_path(scratch.arena, file_path);
FileProperties file_props = os_properties_from_file_path(file_path);
//- rjf: determine if the file is good
B32 file_is_okay = 0;
{
file_is_okay = ((file_props.size == 0 && file_props.created == 0) ||
str8_match(str8_prefix(file_data, 9), str8_lit("// raddbg"), 0));
}
//- rjf: determine file's version
String8 file_version = {0};
if(file_is_okay && file_props.size != 0)
{
file_version = str8_skip(file_data, 10);
U64 line_end = str8_find_needle(file_version, 0, str8_lit("\n"), 0);
file_version = str8_prefix(file_version, line_end);
U64 first_space = str8_find_needle(file_version, 0, str8_lit(" "), 0);
file_version = str8_prefix(file_version, first_space);
file_version = str8_skip_chop_whitespace(file_version);
String8 current_version = str8_lit(BUILD_VERSION_STRING_LITERAL);
if(!str8_match(file_version, current_version, 0))
{
String8 msg = push_str8f(scratch.arena, "You are trying to run a new development version of RADDBG (%S) with old configuration files. This would overwrite your old configuration files, since the code to appropriately handle old configuration files hasn't been written yet. Out of caution, the program will now exit. If you want to use this new version anyway, delete or rename your configuration file at %S, so that the new development version can begin safely saving to it.", current_version, file_path);
os_graphical_message(1, str8_lit("Unsupported Configuration File Version"), msg);
os_abort(1);
}
}
//- rjf: bad file -> alert user
if(!file_is_okay)
{
log_user_errorf("\"%S\" appears to refer to an existing file which is not a RADDBG config file. This would overwrite the file.", file_path);
}
//- rjf: eliminate all old state under this file tree
if(file_is_okay)
{
rd_cfg_release_all_children(file_root);
}
//- rjf: parse the new file, generate cfg entities for it
RD_CfgList file_cfg_list = {0};
if(file_is_okay)
{
file_cfg_list = rd_cfg_tree_list_from_string(scratch.arena, file_path, file_data);
}
//- rjf: store path
if(file_is_okay)
{
switch(kind)
{
default:{}break;
case RD_CmdKind_OpenUser:
{
arena_clear(rd_state->user_path_arena);
rd_state->user_path = push_str8_copy(rd_state->user_path_arena, file_path);
}break;
case RD_CmdKind_OpenProject:
{
arena_clear(rd_state->project_path_arena);
rd_state->project_path = push_str8_copy(rd_state->project_path_arena, file_path);
}break;
}
}
//- rjf: insert the new cfg entities into this file tree
if(file_is_okay)
{
for(RD_CfgNode *n = file_cfg_list.first; n != 0; n = n->next)
{
rd_cfg_insert_child(file_root, file_root->last, n->v);
}
}
//- rjf: if config did not open any windows for the user, then we need to open a sensible default
if(file_is_okay && kind == RD_CmdKind_OpenUser)
{
RD_CfgList all_user_windows = rd_cfg_child_list_from_string(scratch.arena, file_root, str8_lit("window"));
if(all_user_windows.count == 0)
{
OS_Handle monitor = os_primary_monitor();
String8 monitor_name = os_name_from_monitor(scratch.arena, monitor);
Vec2F32 monitor_dim = os_dim_from_monitor(monitor);
F32 monitor_dpi = os_dpi_from_monitor(monitor);
Vec2F32 window_dim = v2f32(monitor_dim.x*4/5, monitor_dim.y*4/5);
RD_Cfg *new_window = rd_cfg_new(file_root, str8_lit("window"));
RD_Cfg *size = rd_cfg_new(new_window, str8_lit("size"));
rd_cfg_newf(size, "%f", window_dim.x);
rd_cfg_newf(size, "%f", window_dim.y);
F32 line_height_guess = 11.f * (monitor_dpi / 96.f);
F32 num_lines_in_monitor_height = monitor_dim.y / line_height_guess;
if(num_lines_in_monitor_height < 100)
{
rd_cmd(RD_CmdKind_ResetToCompactPanels, .window = new_window->id);
}
else
{
rd_cmd(RD_CmdKind_ResetToDefaultPanels, .window = new_window->id);
}
}
}
//- rjf: if config did not define any keybindings for the user, then we need to build a sensible default
if(file_is_okay && kind == RD_CmdKind_OpenUser)
{
RD_CfgList all_keybindings = rd_cfg_child_list_from_string(scratch.arena, file_root, str8_lit("keybindings"));
if(all_keybindings.count == 0)
{
RD_Cfg *keybindings = rd_cfg_new(file_root, str8_lit("keybindings"));
for EachElement(idx, rd_default_binding_table)
{
String8 name = rd_default_binding_table[idx].string;
RD_Binding binding = rd_default_binding_table[idx].binding;
RD_Cfg *binding_root = rd_cfg_new(keybindings, str8_zero());
rd_cfg_new(binding_root, name);
rd_cfg_new(binding_root, os_g_key_cfg_string_table[binding.key]);
if(binding.modifiers & OS_Modifier_Ctrl) {rd_cfg_newf(binding_root, "ctrl");}
if(binding.modifiers & OS_Modifier_Shift) {rd_cfg_newf(binding_root, "shift");}
if(binding.modifiers & OS_Modifier_Alt) {rd_cfg_newf(binding_root, "alt");}
}
}
}
//- rjf: record recently-opened projects in the user
if(file_is_okay && kind == RD_CmdKind_OpenProject)
{
RD_Cfg *user = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
RD_CfgList recent_projects = rd_cfg_child_list_from_string(scratch.arena, user, str8_lit("recent_project"));
RD_Cfg *recent_project = &rd_nil_cfg;
for(RD_CfgNode *n = recent_projects.first; n != 0; n = n->next)
{
if(path_match_normalized(rd_path_from_cfg(n->v), file_path))
{
recent_project = n->v;
break;
}
}
if(recent_project == &rd_nil_cfg)
{
recent_project = rd_cfg_new(user, str8_lit("recent_project"));
RD_Cfg *path_root = rd_cfg_new(recent_project, str8_lit("path"));
rd_cfg_new(path_root, path_normalized_from_string(scratch.arena, file_path));
}
rd_cfg_unhook(user, recent_project);
rd_cfg_insert_child(user, &rd_nil_cfg, recent_project);
recent_projects = rd_cfg_child_list_from_string(scratch.arena, user, str8_lit("recent_project"));
if(recent_projects.count > 32)
{
rd_cfg_release(recent_projects.last->v);
}
}
}break;
//- rjf: writing config changes
case RD_CmdKind_WriteUserData: dst_path = rd_state->user_path; bucket_name = str8_lit("user"); goto write;
case RD_CmdKind_WriteProjectData: dst_path = rd_state->project_path; bucket_name = str8_lit("project"); goto write;
write:;
{
B32 dst_exists = (os_properties_from_file_path(dst_path).created != 0);
String8 temp_path = push_str8f(scratch.arena, "%S.temp", dst_path);
String8 overwritten_path = push_str8f(scratch.arena, "%S.old", dst_path);
RD_Cfg *tree_root = rd_cfg_child_from_string(rd_state->root_cfg, bucket_name);
String8List strings = {0};
str8_list_pushf(scratch.arena, &strings, "// raddbg %s %S file\n\n", BUILD_VERSION_STRING_LITERAL, bucket_name);
for(RD_Cfg *child = tree_root->first; child != &rd_nil_cfg; child = child->next)
{
str8_list_push(scratch.arena, &strings, rd_string_from_cfg_tree(scratch.arena, dst_path, child));
}
String8 data = str8_list_join(scratch.arena, &strings, 0);
B32 temp_write_good = os_write_data_to_file_path(temp_path, data);
B32 old_move_good = (temp_write_good && (!dst_exists || os_move_file_path(overwritten_path, dst_path)));
B32 new_move_good = (old_move_good && os_move_file_path(dst_path, temp_path));
if(new_move_good && dst_exists)
{
os_delete_file_at_path(overwritten_path);
}
else if(!new_move_good && old_move_good && dst_exists)
{
os_move_file_path(dst_path, overwritten_path);
}
}break;
//- rjf: opening user/project settings
case RD_CmdKind_UserSettings:
{
rd_cmd(RD_CmdKind_PushQuery, .expr = str8_lit("query:user_settings"), .do_implicit_root = 1, .do_big_rows = 1, .do_lister = 1);
}break;
case RD_CmdKind_ProjectSettings:
{
rd_cmd(RD_CmdKind_PushQuery, .expr = str8_lit("query:project_settings"), .do_implicit_root = 1, .do_big_rows = 1, .do_lister = 1);
}break;
//- rjf: font sizes
case RD_CmdKind_IncWindowFontSize: cfg = rd_cfg_from_id(rd_regs()->window); rd_regs()->view = 0; rd_regs()->tab = 0; goto inc_font_size;
case RD_CmdKind_IncViewFontSize: cfg = rd_cfg_from_id(rd_regs()->view); goto inc_font_size;
inc_font_size:;
if(cfg != &rd_nil_cfg)
{
fnt_reset();
F32 current_font_size = rd_setting_f32_from_name(str8_lit("font_size"));
F32 new_font_size = current_font_size+1;
new_font_size = ClampBot(1, new_font_size);
RD_Cfg *font_size_cfg = rd_cfg_child_from_string_or_alloc(cfg, str8_lit("font_size"));
rd_cfg_new_replacef(font_size_cfg, "%I64u", (U64)new_font_size);
}break;
case RD_CmdKind_DecWindowFontSize: cfg = rd_cfg_from_id(rd_regs()->window); rd_regs()->view = 0; rd_regs()->tab = 0; goto dec_font_size;
case RD_CmdKind_DecViewFontSize: cfg = rd_cfg_from_id(rd_regs()->view); goto dec_font_size;
dec_font_size:;
if(cfg != &rd_nil_cfg)
{
fnt_reset();
F32 current_font_size = rd_setting_f32_from_name(str8_lit("font_size"));
F32 new_font_size = current_font_size-1;
new_font_size = ClampBot(1, new_font_size);
RD_Cfg *font_size_cfg = rd_cfg_child_from_string_or_alloc(cfg, str8_lit("font_size"));
rd_cfg_new_replacef(font_size_cfg, "%I64u", (U64)new_font_size);
}break;
//- rjf: panel creation
case RD_CmdKind_NewPanelLeft: {split_dir = Dir2_Left;}goto split;
case RD_CmdKind_NewPanelUp: {split_dir = Dir2_Up;}goto split;
case RD_CmdKind_NewPanelRight:{split_dir = Dir2_Right;}goto split;
case RD_CmdKind_NewPanelDown: {split_dir = Dir2_Down;}goto split;
case RD_CmdKind_SplitPanel:
{
split_dir = rd_regs()->dir2;
split_panel = rd_cfg_from_id(rd_regs()->dst_panel);
}goto split;
split:;
if(split_dir != Dir2_Invalid)
{
// rjf: unpack
Axis2 split_axis = axis2_from_dir2(split_dir);
Side split_side = side_from_dir2(split_dir);
if(split_panel == &rd_nil_cfg)
{
split_panel = rd_cfg_from_id(rd_regs()->panel);
}
RD_Cfg *new_panel_cfg = &rd_nil_cfg;
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, split_panel);
RD_PanelNode *panel_root = panel_tree.root;
RD_PanelNode *panel = rd_panel_node_from_tree_cfg(panel_root, split_panel);
RD_PanelNode *parent = panel->parent;
// rjf: splitting on same axis as parent -> insert new sibling on same axis, adjust sizes
if(parent != &rd_nil_panel_node && parent->split_axis == split_axis)
{
RD_Cfg *parent_cfg = parent->cfg;
RD_Cfg *panel_cfg = panel->cfg;
RD_Cfg *new_cfg = rd_cfg_alloc();
rd_cfg_insert_child(parent_cfg, split_side == Side_Max ? panel_cfg : panel_cfg->prev, new_cfg);
rd_cfg_equip_stringf(new_cfg, "%f", 1.f/(parent->child_count+1));
for(RD_PanelNode *child = parent->first; child != &rd_nil_panel_node; child = child->next)
{
F32 old_pct = child->pct_of_parent;
F32 new_pct = old_pct * ((F32)(parent->child_count) / (parent->child_count+1));
rd_cfg_equip_stringf(child->cfg, "%f", new_pct);
}
new_panel_cfg = new_cfg;
}
// rjf: splitting on opposite axis as parent - need to create new replacement node, + new sibling
else
{
RD_Cfg *split_panel_prev = panel->prev->cfg;
RD_Cfg *new_parent = rd_cfg_alloc();
RD_Cfg *new_sibling = rd_cfg_alloc();
rd_cfg_equip_string(new_parent, split_panel->string);
rd_cfg_equip_string(split_panel, str8_lit("0.5"));
rd_cfg_equip_string(new_sibling, str8_lit("0.5"));
if(parent->cfg != &rd_nil_cfg)
{
rd_cfg_unhook(parent->cfg, split_panel);
rd_cfg_insert_child(parent->cfg, split_panel_prev, new_parent);
}
else
{
rd_cfg_equip_string(new_parent, str8_lit("panels"));
RD_Cfg *window_cfg = rd_window_from_cfg(split_panel);
rd_cfg_insert_child(window_cfg, window_cfg->last, new_parent);
}
RD_Cfg *min = split_panel;
RD_Cfg *max = new_sibling;
if(split_side == Side_Min)
{
Swap(RD_Cfg *, min, max);
}
rd_cfg_insert_child(new_parent, new_parent->last, min);
rd_cfg_insert_child(new_parent, new_parent->last, max);
new_panel_cfg = new_sibling;
}
// rjf: pre-emptively set up the animation rectangle, depending on where
// the new panel was inserted
{
RD_WindowState *ws = rd_window_state_from_cfg(new_panel_cfg);
if(ws != &rd_nil_window_state)
{
ui_select_state(ws->ui);
RD_PanelTree new_panel_tree = rd_panel_tree_from_cfg(scratch.arena, new_panel_cfg);
RD_PanelNode *new_panel = rd_panel_node_from_tree_cfg(new_panel_tree.root, new_panel_cfg);
Rng2F32 stub_content_rect = r2f32p(0, 0, 1000, 1000);
Vec2F32 stub_content_rect_dim = dim_2f32(stub_content_rect);
Rng2F32 new_rect_px = rd_target_rect_from_panel_node(stub_content_rect, new_panel_tree.root, new_panel);
Rng2F32 new_rect_pct = r2f32p(new_rect_px.x0/stub_content_rect_dim.x,
new_rect_px.y0/stub_content_rect_dim.y,
new_rect_px.x1/stub_content_rect_dim.x,
new_rect_px.y1/stub_content_rect_dim.y);
if(new_panel->prev != &rd_nil_panel_node)
{
Rng2F32 target_prev_rect_px = rd_target_rect_from_panel_node(stub_content_rect, panel_tree.root, rd_panel_node_from_tree_cfg(panel_tree.root, new_panel->prev->cfg));
Rng2F32 target_prev_rect_pct = r2f32p(target_prev_rect_px.x0/stub_content_rect_dim.x,
target_prev_rect_px.y0/stub_content_rect_dim.y,
target_prev_rect_px.x1/stub_content_rect_dim.x,
target_prev_rect_px.y1/stub_content_rect_dim.y);
Rng2F32 prev_rect_pct = r2f32p(ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x0", new_panel->prev->cfg), target_prev_rect_pct.x0, .initial = target_prev_rect_pct.x0),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y0", new_panel->prev->cfg), target_prev_rect_pct.y0, .initial = target_prev_rect_pct.y0),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x1", new_panel->prev->cfg), target_prev_rect_pct.x1, .initial = target_prev_rect_pct.x1),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y1", new_panel->prev->cfg), target_prev_rect_pct.y1, .initial = target_prev_rect_pct.y1));
new_rect_pct = prev_rect_pct;
new_rect_pct.p0.v[split_axis] = new_rect_pct.p1.v[split_axis];
}
if(new_panel->next != &rd_nil_panel_node)
{
Rng2F32 target_next_rect_px = rd_target_rect_from_panel_node(stub_content_rect, panel_tree.root, rd_panel_node_from_tree_cfg(panel_tree.root, new_panel->next->cfg));
Rng2F32 target_next_rect_pct = r2f32p(target_next_rect_px.x0/stub_content_rect_dim.x,
target_next_rect_px.y0/stub_content_rect_dim.y,
target_next_rect_px.x1/stub_content_rect_dim.x,
target_next_rect_px.y1/stub_content_rect_dim.y);
Rng2F32 next_rect_pct = r2f32p(ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x0", new_panel->next->cfg), target_next_rect_pct.x0, .initial = target_next_rect_pct.x0),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y0", new_panel->next->cfg), target_next_rect_pct.y0, .initial = target_next_rect_pct.y0),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x1", new_panel->next->cfg), target_next_rect_pct.x1, .initial = target_next_rect_pct.x1),
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y1", new_panel->next->cfg), target_next_rect_pct.y1, .initial = target_next_rect_pct.y1));
new_rect_pct = next_rect_pct;
new_rect_pct.p1.v[split_axis] = new_rect_pct.p0.v[split_axis];
}
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x0", new_panel->cfg), new_rect_pct.x0, .initial = new_rect_pct.x0, .reset = 1);
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_x1", new_panel->cfg), new_rect_pct.x1, .initial = new_rect_pct.x1, .reset = 1);
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y0", new_panel->cfg), new_rect_pct.y0, .initial = new_rect_pct.y0, .reset = 1);
ui_anim(ui_key_from_stringf(ui_key_zero(), "panel_%p_y1", new_panel->cfg), new_rect_pct.y1, .initial = new_rect_pct.y1, .reset = 1);
}
}
// rjf: if this split was caused by drag/dropping a tab, and the originating panel
// has no further tabs, then close the originating panel
RD_Cfg *dragdrop_origin_panel_cfg = rd_cfg_from_id(rd_regs()->panel);
RD_Cfg *dragdrop_tab = rd_cfg_from_id(rd_regs()->view);
if(kind == RD_CmdKind_SplitPanel &&
new_panel_cfg != &rd_nil_cfg && dragdrop_tab != &rd_nil_cfg && dragdrop_origin_panel_cfg != &rd_nil_cfg)
{
rd_cfg_unhook(dragdrop_origin_panel_cfg, dragdrop_tab);
rd_cfg_insert_child(new_panel_cfg, new_panel_cfg->last, dragdrop_tab);
RD_PanelTree origin_panel_tree = rd_panel_tree_from_cfg(scratch.arena, dragdrop_origin_panel_cfg);
RD_PanelNode *origin_panel = rd_panel_node_from_tree_cfg(origin_panel_tree.root, dragdrop_origin_panel_cfg);
if(origin_panel->selected_tab == &rd_nil_cfg)
{
for(RD_CfgNode *n = origin_panel->tabs.first; n != 0; n = n->next)
{
if(!rd_cfg_is_project_filtered(n->v))
{
rd_cmd(RD_CmdKind_FocusTab, .panel = origin_panel->cfg->id, .tab = n->v->id);
break;
}
}
}
if(origin_panel->cfg != split_panel && origin_panel->tabs.count == 0)
{
rd_cmd(RD_CmdKind_ClosePanel);
}
rd_cmd(RD_CmdKind_FocusTab, .panel = new_panel_cfg->id, .tab = dragdrop_tab->id);
}
// rjf: focus new panel
if(new_panel_cfg != &rd_nil_cfg)
{
rd_cmd(RD_CmdKind_FocusPanel, .panel = new_panel_cfg->id);
}
// rjf: tabs on bottom on split panel? -> tabs on bottom on new panel
if(panel->tab_side == Side_Max && split_axis == Axis2_X)
{
rd_cmd(RD_CmdKind_TabBarBottom, .panel = new_panel_cfg->id);
}
}break;
//- rjf: panel rotation
case RD_CmdKind_RotatePanelColumns:
{
RD_Cfg *panel_cfg = rd_cfg_from_id(rd_regs()->panel);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, panel_cfg);
RD_PanelNode *panel = rd_panel_node_from_tree_cfg(panel_tree.root, panel_cfg);
RD_PanelNode *parent = &rd_nil_panel_node;
for(RD_PanelNode *p = panel->parent; p != &rd_nil_panel_node; p = p->parent)
{
if(p->split_axis == Axis2_X)
{
parent = p;
break;
}
}
if(parent != &rd_nil_panel_node && parent->child_count > 1)
{
RD_Cfg *rotated = parent->first->cfg;
rd_cfg_unhook(parent->cfg, parent->first->cfg);
rd_cfg_insert_child(parent->cfg, parent->last->cfg, rotated);
}
}break;
//- rjf: panel focusing
case RD_CmdKind_NextPanel: panel_sib_off = OffsetOf(RD_PanelNode, next); panel_child_off = OffsetOf(RD_PanelNode, first); goto cycle;
case RD_CmdKind_PrevPanel: panel_sib_off = OffsetOf(RD_PanelNode, prev); panel_child_off = OffsetOf(RD_PanelNode, last); goto cycle;
cycle:;
{
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, rd_cfg_from_id(rd_regs()->window));
RD_PanelNode *next_focused = &rd_nil_panel_node;
for(RD_PanelNode *p = panel_tree.focused;
p != &rd_nil_panel_node;
p = rd_panel_node_rec__depth_first(panel_tree.root, p, panel_sib_off, panel_child_off).next)
{
if(p != panel_tree.focused && p->first == &rd_nil_panel_node)
{
next_focused = p;
break;
}
}
if(next_focused == &rd_nil_panel_node)
{
for(RD_PanelNode *p = panel_tree.root;
p != &rd_nil_panel_node;
p = rd_panel_node_rec__depth_first(panel_tree.root, p, panel_sib_off, panel_child_off).next)
{
if(p != panel_tree.focused && p->first == &rd_nil_panel_node)
{
next_focused = p;
break;
}
}
}
rd_cmd(RD_CmdKind_FocusPanel, .panel = next_focused->cfg->id);
}break;
case RD_CmdKind_FocusPanel:
{
RD_Cfg *panel = rd_cfg_from_id(rd_regs()->panel);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, panel);
RD_Cfg *selection_cfg = &rd_nil_cfg;
for(RD_PanelNode *p = panel_tree.root;
p != &rd_nil_panel_node;
p = rd_panel_node_rec__depth_first_pre(panel_tree.root, p).next)
{
RD_Cfg *p_cfg = p->cfg;
RD_Cfg *p_selection = rd_cfg_child_from_string(p_cfg, str8_lit("selected"));
if(selection_cfg == &rd_nil_cfg)
{
selection_cfg = p_selection;
}
else for(RD_Cfg *s = p_selection; s != &rd_nil_cfg; s = rd_cfg_child_from_string(p_cfg, str8_lit("selected")))
{
rd_cfg_release(s);
}
}
if(selection_cfg == &rd_nil_cfg)
{
selection_cfg = rd_cfg_alloc();
rd_cfg_equip_string(selection_cfg, str8_lit("selected"));
}
if(panel != &rd_nil_cfg)
{
rd_cfg_insert_child(panel, &rd_nil_cfg, selection_cfg);
RD_Cfg *window = rd_window_from_cfg(panel);
RD_WindowState *ws = rd_window_state_from_cfg(window);
ws->menu_bar_focused = 0;
}
}break;
//- rjf: directional panel focus changing
case RD_CmdKind_FocusPanelRight: panel_change_dir = v2s32(+1, +0); goto focus_panel_dir;
case RD_CmdKind_FocusPanelLeft: panel_change_dir = v2s32(-1, +0); goto focus_panel_dir;
case RD_CmdKind_FocusPanelUp: panel_change_dir = v2s32(+0, -1); goto focus_panel_dir;
case RD_CmdKind_FocusPanelDown: panel_change_dir = v2s32(+0, +1); goto focus_panel_dir;
focus_panel_dir:;
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_PanelNode *src_panel = panel_tree.focused;
Rng2F32 src_panel_rect = rd_target_rect_from_panel_node(r2f32(v2f32(0, 0), v2f32(1000, 1000)), panel_tree.root, src_panel);
Vec2F32 src_panel_center = center_2f32(src_panel_rect);
Vec2F32 src_panel_half_dim = scale_2f32(dim_2f32(src_panel_rect), 0.5f);
Vec2F32 travel_dim = add_2f32(src_panel_half_dim, v2f32(10.f, 10.f));
Vec2F32 travel_dst = add_2f32(src_panel_center, mul_2f32(travel_dim, v2f32((F32)panel_change_dir.x, (F32)panel_change_dir.y)));
RD_PanelNode *dst_root = &rd_nil_panel_node;
for(RD_PanelNode *p = panel_tree.root; p != &rd_nil_panel_node; p = rd_panel_node_rec__depth_first_pre(panel_tree.root, p).next)
{
if(p == src_panel || p->first != &rd_nil_panel_node)
{
continue;
}
Rng2F32 p_rect = rd_target_rect_from_panel_node(r2f32(v2f32(0, 0), v2f32(1000, 1000)), panel_tree.root, p);
if(contains_2f32(p_rect, travel_dst))
{
dst_root = p;
break;
}
}
if(dst_root != &rd_nil_panel_node)
{
RD_PanelNode *dst_panel = &rd_nil_panel_node;
for(RD_PanelNode *p = dst_root; p != &rd_nil_panel_node; p = rd_panel_node_rec__depth_first_pre(dst_root, p).next)
{
if(p->first == &rd_nil_panel_node && p != src_panel)
{
dst_panel = p;
break;
}
}
rd_cmd(RD_CmdKind_FocusPanel, .panel = dst_panel->cfg->id);
}
}break;
//- rjf: undo/redo
case RD_CmdKind_Undo:{}break;
case RD_CmdKind_Redo:{}break;
//- rjf: focus history
case RD_CmdKind_GoBack:{}break;
case RD_CmdKind_GoForward:{}break;
//- rjf: files
case RD_CmdKind_SetCurrentPath:
{
RD_Cfg *user = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
RD_Cfg *current_path = rd_cfg_child_from_string_or_alloc(user, str8_lit("current_path"));
rd_cfg_new_replace(current_path, rd_regs()->file_path);
}break;
case RD_CmdKind_SetFileReplacementPath:
{
// NOTE(rjf):
//
// C:/foo/bar/baz.c
// D:/foo/bar/baz.c
// -> override C: -> D:
//
// C:/1/2/foo/bar.c
// C:/2/3/foo/bar.c
// -> override C:/1/2 -> C:2/3
//
// C:/foo/bar/baz.c
// D:/1/2/3.c
// -> override C:/foo/bar/baz.c -> D:/1/2/3.c
//- rjf: unpack
String8 src_path = rd_regs()->string;
String8 dst_path = rd_regs()->file_path;
String8 src_path__normalized = path_normalized_from_string(scratch.arena, src_path);
String8 dst_path__normalized = path_normalized_from_string(scratch.arena, dst_path);
String8List src_path_parts = str8_split_path(scratch.arena, src_path__normalized);
String8List dst_path_parts = str8_split_path(scratch.arena, dst_path__normalized);
//- rjf: reverse path parts
String8List src_path_parts__reversed = {0};
String8List dst_path_parts__reversed = {0};
for(String8Node *n = src_path_parts.first; n != 0; n = n->next)
{
str8_list_push_front(scratch.arena, &src_path_parts__reversed, n->string);
}
for(String8Node *n = dst_path_parts.first; n != 0; n = n->next)
{
str8_list_push_front(scratch.arena, &dst_path_parts__reversed, n->string);
}
//- rjf: trace from each path upwards, in lock-step, to find the first difference
// between the paths
String8Node *first_diff_src = src_path_parts__reversed.first;
String8Node *first_diff_dst = dst_path_parts__reversed.first;
for(;first_diff_src != 0 && first_diff_dst != 0;)
{
if(!str8_match(first_diff_src->string, first_diff_dst->string, StringMatchFlag_CaseInsensitive))
{
break;
}
first_diff_src = first_diff_src->next;
first_diff_dst = first_diff_dst->next;
}
//- rjf: form final map paths
String8List map_src_parts = {0};
String8List map_dst_parts = {0};
for(String8Node *n = first_diff_src; n != 0; n = n->next)
{
str8_list_push_front(scratch.arena, &map_src_parts, n->string);
}
for(String8Node *n = first_diff_dst; n != 0; n = n->next)
{
str8_list_push_front(scratch.arena, &map_dst_parts, n->string);
}
StringJoin map_join = {.sep = str8_lit("/")};
String8 map_src = str8_list_join(scratch.arena, &map_src_parts, &map_join);
String8 map_dst = str8_list_join(scratch.arena, &map_dst_parts, &map_join);
//- rjf: store as file path map cfg
RD_Cfg *user = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
RD_Cfg *map = rd_cfg_new(user, str8_lit("file_path_map"));
RD_Cfg *src = rd_cfg_new(map, str8_lit("source"));
RD_Cfg *dst = rd_cfg_new(map, str8_lit("dest"));
rd_cfg_new(src, map_src);
rd_cfg_new(dst, map_dst);
}break;
//- rjf: panel removal
case RD_CmdKind_ClosePanel:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_PanelNode *panel = rd_panel_node_from_tree_cfg(panel_tree.root, rd_cfg_from_id(rd_regs()->panel));
RD_PanelNode *parent = panel->parent;
if(parent != &rd_nil_panel_node)
{
Axis2 split_axis = parent->split_axis;
// NOTE(rjf): If we're removing all but the last child of this parent,
// we should just remove both children.
if(parent->child_count == 2)
{
RD_PanelNode *discard_child = panel;
RD_PanelNode *keep_child = (panel == parent->first ? parent->last : parent->first);
RD_PanelNode *grandparent = parent->parent;
RD_PanelNode *parent_prev = parent->prev;
F32 pct_of_parent = parent->pct_of_parent;
// rjf: unhook kept child
rd_cfg_unhook(parent->cfg, keep_child->cfg);
// rjf: unhook this subtree
if(grandparent != &rd_nil_panel_node)
{
rd_cfg_unhook(grandparent->cfg, parent->cfg);
}
// rjf: release the containing tree
{
rd_cfg_release(parent->cfg);
}
// rjf: re-hook our kept child into the overall tree
if(grandparent == &rd_nil_panel_node)
{
rd_cfg_equip_string(keep_child->cfg, str8_lit("panels"));
rd_cfg_insert_child(window, window->last, keep_child->cfg);
}
else
{
rd_cfg_insert_child(grandparent->cfg, parent_prev->cfg, keep_child->cfg);
rd_cfg_equip_stringf(keep_child->cfg, "%f", pct_of_parent);
}
// rjf: keep-child split-axis == grandparent split-axis? bubble keep-child up into grandparent's children
if(grandparent != &rd_nil_panel_node && grandparent->split_axis == keep_child->split_axis && keep_child->first != &rd_nil_panel_node)
{
rd_cfg_unhook(grandparent->cfg, keep_child->cfg);
RD_Cfg *prev = parent_prev->cfg;
for(RD_PanelNode *child = keep_child->first, *next = &rd_nil_panel_node; child != &rd_nil_panel_node; child = next)
{
next = child->next;
rd_cfg_unhook(keep_child->cfg, child->cfg);
rd_cfg_insert_child(grandparent->cfg, prev, child->cfg);
prev = child->cfg;
F32 old_pct = child->pct_of_parent;
F32 new_pct = old_pct * pct_of_parent;
rd_cfg_equip_stringf(child->cfg, "%f", new_pct);
}
rd_cfg_release(keep_child->cfg);
}
// rjf: reset focus, if needed
if(panel_tree.focused == discard_child)
{
RD_PanelTree new_panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_PanelNode *new_focused = rd_panel_node_from_tree_cfg(panel_tree.root, keep_child->cfg);
for(RD_PanelNode *grandchild = new_focused; grandchild != &rd_nil_panel_node; grandchild = grandchild->first)
{
new_focused = grandchild;
}
rd_cmd(RD_CmdKind_FocusPanel, .panel = new_focused->cfg->id);
}
}
// NOTE(rjf): Otherwise we can just remove this child.
else
{
// rjf: remove
RD_PanelNode *next = &rd_nil_panel_node;
F32 removed_size_pct = panel->pct_of_parent;
if(next == &rd_nil_panel_node) { next = panel->prev; }
if(next == &rd_nil_panel_node) { next = panel->next; }
rd_cfg_unhook(parent->cfg, panel->cfg);
rd_cfg_release(panel->cfg);
// rjf: resize siblings to this node
{
RD_PanelTree new_panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_PanelNode *new_parent = rd_panel_node_from_tree_cfg(new_panel_tree.root, parent->cfg);
for(RD_PanelNode *child = new_parent->first; child != &rd_nil_panel_node; child = child->next)
{
RD_Cfg *cfg = child->cfg;
F32 old_pct = child->pct_of_parent;
F32 new_pct = old_pct / (1.f-removed_size_pct);
rd_cfg_equip_stringf(cfg, "%f", new_pct);
}
}
// rjf: reset focus, if needed
if(panel_tree.focused == panel)
{
RD_PanelTree new_panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_PanelNode *new_focused = rd_panel_node_from_tree_cfg(panel_tree.root, next->cfg);
for(RD_PanelNode *grandchild = new_focused; grandchild != &rd_nil_panel_node; grandchild = grandchild->first)
{
new_focused = grandchild;
}
rd_cmd(RD_CmdKind_FocusPanel, .panel = new_focused->cfg->id);
}
}
}
}break;
//- rjf: panel tab controls
case RD_CmdKind_FocusTab:
{
RD_Cfg *tab = rd_cfg_from_id(rd_regs()->tab);
RD_Cfg *panel = tab->parent;
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, panel);
RD_PanelNode *panel_node = rd_panel_node_from_tree_cfg(panel_tree.root, panel);
RD_Cfg *selection_cfg = &rd_nil_cfg;
for(RD_CfgNode *n = panel_node->tabs.first; n != 0; n = n->next)
{
RD_Cfg *tab_selection_cfg = rd_cfg_child_from_string(n->v, str8_lit("selected"));
if(selection_cfg == &rd_nil_cfg)
{
selection_cfg = tab_selection_cfg;
}
else for(RD_Cfg *s = tab_selection_cfg; s != &rd_nil_cfg; s = rd_cfg_child_from_string(n->v, str8_lit("selected")))
{
rd_cfg_release(s);
}
}
if(selection_cfg == &rd_nil_cfg)
{
selection_cfg = rd_cfg_alloc();
rd_cfg_equip_string(selection_cfg, str8_lit("selected"));
}
rd_cfg_insert_child(tab, &rd_nil_cfg, selection_cfg);
}break;
case RD_CmdKind_NextTab:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_PanelNode *focused = panel_tree.focused;
RD_CfgNode *selected_tab_n = 0;
for(RD_CfgNode *n = focused->tabs.first; n != 0; n = n->next)
{
if(n->v == focused->selected_tab)
{
selected_tab_n = n;
break;
}
}
RD_Cfg *next_selected_tab = &rd_nil_cfg;
U64 idx = 0;
for(RD_CfgNode *tab_n = selected_tab_n;
tab_n != 0 && (tab_n != selected_tab_n || idx == 0);
((tab_n->next == 0) ? (tab_n = focused->tabs.first) : (tab_n = tab_n->next)), idx += 1)
{
if(!rd_cfg_is_project_filtered(tab_n->v) && tab_n != selected_tab_n)
{
next_selected_tab = tab_n->v;
break;
}
}
if(next_selected_tab != &rd_nil_cfg)
{
rd_cmd(RD_CmdKind_FocusTab, .tab = next_selected_tab->id);
}
}break;
case RD_CmdKind_PrevTab:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_PanelNode *focused = panel_tree.focused;
RD_CfgNode *selected_tab_n = 0;
for(RD_CfgNode *n = focused->tabs.last; n != 0; n = n->prev)
{
if(n->v == focused->selected_tab)
{
selected_tab_n = n;
break;
}
}
RD_Cfg *next_selected_tab = &rd_nil_cfg;
U64 idx = 0;
for(RD_CfgNode *tab_n = selected_tab_n;
tab_n != 0 && (tab_n != selected_tab_n || idx == 0);
((tab_n->prev == 0) ? (tab_n = focused->tabs.last) : (tab_n = tab_n->prev)), idx += 1)
{
if(!rd_cfg_is_project_filtered(tab_n->v) && tab_n != selected_tab_n)
{
next_selected_tab = tab_n->v;
break;
}
}
if(next_selected_tab != &rd_nil_cfg)
{
rd_cmd(RD_CmdKind_FocusTab, .tab = next_selected_tab->id);
}
}break;
case RD_CmdKind_MoveTabRight:
case RD_CmdKind_MoveTabLeft:
{
RD_Cfg *tab = rd_cfg_from_id(rd_regs()->tab);
RD_Cfg *window = rd_window_from_cfg(tab);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
RD_PanelNode *panel = rd_panel_node_from_tree_cfg(panel_tree.root, tab->parent);
RD_CfgList filtered_tabs = {0};
for(RD_CfgNode *n = panel->tabs.first; n != 0; n = n->next)
{
if(rd_cfg_is_project_filtered(n->v))
{
continue;
}
rd_cfg_list_push(scratch.arena, &filtered_tabs, n->v);
}
RD_Cfg *tab_prev2 = &rd_nil_cfg;
RD_Cfg *tab_prev = &rd_nil_cfg;
RD_Cfg *tab_next = &rd_nil_cfg;
{
RD_Cfg *prev2 = &rd_nil_cfg;
RD_Cfg *prev = &rd_nil_cfg;
RD_Cfg *next = &rd_nil_cfg;
for(RD_CfgNode *n = filtered_tabs.first; n != 0; (prev2 = prev, prev = n->v, n = n->next))
{
next = n->next ? n->next->v : &rd_nil_cfg;
if(n->v == tab)
{
tab_prev2 = prev2;
tab_prev = prev;
tab_next = next;
break;
}
}
}
RD_Cfg *new_prev = (kind == RD_CmdKind_MoveTabRight ? tab_next : tab_prev2);
if(new_prev == tab_prev && filtered_tabs.last)
{
new_prev = filtered_tabs.last->v;
}
rd_cmd(RD_CmdKind_MoveView,
.dst_panel = panel->cfg->id,
.view = tab->id,
.prev_tab = new_prev->id);
}break;
case RD_CmdKind_BuildTab:
{
String8 expr_file_path = rd_file_path_from_eval_string(scratch.arena, rd_regs()->expr);
RD_Cfg *panel = rd_cfg_from_id(rd_regs()->panel);
RD_Cfg *tab = rd_cfg_new(panel, rd_regs()->string);
RD_Cfg *expr = rd_cfg_new(tab, str8_lit("expression"));
rd_cfg_new(expr, rd_regs()->expr);
if(expr_file_path.size != 0)
{
RD_Cfg *project = rd_cfg_new(tab, str8_lit("project"));
rd_cfg_new(project, rd_state->project_path);
}
rd_cmd(RD_CmdKind_FocusTab, .tab = tab->id);
}break;
case RD_CmdKind_DuplicateTab:
{
RD_Cfg *src = rd_cfg_from_id(rd_regs()->tab);
RD_Cfg *dst = rd_cfg_deep_copy(src);
rd_cfg_insert_child(src->parent, src, dst);
rd_cmd(RD_CmdKind_FocusTab, .tab = dst->id);
}break;
case RD_CmdKind_CloseTab:
{
RD_Cfg *tab = rd_cfg_from_id(rd_regs()->tab);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, tab);
RD_PanelNode *panel = rd_panel_node_from_tree_cfg(panel_tree.root, tab->parent);
B32 found_selected = 0;
RD_Cfg *next_selected_tab = &rd_nil_cfg;
for(RD_CfgNode *n = panel->tabs.first; n != 0; n = n->next)
{
if(n->v == panel->selected_tab)
{
found_selected = 1;
}
else if(!rd_cfg_is_project_filtered(n->v))
{
next_selected_tab = n->v;
if(found_selected)
{
break;
}
}
}
rd_cmd(RD_CmdKind_FocusTab, .tab = next_selected_tab->id);
rd_cfg_release(tab);
}break;
case RD_CmdKind_MoveView:
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_Cfg *prev_tab = rd_cfg_from_id(rd_regs()->prev_tab);
RD_Cfg *src_panel = view->parent;
RD_Cfg *dst_panel = rd_cfg_from_id(rd_regs()->dst_panel);
if(dst_panel != &rd_nil_cfg && prev_tab != view)
{
rd_cfg_unhook(src_panel, view);
rd_cfg_insert_child(dst_panel, prev_tab, view);
rd_cmd(RD_CmdKind_FocusTab, .panel = dst_panel->id, .tab = view->id);
rd_cmd(RD_CmdKind_FocusPanel, .panel = dst_panel->id);
RD_PanelTree src_panel_tree = rd_panel_tree_from_cfg(scratch.arena, src_panel);
RD_PanelNode *src_panel_node = rd_panel_node_from_tree_cfg(src_panel_tree.root, src_panel);
B32 src_panel_is_empty = 0;
if(src_panel != dst_panel)
{
src_panel_is_empty = 1;
for(RD_CfgNode *n = src_panel_node->tabs.first; n != 0; n = n->next)
{
if(!rd_cfg_is_project_filtered(n->v))
{
rd_cmd(RD_CmdKind_FocusTab, .panel = src_panel->id, .tab = n->v->id);
src_panel_is_empty = 0;
break;
}
}
}
if(src_panel_is_empty)
{
rd_cmd(RD_CmdKind_ClosePanel, .panel = src_panel->id);
}
}
}break;
case RD_CmdKind_TabBarTop:
{
RD_Cfg *panel = rd_cfg_from_id(rd_regs()->panel);
rd_cfg_release(rd_cfg_child_from_string(panel, str8_lit("tabs_on_bottom")));
}break;
case RD_CmdKind_TabBarBottom:
{
RD_Cfg *panel = rd_cfg_from_id(rd_regs()->panel);
rd_cfg_child_from_string_or_alloc(panel, str8_lit("tabs_on_bottom"));
}break;
case RD_CmdKind_TabSettings:
{
String8 expr = push_str8f(scratch.arena, "query:config.$%I64x", rd_regs()->tab);
rd_cmd(RD_CmdKind_PushQuery, .expr = expr, .do_implicit_root = 1, .do_big_rows = 1, .do_lister = 1);
}break;
//- rjf: files
case RD_CmdKind_Open:
{
String8 path = rd_regs()->file_path;
FileProperties props = os_properties_from_file_path(path);
if(props.created != 0)
{
rd_cmd(RD_CmdKind_RecordFileInProject);
rd_cmd(RD_CmdKind_BuildTab, .string = str8_lit("pending"), .expr = rd_eval_string_from_file_path(scratch.arena, path));
}
else
{
log_user_errorf("Couldn't open file at \"%S\".", path);
}
}break;
case RD_CmdKind_Switch:
{
RD_Cfg *recent_file = rd_cfg_from_id(rd_regs()->cfg);
RD_Cfg *path_root = rd_cfg_child_from_string(recent_file, str8_lit("path"));
String8 path = path_root->first->string;
rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = path, .cursor = txt_pt(0, 0), .vaddr = 0);
}break;
case RD_CmdKind_SwitchToPartnerFile:
{
String8 file_path = rd_regs()->file_path;
String8 file_full_path = path_normalized_from_string(scratch.arena, file_path);
String8 file_folder = str8_chop_last_slash(file_full_path);
String8 file_name = str8_skip_last_slash(str8_chop_last_dot(file_full_path));
String8 file_ext = str8_skip_last_dot(file_full_path);
String8 partner_ext_candidates[] =
{
str8_lit_comp("h"),
str8_lit_comp("hpp"),
str8_lit_comp("hxx"),
str8_lit_comp("c"),
str8_lit_comp("cc"),
str8_lit_comp("cxx"),
str8_lit_comp("cpp"),
};
for(U64 idx = 0; idx < ArrayCount(partner_ext_candidates); idx += 1)
{
if(!str8_match(partner_ext_candidates[idx], file_ext, StringMatchFlag_CaseInsensitive))
{
String8 candidate = push_str8f(scratch.arena, "%S.%S", file_name, partner_ext_candidates[idx]);
String8 candidate_path = push_str8f(scratch.arena, "%S/%S", file_folder, candidate);
FileProperties candidate_props = os_properties_from_file_path(candidate_path);
if(candidate_props.modified != 0)
{
rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = candidate_path, .cursor = txt_pt(0, 0), .vaddr = 0);
break;
}
}
}
}break;
case RD_CmdKind_RecordFileInProject:
if(rd_regs()->file_path.size != 0)
{
String8 path = path_normalized_from_string(scratch.arena, rd_regs()->file_path);
RD_Cfg *project = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("project"));
RD_CfgList recent_files = rd_cfg_child_list_from_string(scratch.arena, project, str8_lit("recent_file"));
RD_Cfg *recent_file = &rd_nil_cfg;
for(RD_CfgNode *n = recent_files.first; n != 0; n = n->next)
{
if(path_match_normalized(rd_path_from_cfg(n->v), path))
{
recent_file = n->v;
break;
}
}
if(recent_file == &rd_nil_cfg)
{
recent_file = rd_cfg_new(project, str8_lit("recent_file"));
RD_Cfg *path_root = rd_cfg_new(recent_file, str8_lit("path"));
rd_cfg_new(path_root, path);
}
rd_cfg_unhook(project, recent_file);
rd_cfg_insert_child(project, &rd_nil_cfg, recent_file);
recent_files = rd_cfg_child_list_from_string(scratch.arena, project, str8_lit("recent_file"));
if(recent_files.count > 256)
{
rd_cfg_release(recent_files.last->v);
}
}break;
case RD_CmdKind_ShowFileInExplorer:
if(rd_regs()->file_path.size != 0)
{
String8 full_path = path_normalized_from_string(scratch.arena, rd_regs()->file_path);
os_show_in_filesystem_ui(full_path);
}break;
//- rjf: source <-> disasm
case RD_CmdKind_GoToDisassembly:
{
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->thread);
U64 vaddr = 0;
for(D_LineNode *n = rd_regs()->lines.first; n != 0; n = n->next)
{
CTRL_EntityList modules = ctrl_modules_from_dbgi_key(scratch.arena, d_state->ctrl_entity_store, &n->v.dbgi_key);
CTRL_Entity *module = ctrl_module_from_thread_candidates(d_state->ctrl_entity_store, thread, &modules);
if(module != &ctrl_entity_nil)
{
vaddr = ctrl_vaddr_from_voff(module, n->v.voff_range.min);
break;
}
}
rd_cmd(RD_CmdKind_FindCodeLocation, .process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process)->handle, .vaddr = vaddr, .prefer_disasm = 1);
}break;
case RD_CmdKind_GoToSource:
{
if(rd_regs()->lines.first != 0)
{
rd_cmd(RD_CmdKind_FindCodeLocation,
.file_path = rd_regs()->lines.first->v.file_path,
.cursor = rd_regs()->lines.first->v.pt,
.vaddr = 0,
.process = ctrl_handle_zero(),
.prefer_disasm = 0);
}
}break;
//- rjf: panel built-in layout builds
case RD_CmdKind_ResetToDefaultPanels:
case RD_CmdKind_ResetToCompactPanels:
case RD_CmdKind_ResetToSimplePanels:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_Cfg *panels = rd_cfg_child_from_string(window, str8_lit("panels"));
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
//- rjf: define all of the "fixed" tabs we care about
#define X(name) RD_Cfg *name = &rd_nil_cfg;
#define Y(name, rule, expr) RD_Cfg *name = &rd_nil_cfg;
#define Z(name) RD_Cfg *name = &rd_nil_cfg;
RD_FixedTabXList
#undef X
#undef Y
#undef Z
//- rjf: find all the fixed tabs, and all text viewers
B32 any_fixed_tabs_found = 0;
RD_CfgList texts = {0};
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
for(RD_CfgNode *n = panel->tabs.first; n != 0; n = n->next)
{
RD_Cfg *tab = n->v;
B32 need_unhook = 1;
if(0){}
#define X(name) else if(str8_match(tab->string, str8_lit("watch"), 0) && str8_match(rd_expr_from_cfg(tab), str8_lit("query:" #name), 0)) {name = tab;}
#define Y(name, rule, expr) else if(str8_match(tab->string, str8_lit(#rule), 0) && str8_match(rd_expr_from_cfg(tab), str8_lit(expr), 0)) {name = tab;}
#define Z(name) else if(str8_match(tab->string, str8_lit(#name), 0)) {name = tab;}
RD_FixedTabXList
#undef X
#undef Y
#undef Z
else if(str8_match(tab->string, str8_lit("text"), 0)) {rd_cfg_list_push(scratch.arena, &texts, tab);}
else
{
need_unhook = 0;
}
if(need_unhook)
{
rd_cfg_unhook(panel->cfg, tab);
any_fixed_tabs_found = 1;
}
}
}
//- rjf: release the old panel tree
rd_cfg_release(panels);
//- rjf: allocate any missing tabs
#define X(name) if(name == &rd_nil_cfg) {name = rd_cfg_alloc(); rd_cfg_equip_string(name, str8_lit("watch")); RD_Cfg *expr_cfg = rd_cfg_new(name, str8_lit("expression")); rd_cfg_new(expr_cfg, str8_lit("query:" #name));}
#define Y(name, rule, expr) if(name == &rd_nil_cfg) {name = rd_cfg_alloc(); rd_cfg_equip_string(name, str8_lit(#rule)); RD_Cfg *expr_cfg = rd_cfg_new(name, str8_lit("expression")); rd_cfg_new(expr_cfg, str8_lit(expr));}
#define Z(name) if(name == &rd_nil_cfg && !any_fixed_tabs_found) {name = rd_cfg_alloc(); rd_cfg_equip_string(name, str8_lit(#name));}
RD_FixedTabXList
#undef X
#undef Y
#undef Z
//- rjf: eliminate all tab selections
#define X(name) if(name != &rd_nil_cfg) {rd_cfg_release(rd_cfg_child_from_string(name, str8_lit("selected")));}
#define Y(name, rule, expr) if(name != &rd_nil_cfg) {rd_cfg_release(rd_cfg_child_from_string(name, str8_lit("selected")));}
#define Z(name) if(name != &rd_nil_cfg) {rd_cfg_release(rd_cfg_child_from_string(name, str8_lit("selected")));}
RD_FixedTabXList
#undef X
#undef Y
#undef Z
for(RD_CfgNode *n = texts.first; n != 0; n = n->next)
{
rd_cfg_release(rd_cfg_child_from_string(n->v, str8_lit("selected")));
}
//- rjf: create the panel root
panels = rd_cfg_new(window, str8_lit("panels"));
//- rjf: rebuild the new panel tree
switch(kind)
{
default:{}break;
//- rjf: (default layout)
case RD_CmdKind_ResetToDefaultPanels:
{
// rjf: root split
rd_cfg_child_from_string_or_alloc(window, str8_lit("split_x"));
RD_Cfg *root_0 = rd_cfg_new(panels, str8_lit("0.85"));
RD_Cfg *root_1 = rd_cfg_new(panels, str8_lit("0.15"));
// rjf: root_0 split
RD_Cfg *root_0_0 = rd_cfg_new(root_0, str8_lit("0.80"));
RD_Cfg *root_0_1 = rd_cfg_new(root_0, str8_lit("0.20"));
// rjf: root_1 split
RD_Cfg *root_1_0 = rd_cfg_new(root_1, str8_lit("0.50"));
RD_Cfg *root_1_1 = rd_cfg_new(root_1, str8_lit("0.50"));
rd_cfg_insert_child(root_1_0, root_1_0->last, targets);
rd_cfg_insert_child(root_1_1, root_1_1->last, threads);
rd_cfg_insert_child(root_1_1, root_1_1->last, processes);
rd_cfg_insert_child(root_1_1, root_1_1->last, machines);
rd_cfg_new(targets, str8_lit("selected"));
rd_cfg_new(threads, str8_lit("selected"));
// rjf: root 0_0 split
RD_Cfg *root_0_0_0 = rd_cfg_new(root_0_0, str8_lit("0.25"));
RD_Cfg *root_0_0_1 = rd_cfg_new(root_0_0, str8_lit("0.75"));
// rjf: root_0_0_0 split
RD_Cfg *root_0_0_0_0 = rd_cfg_new(root_0_0_0, str8_lit("0.50"));
RD_Cfg *root_0_0_0_1 = rd_cfg_new(root_0_0_0, str8_lit("0.50"));
rd_cfg_insert_child(root_0_0_0_0, root_0_0_0_0->last, disasm);
rd_cfg_new(disasm, str8_lit("selected"));
rd_cfg_insert_child(root_0_0_0_1, root_0_0_0_1->last, breakpoints);
rd_cfg_insert_child(root_0_0_0_1, root_0_0_0_1->last, watch_pins);
rd_cfg_insert_child(root_0_0_0_1, root_0_0_0_1->last, output);
rd_cfg_insert_child(root_0_0_0_1, root_0_0_0_1->last, memory);
rd_cfg_new(output, str8_lit("selected"));
// rjf: root_0_1 split
RD_Cfg *root_0_1_0 = rd_cfg_new(root_0_1, str8_lit("0.60"));
RD_Cfg *root_0_1_1 = rd_cfg_new(root_0_1, str8_lit("0.40"));
rd_cfg_insert_child(root_0_1_0, root_0_1_0->last, watches);
rd_cfg_insert_child(root_0_1_0, root_0_1_0->last, locals);
rd_cfg_insert_child(root_0_1_0, root_0_1_0->last, registers);
rd_cfg_insert_child(root_0_1_0, root_0_1_0->last, globals);
rd_cfg_insert_child(root_0_1_0, root_0_1_0->last, thread_locals);
rd_cfg_insert_child(root_0_1_0, root_0_1_0->last, types);
rd_cfg_insert_child(root_0_1_0, root_0_1_0->last, procedures);
rd_cfg_new(watches, str8_lit("selected"));
rd_cfg_new(root_0_1_0, str8_lit("tabs_on_bottom"));
rd_cfg_insert_child(root_0_1_1, root_0_1_1->last, call_stack);
rd_cfg_insert_child(root_0_1_1, root_0_1_1->last, modules);
rd_cfg_new(call_stack, str8_lit("selected"));
rd_cfg_new(root_0_1_1, str8_lit("tabs_on_bottom"));
// rjf: fill main panel with getting started, OR all collected code views
RD_Cfg *main_panel = root_0_0_1;
if(getting_started != &rd_nil_cfg)
{
rd_cfg_insert_child(main_panel, main_panel->last, getting_started);
rd_cfg_new(getting_started, str8_lit("selected"));
}
else if(texts.first)
{
rd_cfg_new(texts.first->v, str8_lit("selected"));
}
for(RD_CfgNode *n = texts.first; n != 0; n = n->next)
{
rd_cfg_insert_child(main_panel, main_panel->last, n->v);
}
// rjf: set main panel as selected
rd_cfg_new(main_panel, str8_lit("selected"));
}break;
//- rjf: (compact layout)
case RD_CmdKind_ResetToCompactPanels:
{
// rjf: root split
rd_cfg_child_from_string_or_alloc(window, str8_lit("split_x"));
RD_Cfg *root_0 = rd_cfg_new(panels, str8_lit("0.25"));
RD_Cfg *root_1 = rd_cfg_new(panels, str8_lit("0.75"));
// rjf: root_0 split
RD_Cfg *root_0_0 = rd_cfg_new(root_0, str8_lit("0.25"));
RD_Cfg *root_0_1 = rd_cfg_new(root_0, str8_lit("0.25"));
RD_Cfg *root_0_2 = rd_cfg_new(root_0, str8_lit("0.25"));
RD_Cfg *root_0_3 = rd_cfg_new(root_0, str8_lit("0.25"));
rd_cfg_insert_child(root_0_0, root_0_0->last, watches);
rd_cfg_insert_child(root_0_0, root_0_0->last, types);
rd_cfg_new(watches, str8_lit("selected"));
rd_cfg_insert_child(root_0_1, root_0_1->last, threads);
rd_cfg_insert_child(root_0_1, root_0_1->last, targets);
rd_cfg_insert_child(root_0_1, root_0_1->last, breakpoints);
rd_cfg_insert_child(root_0_1, root_0_1->last, watch_pins);
rd_cfg_new(threads, str8_lit("selected"));
rd_cfg_insert_child(root_0_2, root_0_2->last, disasm);
rd_cfg_insert_child(root_0_2, root_0_2->last, output);
rd_cfg_new(disasm, str8_lit("selected"));
rd_cfg_insert_child(root_0_3, root_0_3->last, call_stack);
rd_cfg_insert_child(root_0_3, root_0_3->last, modules);
rd_cfg_new(call_stack, str8_lit("selected"));
// rjf: fill main panel with getting started, OR all collected code views
RD_Cfg *main_panel = root_1;
if(getting_started != &rd_nil_cfg)
{
rd_cfg_insert_child(main_panel, main_panel->last, getting_started);
rd_cfg_new(getting_started, str8_lit("selected"));
}
else if(texts.first)
{
rd_cfg_new(texts.first->v, str8_lit("selected"));
}
for(RD_CfgNode *n = texts.first; n != 0; n = n->next)
{
rd_cfg_insert_child(main_panel, main_panel->last, n->v);
}
// rjf: set main panel as selected
rd_cfg_new(main_panel, str8_lit("selected"));
}break;
//- rjf: simple layout
case RD_CmdKind_ResetToSimplePanels:
{
// rjf: root split
rd_cfg_child_from_string_or_alloc(window, str8_lit("split_x"));
RD_Cfg *root_0 = rd_cfg_new(panels, str8_lit("0.25"));
RD_Cfg *root_1 = rd_cfg_new(panels, str8_lit("0.75"));
// rjf: fill smaller panel with watch
rd_cfg_insert_child(root_0, root_0->last, watches);
rd_cfg_new(watches, str8_lit("selected"));
// rjf: fill main panel with getting started, OR all collected code views
RD_Cfg *main_panel = root_1;
if(getting_started != &rd_nil_cfg)
{
rd_cfg_insert_child(main_panel, main_panel->last, getting_started);
rd_cfg_new(getting_started, str8_lit("selected"));
}
else if(texts.first)
{
rd_cfg_new(texts.first->v, str8_lit("selected"));
}
for(RD_CfgNode *n = texts.first; n != 0; n = n->next)
{
rd_cfg_insert_child(main_panel, main_panel->last, n->v);
}
// rjf: set main panel as selected
rd_cfg_new(main_panel, str8_lit("selected"));
}break;
}
//- rjf: release any unused views from the previous layout
#define X(name) if(name->parent == &rd_nil_cfg) {rd_cfg_release(name);}
#define Y(name, rule, expr) if(name->parent == &rd_nil_cfg) {rd_cfg_release(name);}
#define Z(name) if(name->parent == &rd_nil_cfg) {rd_cfg_release(name);}
RD_FixedTabXList
#undef X
#undef Y
#undef Z
//- rjf: remember that we reset the panel layouts
RD_WindowState *ws = rd_window_state_from_cfg(window);
if(ws != &rd_nil_window_state)
{
ws->window_layout_reset = 1;
}
}break;
//- rjf: thread finding
case RD_CmdKind_FindThread:
for(RD_WindowState *ws = rd_state->first_window_state; ws != &rd_nil_window_state; ws = ws->order_next)
RD_RegsScope(.window = ws->cfg_id)
{
DI_Scope *scope = di_scope_open();
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->thread);
U64 unwind_index = rd_regs()->unwind_count;
U64 inline_depth = rd_regs()->inline_depth;
if(thread->kind == CTRL_EntityKind_Thread)
{
// rjf: grab rip
U64 rip_vaddr = d_query_cached_rip_from_thread_unwind(thread, unwind_index);
// rjf: extract thread/rip info
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process);
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, rip_vaddr);
DI_Key dbgi_key = ctrl_dbgi_key_from_module(module);
RDI_Parsed *rdi = di_rdi_from_key(scope, &dbgi_key, 0);
U64 rip_voff = ctrl_voff_from_vaddr(module, rip_vaddr);
D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &dbgi_key, rip_voff);
D_Line line = {0};
{
U64 idx = 0;
for(D_LineNode *n = lines.first; n != 0; n = n->next, idx += 1)
{
line = n->v;
if(idx == inline_depth)
{
break;
}
}
}
// rjf: snap to resolved line
B32 missing_rip = (rip_vaddr == 0);
B32 dbgi_missing = (dbgi_key.min_timestamp == 0 || dbgi_key.path.size == 0);
B32 dbgi_pending = !dbgi_missing && rdi == &rdi_parsed_nil;
B32 has_line_info = (line.voff_range.max != 0);
B32 has_module = (module != &ctrl_entity_nil);
B32 has_dbg_info = has_module && !dbgi_missing;
if(!dbgi_pending && (has_line_info || has_module))
{
rd_cmd(RD_CmdKind_FindCodeLocation,
.file_path = line.file_path,
.cursor = line.pt,
.process = process->handle,
.voff = rip_voff,
.vaddr = rip_vaddr,
.unwind_count = unwind_index,
.inline_depth = inline_depth);
}
// rjf: snap to resolved address w/o line info
if(!missing_rip && !dbgi_pending && !has_line_info && !has_module)
{
rd_cmd(RD_CmdKind_FindCodeLocation,
.file_path = str8_zero(),
.process = process->handle,
.module = module->handle,
.voff = rip_voff,
.vaddr = rip_vaddr,
.unwind_count = unwind_index,
.inline_depth = inline_depth);
}
// rjf: retry on stopped, pending debug info
if(!d_ctrl_targets_running() && (dbgi_pending || missing_rip))
{
find_thread_retry = thread->handle;
}
}
di_scope_close(scope);
}break;
case RD_CmdKind_FindSelectedThread:
for(RD_WindowState *ws = rd_state->first_window_state; ws != &rd_nil_window_state; ws = ws->order_next)
RD_RegsScope(.window = ws->cfg_id)
{
CTRL_Entity *selected_thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_base_regs()->thread);
rd_cmd(RD_CmdKind_FindThread,
.thread = selected_thread->handle,
.unwind_count = rd_base_regs()->unwind_count,
.inline_depth = rd_base_regs()->inline_depth);
}break;
//- rjf: name finding
case RD_CmdKind_GoToName:
{
String8 name = rd_regs()->string;
if(name.size != 0)
{
B32 name_resolved = 0;
// rjf: try to resolve name as a symbol
U64 voff = 0;
DI_Key voff_dbgi_key = {0};
if(!name_resolved)
{
DI_KeyList keys = d_push_active_dbgi_key_list(scratch.arena);
for(DI_KeyNode *n = keys.first; n != 0; n = n->next)
{
U64 binary_voff = d_voff_from_dbgi_key_symbol_name(&n->v, name);
if(binary_voff != 0)
{
voff = binary_voff;
voff_dbgi_key = n->v;
name_resolved = 1;
break;
}
}
}
// rjf: try to resolve name as a file
String8 file_path = {0};
if(!name_resolved)
{
// rjf: unpack quoted portion of string
String8 file_part_of_name = name;
U64 quote_pos = str8_find_needle(name, 0, str8_lit("\""), 0);
if(quote_pos < name.size)
{
file_part_of_name = str8_skip(name, quote_pos+1);
U64 ender_quote_pos = str8_find_needle(file_part_of_name, 0, str8_lit("\""), 0);
file_part_of_name = str8_prefix(file_part_of_name, ender_quote_pos);
}
String8List search_parts = str8_split_path(scratch.arena, file_part_of_name);
// rjf: get source path
RD_Cfg *src_view = rd_cfg_from_id(rd_regs()->view);
String8 src_view_expr = rd_expr_from_cfg(src_view);
String8 src_file_path = rd_file_path_from_eval_string(scratch.arena, src_view_expr);
String8List src_file_parts = str8_split_path(scratch.arena, src_file_path);
// rjf: search for actual file
Temp temp = temp_begin(scratch.arena);
for(String8Node *n = src_file_parts.first; n != 0; n = n->next)
{
temp_end(temp);
String8List try_path_parts = {0};
for(String8Node *src_n = src_file_parts.first; src_n != n && src_n != 0; src_n = src_n->next)
{
str8_list_push(temp.arena, &try_path_parts, src_n->string);
}
for(String8Node *try_n = search_parts.first; try_n != 0; try_n = try_n->next)
{
str8_list_push(temp.arena, &try_path_parts, try_n->string);
}
String8 try_path = str8_list_join(temp.arena, &try_path_parts, &(StringJoin){.sep = str8_lit("/")});
FileProperties try_props = os_properties_from_file_path(try_path);
if(try_props.modified != 0)
{
name_resolved = 1;
file_path = try_path;
break;
}
}
}
// rjf: process resolved info
if(!name_resolved)
{
log_user_errorf("`%S` could not be found.", name);
}
// rjf: name resolved to voff * dbg info
if(name_resolved && voff != 0)
{
D_LineList lines = d_lines_from_dbgi_key_voff(scratch.arena, &voff_dbgi_key, voff);
if(lines.first != 0)
{
CTRL_Entity *process = &ctrl_entity_nil;
U64 vaddr = 0;
if(voff_dbgi_key.path.size != 0)
{
CTRL_EntityList modules = ctrl_modules_from_dbgi_key(scratch.arena, d_state->ctrl_entity_store, &voff_dbgi_key);
CTRL_Entity *module = ctrl_entity_list_first(&modules);
process = ctrl_entity_ancestor_from_kind(module, CTRL_EntityKind_Process);
if(process != &ctrl_entity_nil)
{
vaddr = module->vaddr_range.min + lines.first->v.voff_range.min;
}
}
rd_cmd(RD_CmdKind_FindCodeLocation,
.file_path = lines.first->v.file_path,
.cursor = lines.first->v.pt,
.process = process->handle,
.module = module->handle,
.vaddr = module->vaddr_range.min + lines.first->v.voff_range.min);
}
}
// rjf: name resolved to a file path
if(name_resolved && file_path.size != 0)
{
rd_cmd(RD_CmdKind_FindCodeLocation, .file_path = file_path, .cursor = txt_pt(1, 1), .vaddr = 0);
}
}
}break;
//- rjf: snap-to-code-location
case RD_CmdKind_FindCodeLocation:
{
// NOTE(rjf): This command is where a lot of high-level flow things
// in the debugger come together. It's that codepath that runs any
// time a source code location is clicked in the UI, when a thread
// is selected, or when a thread causes a halt (hitting a breakpoint
// or exception or something). This is the logic that manages the
// flow of how views and panels are changed, opened, etc. when
// something like that happens.
//
// The gist of the intended rule for textual source code locations
// is the following:
//
// 1. Try to find a panel that's viewing the file (has it open in a
// tab, *and* that tab is selected).
// 2. Try to find a panel that has the file open in a tab, but does not
// currently have that tab selected.
// 3. Try to find a panel that has ANY source code open in any tab.
// 4. If the above things fail, try to pick the biggest panel, which
// is generally a decent rule (because it matches the popular
// debugger usage UI paradigm).
//
// The reason why this is a little more complicated than you might
// imagine is because this debugger frontend does not have any special
// "code panels" or anything like that, unlike e.g. VS or Remedy. All
// panels are identical in nature to allow for the user to organize
// the interface how they want, but in cases like this, we have to
// "fish out" the best option given the user's configuration. This
// can't be what the user wants in 100% of cases (this program cannot
// read anyone's mind), but it does provide expected behavior in
// common cases.
//
// The gist of the intended rule for finding disassembly locations is
// the following:
//
// 1. Try to find a panel that's viewing disassembly already - if so,
// snap it to the right address.
// 2. If there is no disassembly tab open, then we need to open one
// ONLY if source code was not found.
// 3. If we need to open a disassembly tab, we will first try to pick
// the biggest empty panel.
// 4. If there is no empty panel, then we will pick the biggest
// panel.
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
// rjf: grab things to find. path * point, process * address, etc.
String8 file_path = {0};
TxtPt point = {0};
CTRL_Entity *thread = &ctrl_entity_nil;
CTRL_Entity *process = &ctrl_entity_nil;
U64 vaddr = 0;
B32 require_disasm_snap = 0;
{
file_path = rd_mapped_from_file_path(scratch.arena, rd_regs()->file_path);
point = rd_regs()->cursor;
thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->thread);
process = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->process);
vaddr = rd_regs()->vaddr;
if(file_path.size == 0)
{
require_disasm_snap = 1;
}
}
// rjf: given a src code location, if no vaddr is specified,
// try to map the src coordinates to a vaddr via line info
if(vaddr == 0 && file_path.size != 0)
{
D_LineList lines = d_lines_from_file_path_line_num(scratch.arena, file_path, point.line);
for(D_LineNode *n = lines.first; n != 0; n = n->next)
{
CTRL_EntityList modules = ctrl_modules_from_dbgi_key(scratch.arena, d_state->ctrl_entity_store, &n->v.dbgi_key);
CTRL_Entity *module = ctrl_module_from_thread_candidates(d_state->ctrl_entity_store, thread, &modules);
vaddr = ctrl_vaddr_from_voff(module, n->v.voff_range.min);
break;
}
}
// rjf: first, try to find panel/view pair that already has the src file open
RD_PanelNode *panel_w_this_src_code = &rd_nil_panel_node;
RD_Cfg *view_w_this_src_code = &rd_nil_cfg;
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
if(panel->first != &rd_nil_panel_node)
{
continue;
}
for(RD_CfgNode *tab_n = panel->tabs.first; tab_n != 0; tab_n = tab_n->next)
{
RD_Cfg *tab = tab_n->v;
if(rd_cfg_is_project_filtered(tab)) { continue; }
String8 tab_expr = rd_expr_from_cfg(tab);
String8 tab_file_path = rd_file_path_from_eval_string(scratch.arena, tab_expr);
if((str8_match(tab->string, str8_lit("text"), 0) || str8_match(tab->string, str8_lit("pending"), 0)) &&
path_match_normalized(tab_file_path, file_path))
{
panel_w_this_src_code = panel;
view_w_this_src_code = tab;
if(tab == panel->selected_tab)
{
break;
}
}
}
}
// rjf: try to find panel/view pair that has any *auto* source code tab open
RD_PanelNode *panel_w_auto = &rd_nil_panel_node;
RD_Cfg *view_w_auto = &rd_nil_cfg;
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
if(panel->first != &rd_nil_panel_node)
{
continue;
}
for(RD_CfgNode *tab_n = panel->tabs.first; tab_n != 0; tab_n = tab_n->next)
{
RD_Cfg *tab = tab_n->v;
if(rd_cfg_is_project_filtered(tab)) { continue; }
RD_RegsScope(.tab = tab->id, .view = tab->id)
{
if(str8_match(tab->string, str8_lit("text"), 0) &&
rd_view_cfg_b32_from_string(str8_lit("auto")))
{
panel_w_auto = panel;
view_w_auto = tab;
}
}
}
}
// rjf: find a panel that already has *any* code open (prioritize largest)
RD_PanelNode *panel_w_any_src_code = &rd_nil_panel_node;
{
Rng2F32 root_rect = r2f32(v2f32(0, 0), v2f32(1000, 1000));
F32 best_panel_area = 0;
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
if(panel->first != &rd_nil_panel_node)
{
continue;
}
Rng2F32 panel_rect = rd_target_rect_from_panel_node(root_rect, panel_tree.root, panel);
Vec2F32 panel_rect_dim = dim_2f32(panel_rect);
F32 panel_area = panel_rect_dim.x*panel_rect_dim.y;
for(RD_CfgNode *tab_n = panel->tabs.first; tab_n != 0; tab_n = tab_n->next)
{
RD_Cfg *tab = tab_n->v;
if(rd_cfg_is_project_filtered(tab)) { continue; }
String8 view_expr = rd_expr_from_cfg(tab);
String8 file_path = rd_file_path_from_eval_string(scratch.arena, view_expr);
if(str8_match(tab->string, str8_lit("text"), 0) && file_path.size != 0 && panel_area > best_panel_area)
{
panel_w_any_src_code = panel;
best_panel_area = panel_area;
break;
}
}
}
}
// rjf: try to find panel/view pair that has disassembly open (prioritize largest)
RD_PanelNode *panel_w_disasm = &rd_nil_panel_node;
RD_Cfg *view_w_disasm = &rd_nil_cfg;
{
Rng2F32 root_rect = r2f32(v2f32(0, 0), v2f32(1000, 1000));
F32 best_panel_area = 0;
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
if(panel->first != &rd_nil_panel_node)
{
continue;
}
Rng2F32 panel_rect = rd_target_rect_from_panel_node(root_rect, panel_tree.root, panel);
Vec2F32 panel_rect_dim = dim_2f32(panel_rect);
F32 panel_area = panel_rect_dim.x*panel_rect_dim.y;
for(RD_CfgNode *tab_n = panel->tabs.first; tab_n != 0; tab_n = tab_n->next)
{
RD_Cfg *tab = tab_n->v;
if(rd_cfg_is_project_filtered(tab)) { continue; }
RD_RegsScope(.view = tab->id, .tab = tab->id)
{
B32 tab_is_selected = (tab == panel->selected_tab);
String8 expr_string = rd_expr_from_cfg(tab);
if(str8_match(tab->string, str8_lit("disasm"), 0) && expr_string.size == 0 && panel_area > best_panel_area)
{
panel_w_disasm = panel;
view_w_disasm = tab;
best_panel_area = panel_area;
if(tab_is_selected)
{
break;
}
}
}
}
}
}
// rjf: find the biggest panel
RD_PanelNode *biggest_panel = &rd_nil_panel_node;
{
Rng2F32 root_rect = r2f32(v2f32(0, 0), v2f32(1000, 1000));
F32 best_panel_area = 0;
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
if(panel->first != &rd_nil_panel_node)
{
continue;
}
Rng2F32 panel_rect = rd_target_rect_from_panel_node(root_rect, panel_tree.root, panel);
Vec2F32 panel_rect_dim = dim_2f32(panel_rect);
F32 panel_area = panel_rect_dim.x*panel_rect_dim.y;
if((best_panel_area == 0 || panel_area > best_panel_area))
{
best_panel_area = panel_area;
biggest_panel = panel;
}
}
}
// rjf: find the biggest empty panel
RD_PanelNode *biggest_empty_panel = &rd_nil_panel_node;
{
Rng2F32 root_rect = r2f32(v2f32(0, 0), v2f32(1000, 1000));
F32 best_panel_area = 0;
for(RD_PanelNode *panel = panel_tree.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panel_tree.root, panel).next)
{
if(panel->first != &rd_nil_panel_node)
{
continue;
}
Rng2F32 panel_rect = rd_target_rect_from_panel_node(root_rect, panel_tree.root, panel);
Vec2F32 panel_rect_dim = dim_2f32(panel_rect);
F32 panel_area = panel_rect_dim.x*panel_rect_dim.y;
B32 panel_is_empty = 1;
for(RD_CfgNode *n = panel->tabs.first; n != 0; n = n->next)
{
RD_Cfg *tab = n->v;
if(!rd_cfg_is_project_filtered(tab))
{
panel_is_empty = 0;
break;
}
}
if(panel_is_empty && (best_panel_area == 0 || panel_area > best_panel_area))
{
best_panel_area = panel_area;
biggest_empty_panel = panel;
}
}
}
// rjf: choose panel for source code
RD_PanelNode *src_code_dst_panel = &rd_nil_panel_node;
if(file_path.size != 0)
{
if(src_code_dst_panel == &rd_nil_panel_node) { src_code_dst_panel = panel_w_this_src_code; }
if(src_code_dst_panel == &rd_nil_panel_node) { src_code_dst_panel = panel_w_auto; }
if(src_code_dst_panel == &rd_nil_panel_node) { src_code_dst_panel = panel_w_any_src_code; }
if(src_code_dst_panel == &rd_nil_panel_node) { src_code_dst_panel = biggest_empty_panel; }
if(src_code_dst_panel == &rd_nil_panel_node) { src_code_dst_panel = biggest_panel; }
}
// rjf: choose panel for disassembly
RD_PanelNode *disasm_dst_panel = &rd_nil_panel_node;
if(vaddr != 0)
{
if(disasm_dst_panel == &rd_nil_panel_node) { disasm_dst_panel = panel_w_disasm; }
if(disasm_dst_panel == &rd_nil_panel_node) { disasm_dst_panel = biggest_empty_panel; }
if(disasm_dst_panel == &rd_nil_panel_node) { disasm_dst_panel = biggest_panel; }
}
// rjf: if disasm and source code match:
// if disasm preferred, cancel source
// if source preferred, cancel disasm
if(disasm_dst_panel == src_code_dst_panel)
{
if(rd_regs()->prefer_disasm)
{
src_code_dst_panel = &rd_nil_panel_node;
}
else
{
disasm_dst_panel = &rd_nil_panel_node;
}
}
// rjf: if disasm is not preferred, and we have no disassembly view
// open at all, cancel disasm, so that it doesn't open if the user
// doesn't want it.
if(!rd_regs()->prefer_disasm && panel_w_disasm == &rd_nil_panel_node && file_path.size != 0)
{
disasm_dst_panel = &rd_nil_panel_node;
}
// rjf: if disasm is not preferred, and we have no disassembly view
// *selected* at all, cancel disasm, so that it doesn't open if the user
// doesn't want it.
if(!rd_regs()->prefer_disasm && view_w_disasm != &rd_nil_cfg && rd_cfg_child_from_string(view_w_disasm, str8_lit("selected")) == &rd_nil_cfg &&
file_path.size != 0)
{
disasm_dst_panel = &rd_nil_panel_node;
}
// rjf: given the above, find source code location.
if(file_path.size != 0 && src_code_dst_panel != &rd_nil_panel_node)
{
RD_PanelNode *dst_panel = src_code_dst_panel;
// rjf: construct new view if needed
RD_Cfg *dst_tab = view_w_this_src_code;
if(dst_tab == &rd_nil_cfg && dst_panel == panel_w_auto && view_w_auto != &rd_nil_cfg)
{
dst_tab = view_w_auto;
RD_ViewState *vs = rd_view_state_from_cfg(dst_tab);
vs->last_frame_index_built = 0;
RD_Cfg *expr = rd_cfg_child_from_string_or_alloc(dst_tab, str8_lit("expression"));
rd_cfg_new_replace(expr, rd_eval_string_from_file_path(scratch.arena, file_path));
rd_cfg_new_replace(rd_cfg_child_from_string(dst_tab, str8_lit("cursor_line")), str8_lit("1"));
rd_cfg_new_replace(rd_cfg_child_from_string(dst_tab, str8_lit("cursor_column")), str8_lit("1"));
rd_cfg_new_replace(rd_cfg_child_from_string(dst_tab, str8_lit("mark_line")), str8_lit("1"));
rd_cfg_new_replace(rd_cfg_child_from_string(dst_tab, str8_lit("mark_column")), str8_lit("1"));
}
else if(dst_panel != &rd_nil_panel_node && dst_tab == &rd_nil_cfg)
{
dst_tab = rd_cfg_new(dst_panel->cfg, str8_lit("text"));
RD_Cfg *expr = rd_cfg_new(dst_tab, str8_lit("expression"));
rd_cfg_new(expr, rd_eval_string_from_file_path(scratch.arena, file_path));
RD_Cfg *auto_root = rd_cfg_new(dst_tab, str8_lit("auto"));
rd_cfg_new(auto_root, str8_lit("1"));
}
// rjf: determine if we need a contain or center
RD_CmdKind cursor_snap_kind = RD_CmdKind_CenterCursor;
if(dst_panel != &rd_nil_panel_node && dst_tab == view_w_this_src_code && dst_panel->selected_tab == dst_tab)
{
cursor_snap_kind = RD_CmdKind_ContainCursor;
}
// rjf: move cursor & snap-to-cursor
if(dst_panel != &rd_nil_panel_node) RD_RegsScope(.panel = dst_panel->cfg->id,
.view = dst_tab->id,
.tab = dst_tab->id)
{
rd_cmd(RD_CmdKind_FocusTab);
if(point.line != 0)
{
rd_cmd(RD_CmdKind_GoToLine, .cursor = point);
}
rd_cmd(cursor_snap_kind);
}
// rjf: record
rd_cmd(RD_CmdKind_RecordFileInProject, .file_path = file_path);
}
// rjf: given the above, find disassembly location.
if(process != &ctrl_entity_nil && vaddr != 0 && disasm_dst_panel != &rd_nil_panel_node)
{
RD_PanelNode *dst_panel = disasm_dst_panel;
// rjf: construct new tab if needed
RD_Cfg *dst_tab = view_w_disasm;
if(dst_panel != &rd_nil_panel_node && view_w_disasm == &rd_nil_cfg)
{
dst_tab = rd_cfg_new(dst_panel->cfg, str8_lit("disasm"));
}
// rjf: determine if we need a contain or center
RD_CmdKind cursor_snap_kind = RD_CmdKind_CenterCursor;
if(dst_tab == view_w_disasm && dst_panel->selected_tab == dst_tab)
{
cursor_snap_kind = RD_CmdKind_ContainCursor;
}
// rjf: move cursor & snap-to-cursor
if(dst_panel != &rd_nil_panel_node) RD_RegsScope(.panel = dst_panel->cfg->id,
.tab = dst_tab->id,
.view = dst_tab->id)
{
rd_cmd(RD_CmdKind_FocusTab);
rd_cmd(RD_CmdKind_GoToAddress, .process = process->handle, .vaddr = vaddr);
rd_cmd(cursor_snap_kind);
}
}
}break;
//- rjf: queries
case RD_CmdKind_PushQuery:
{
String8 cmd_name = rd_regs()->cmd_name;
RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name);
// rjf: floating queries -> set up window to build immediate-mode top-level query
RD_Cfg *view = &rd_nil_cfg;
B32 is_floating = (cmd_name.size == 0 || cmd_kind_info->query.flags & RD_QueryFlag_Floating);
if(is_floating)
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
if(ws != &rd_nil_window_state)
{
ws->query_is_active = 1;
arena_clear(ws->query_arena);
ws->query_regs = rd_regs_copy(ws->query_arena, rd_regs());
}
RD_Cfg *window_query = rd_immediate_cfg_from_keyf("window_query_%p", window);
rd_cfg_release_all_children(window_query);
view = rd_cfg_child_from_string_or_alloc(window_query, str8_lit("watch"));
RD_Cfg *expr = rd_cfg_child_from_string_or_alloc(view, str8_lit("expression"));
rd_cfg_new_replace(expr, rd_regs()->expr);
}
// rjf: non-floating -> embed in tab parameter
else
{
view = rd_cfg_from_id(rd_regs()->tab);
}
// rjf: determine if the target view is a lister (and thus already has a command)
B32 view_is_lister = (rd_cfg_child_from_string(view, str8_lit("lister")) != &rd_nil_cfg);
// rjf: target view is a lister -> do not do anything - cannot replace the command
if(!view_is_lister)
{
// rjf: unpack view's query info
RD_Cfg *query = rd_cfg_child_from_string_or_alloc(view, str8_lit("query"));
RD_Cfg *cmd = rd_cfg_child_from_string_or_alloc(query, str8_lit("cmd"));
RD_Cfg *input = rd_cfg_child_from_string_or_alloc(query, str8_lit("input"));
if(is_floating)
{
if(rd_regs()->do_implicit_root)
{
rd_cfg_release(rd_cfg_child_from_string(view, str8_lit("explicit_root")));
}
else
{
rd_cfg_child_from_string_or_alloc(view, str8_lit("explicit_root"));
}
if(!rd_regs()->do_lister)
{
rd_cfg_release(rd_cfg_child_from_string(view, str8_lit("lister")));
}
else
{
rd_cfg_child_from_string_or_alloc(view, str8_lit("lister"));
}
}
// rjf: choose initial input string
String8 initial_input = {0};
if(cmd_name.size != 0)
{
if(cmd_kind_info->query.slot == RD_RegSlot_FilePath)
{
RD_Cfg *user = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
RD_Cfg *current_path = rd_cfg_child_from_string(user, str8_lit("current_path"));
String8 current_path_string = current_path->first->string;
if(current_path_string.size == 0)
{
current_path_string = path_normalized_from_string(scratch.arena, os_get_current_path(scratch.arena));
}
else
{
current_path_string = path_normalized_from_string(scratch.arena, current_path_string);
}
initial_input = path_normalized_from_string(scratch.arena, current_path_string);
initial_input = push_str8f(scratch.arena, "%S/", initial_input);
}
else if(cmd_kind_info->query.flags & RD_QueryFlag_KeepOldInput)
{
initial_input = input->first->string;
}
}
// rjf: build query state
String8 current_query_cmd_name = cmd->first->string;
rd_cfg_new_replace(input, initial_input);
rd_cfg_new_replace(cmd, cmd_name);
RD_ViewState *vs = rd_view_state_from_cfg(view);
if(cmd_name.size != 0)
{
if(!vs->query_is_selected && cmd_kind_info->query.flags & RD_QueryFlag_SelectOldInput)
{
vs->query_cursor = txt_pt(1, 1+input->first->string.size);
vs->query_mark = txt_pt(1, 1);
}
else
{
vs->query_cursor = txt_pt(1, 1+input->first->string.size);
vs->query_mark = vs->query_cursor;
}
if(!str8_match(current_query_cmd_name, cmd_name, 0))
{
vs->query_is_selected = 1;
}
else
{
vs->query_is_selected ^= 1;
}
}
if(rd_regs()->do_lister)
{
vs->query_is_selected = 1;
}
}
}break;
case RD_CmdKind_CompleteQuery:
{
// rjf: unpack params
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
String8 cmd_name = rd_view_query_cmd();
// rjf: find out if this view is a lister
B32 is_lister = (rd_cfg_child_from_string(view, str8_lit("lister")) != &rd_nil_cfg);
// rjf: push command
if(cmd_name.size != 0) RD_RegsScope()
{
if(is_lister)
{
rd_regs()->view = ws->query_regs->view;
}
rd_push_cmd(cmd_name, rd_regs());
}
// rjf: complete query, either by closing the query popup, or closing the
// tab-embedded query edit
RD_CmdKindInfo *cmd_kind_info = rd_cmd_kind_info_from_string(cmd_name);
if(is_lister)
{
ws->query_is_active = 0;
}
else if(!(cmd_kind_info->query.flags & RD_QueryFlag_KeepOldInput))
{
RD_ViewState *vs = rd_view_state_from_cfg(view);
vs->query_is_selected = 0;
vs->query_string_size = 0;
}
}break;
case RD_CmdKind_CancelQuery:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
if(ws != &rd_nil_window_state)
{
ws->query_is_active = 0;
arena_clear(ws->query_arena);
ws->query_regs = 0;
}
}break;
case RD_CmdKind_UpdateQuery:
{
RD_Cfg *view = rd_cfg_from_id(rd_regs()->view);
RD_Cfg *query = rd_cfg_child_from_string_or_alloc(view, str8_lit("query"));
RD_Cfg *input = rd_cfg_child_from_string_or_alloc(query, str8_lit("input"));
rd_cfg_new_replace(input, rd_regs()->string);
RD_ViewState *vs = rd_view_state_from_cfg(view);
vs->query_cursor = vs->query_mark = txt_pt(1, rd_regs()->string.size+1);
vs->query_string_size = Min(sizeof(vs->query_buffer), rd_regs()->string.size);
MemoryCopy(vs->query_buffer, rd_regs()->string.str, vs->query_string_size);
}break;
//- rjf: developer commands
case RD_CmdKind_ToggleDevMenu:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
ws->dev_menu_is_open ^= 1;
}break;
//- rjf: general entity operations
case RD_CmdKind_SelectCfg:
case RD_CmdKind_SelectTarget:
{
RD_Cfg *cfg = rd_cfg_from_id(rd_regs()->cfg);
RD_CfgList all_of_the_same_kind = rd_cfg_top_level_list_from_string(scratch.arena, cfg->string);
B32 is_selected = !rd_disabled_from_cfg(cfg);
for(RD_CfgNode *n = all_of_the_same_kind.first; n != 0; n = n->next)
{
RD_Cfg *c = n->v;
rd_cfg_release(rd_cfg_child_from_string(c, str8_lit("enabled")));
}
RD_Cfg *enabled_root = rd_cfg_child_from_string_or_alloc(cfg, str8_lit("enabled"));
rd_cfg_new_replace(enabled_root, str8_lit("1"));
}break;
case RD_CmdKind_EnableCfg:
case RD_CmdKind_EnableBreakpoint:
case RD_CmdKind_EnableTarget:
{
RD_Cfg *cfg = rd_cfg_from_id(rd_regs()->cfg);
RD_Cfg *enabled_root = rd_cfg_child_from_string_or_alloc(cfg, str8_lit("enabled"));
rd_cfg_new_replacef(enabled_root, "1");
}break;
case RD_CmdKind_DisableCfg:
case RD_CmdKind_DisableBreakpoint:
case RD_CmdKind_DisableTarget:
case RD_CmdKind_DeselectCfg:
{
RD_Cfg *cfg = rd_cfg_from_id(rd_regs()->cfg);
RD_Cfg *enabled_root = rd_cfg_child_from_string_or_alloc(cfg, str8_lit("enabled"));
rd_cfg_new_replacef(enabled_root, "0");
}break;
case RD_CmdKind_RemoveCfg:
{
RD_Cfg *cfg = rd_cfg_from_id(rd_regs()->cfg);
rd_cfg_release(cfg);
}break;
case RD_CmdKind_NameCfg:
{
RD_Cfg *cfg = rd_cfg_from_id(rd_regs()->cfg);
if(rd_regs()->string.size != 0)
{
RD_Cfg *label = rd_cfg_child_from_string_or_alloc(cfg, str8_lit("label"));
rd_cfg_new(label, rd_regs()->string);
}
else
{
rd_cfg_release(rd_cfg_child_from_string(cfg, str8_lit("label")));
}
}break;
case RD_CmdKind_ConditionCfg:
{
RD_Cfg *cfg = rd_cfg_from_id(rd_regs()->cfg);
if(rd_regs()->string.size != 0)
{
RD_Cfg *cnd = rd_cfg_child_from_string_or_alloc(cfg, str8_lit("condition"));
rd_cfg_new(cnd, rd_regs()->string);
}
else
{
rd_cfg_release(rd_cfg_child_from_string(cfg, str8_lit("condition")));
}
}break;
case RD_CmdKind_DuplicateCfg:
{
RD_Cfg *src = rd_cfg_from_id(rd_regs()->cfg);
RD_Cfg *dst = rd_cfg_deep_copy(src);
rd_cfg_insert_child(src->parent, src, dst);
}break;
case RD_CmdKind_RelocateCfg:
{
RD_Cfg *cfg = rd_cfg_from_id(rd_regs()->cfg);
// rjf: release old location info
{
RD_Cfg *src_loc = rd_cfg_child_from_string(cfg, str8_lit("source_location"));
RD_Cfg *addr_loc = rd_cfg_child_from_string(cfg, str8_lit("address_location"));
rd_cfg_release(src_loc);
rd_cfg_release(addr_loc);
}
// rjf: attach new location info
{
String8 file_path = rd_regs()->file_path;
TxtPt pt = rd_regs()->cursor;
String8 expr_string = rd_regs()->expr;
U64 vaddr = rd_regs()->vaddr;
if(expr_string.size == 0 && vaddr != 0)
{
expr_string = push_str8f(scratch.arena, "0x%I64x", vaddr);
}
if(file_path.size != 0 && pt.line != 0)
{
RD_Cfg *src_loc = rd_cfg_new(cfg, str8_lit("source_location"));
rd_cfg_newf(src_loc, "%S:%I64d:%I64d", file_path, pt.line, pt.column);
}
else if(expr_string.size != 0)
{
RD_Cfg *vaddr_loc = rd_cfg_new(cfg, str8_lit("address_location"));
rd_cfg_new(vaddr_loc, expr_string);
}
}
}break;
//- rjf: breakpoints
case RD_CmdKind_AddBreakpoint:
case RD_CmdKind_ToggleBreakpoint:
{
String8 file_path = rd_regs()->file_path;
TxtPt pt = rd_regs()->cursor;
U64 vaddr = rd_regs()->vaddr;
String8 expr = rd_regs()->expr;
if(expr.size == 0 && vaddr != 0)
{
expr = push_str8f(scratch.arena, "0x%I64x", vaddr);
}
if(file_path.size != 0 || expr.size != 0)
{
B32 already_exists = 0;
RD_CfgList bps = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("breakpoint"));
for(RD_CfgNode *n = bps.first; n != 0; n = n->next)
{
RD_Cfg *bp = n->v;
RD_Cfg *cnd = rd_cfg_child_from_string(bp, str8_lit("condition"));
RD_Location loc = rd_location_from_cfg(bp);
B32 loc_matches_file_pt = (file_path.size != 0 && path_match_normalized(loc.file_path, file_path) && loc.pt.line == pt.line);
B32 loc_matches_expr = (expr.size != 0 && str8_match(expr, loc.expr, 0));
if((loc_matches_file_pt || loc_matches_expr) && cnd->first->string.size == 0)
{
if(kind == RD_CmdKind_ToggleBreakpoint)
{
rd_cfg_release(bp);
}
already_exists = 1;
break;
}
}
if(!already_exists)
{
RD_Cfg *project = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("project"));
RD_Cfg *bp = rd_cfg_new(project, str8_lit("breakpoint"));
rd_cmd(RD_CmdKind_RelocateCfg, .cfg = bp->id);
if(rd_regs()->do_lister)
{
rd_cmd(RD_CmdKind_PushQuery, .expr = push_str8f(scratch.arena, "query:config.$%I64x", bp->id), .do_lister = 0);
}
}
}
}break;
case RD_CmdKind_AddAddressBreakpoint:
{
rd_cmd(RD_CmdKind_AddBreakpoint, .file_path = str8_zero(), .do_lister = 1);
}break;
//- rjf: watch pins
case RD_CmdKind_AddWatchPin:
case RD_CmdKind_ToggleWatchPin:
{
String8 file_path = rd_regs()->file_path;
TxtPt pt = rd_regs()->cursor;
String8 expr_string = rd_regs()->expr;
U64 vaddr = rd_regs()->vaddr;
B32 removed_already_existing = 0;
if(kind == RD_CmdKind_ToggleWatchPin)
{
RD_CfgList wps = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("watch_pin"));
for(RD_CfgNode *n = wps.first; n != 0; n = n->next)
{
RD_Cfg *wp = n->v;
RD_Cfg *expr = rd_cfg_child_from_string(wp, str8_lit("expression"));
RD_Location loc = rd_location_from_cfg(wp);
B32 loc_matches_file_pt = (file_path.size != 0 && path_match_normalized(loc.file_path, file_path) && loc.pt.line == pt.line);
B32 loc_matches_expr = (expr_string.size != 0 && str8_match(expr_string, loc.expr, 0));
if((loc_matches_file_pt || loc_matches_expr) && str8_match(expr->first->string, expr_string, 0))
{
rd_cfg_release(wp);
removed_already_existing = 1;
}
}
}
if(!removed_already_existing)
{
RD_Cfg *project = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("project"));
RD_Cfg *wp = rd_cfg_new(project, str8_lit("watch_pin"));
RD_Cfg *expr = rd_cfg_new(wp, str8_lit("expression"));
rd_cfg_new(expr, expr_string);
rd_cmd(RD_CmdKind_RelocateCfg, .cfg = wp->id, .expr = str8_zero());
}
}break;
//- rjf: auto view rules
case RD_CmdKind_AddAutoViewRule:
{
RD_Cfg *project = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("project"));
rd_cfg_new(project, str8_lit("auto_view_rule"));
}break;
//- rjf: file path maps
case RD_CmdKind_AddFilePathMap:
{
RD_Cfg *project = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
rd_cfg_new(project, str8_lit("file_path_map"));
}break;
//- rjf: themes
case RD_CmdKind_OpenTheme:
{
RD_Cfg *user = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("user"));
RD_Cfg *theme_file = rd_cfg_child_from_string_or_alloc(user, str8_lit("theme_file"));
rd_cfg_new_replace(theme_file, rd_regs()->file_path);
}break;
case RD_CmdKind_AddThemeColor:
{
RD_Cfg *parent = rd_cfg_from_id(rd_regs()->cfg);
RD_Cfg *color = rd_cfg_new(parent, str8_lit("theme_color"));
rd_cfg_new(color, str8_lit("tags"));
RD_Cfg *value = rd_cfg_new(color, str8_lit("value"));
rd_cfg_new(value, str8_lit("0xffffffff"));
}break;
case RD_CmdKind_ForkLoadedThemeColors:
{
RD_Cfg *parent = rd_cfg_from_id(rd_regs()->cfg);
RD_CfgList colors = rd_cfg_child_list_from_string(scratch.arena, parent, str8_lit("theme_color"));
for(RD_CfgNode *n = colors.first; n != 0; n = n->next)
{
rd_cfg_release(n->v);
}
String8 color_preset = rd_setting_from_name(str8_lit("theme_preset"));
String8 color_file = rd_setting_from_name(str8_lit("theme_file"));
RD_ThemePreset preset = RD_ThemePreset_DefaultDark;
// TODO(rjf): map preset via string
MD_Node *theme_tree = rd_state->theme_preset_trees[preset];
for(MD_Node *n = theme_tree; !md_node_is_nil(n); n = md_node_rec_depth_first_pre(n, theme_tree).next)
{
if(str8_match(n->string, str8_lit("theme_color"), 0))
{
RD_Cfg *color = rd_cfg_new(parent, str8_lit("theme_color"));
RD_Cfg *tags = rd_cfg_new(color, str8_lit("tags"));
RD_Cfg *value = rd_cfg_new(color, str8_lit("value"));
rd_cfg_new(tags, md_child_from_string(n, str8_lit("tags"), 0)->first->string);
rd_cfg_new(value, md_child_from_string(n, str8_lit("value"), 0)->first->string);
}
}
}break;
//- rjf: watches
case RD_CmdKind_ToggleWatchExpression:
if(rd_regs()->string.size != 0)
{
// rjf: pick a watch tab from all the windows to toggle this expression within
RD_Cfg *watch_tab = &rd_nil_cfg;
{
B32 watch_tab_has_no_label = 0;
B32 watch_tab_matches_src_window = 0;
RD_CfgList windows = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("window"));
for(RD_CfgNode *n = windows.first; n != 0; n = n->next)
{
RD_Cfg *window = n->v;
RD_PanelTree panels = rd_panel_tree_from_cfg(scratch.arena, window);
for(RD_PanelNode *panel = panels.root;
panel != &rd_nil_panel_node;
panel = rd_panel_node_rec__depth_first_pre(panels.root, panel).next)
{
for(RD_CfgNode *tab_n = panel->tabs.first; tab_n != 0; tab_n = tab_n->next)
{
RD_Cfg *tab = tab_n->v;
RD_Cfg *label = rd_cfg_child_from_string(tab, str8_lit("label"));
if(str8_match(tab->string, str8_lit("watch"), 0) &&
rd_expr_from_cfg(tab).size == 0)
{
B32 tab_has_no_label = (label->first->string.size == 0);
B32 tab_matches_src_window = (window->id == rd_regs()->window);
if(tab_has_no_label > watch_tab_has_no_label ||
tab_matches_src_window > watch_tab_matches_src_window ||
watch_tab == &rd_nil_cfg)
{
watch_tab = tab;
if(tab_has_no_label && tab_matches_src_window)
{
goto end_watch_tab_search;
}
}
}
}
}
}
end_watch_tab_search:;
}
// rjf: find the existing watch in the selected tab, if it exists
RD_Cfg *existing_watch = &rd_nil_cfg;
for(RD_Cfg *child = watch_tab->first; child != &rd_nil_cfg; child = child->next)
{
if(str8_match(child->string, str8_lit("watch"), 0) && str8_match(child->first->string, rd_regs()->string, 0))
{
existing_watch = child;
break;
}
}
// rjf: if this watch exists -> delete it
if(existing_watch != &rd_nil_cfg)
{
rd_cfg_release(existing_watch);
}
// rjf: otherwise, create it
else if(watch_tab != &rd_nil_cfg)
{
RD_Cfg *watch = rd_cfg_new(watch_tab, str8_lit("watch"));
rd_cfg_new(watch, rd_regs()->string);
}
}break;
//- rjf: cursor operations
case RD_CmdKind_GoToNameAtCursor:
case RD_CmdKind_ToggleWatchExpressionAtCursor:
{
HS_Scope *hs_scope = hs_scope_open();
TXT_Scope *txt_scope = txt_scope_open();
RD_Regs *regs = rd_regs();
U128 text_key = regs->text_key;
TXT_LangKind lang_kind = regs->lang_kind;
TxtRng range = txt_rng(regs->cursor, regs->mark);
U128 hash = {0};
TXT_TextInfo info = txt_text_info_from_key_lang(txt_scope, text_key, lang_kind, &hash);
String8 data = hs_data_from_hash(hs_scope, hash);
Rng1U64 expr_off_range = {0};
if(range.min.column != range.max.column)
{
expr_off_range = r1u64(txt_off_from_info_pt(&info, range.min), txt_off_from_info_pt(&info, range.max));
}
else
{
expr_off_range = txt_expr_off_range_from_info_data_pt(&info, data, range.min);
}
String8 expr = str8_substr(data, expr_off_range);
rd_cmd((kind == RD_CmdKind_GoToNameAtCursor ? RD_CmdKind_GoToName :
kind == RD_CmdKind_ToggleWatchExpressionAtCursor ? RD_CmdKind_ToggleWatchExpression :
RD_CmdKind_GoToName),
.string = expr);
txt_scope_close(txt_scope);
hs_scope_close(hs_scope);
}break;
case RD_CmdKind_SetNextStatement:
{
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->thread);
String8 file_path = rd_regs()->file_path;
U64 new_rip_vaddr = rd_regs()->vaddr_range.min;
if(file_path.size != 0)
{
D_LineList *lines = &rd_regs()->lines;
for(D_LineNode *n = lines->first; n != 0; n = n->next)
{
CTRL_EntityList modules = ctrl_modules_from_dbgi_key(scratch.arena, d_state->ctrl_entity_store, &n->v.dbgi_key);
CTRL_Entity *module = ctrl_module_from_thread_candidates(d_state->ctrl_entity_store, thread, &modules);
if(module != &ctrl_entity_nil)
{
new_rip_vaddr = ctrl_vaddr_from_voff(module, n->v.voff_range.min);
break;
}
}
}
rd_cmd(RD_CmdKind_SetThreadIP, .vaddr = new_rip_vaddr);
}break;
//- rjf: targets
case RD_CmdKind_AddTarget:
{
String8 file_path = rd_regs()->file_path;
RD_Cfg *project = rd_cfg_child_from_string(rd_state->root_cfg, str8_lit("project"));
RD_Cfg *target = rd_cfg_new(project, str8_lit("target"));
RD_Cfg *exe = rd_cfg_new(target, str8_lit("executable"));
rd_cfg_new(exe, file_path);
String8 working_directory = str8_chop_last_slash(file_path);
if(working_directory.size != 0)
{
RD_Cfg *wdir = rd_cfg_new(target, str8_lit("working_directory"));
rd_cfg_newf(wdir, "%S/", working_directory);
}
rd_cmd(RD_CmdKind_SelectTarget, .cfg = target->id);
rd_cmd(RD_CmdKind_PushQuery, .expr = push_str8f(scratch.arena, "query:config.$%I64x", target->id));
}break;
//- rjf: jit-debugger registration
case RD_CmdKind_RegisterAsJITDebugger:
{
#if OS_WINDOWS
char filename_cstr[MAX_PATH] = {0};
GetModuleFileName(0, filename_cstr, sizeof(filename_cstr));
String8 debugger_binary_path = str8_cstring(filename_cstr);
String8 name8 = str8_lit("Debugger");
String8 data8 = push_str8f(scratch.arena, "%S --jit_pid:%%ld --jit_code:%%ld --jit_addr:0x%%p", debugger_binary_path);
String16 name16 = str16_from_8(scratch.arena, name8);
String16 data16 = str16_from_8(scratch.arena, data8);
B32 likely_not_in_admin_mode = 0;
{
HKEY reg_key = 0;
LSTATUS status = 0;
status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, &reg_key);
likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED);
status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2);
RegCloseKey(reg_key);
}
{
HKEY reg_key = 0;
LSTATUS status = 0;
status = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug\\", 0, KEY_SET_VALUE, &reg_key);
likely_not_in_admin_mode = (status == ERROR_ACCESS_DENIED);
status = RegSetValueExW(reg_key, (LPCWSTR)name16.str, 0, REG_SZ, (BYTE *)data16.str, data16.size*sizeof(U16)+2);
RegCloseKey(reg_key);
}
if(likely_not_in_admin_mode)
{
log_user_error(str8_lit("Could not register as the just-in-time debugger, access was denied; try running the debugger as administrator."));
}
#else
log_user_error(str8_lit("Registering as the just-in-time debugger is currently not supported on this system."));
#endif
}break;
//- rjf: developer commands
case RD_CmdKind_LogMarker:
{
log_infof("\"#MARKER\"");
}break;
//- rjf: os event passthrough
case RD_CmdKind_OSEvent:
{
OS_Event *os_event = rd_regs()->os_event;
RD_WindowState *ws = rd_window_state_from_os_handle(os_event->window);
if(os_event != 0 && ws != &rd_nil_window_state)
{
UI_Event ui_event = zero_struct;
UI_EventKind kind = UI_EventKind_Null;
{
switch(os_event->kind)
{
default:{}break;
case OS_EventKind_Press: {kind = UI_EventKind_Press;}break;
case OS_EventKind_Release: {kind = UI_EventKind_Release;}break;
case OS_EventKind_MouseMove: {kind = UI_EventKind_MouseMove;}break;
case OS_EventKind_Text: {kind = UI_EventKind_Text;}break;
case OS_EventKind_Scroll: {kind = UI_EventKind_Scroll;}break;
case OS_EventKind_FileDrop: {kind = UI_EventKind_FileDrop;}break;
}
}
ui_event.kind = kind;
ui_event.key = os_event->key;
ui_event.modifiers = os_event->modifiers;
ui_event.string = os_event->character ? str8_from_32(ui_build_arena(), str32(&os_event->character, 1)) : str8_zero();
ui_event.paths = str8_list_copy(ui_build_arena(), &os_event->strings);
ui_event.pos = os_event->pos;
ui_event.delta_2f32 = os_event->delta;
ui_event.timestamp_us = os_event->timestamp_us;
ui_event_list_push(scratch.arena, &ws->ui_events, &ui_event);
}
}break;
//- rjf: debug control context management operations
case RD_CmdKind_SelectEntity:
{
rd_cmd(RD_CmdKind_SelectThread, .thread = rd_regs()->ctrl_entity);
}break;
case RD_CmdKind_SelectThread:
{
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_regs()->thread);
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process);
CTRL_Entity *module = ctrl_module_from_process_vaddr(process, ctrl_query_cached_rip_from_thread(d_state->ctrl_entity_store, thread->handle));
CTRL_Entity *machine = ctrl_entity_ancestor_from_kind(process, CTRL_EntityKind_Machine);
rd_state->base_regs.v.unwind_count = 0;
rd_state->base_regs.v.inline_depth = 0;
rd_state->base_regs.v.thread = thread->handle;
rd_state->base_regs.v.module = module->handle;
rd_state->base_regs.v.process = process->handle;
rd_state->base_regs.v.machine = machine->handle;
rd_cmd(RD_CmdKind_FindThread, .thread = thread->handle, .unwind_count = 0, .inline_depth = 0);
}break;
case RD_CmdKind_SelectUnwind:
{
DI_Scope *di_scope = di_scope_open();
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_base_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);
CTRL_CallStack call_stack = ctrl_call_stack_from_unwind(scratch.arena, di_scope, process, &base_unwind);
CTRL_CallStackFrame *frame = ctrl_call_stack_frame_from_unwind_and_inline_depth(&call_stack, rd_regs()->unwind_count, rd_regs()->inline_depth);
if(frame == 0)
{
frame = ctrl_call_stack_frame_from_unwind_and_inline_depth(&call_stack, rd_regs()->unwind_count, 0);
}
if(frame)
{
rd_state->base_regs.v.unwind_count = rd_regs()->unwind_count;
rd_state->base_regs.v.inline_depth = rd_regs()->inline_depth;
}
rd_cmd(RD_CmdKind_FindThread, .thread = thread->handle, .unwind_count = rd_state->base_regs.v.unwind_count, .inline_depth = rd_state->base_regs.v.inline_depth);
di_scope_close(di_scope);
}break;
case RD_CmdKind_UpOneFrame:
case RD_CmdKind_DownOneFrame:
{
DI_Scope *di_scope = di_scope_open();
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_base_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);
CTRL_CallStack call_stack = ctrl_call_stack_from_unwind(scratch.arena, di_scope, process, &base_unwind);
CTRL_CallStackFrame *current_frame = ctrl_call_stack_frame_from_unwind_and_inline_depth(&call_stack, rd_regs()->unwind_count, rd_regs()->inline_depth);
CTRL_CallStackFrame *next_frame = current_frame;
if(current_frame != 0) switch(kind)
{
default:{}break;
case RD_CmdKind_UpOneFrame:
if(current_frame > call_stack.frames)
{
next_frame = current_frame-1;
}break;
case RD_CmdKind_DownOneFrame:
if(current_frame+1 < call_stack.frames + call_stack.count)
{
next_frame = current_frame+1;
}break;
}
if(next_frame != 0)
{
CTRL_CallStackFrame *next_base_frame = next_frame + next_frame->inline_depth;
rd_cmd(RD_CmdKind_SelectUnwind,
.unwind_count = next_frame->unwind_count,
.inline_depth = next_frame->inline_depth);
}
di_scope_close(di_scope);
}break;
//- rjf: meta controls
case RD_CmdKind_Edit:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Press;
evt.slot = UI_EventActionSlot_Edit;
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_Accept:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Press;
evt.slot = UI_EventActionSlot_Accept;
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_Cancel:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Press;
evt.slot = UI_EventActionSlot_Cancel;
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
//- rjf: directional movement & text controls
//
// NOTE(rjf): These all get funneled into a separate intermediate that
// can be used by the UI build phase for navigation and stuff, as well
// as builder codepaths that want to use these controls to modify text.
//
case RD_CmdKind_MoveLeft:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_PickSelectSide|UI_EventFlag_ZeroDeltaOnSelect|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveRight:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_PickSelectSide|UI_EventFlag_ZeroDeltaOnSelect|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUp:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDown:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveLeftSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveRightSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUpSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDownSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveLeftChunk:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveRightChunk:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUpChunk:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDownChunk:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUpPage:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.delta_unit = UI_EventDeltaUnit_Page;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDownPage:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.delta_unit = UI_EventDeltaUnit_Page;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUpWhole:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.delta_unit = UI_EventDeltaUnit_Whole;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDownWhole:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.delta_unit = UI_EventDeltaUnit_Whole;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveLeftChunkSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveRightChunkSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUpChunkSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDownChunkSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark|UI_EventFlag_ExplicitDirectional;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUpPageSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark;
evt.delta_unit = UI_EventDeltaUnit_Page;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDownPageSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark;
evt.delta_unit = UI_EventDeltaUnit_Page;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUpWholeSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark;
evt.delta_unit = UI_EventDeltaUnit_Whole;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDownWholeSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark;
evt.delta_unit = UI_EventDeltaUnit_Whole;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveUpReorder:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_Reorder;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveDownReorder:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_Reorder;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveHome:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.delta_unit = UI_EventDeltaUnit_Line;
evt.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveEnd:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.delta_unit = UI_EventDeltaUnit_Line;
evt.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveHomeSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark;
evt.delta_unit = UI_EventDeltaUnit_Line;
evt.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MoveEndSelect:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_KeepMark;
evt.delta_unit = UI_EventDeltaUnit_Line;
evt.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_SelectAll:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt1 = zero_struct;
evt1.kind = UI_EventKind_Navigate;
evt1.delta_unit = UI_EventDeltaUnit_Whole;
evt1.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt1);
UI_Event evt2 = zero_struct;
evt2.kind = UI_EventKind_Navigate;
evt2.flags = UI_EventFlag_KeepMark;
evt2.delta_unit = UI_EventDeltaUnit_Whole;
evt2.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt2);
}break;
case RD_CmdKind_DeleteSingle:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Edit;
evt.flags = UI_EventFlag_Delete;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_DeleteChunk:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Edit;
evt.flags = UI_EventFlag_Delete;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(+1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_BackspaceSingle:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Edit;
evt.flags = UI_EventFlag_Delete|UI_EventFlag_ZeroDeltaOnSelect;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_BackspaceChunk:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Edit;
evt.flags = UI_EventFlag_Delete;
evt.delta_unit = UI_EventDeltaUnit_Word;
evt.delta_2s32 = v2s32(-1, +0);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_Copy:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Edit;
evt.flags = UI_EventFlag_Copy|UI_EventFlag_KeepMark;
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_Cut:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Edit;
evt.flags = UI_EventFlag_Copy|UI_EventFlag_Delete;
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_Paste:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Text;
evt.string = os_get_clipboard_text(scratch.arena);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_InsertText:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Text;
evt.string = rd_regs()->string;
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
//- rjf: secondary navigation
case RD_CmdKind_MoveNext:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_Secondary;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+0, +1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
case RD_CmdKind_MovePrev:
{
RD_Cfg *window = rd_cfg_from_id(rd_regs()->window);
RD_WindowState *ws = rd_window_state_from_cfg(window);
UI_Event evt = zero_struct;
evt.kind = UI_EventKind_Navigate;
evt.flags = UI_EventFlag_Secondary;
evt.delta_unit = UI_EventDeltaUnit_Char;
evt.delta_2s32 = v2s32(+0, -1);
ui_event_list_push(scratch.arena, &ws->ui_events, &evt);
}break;
}
}
}
////////////////////////////
//- rjf: gather targets
//
D_TargetArray targets = {0};
ProfScope("gather targets")
{
RD_CfgList target_cfgs = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("target"));
targets.count = target_cfgs.count;
targets.v = push_array(scratch.arena, D_Target, targets.count);
U64 idx = 0;
for(RD_CfgNode *n = target_cfgs.first; n != 0; n = n->next)
{
RD_Cfg *src = n->v;
B32 src_is_disabled = rd_disabled_from_cfg(src);
if(src_is_disabled)
{
targets.count -= 1;
continue;
}
targets.v[idx] = rd_target_from_cfg(scratch.arena, src);
idx += 1;
}
}
////////////////////////////
//- rjf: gather breakpoints & meta-evals (for the engine, meta-evals can only be referenced by breakpoints)
//
D_BreakpointArray breakpoints = {0};
ProfScope("gather breakpoints & meta-evals")
{
RD_CfgList bp_cfgs = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("breakpoint"));
breakpoints.count = bp_cfgs.count;
breakpoints.v = push_array(scratch.arena, D_Breakpoint, breakpoints.count);
U64 idx = 0;
for(RD_CfgNode *n = bp_cfgs.first; n != 0; n = n->next)
{
RD_Cfg *src_bp = n->v;
B32 src_bp_is_disabled = rd_disabled_from_cfg(src_bp);
if(src_bp_is_disabled)
{
breakpoints.count -= 1;
continue;
}
RD_Location src_bp_loc = rd_location_from_cfg(src_bp);
String8 src_bp_cnd = rd_cfg_child_from_string(src_bp, str8_lit("condition"))->first->string;
//- rjf: walk conditional breakpoint expression tree - for each leaf identifier,
// determine if it resolves to a meta-evaluation. if it does, compute the meta
// evaluation data & store.
//
// for many conditions, we can statically-disqualify the breakpoint, if it only
// references frontend-controlled meta-evaluation state. in such cases, we just
// want to never send the user breakpoint to the control thread, since it cannot
// be hit anyways. so in this pass, we can also gather information about whether
// or not it is 'static', w.r.t. the control thread.
//
B32 is_static_for_ctrl_thread = 0;
if(src_bp_cnd.size != 0)
{
typedef struct ExprWalkTask ExprWalkTask;
struct ExprWalkTask
{
ExprWalkTask *next;
E_Expr *expr;
};
E_Expr *expr = e_parse_from_string(src_bp_cnd).expr;
ExprWalkTask start_task = {0, expr};
ExprWalkTask *first_task = &start_task;
for(ExprWalkTask *t = first_task; t != 0; t = t->next)
{
if(t->expr->kind == E_ExprKind_LeafIdentifier)
{
E_Expr *macro_expr = e_string2expr_map_lookup(e_ir_ctx->macro_map, t->expr->string);
E_Eval eval = e_eval_from_string(t->expr->string);
switch(eval.space.kind)
{
default:{is_static_for_ctrl_thread = 0;}break;
case E_SpaceKind_Null:
case RD_EvalSpaceKind_MetaCfg:
{
is_static_for_ctrl_thread = 1;
}break;
}
}
for(E_Expr *child = t->expr->first; child != &e_expr_nil; child = child->next)
{
ExprWalkTask *task = push_array(scratch.arena, ExprWalkTask, 1);
task->expr = child;
task->next = t->next;
t->next = task;
}
}
}
//- rjf: if this breakpoint is conditioned & static for the control thread, then
// we can evaluate this condition early, and decide whether or not to send this
// breakpoint.
B32 is_statically_disqualified = 0;
String8 non_ctrl_thread_static_condition = src_bp_cnd;
if(is_static_for_ctrl_thread)
{
E_Eval eval = e_eval_from_string(src_bp_cnd);
E_Eval value_eval = e_value_eval_from_eval(eval);
if(value_eval.value.u64 == 0)
{
is_statically_disqualified = 1;
}
MemoryZeroStruct(&non_ctrl_thread_static_condition);
}
//- rjf: statically disqualified? -> skip
if(is_statically_disqualified)
{
breakpoints.count -= 1;
continue;
}
//- rjf: compute breakpoint flags
D_BreakpointFlags flags = 0;
if(str8_match(rd_cfg_child_from_string(src_bp, str8_lit("break_on_write"))->first->string, str8_lit("1"), 0))
{
flags |= D_BreakpointFlag_BreakOnWrite;
}
if(str8_match(rd_cfg_child_from_string(src_bp, str8_lit("break_on_read"))->first->string, str8_lit("1"), 0))
{
flags |= D_BreakpointFlag_BreakOnRead;
}
if(str8_match(rd_cfg_child_from_string(src_bp, str8_lit("break_on_execute"))->first->string, str8_lit("1"), 0))
{
flags |= D_BreakpointFlag_BreakOnExecute;
}
//- rjf: compute address range size
U64 addr_range_size = 0;
{
RD_Cfg *address_range_size_cfg = rd_cfg_child_from_string(src_bp, str8_lit("address_range_size"));
try_u64_from_str8_c_rules(address_range_size_cfg->first->string, &addr_range_size);
}
//- rjf: fill breakpoint
D_Breakpoint *dst_bp = &breakpoints.v[idx];
dst_bp->flags = flags;
dst_bp->id = src_bp->id;
dst_bp->file_path = src_bp_loc.file_path;
dst_bp->pt = src_bp_loc.pt;
dst_bp->vaddr_expr = src_bp_loc.expr;
dst_bp->condition = non_ctrl_thread_static_condition;
dst_bp->size = addr_range_size;
idx += 1;
}
}
////////////////////////////
//- rjf: gather path maps
//
D_PathMapArray path_maps = {0};
{
RD_CfgList maps = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("file_path_map"));
path_maps.count = maps.count;
path_maps.v = push_array(scratch.arena, D_PathMap, path_maps.count);
U64 idx = 0;
for(RD_CfgNode *n = maps.first; n != 0; n = n->next, idx += 1)
{
RD_Cfg *map = n->v;
path_maps.v[idx].src = rd_cfg_child_from_string(map, str8_lit("source"))->first->string;
path_maps.v[idx].dst = rd_cfg_child_from_string(map, str8_lit("dest"))->first->string;
}
}
////////////////////////////
//- rjf: gather exception code filters
//
U64 exception_code_filters[(CTRL_ExceptionCodeKind_COUNT+63)/64] = {0};
{
for EachNonZeroEnumVal(CTRL_ExceptionCodeKind, k)
{
String8 name = ctrl_exception_code_kind_lowercase_code_string_table[k];
B32 setting = rd_setting_b32_from_name(name);
if(setting)
{
exception_code_filters[k/64] |= 1ull<<(k%64);
}
}
}
////////////////////////////
//- rjf: tick debug engine
//
U64 cmd_count_pre_tick = rd_state->cmds[0].count;
D_EventList engine_events = d_tick(scratch.arena, &targets, &breakpoints, &path_maps, exception_code_filters);
////////////////////////////
//- rjf: process debug engine events
//
for(D_EventNode *n = engine_events.first; n != 0; n = n->next)
{
D_Event *evt = &n->v;
switch(evt->kind)
{
default:{}break;
case D_EventKind_ProcessEnd:
if(rd_state->quit_after_success)
{
CTRL_EntityArray processes = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Process);
if(evt->code == 0 && processes.count == 0)
{
rd_cmd(RD_CmdKind_Exit);
}
else if(evt->code != 0)
{
rd_state->quit_after_success = 0;
}
}break;
case D_EventKind_Stop:
{
B32 need_refocus = (evt->cause != D_EventCause_SoftHalt);
CTRL_Entity *thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, evt->thread);
U64 vaddr = evt->vaddr;
CTRL_Entity *process = ctrl_entity_ancestor_from_kind(thread, CTRL_EntityKind_Process);
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);
U64 test_cached_vaddr = ctrl_query_cached_rip_from_thread(d_state->ctrl_entity_store, thread->handle);
// rjf: valid stop thread? -> select & snap
if(need_refocus && thread != &ctrl_entity_nil && evt->cause != D_EventCause_Halt)
{
rd_cmd(RD_CmdKind_SelectThread, .thread = thread->handle);
}
// rjf: no stop-causing thread, but have selected thread? -> snap to selected
CTRL_Entity *selected_thread = ctrl_entity_from_handle(d_state->ctrl_entity_store, rd_base_regs()->thread);
if(need_refocus && (evt->cause == D_EventCause_Halt || thread == &ctrl_entity_nil) && selected_thread != &ctrl_entity_nil)
{
rd_cmd(RD_CmdKind_SelectThread, .thread = selected_thread->handle);
}
// rjf: no stop-causing thread, but don't have selected thread? -> snap to first available thread
if(need_refocus && thread == &ctrl_entity_nil && selected_thread == &ctrl_entity_nil)
{
CTRL_EntityArray threads = ctrl_entity_array_from_kind(d_state->ctrl_entity_store, CTRL_EntityKind_Thread);
CTRL_Entity *first_available_thread = ctrl_entity_array_first(&threads);
rd_cmd(RD_CmdKind_SelectThread, .thread = first_available_thread->handle);
}
// rjf: increment breakpoint hit counts
if(evt->cause == D_EventCause_UserBreakpoint)
{
RD_Cfg *bp = rd_cfg_from_id(evt->id);
if(bp != &rd_nil_cfg)
{
RD_Cfg *hit_count_root = rd_cfg_child_from_string_or_alloc(bp, str8_lit("hit_count"));
U64 hit_count = 0;
try_u64_from_str8_c_rules(hit_count_root->first->string, &hit_count);
hit_count += 1;
rd_cfg_new_replacef(hit_count_root, "%I64u", hit_count);
}
}
// rjf: focus window if none focused, and if we have a thread to snap to
if(need_refocus && (selected_thread != &ctrl_entity_nil || thread != &ctrl_entity_nil))
{
B32 any_window_is_focused = 0;
for(RD_WindowState *ws = rd_state->first_window_state; ws != &rd_nil_window_state; ws = ws->order_next)
{
if(os_window_is_focused(ws->os))
{
any_window_is_focused = 1;
break;
}
}
if(!any_window_is_focused)
{
RD_Cfg *last_focused_window = rd_cfg_from_id(rd_state->last_focused_window);
RD_WindowState *ws = rd_window_state_from_cfg(last_focused_window);
if(ws == &rd_nil_window_state)
{
ws = rd_state->first_window_state;
}
if(ws != &rd_nil_window_state)
{
os_window_set_minimized(ws->os, 0);
os_window_bring_to_front(ws->os);
os_window_focus(ws->os);
}
}
}
}break;
}
}
////////////////////////////
//- rjf: early-out if no new commands
//
if(rd_state->cmds[0].count == cmd_count_pre_tick)
{
break;
}
}
//////////////////////////////
//- rjf: retry find-thread
//
if(!ctrl_handle_match(ctrl_handle_zero(), find_thread_retry))
{
rd_cmd(RD_CmdKind_FindThread, .thread = find_thread_retry);
}
//////////////////////////////
//- rjf: animate confirmation
//
{
F32 rate = rd_setting_b32_from_name(str8_lit("menu_animations")) ? 1 - pow_f32(2, (-10.f * rd_state->frame_dt)) : 1.f;
B32 popup_open = rd_state->popup_active;
rd_state->popup_t += rate * ((F32)!!popup_open-rd_state->popup_t);
if(abs_f32(rd_state->popup_t - (F32)!!popup_open) > 0.005f)
{
rd_request_frame();
}
}
////////////////////////////
//- rjf: rotate command slots, bump command gen counter
//
// in this step, we rotate the ring buffer of command batches (command
// arenas & lists). when the cmds_gen (the position of the ring buffer)
// is even, the command queue is in a "read/write" mode, and this is uniquely
// usable by the core - this is done so that commands in the core can push
// other commands, and have those other commands processed on the same frame.
//
// in view code, however, they can only use the current command queue in a
// "read only" mode, because new commands pushed by those views must be
// processed first by the core. so, before calling into view code, the
// cmds_gen is incremented to be *odd*. this way, the views will *write*
// commands into the 0 slot, but *read* from the 1 slot (which will contain
// this frame's commands).
//
// after view code runs, the generation number is incremented back to even.
// the commands pushed by the view will be in the queue, and the core can
// treat that queue as r/w again.
//
if(rd_state->frame_depth == 0)
{
// rjf: rotate
{
Arena *first_arena = rd_state->cmds_arenas[0];
RD_CmdList first_cmds = rd_state->cmds[0];
MemoryCopy(rd_state->cmds_arenas,
rd_state->cmds_arenas+1,
sizeof(rd_state->cmds_arenas[0])*(ArrayCount(rd_state->cmds_arenas)-1));
MemoryCopy(rd_state->cmds,
rd_state->cmds+1,
sizeof(rd_state->cmds[0])*(ArrayCount(rd_state->cmds)-1));
rd_state->cmds_arenas[ArrayCount(rd_state->cmds_arenas)-1] = first_arena;
rd_state->cmds[ArrayCount(rd_state->cmds_arenas)-1] = first_cmds;
}
// rjf: clear next batch
{
arena_clear(rd_state->cmds_arenas[0]);
MemoryZeroStruct(&rd_state->cmds[0]);
}
// rjf: bump
{
rd_state->cmds_gen += 1;
}
}
//////////////////////////////
//- rjf: compute all ambiguous paths from view titles
//
ProfScope("compute all ambiguous paths from view titles")
{
Temp scratch = scratch_begin(0, 0);
rd_state->ambiguous_path_slots_count = 512;
rd_state->ambiguous_path_slots = push_array(rd_frame_arena(), RD_AmbiguousPathNode *, rd_state->ambiguous_path_slots_count);
for(RD_WindowState *ws = rd_state->first_window_state; ws != &rd_nil_window_state; ws = ws->order_next)
{
RD_Cfg *window = rd_cfg_from_id(ws->cfg_id);
RD_PanelTree panel_tree = rd_panel_tree_from_cfg(scratch.arena, window);
for(RD_PanelNode *p = panel_tree.root; p != &rd_nil_panel_node; p = rd_panel_node_rec__depth_first_pre(panel_tree.root, p).next)
{
for(RD_CfgNode *tab_n = p->tabs.first; tab_n != 0; tab_n = tab_n->next)
{
RD_Cfg *tab = tab_n->v;
if(rd_cfg_is_project_filtered(tab))
{
continue;
}
RD_RegsScope(.tab = tab->id, .view = tab->id)
{
String8 eval_string = rd_expr_from_cfg(tab);
String8 file_path = rd_file_path_from_eval_string(scratch.arena, eval_string);
if(file_path.size != 0)
{
String8 name = str8_skip_last_slash(file_path);
U64 hash = d_hash_from_string__case_insensitive(name);
U64 slot_idx = hash%rd_state->ambiguous_path_slots_count;
RD_AmbiguousPathNode *node = 0;
for(RD_AmbiguousPathNode *n = rd_state->ambiguous_path_slots[slot_idx];
n != 0;
n = n->next)
{
if(str8_match(n->name, name, StringMatchFlag_CaseInsensitive))
{
node = n;
break;
}
}
if(node == 0)
{
node = push_array(rd_frame_arena(), RD_AmbiguousPathNode, 1);
SLLStackPush(rd_state->ambiguous_path_slots[slot_idx], node);
node->name = push_str8_copy(rd_frame_arena(), name);
}
str8_list_push(rd_frame_arena(), &node->paths, push_str8_copy(rd_frame_arena(), file_path));
}
}
}
}
}
scratch_end(scratch);
}
//////////////////////////////
//- rjf: set name matching parameters; begin matching
//
{
DI_KeyList keys_list = d_push_active_dbgi_key_list(scratch.arena);
DI_KeyArray keys = di_key_array_from_list(scratch.arena, &keys_list);
di_match_store_begin(rd_state->match_store, keys);
}
//////////////////////////////
//- rjf: update/render all windows
//
{
dr_begin_frame(rd_font_from_slot(RD_FontSlot_Icons));
RD_CfgList windows = rd_cfg_top_level_list_from_string(scratch.arena, str8_lit("window"));
for(RD_CfgNode *n = windows.first; n != 0; n = n->next)
{
RD_Cfg *window = n->v;
RD_WindowState *w = rd_window_state_from_cfg(window);
B32 window_is_focused = os_window_is_focused(w->os);
if(window_is_focused)
{
rd_state->last_focused_window = w->cfg_id;
}
rd_push_regs();
rd_regs()->window = w->cfg_id;
rd_window_frame();
MemoryZeroStruct(&w->ui_events);
RD_Regs *window_regs = rd_pop_regs();
if(rd_state->last_focused_window == w->cfg_id)
{
MemoryCopyStruct(rd_regs(), window_regs);
}
}
}
//////////////////////////////
//- rjf: simulate lag
//
if(DEV_simulate_lag)
{
os_sleep_milliseconds(300);
}
//////////////////////////////
//- rjf: end drag/drop if needed
//
if(rd_state->drag_drop_state == RD_DragDropState_Dropping)
{
rd_state->drag_drop_state = RD_DragDropState_Null;
}
//////////////////////////////
//- rjf: clear frame request state
//
if(rd_state->num_frames_requested > 0)
{
rd_state->num_frames_requested -= 1;
}
//////////////////////////////
//- rjf: close frame debug info scope
//
{
di_scope_close(rd_state->frame_di_scope);
}
//////////////////////////////
//- rjf: submit rendering to all windows
//
ProfScope("submit rendering to all windows")
{
r_begin_frame();
for(RD_WindowState *w = rd_state->first_window_state; w != &rd_nil_window_state; w = w->order_next)
{
r_window_begin_frame(w->os, w->r);
dr_submit_bucket(w->os, w->r, w->draw_bucket);
r_window_end_frame(w->os, w->r);
}
r_end_frame();
}
//////////////////////////////
//- rjf: show windows after first frame
//
if(rd_state->frame_depth == 0)
{
RD_CfgIDList windows_to_show = {0};
for(RD_WindowState *w = rd_state->first_window_state; w != &rd_nil_window_state; w = w->order_next)
{
if(w->frames_alive == 1)
{
rd_cfg_id_list_push(scratch.arena, &windows_to_show, w->cfg_id);
}
}
for(RD_CfgIDNode *n = windows_to_show.first; n != 0; n = n->next)
{
RD_Cfg *window = rd_cfg_from_id(n->v);
RD_WindowState *ws = rd_window_state_from_cfg(window);
DeferLoop(rd_state->frame_depth += 1, rd_state->frame_depth -= 1) os_window_first_paint(ws->os);
}
}
//////////////////////////////
//- rjf: determine frame time, record into history
//
U64 end_time_us = os_now_microseconds();
U64 frame_time_us = end_time_us-begin_time_us;
rd_state->frame_time_us_history[rd_state->frame_index%ArrayCount(rd_state->frame_time_us_history)] = frame_time_us;
//////////////////////////////
//- rjf: bump frame time counters
//
rd_state->frame_index += 1;
rd_state->time_in_seconds += rd_state->frame_dt;
rd_state->time_in_us += frame_time_us;
//////////////////////////////
//- rjf: bump command batch ring buffer generation
//
if(rd_state->frame_depth == 0)
{
rd_state->cmds_gen += 1;
}
//////////////////////////////
//- rjf: collect logs
//
ProfScope("collect logs")
{
LogScopeResult log = log_scope_end(scratch.arena);
os_append_data_to_file_path(rd_state->log_path, log.strings[LogMsgKind_Info]);
if(log.strings[LogMsgKind_UserError].size != 0)
{
for(RD_WindowState *ws = rd_state->first_window_state; ws != &rd_nil_window_state; ws = ws->order_next)
{
ws->error_string_size = Min(sizeof(ws->error_buffer), log.strings[LogMsgKind_UserError].size);
MemoryCopy(ws->error_buffer, log.strings[LogMsgKind_UserError].str, ws->error_string_size);
ws->error_t = 1.f;
}
}
}
scratch_end(scratch);
ProfEnd();
}