4coder-non-source/test_data/lots_of_files/cd_hierarchy_editor.cpp

845 lines
32 KiB
C++

/*
Development tool: Hierarchy editor
-Allen
28.06.2016
*/
// TOP
static void
extend_hierarchy_vars(System_API *system, Partition *part, Hierarchy_Vars *vars, i32 new_assets){
void *new_mem = push_array(part, Editor_Asset_State, new_assets);
if (new_mem == 0){
DBG_expand_partition(system, part, new_assets*sizeof(Editor_Asset_State));
new_mem = push_array(part, Editor_Asset_State, new_assets);
}
Assert(new_mem);
cd_memset(new_mem, 0, new_assets*sizeof(Editor_Asset_State));
vars->asset_count += new_assets;
}
static void
make_hierarchy_vars(System_API *system, Partition *part, Hierarchy_Vars *vars, i32 asset_count){
extend_hierarchy_vars(system, part, vars, asset_count);
vars->asset_states = (Editor_Asset_State*)part->base;
vars->backup_timer = BACKUP_FREQUENCY*.5f;
}
static void
clear_details(Slot_Details *details){
cd_memset(details, 0, sizeof(*details));
}
static void
full_sibling_insert_before(Asset_Node *node, Asset_Node *pos, void *manifest_memory){
i32 pos_parent_self = pos->parent;
Asset_Node *pos_parent = to_node_ptr(pos_parent_self, manifest_memory);
rptr32 pos_self = to_rptr32(pos, manifest_memory);
// NOTE(allen): If we just say "sibling_insert_before" it will
// not update the "first_child" of pos_parent. So if we happen
// to be inserting before first here, we actually want to use
// an insert under call which can adjust the parent's first child.
if (pos_parent->first_child == pos_self){
insert_under(node, pos_parent, manifest_memory, true);
}
else{
sibling_insert_before(node, pos, manifest_memory);
}
}
static b32
gui_do_text_field(GUI *gui, GUI_State *state, String *str, u32 flags){
b32 result = false;
if (gui_do_command(gui, gui_make_text_field(str, flags))){
GUI_id id = guid_1(str);
if ((flags & TextField_Selectable) &&
(flags & TextField_StaticString)){
if (guid_eq(state->selected, id)){
result = true;
}
}
else{
if (guid_eq(state->activated, id)){
result = true;
}
}
}
return(result);
}
static b32
gui_do_image_preview(GUI *gui, GUI_State *state, i32 image_id){
b32 result = false;
if (gui_do_command(gui, gui_make_image_preview(image_id))){
result = true;
}
return(result);
}
static b32
gui_do_deselect(GUI *gui, GUI_State *state){
b32 result = false;
if (gui_do_command(gui, gui_make_deselect())){
result = true;
}
return(result);
}
static String type_name_strs[AssetType_TypeCount];
static void
hierarchy_editor_step(System_API *system, Partition *part, Asset_Bank *bank,
Render_Target *ed_target, Dev_Input_State *dev_input,
Hierarchy_Vars *vars, Partition *manifest_part, Partition *edit_part,
vec2 start_pos, f32 descend, f32 gui_alpha){
Asset_Manifest *manifest_memory = (Asset_Manifest*)manifest_part->base;
// TODO(allen): Could stand to metaprogram this
if (type_name_strs[0].str == 0){
for (i32 i = 0; i < AssetType_TypeCount; ++i){
String s = {0};
switch (i){
case AssetType_GenericFolder: {
s = make_lit_string("Generic Folder");
}break;
case AssetType_Image: {
s = make_lit_string("Image");
}break;
case AssetType_ObstacleType: {
s = make_lit_string("Obstacle");
}break;
}
type_name_strs[i] = s;
}
}
i32 new_asset_count = manifest_memory->asset_node_count - vars->asset_count;
if (new_asset_count > 0){
extend_hierarchy_vars(system, edit_part, vars, new_asset_count);
}
vars->asset_states = (Editor_Asset_State*)edit_part->base;
f32 indent = 16.f;
f32 height = 22.f;
f32 hit_margin = 5.f;
f32 v_gap = 5.f;
f32 half_v_gap = 2.5f;
f32 width = 400.f;
// orange
vec3 gui_color_1 = v3(1.f, 0.75f, 0.f); AllowLocal(gui_color_1);
vec3 gui_color_2 = v3(1.f, 0.5f, 0.f); AllowLocal(gui_color_2);
vec3 gui_color_3 = v3(1.f, 0.25f, 0.f); AllowLocal(gui_color_3);
// purple
vec3 gui_color_4 = v3(1.f, 0.f, 0.25f); AllowLocal(gui_color_4);
vec3 gui_color_5 = v3(1.f, 0.f, 0.5f); AllowLocal(gui_color_5);
vec3 gui_color_6 = v3(1.f, 0.f, 0.75f); AllowLocal(gui_color_6);
// indigo
vec3 gui_color_7 = v3(0.25f, 0.f, 1.f); AllowLocal(gui_color_7);
vec3 gui_color_8 = v3(0.5f, 0.f, 1.f); AllowLocal(gui_color_8);
vec3 gui_color_9 = v3(0.75f, 0.f, 1.f); AllowLocal(gui_color_9);
// cyan
vec3 gui_color_10 = v3(0.f, 0.25f, 1.f); AllowLocal(gui_color_10);
vec3 gui_color_11 = v3(0.f, 0.5f, 1.f); AllowLocal(gui_color_11);
vec3 gui_color_12 = v3(0.f, 0.75f, 1.f); AllowLocal(gui_color_12);
// teal
vec3 gui_color_13 = v3(0.f, 1.f, 0.75f); AllowLocal(gui_color_13);
vec3 gui_color_14 = v3(0.f, 1.f, 0.5f); AllowLocal(gui_color_14);
vec3 gui_color_15 = v3(0.f, 1.f, 0.25f); AllowLocal(gui_color_15);
// mint
vec3 gui_color_16 = v3(0.25f, 1.f, 0.f); AllowLocal(gui_color_16);
vec3 gui_color_17 = v3(0.5f, 1.f, 0.f); AllowLocal(gui_color_17);
vec3 gui_color_18 = v3(0.75f, 1.f, 0.f); AllowLocal(gui_color_18);
Temp_Memory temp = begin_temp(part);
i32 op_max = 2;
i32 op_count = 0;
Tree_Operation *operations = push_array(part, Tree_Operation, op_max);
vec2 info_pos = start_pos;
info_pos.y -= descend;
b32 has_drop = (dev_input->drop_count > 0);
i32 mx = dev_input->input.mx;
i32 my = dev_input->input.my;
f32 drag_offset_x = 0;
f32 drag_offset_y = 0;
Asset_Walker walker = {0};
Asset_Node *node = walk_first_asset_node(manifest_memory, &walker);
for (; node != 0; ){
Editor_Asset_State *edit_state = &vars->asset_states[node->image_id-1];
i32 level = walker.current_level;
vec2 p0 = info_pos;
vec2 p1 = {0};
vec2 pn = info_pos;
pn.y -= height + v_gap;
p0.x += indent*level;
if (vars->dragged_asset == node->image_id){
drag_offset_x = mx - vars->dragged_offset_x - p0.x;
drag_offset_y = my - vars->dragged_offset_y - p0.y;
vars->dragged_level = level;
}
else if (level <= vars->dragged_level){
drag_offset_x = 0;
drag_offset_y = 0;
}
p0.x += drag_offset_x;
p0.y += drag_offset_y;
p1.x = p0.x + width;
p1.y = p0.y - height;
if (p1.y < 0) break;
f32 alpha_mult = 0.5f;
f32 above_alpha_mult = 0.f;
f32 below_alpha_mult = 0.f;
f32_rect above_rect = v4(p0.x, p0.y, p1.x, p0.y + v_gap);
f32_rect slot_rect = v4(p0.x, p1.y, p1.x, p0.y);
f32_rect below_rect = v4(p0.x, pn.y, p1.x, p1.y);
f32_rect above_target = v4(p0.x, p0.y - hit_margin, p1.x, p0.y + half_v_gap);
f32_rect hit_target = v4(p0.x, p1.y + hit_margin, p1.x, p0.y - hit_margin);
f32_rect below_target = v4(p0.x, p1.y - half_v_gap, p1.x, p1.y + hit_margin);
b32 below_is_first_child = false;
if (node->first_child && !edit_state->collapsed){
below_is_first_child = true;
below_rect.x0 += indent;
below_rect.x1 += indent;
}
enum{
hit_none,
hit_above,
hit_direct,
hit_below
};
i32 hit_type = hit_none;
if (hit_check(mx, my, above_target)){
above_alpha_mult = 1.f;
hit_type = hit_above;
}
else if (hit_check(mx, my, hit_target)){
alpha_mult = 1.f;
hit_type = hit_direct;
}
else if (hit_check(mx, my, below_target)){
below_alpha_mult = 1.f;
hit_type = hit_below;
}
i32 pos_id = node->image_id;
if (vars->clicked_asset){
if (vars->clicked_asset == node->image_id && hit_type == hit_direct){
if (btn_released(dev_input->input.left_button)){
edit_state->collapsed = !edit_state->collapsed;
}
}
}
else if (vars->right_clicked_asset){
if (vars->right_clicked_asset == node->image_id && hit_type == hit_direct){
if (btn_released(dev_input->input.right_button)){
vars->detail_asset = node->image_id;
clear_details(&vars->details);
}
}
}
else if (vars->dragged_asset){
if (vars->dragged_asset != node->image_id){
if (!btn_down(dev_input->input.left_button)){
if (hit_type != hit_none){
Assert(op_count + 2 <= op_max);
i32 asset_id = vars->dragged_asset;
i32 op_count_original = op_count;
operations[op_count++] = make_op(TreeOp_RemoveSubtree, asset_id, 0);
switch (hit_type){
case hit_above:
{
if (pos_id == ROOT){
op_count = op_count_original;
}
else{
operations[op_count++] =
make_op(TreeOp_InsertSiblingSubtreeBefore, asset_id, pos_id);
}
}break;
case hit_direct:
{
operations[op_count++] =
make_op(TreeOp_InsertLastChildSubtree, asset_id, pos_id);
}break;
case hit_below:
{
if (below_is_first_child){
operations[op_count++] =
make_op(TreeOp_InsertFirstChildSubtree, asset_id, pos_id);
}
else{
if (pos_id == ROOT){
op_count = op_count_original;
}
else{
operations[op_count++] =
make_op(TreeOp_InsertSiblingSubtreeAfter, asset_id, pos_id);
}
}
}break;
}
}
}
}
}
else if (has_drop){
u32 count = dev_input->drop_count; AllowLocal(count);
Dev_File_Drop *drops = dev_input->drops;
u32 i = 0;
String name = make_string_slowly(drops[i].name);
String file_name = front_of_directory(name);
switch (hit_type){
case hit_direct:
{
String replace_name = make_string_slowly(node->name);
b32 do_replace = false;
if (match(replace_name, file_name)){
asset_file_write(system, name.str, file_name.str);
do_replace = true;
}
else{
if (!asset_file_exists(system, file_name.str)){
if (asset_file_write(system, name.str, file_name.str)){
asset_file_delete(system, replace_name.str);
do_replace = true;
}
}
}
if (do_replace){
bank->replace_bmp(system, bank, part,
drops[i].name, node->image_id);
replace_image(manifest_memory, file_name.str, node->image_id);
}
}break;
case hit_above:
{
if (!asset_file_exists(system, file_name.str)){
Assert(op_count + 1 <= op_max);
Assert(pos_id <= manifest_memory->asset_node_count);
operations[op_count++] = make_op(TreeOp_NewNodeBefore, 0, pos_id, file_name.str);
asset_file_write(system, name.str, file_name.str);
}
}break;
case hit_below:
{
if (!asset_file_exists(system, file_name.str)){
Assert(op_count + 1 <= op_max);
if (below_is_first_child){
i32 child_id = get_image_id(node->first_child, manifest_memory);
Assert(child_id <= manifest_memory->asset_node_count);
operations[op_count++] = make_op(TreeOp_NewNodeBefore, 0, child_id, file_name.str);
asset_file_write(system, name.str, file_name.str);
}
else{
Assert(pos_id <= manifest_memory->asset_node_count);
operations[op_count++] = make_op(TreeOp_NewNodeAfter, 0, pos_id, file_name.str);
asset_file_write(system, name.str, file_name.str);
}
}
}break;
}
}
else if (btn_down(dev_input->input.letter['R'])){
switch (hit_type){
case hit_direct:
{
if (btn_pressed(dev_input->input.left_button)){
vars->dragged_asset = node->image_id;
vars->dragged_offset_y = my - p0.y;
vars->dragged_offset_x = mx - p0.x;
}
}break;
}
}
else{
switch (hit_type){
case hit_direct:
{
if (btn_pressed(dev_input->input.left_button)){
vars->clicked_asset = node->image_id;
}
else if (btn_pressed(dev_input->input.right_button)){
vars->right_clicked_asset = node->image_id;
}
}break;
case hit_above:
{
if (btn_pressed(dev_input->input.left_button)){
Assert(op_count + 1 <= op_max);
Assert(pos_id <= manifest_memory->asset_node_count);
operations[op_count++] = make_op(TreeOp_NewNodeBefore, 0, pos_id);
}
}break;
case hit_below:
{
if (btn_pressed(dev_input->input.left_button)){
Assert(op_count + 1 <= op_max);
if (below_is_first_child){
i32 child_id = get_image_id(node->first_child, manifest_memory);
Assert(child_id <= manifest_memory->asset_node_count);
operations[op_count++] = make_op(TreeOp_NewNodeBefore, 0, child_id);
}
else{
Assert(pos_id <= manifest_memory->asset_node_count);
operations[op_count++] = make_op(TreeOp_NewNodeAfter, 0, pos_id);
}
}
}break;
}
}
if (vars->clicked_asset == node->image_id ||
vars->right_clicked_asset == node->image_id){
alpha_mult = clamp_bottom(0.8f, alpha_mult);
}
switch (node->type){
case AssetType_GenericFolder:
{
do_dbg_rectangle(ed_target, slot_rect, v4(gui_color_17, gui_alpha*alpha_mult));
}break;
case AssetType_Image:
{
do_dbg_rectangle(ed_target, slot_rect, v4(gui_color_2, gui_alpha*alpha_mult));
}break;
case AssetType_ObstacleType:
{
do_dbg_rectangle(ed_target, slot_rect, v4(gui_color_8, gui_alpha*alpha_mult));
}break;
}
do_dbg_rectangle(ed_target, above_rect, v4(gui_color_1, gui_alpha*above_alpha_mult));
do_dbg_rectangle(ed_target, below_rect, v4(gui_color_1, gui_alpha*below_alpha_mult));
vec2 c = {p0.x + 5.f, p1.y + 3.f};
if (node->first_child){
if (edit_state->collapsed){
do_string(ed_target, bank, DBG_FONT, c,
"+", 1,
white);
}
else{
do_string(ed_target, bank, DBG_FONT, c,
"-", 1,
white);
}
c.x += 10.f;
}
do_string(ed_target, bank, DBG_FONT, c,
node->name, cd_strlen(node->name),
white);
info_pos.y -= height + v_gap;
if (edit_state->collapsed){
node = walk_skip_children_asset_node(manifest_memory, &walker);
}
else{
node = walk_next_asset_node(manifest_memory, &walker);
}
}
if (!btn_down(dev_input->input.left_button)){
vars->clicked_asset = 0;
vars->dragged_asset = 0;
}
if (!btn_down(dev_input->input.right_button)){
vars->right_clicked_asset = 0;
}
Tree_Operation *op = operations;
for (i32 i = 0; i < op_count; ++i, ++op){
i32 image_id = op->id;
i32 pos_id = op->pos_id;
Asset_Node *node = get_node(image_id, manifest_memory);
Asset_Node *pos = get_node(pos_id, manifest_memory);
char *str = op->str;
switch (op->type){
case TreeOp_RemoveSubtree:
{
tree_remove(node, manifest_memory);
}break;
case TreeOp_InsertLastChildSubtree:
{
insert_under(node, pos, manifest_memory, false);
}break;
case TreeOp_InsertFirstChildSubtree:
{
insert_under(node, pos, manifest_memory, true);
}break;
case TreeOp_InsertSiblingSubtreeBefore:
{
full_sibling_insert_before(node, pos, manifest_memory);
}break;
case TreeOp_InsertSiblingSubtreeAfter:
{
sibling_insert_after(node, pos, manifest_memory);
}break;
case TreeOp_NewNodeBefore:
{
Asset_Node *new_node = get_available_node(system, manifest_part, &manifest_memory);
// NOTE(allen): We need to recompute pointers after calls to get_available_node because
// can potentially rebase all the nodes. Perhaps we should eliminate the Asset_Node*
// parameters from the public interface to avoid this bug.
Asset_Node *pos = get_node(pos_id, manifest_memory);
if (str){
replace_image(manifest_memory, str, new_node->image_id);
}
full_sibling_insert_before(new_node, pos, manifest_memory);
}break;
case TreeOp_NewNodeAfter:
{
Asset_Node *new_node = get_available_node(system, manifest_part, &manifest_memory);
// NOTE(allen): We need to recompute pointers after calls to get_available_node because
// can potentially rebase all the nodes. Perhaps we should eliminate the Asset_Node*
// parameters from the public interface to avoid this bug.
Asset_Node *pos = get_node(pos_id, manifest_memory);
if (str){
replace_image(manifest_memory, str, new_node->image_id);
}
sibling_insert_after(new_node, pos, manifest_memory);
}break;
}
}
if (vars->detail_asset){
Slot_Details *details = &vars->details;
Asset_Node *node = get_node(vars->detail_asset, manifest_memory);
Editor_Asset_State *edit_state =
&vars->asset_states[node->image_id-1];
AllowLocal(edit_state);
if (!details->initialized){
details->initialized = true;
cd_memcpy(details->name_field_space, node->name, sizeof(node->name));
details->name_field = make_string_slowly(details->name_field_space);
details->name_field.memory_size = sizeof(node->name);
details->type_name = make_fixed_width_string(details->type_name_space);
copy(&details->type_name, type_name_strs[node->type]);
}
GUI gui = make_gui(part, 1024);
GUI_State *state = &details->state;
//
// GUI declaration
//
{
b32 doing_radio_buttons = false;
if (gui_do_text_field(&gui, state, &details->type_name,
TextField_Selectable | TextField_StaticString)){
doing_radio_buttons = true;
vars->do_type_radio_buttons = true;
}
if (vars->do_type_radio_buttons){
i32 set_type = AssetType_TypeCount;
for (i32 i = 0;
i < AssetType_TypeCount;
++i){
if (gui_do_text_field(&gui, state, &type_name_strs[i], TextField_StaticString)){
set_type = i;
}
}
if (set_type < AssetType_TypeCount){
vars->do_type_radio_buttons = false;
b32 good_to_set = true;
if (set_type == AssetType_Image){
if (asset_file_exists(system, details->name_field.str)){
good_to_set = false;
}
}
if (good_to_set){
node->type = set_type;
copy(&details->type_name, type_name_strs[set_type]);
}
}
}
vars->do_type_radio_buttons = doing_radio_buttons;
switch (node->type){
case AssetType_GenericFolder:
{
if (gui_do_text_field(&gui, state, &details->name_field, TextField_Selectable)){
cd_memcpy(node->name, details->name_field_space, sizeof(node->name));
}
}break;
case AssetType_Image:
{
if (gui_do_text_field(&gui, state, &details->name_field, TextField_Selectable)){
terminate_with_null(&details->name_field);
if (!asset_file_exists(system, details->name_field.str)){
String ext = file_extension(details->name_field);
if (match(ext, "bmp")){
if (asset_file_write(system, node->name, details->name_field.str)){
asset_file_delete(system, node->name);
cd_memcpy(node->name, details->name_field.str, sizeof(node->name));
}
}
}
}
gui_do_image_preview(&gui, state, node->image_id);
}break;
case AssetType_ObstacleType:
{
if (gui_do_text_field(&gui, state, &details->name_field, TextField_Selectable)){
cd_memcpy(node->name, details->name_field_space, sizeof(node->name));
}
}break;
}
}
vec2 p = start_pos + v2(width*1.5f, -height*2);
//
// GUI execution
//
{
GUI_State *state = &details->state;
b32 reload = false;
if (btn_pressed(dev_input->input.left_button)){
state->hot = guid_zero();
}
if (btn_released(dev_input->input.left_button)){
state->selected = guid_zero();
reload = true;
}
state->activated = guid_zero();
GUI_Command *command = gui.commands;
state->activated = guid_zero();
for (i32 i = 0;
i < gui.count;
++i, ++command){
switch (command->type){
case guicom_text_field:
{
String *edit_str = command->text_field.edit_str;
vec2 p0 = p;
vec2 p1 = p0 + v2(width, -height);
f32_rect rect = v4(p0.x, p1.y, p1.x, p0.y);
p.y -= height;
vec3 color = gui_color_3;
f32 alpha_mult = 0.5f;
if (command->text_field.flags == TextField_StaticString){
color = gui_color_10;
}
GUI_id id = guid_1(edit_str);
if (hit_check(mx, my, rect)){
alpha_mult = 1.f;
if (btn_pressed(dev_input->input.left_button)){
state->hot = id;
}
else if (btn_released(dev_input->input.left_button) && guid_eq(state->hot, id)){
state->hot = guid_zero();
if (command->text_field.flags & TextField_Selectable){
state->selected = id;
}
else{
state->activated = id;
}
}
}
if (guid_eq(state->hot, id)){
alpha_mult = clamp_bottom(0.9f, alpha_mult);
}
if (guid_eq(state->selected, id)){
color = gui_color_4;
alpha_mult = 1.f;
if (!(command->text_field.flags & TextField_StaticString)){
i32 count = dev_input->keys.count;
for (i32 j = 0;
j < count;
++j){
char key = dev_input->keys.events[j];
if (key == key_back){
if (edit_str->size > 0){
--edit_str->size;
terminate_with_null(edit_str);
}
}
else if (key == '\n'){
state->activated = id;
state->selected = guid_zero();
}
else if (key >= ' ' && key <= '~'){
if (edit_str->size+1 < edit_str->memory_size){
append(edit_str, key);
terminate_with_null(edit_str);
}
}
}
}
}
do_dbg_rectangle(ed_target, rect, v4(color, gui_alpha*alpha_mult));
vec2 c = {p0.x + 5.f, p1.y + 3.f};
do_string(ed_target, bank, DBG_FONT, c,
edit_str->str, edit_str->size,
white);
}break;
case guicom_image_preview:
{
i32 image_id = command->image_preview.image_id;
f32 max_w = width;
f32 max_h = height*10;
f32 w = height;
f32 h = height;
Image *image = get_image(bank, image_id);
if (image){
w = image->width;
h = image->height;
f32 ratio_w = 1.;
f32 ratio_h = 1.;
if (w > max_w){
ratio_w = max_w / w;
}
if (h > max_h){
ratio_h = max_h / h;
}
f32 ratio = ratio_w;
if (ratio > ratio_h){
ratio = ratio_h;
}
w *= ratio;
h *= ratio;
}
vec2 p0 = p;
vec2 p1 = p + v2(w, -h);
f32_rect rect = v4(p0.x, p1.y, p1.x, p0.y);
p = v2(p0.x, p1.y);
vec2 x_axis = v2(w*.5f, 0);
vec2 y_axis = v2(0, h*.5f);
do_dbg_rectangle(ed_target, rect, v4(black3, gui_alpha));
do_image_general(ed_target, bank, 0, image_id,
Render_Exact, p0, v2(-1.f, 1.f),
x_axis, y_axis);
}break;
case guicom_deselect:
{
state->selected = guid_zero();
}break;
}
}
if (reload){
if (guid_eq(state->selected, guid_zero()) &&
guid_eq(state->activated, guid_zero())){
clear_details(details);
}
}
}
}
end_temp(temp);
vars->backup_timer -= dev_input->input.dt;
if (vars->backup_timer <= 0.f){
vars->backup_timer = BACKUP_FREQUENCY;
asset_file_backup(system);
}
}
// BOTTOM