#include "client.h"
#if ALLOW_DEVELOPER
#include "panel.h"
#include "font.h"
#include "control.h"
#include "mprintf.h"
#include "world_panel.h"
#include "device_context.h"
#include "game_resource_manager.h"
#include "rendering.h"
#include "movement_panel.h"
#include "particle_manager.h"
#include "entity_manager.h"
#include "lister_panel.h"
#include "entity.h"
#include "entity_panel.h"
#include "undo_panel.h"
#include "portable.h"
#include "ui_manager.h"
#include "editors.h"
#include "panel_utilities.h"
#include "bounding_volumes.h"
#include "entities/group.h"
[REDACTED]
#include "entities/grass.h"
#include "entities/door.h"
[REDACTED]
#include "entities/light_probe.h"
#include "grass_planting.h"
#include "raycast_result.h"
#include "token.h"
#include "mesh.h"
#include <float.h>
#define EDITOR_PANEL_NAME "lister"
Shader *get_shader(const char *name); // NOTE(casey): From rendering.cpp
// NOTE(casey): I don't use FLT_MAX in here because
// I was worried that FLT_MAX wouldn't persist properly, potentially,
// since it's written in text and all that, who knows... didn't
// wnat to risk it!
#define PSEUDO_FLT_MAX 100000.0f
// TODO(casey): No squaring function??
#define square(X) ((X)*(X))
// NOTE(casey): Filters don't all apply to everything, so
// NOT_APPLICABLE is their way of returning "I don't look at these
// types of entities"
#define NOT_APPLICABLE - 1
// TODO(casey): We need a centralized fast lookup for selection!
static Hash_Table *selected_hash;
//
// NOTE(casey): Special Filters
//
#define SPECIAL_FILTER(name) void name(int param, Entity *e, bool op, bool new_value, int *result)
typedef SPECIAL_FILTER(Special_Filter);
static SPECIAL_FILTER(filter_is_killed)
{
*result = (e->portable_flags & PORTABLE_KILLED);
}
static SPECIAL_FILTER(filter_hidden)
{
if(op)
{
change_visibility(e, !new_value);
}
*result = (e->runtime_only_flags & ER_HIDDEN);
}
static SPECIAL_FILTER(filter_inactive)
{
*result = (e->runtime_only_flags & ER_MY_LAYER_IS_INACTIVE);
}
static SPECIAL_FILTER(filter_groups_must_be_layers)
{
// TODO(casey): Allow this to be toggled interactively?
if(e->portable_type == &ptype_Group)
{
Entity_Group *group = (Entity_Group *)e;
if(op)
{
group->is_a_layer = new_value;
note_change(e);
}
*result = group->is_a_layer;
}
}
[REDACTED]
static SPECIAL_FILTER(filter_selected)
{
assert(selected_hash);
if(op)
{
if(new_value)
{
if(!selected_hash->find(e))
{
selected_hash->add(e, e);
add_to_selection(e->portable_id);
}
}
else
{
if(selected_hash->find(e))
{
selected_hash->remove(e);
// TODO(casey): WTF is this called toggle selection, when apparently it only clears the selection?
toggle_selection(e->portable_id);
}
}
}
*result = (selected_hash->find(e) != 0);
}
static SPECIAL_FILTER(filter_entity_flag)
{
if(op)
{
if(new_value)
{
e->entity_flags |= param;
}
else
{
e->entity_flags &= ~param;
}
note_change(e);
}
*result = (e->entity_flags & param);
}
static SPECIAL_FILTER(filter_group_current_visibility)
{
if(e->portable_type == &ptype_Group)
{
Entity_Group *group = (Entity_Group *)e;
if(op)
{
change_group_visibility(group, new_value);
}
*result = (group->current_group_visibility);
}
}
static SPECIAL_FILTER(filter_group_initial_visibility)
{
if(e->portable_type == &ptype_Group)
{
Entity_Group *group = (Entity_Group *)e;
if(op)
{
group->initial_group_visibility = new_value;
note_change(e);
}
*result = (group->initial_group_visibility);
}
}
static SPECIAL_FILTER(filter_door_slides_open)
{
if(e->portable_type == &ptype_Door)
{
Entity_Door *door = (Entity_Door *)e;
*result = (door->slides_open != 0);
}
}
static SPECIAL_FILTER(filter_mounted)
{
*result = (e->mount_parent_id != 0);
}
static SPECIAL_FILTER(filter_animated)
{
*result = (e->animator && e->mesh && e->mesh->name);
}
static SPECIAL_FILTER(filter_door_matches_closed)
{
if(e->portable_type == &ptype_Door)
{
Entity_Door *door = (Entity_Door *)e;
if(e->mount_parent_id == 0)
{
*result = ((door->pos_when_closed == door->position) && (door->ori_when_closed == door->orientation));
}
else
{
*result = ((door->mount_pos_when_closed == door->mount_position) && (door->mount_ori_when_closed == door->mount_orientation));
}
}
}
[REDACTED]
struct Special_Filter_Entry
{
char *save_name;
char *short_name;
char *positive_name;
char *negated_name;
Special_Filter *handler;
int param;
};
Special_Filter_Entry special_filters[] =
{
//
// NOTE(casey): Transient flags
//
{"filter_is_killed", "ki", "killed", "not killed", filter_is_killed},
{"filter_selected", "se", "selected", "not selected", filter_selected},
{"filter_hidden", "hi", "hidden", "not hidden", filter_hidden},
{"filter_inactive", "ia", "inactive", "active", filter_inactive},
//
// NOTE(casey): Persistent flags
//
{"filter_immobile", "im", "immobile", "mobile", filter_entity_flag, ENTITY_IMMOBILE},
{"filter_do_not_collide", "co", "doesn't collide", "collides", filter_entity_flag, ENTITY_DO_NOT_COLLIDE},
{"filter_do_not_cluster", "cl", "doesn't cluster", "clusters", filter_entity_flag, ENTITY_DO_NOT_CLUSTER},
{"filter_do_not_reflect", "rf", "doesn't reflect", "reflects", filter_entity_flag, ENTITY_DO_NOT_REFLECT},
{"filter_do_not_cast_shadows", "ca", "doesn't cast shadows", "casts shadows", filter_entity_flag, ENTITY_DO_NOT_CAST_SHADOWS},
{"filter_mount_in_editor_only", "me", "mounts only in editor", "doesn't mount only in editor", filter_entity_flag, ENTITY_MOUNT_IN_EDITOR_ONLY},
[REDACTED]
{"filter_has_slab_collision", "sc", "has slab collision", "doesn't have slab collision", filter_entity_flag, ENTITY_HAS_SLAB_COLLISION},
{"filter_has_ramp_collision", "rc", "has ramp collision", "doesn't have ramp collision", filter_entity_flag, ENTITY_HAS_RAMP_COLLISION},
[REDACTED]
{"filter_lods_to_nothing", "ln", "lods to nothing", "does not lod to nothing", filter_entity_flag, ENTITY_LODS_TO_NOTHING},
{"filter_rooted", "rt", "rooted", "not rooted", filter_entity_flag, ENTITY_ROOTED},
{"filter_forgiving_collision", "fc", "forgiving collision", "standard collision", filter_entity_flag, ENTITY_USES_FORGIVING_COLLISION_MODEL},
{"filter_is_invisible", "in", "invisible", "visible", filter_entity_flag, ENTITY_INVISIBLE},
{"filter_has_tree_collision", "tc", "has tree collision", "doesn't have tree collision", filter_entity_flag, ENTITY_HAS_TREE_COLLISION},
{"filter_has_skeleton", "sk", "has skeleton", "doesn't have skeleton", filter_entity_flag, ENTITY_HAS_SKELETON},
{"filter_has_convex_collision", "cc", "has convex collision", "doesn't have convex collision", filter_entity_flag, ENTITY_HAS_CONVEX_COLLISION},
#if 0
// NOTE(casey): Andy requested that this button be removed.
{"filter_has_occluder_geometry", "og", "has occluder geometry", "doesn't have occluder geometry", filter_entity_flag, ENTITY_HAS_OCCLUDER_GEOMETRY},
#endif
[REDACTED]
{"filter_lightmap_hidden", "lh", "hidden lightmap", "visible lightmap", filter_entity_flag, ENTITY_LIGHTMAP_HIDDEN},
{"filter_lightmap_static", "ls", "static lightmap", "non-static lightmap", filter_entity_flag, ENTITY_LIGHTMAP_STATIC},
{"filter_no_sun_light", "sn", "doesn't receive sunlight", "receives sunlight", filter_entity_flag, ENTITY_NO_SUN_LIGHT},
{"filter_lightmap_visible", "lv", "visible to lightmap", "visible to lightmap", filter_entity_flag, ENTITY_LIGHTMAP_VISIBLE},
{"filter_raycast_class1", "r1", "raycast class 1", "not raycast class 1", filter_entity_flag, ENTITY_RAYCAST_CLASS1},
{"filter_lightmap_high_priority", "hp", "high-priority lightmap", "normal priority lightmap", filter_entity_flag, ENTITY_LIGHTMAP_HIGH_PRIORITY},
{"filter_acts_as_ground", "ag", "acts as ground", "doesn't act as ground", filter_entity_flag, ENTITY_ACTS_AS_GROUND},
{"filter_locked", "lk", "locked", "unlocked", filter_entity_flag, ENTITY_LOCKED}, // {"filter_pose_does_not_matter", "pd", "pose doesn't matter", "pose matters", filter_entity_flag, ENTITY_POSE_DOES_NOT_MATTER},
{"filter_ignore_grass_avoidance", "ga", "ignores grass avoids", "doesn't ignore grass avoids", filter_entity_flag, ENTITY_IGNORES_GRASS_AVOIDANCE},
//
// NOTE(casey): Entity variables
//
{"filter_mounted", "mt", "mounted", "not mounted", filter_mounted},
{"filter_animated", "an", "animated", "not animated", filter_animated},
//
// NOTE(casey): Type-specific
//
[REDACTED]
{"filter_groups_must_be_layers", "gt", "group is layer", "group is not layer", filter_groups_must_be_layers},
{"filter_group_current_visibility", "gv", "group visible now", "group invisible now", filter_group_current_visibility},
#if 0
// NOTE(casey): Jon decided he did not want this button available to people,
// and that they should have to go through the entity panel to do this
// operation if they wanted to.
{"filter_group_initial_visibility", "gi", "group visible initially", "group invisible initially", filter_group_initial_visibility},
#endif
{"filter_door_slides_open", "ds", "door slides open", "door swings open", filter_door_slides_open},
{"filter_door_matches_closed", "dc", "door matches closed", "door differs from closed", filter_door_matches_closed},
[REDACTED]
};
//
// NOTE(casey): Visors
//
#define VISOR_HANDLER(name) void name(Visor_Settings &settings, Entity *e)
typedef VISOR_HANDLER(Visor_Handler);
inline void draw_circle(Vector3 const &p, float r, int unsigned color)
{
Vector3 e1 = globals.particle_manager->e1;
Vector3 e2 = globals.particle_manager->e2;
Vector3 p0 = p - r*e1 - r*e2;
Vector3 p1 = p + r*e1 - r*e2;
Vector3 p2 = p + r*e1 + r*e2;
Vector3 p3 = p - r*e1 + r*e2;
Vector2 uv0(0, 0);
Vector2 uv1(1, 0);
Vector2 uv2(1, 1);
Vector2 uv3(0, 1);
Device_Context::im_vertex(p0, uv0, color);
Device_Context::im_vertex(p1, uv1, color);
Device_Context::im_vertex(p2, uv2, color);
Device_Context::im_vertex(p0, uv0, color);
Device_Context::im_vertex(p2, uv2, color);
Device_Context::im_vertex(p3, uv3, color);
}
inline void draw_standard_marker(Vector3 const &p, int unsigned color)
{
// NOTE(casey): Grow the markers a bit based on camera distance so that when
// you zoom way out on the map, they're nice and big and you can see them clearly.
float radius = 0.01f*distance(globals.camera_position, p);
draw_circle(p, 1.5f*radius, color);
}
VISOR_HANDLER(visor_entity_position)
{
draw_standard_marker(e->position, argb_color(0, 1, 0, 1));
}
[REDACTED]
VISOR_HANDLER(visor_grass_planting_distribution)
{
if(e->portable_type == &ptype_Grass)
{
Entity_Grass *grass = (Entity_Grass *)e;
Grass_Source *source = grass->grass_source;
if(source)
{
int unsigned color = argb_color(0, 1, 0, 1);
{for(int inst_index = 0;
inst_index < source->instance_count;
++inst_index)
{
if(grass->fancy && inst_index == source->active_base_count)
{
color = argb_color(0, 0, 1, 1);
}
Grass_Instance &inst = source->instance_array[inst_index];
float radius = 0.1f*inst.scale;
Vector3 p = object_to_world_space(grass, inst.root_location);
draw_circle(p, 1.5f*radius, color);
}}
}
}
}
VISOR_HANDLER(visor_grass_planting_boundaries)
{
if(e->portable_type == &ptype_Grass)
{
Entity_Grass *grass = (Entity_Grass *)e;
Grass_Source *source = grass->grass_source;
if(source)
{
{for(int cell_y = 0; cell_y < (GRASS_CELL_DIMENSION + 1); ++cell_y)
{
{for(int cell_x = 0; cell_x < (GRASS_CELL_DIMENSION + 1); ++cell_x)
{
float &cell_d = source->distance_from_edge[cell_y][cell_x];
Vector2 cell_xy = get_xy_for_cell(*source, cell_x, cell_y);
Vector3 p = object_to_world_space(grass, Vector3(cell_xy.x, cell_xy.y, 0.0f));
float max_r = Max(grass->r1, grass->r2);
float gray = (cell_d / max_r);
int unsigned color = argb_color(gray, gray, gray, 1);
draw_circle(p, 0.1f, color);
}}
}}
}
}
}
static void thick_line(Vector3 const &from_p, Vector3 const &to_p, int unsigned color, float thickness = 0.2f)
{
Vector3 diff = to_p - from_p;
Vector3 perp = cross_product(diff, axis_up);
perp.normalize_or_zero();
Device_Context::im_vertex(from_p - thickness*perp, color);
Device_Context::im_vertex(to_p - thickness*perp, color);
Device_Context::im_vertex(to_p + thickness*perp, color);
Device_Context::im_vertex(from_p - thickness*perp, color);
Device_Context::im_vertex(to_p + thickness*perp, color);
Device_Context::im_vertex(from_p + thickness*perp, color);
}
static bool get_ground_point(Entity_Grass *grass, Vector3 start_p, Vector3 *result_p, float default_z)
{
bool result = false;
*result_p = start_p;
Ground_Hit ground_hit;
Raycast_Result raycast_result;
if(get_ground_hit_fancy(grass, start_p, 0, &ground_hit, &raycast_result))
{
result = true;
result_p->z = ground_hit.height;
}
else
{
result_p->z = default_z;
}
return(result);
}
VISOR_HANDLER(visor_grass_planting_area)
{
// TODO(casey): Really this is _half_ thickness
float thickness = 0.2f;
if(e->portable_type == &ptype_Grass)
{
Entity_Grass *grass = (Entity_Grass *)e;
int unsigned color = settings.color[1];
if(grass->is_rectangular)
{
Vector3 corner[4] =
{
Vector3(-grass->r1, -grass->r2, grass->position.z), Vector3(grass->r1, -grass->r2, grass->position.z), Vector3(grass->r1, grass->r2, grass->position.z), Vector3(-grass->r1, grass->r2, grass->position.z),
};
// TODO(casey): Need to do a prepass to record all the points,
// so those that miss the ground can be given a default Z
// based on ones that did hit the ground.
float default_z = grass->position.z;
Vector3 first_ground_p;
if(get_ground_point(grass, object_to_world_space(grass, corner[3]), &first_ground_p, default_z))
{
default_z = first_ground_p.z;
}
Vector3 last_p = first_ground_p;
int prev_corner_index = 3;
{for(int corner_index = 0;
corner_index <= 3;
prev_corner_index = corner_index++)
{
int step_count = 3;
{for(int step_index = 0;
step_index < step_count;
++step_index)
{
float t = (float)step_index / (float)step_count;
Vector3 c_p = (1.0f - t)*corner[prev_corner_index] + t*corner[corner_index];
Vector3 this_p;
if(get_ground_point(grass, object_to_world_space(grass, c_p), &this_p, default_z))
{
default_z = this_p.z;
}
thick_line(last_p, this_p, color, thickness);
last_p = this_p;
}}
}}
thick_line(last_p, first_ground_p, color, thickness);
}
else
{
// TODO(casey): Need to do a prepass to record all the points,
// so those that miss the ground can be given a default Z
// based on ones that did hit the ground.
float default_z = grass->position.z;
Vector3 first_ground_p;
if(get_ground_point(grass, object_to_world_space(grass, Vector3(grass->r1, 0, grass->position.z)), &first_ground_p, default_z))
{
default_z = first_ground_p.z;
}
Vector3 last_arm(1.0f, 0.0f, 0.0f);
Vector3 last_ground_p = first_ground_p;
int step_count = 16;
{for(int step_index = 1;
step_index < step_count;
++step_index)
{
float angle = TAU * ((float)step_index / (float)step_count);
Vector3 arm = Vector3(cos(angle), sin(angle), 0.0f);
Vector3 this_ground_p = Vector3(grass->r1*arm.x, grass->r2*arm.y, grass->position.z);
if(get_ground_point(grass, object_to_world_space(grass, this_ground_p), &this_ground_p, default_z))
{
default_z = this_ground_p.z;
}
Device_Context::im_vertex(last_ground_p - thickness*arm, color);
Device_Context::im_vertex(this_ground_p - thickness*arm, color);
Device_Context::im_vertex(this_ground_p + thickness*arm, color);
Device_Context::im_vertex(last_ground_p - thickness*arm, color);
Device_Context::im_vertex(this_ground_p + thickness*arm, color);
Device_Context::im_vertex(last_ground_p + thickness*arm, color);
last_ground_p = this_ground_p;
last_arm = arm;
}}
}
}
}
enum Planting_Curve_Type
{
PLANTING_CURVE_SCALE,
PLANTING_CURVE_DISTANCE,
};
static void draw_planting_curve(Entity_Grass *grass, Planting_Curve_Type type,
int unsigned min_color, int unsigned max_color,
float z_offset)
{
Ground_Hit ground_hit;
Raycast_Result raycast_result;
if(get_ground_hit_fancy(grass, grass->position, 0, &ground_hit, &raycast_result))
{
float mesh_radius = 1.0f;
if(grass->mesh)
{
mesh_radius = grass->mesh->sphere.radius;
}
Vector3 target_point;
if(grass->is_rectangular)
{
target_point = Vector3(grass->r1, grass->r2, 0);
}
else
{
target_point = Vector3(grass->r1, 0, 0);
}
Vector3 perp = cross_product(target_point, axis_up);
perp.normalize_or_zero();
int step_count = 10;
{for(int step_index = 0;
step_index < step_count;
++step_index)
{
float d0 = (float)step_index / (float)step_count;
float d1 = (float)(step_index + 1) / (float)step_count;
Vector3 p0 = d0*target_point;
Vector3 p1 = d1*target_point;
Grass_Planting_Values v0, v1;
get_grass_planting_values_at(*grass, p0, &v0);
get_grass_planting_values_at(*grass, p1, &v1);
Vector3 pw0 = object_to_world_space(grass, p0);
Vector3 pw1 = object_to_world_space(grass, p1);
pw0.z = ground_hit.height + z_offset;
pw1.z = ground_hit.height + z_offset;
float v0_min;
float v0_max;
float v1_min;
float v1_max;
if(type == PLANTING_CURVE_SCALE)
{
v0_min = v0.min_scale*mesh_radius;
v0_max = v0.max_scale*mesh_radius;
v1_min = v1.min_scale*mesh_radius;
v1_max = v1.max_scale*mesh_radius;
}
else
{
assert(type == PLANTING_CURVE_DISTANCE);
v0_min = 0.5f*v0.planting_distance*v0.min_scale;
v0_max = 0.5f*v0.planting_distance*v0.max_scale;
v1_min = 0.5f*v1.planting_distance*v1.min_scale;
v1_max = 0.5f*v1.planting_distance*v1.max_scale;
}
{for(int side = 0;
side <= 1;
++side)
{
Device_Context::im_vertex(pw0, min_color);
Device_Context::im_vertex(pw1, min_color);
Device_Context::im_vertex(pw1 + v1_min*perp, min_color);
Device_Context::im_vertex(pw0, min_color);
Device_Context::im_vertex(pw1 + v1_min*perp, min_color);
Device_Context::im_vertex(pw0 + v0_min*perp, min_color);
Device_Context::im_vertex(pw0 + v0_min*perp, max_color);
Device_Context::im_vertex(pw1 + v1_min*perp, max_color);
Device_Context::im_vertex(pw1 + v1_max*perp, max_color);
Device_Context::im_vertex(pw0 + v0_min*perp, max_color);
Device_Context::im_vertex(pw1 + v1_max*perp, max_color);
Device_Context::im_vertex(pw0 + v0_max*perp, max_color);
perp = -perp;
}}
}}
}
}
VISOR_HANDLER(visor_grass_planting_distance_curve)
{
if(e->portable_type == &ptype_Grass)
{
Entity_Grass *grass = (Entity_Grass *)e;
draw_planting_curve(grass, PLANTING_CURVE_DISTANCE, settings.color[1], settings.color[2], 1.0f);
}
}
VISOR_HANDLER(visor_grass_planting_scale_curve)
{
if(e->portable_type == &ptype_Grass)
{
Entity_Grass *grass = (Entity_Grass *)e;
draw_planting_curve(grass, PLANTING_CURVE_SCALE, settings.color[1], settings.color[2], 0.25f);
}
}
VISOR_HANDLER(visor_light_probe_radius)
{
if(e->portable_type == &ptype_Light_Probe)
{
Entity_Light_Probe * light_probe = (Entity_Light_Probe *)e;
if (light_probe->filtered) {
float radius = 16;
uint color = argb_color(1, 0, 0, 0.2);
extern void draw_sphere_cheap_im(const Vector3 & center, float radius, unsigned long color);
draw_sphere_cheap_im(light_probe->position, radius, color);
}
}
}
enum Visor_Flag
{
VISOR_USES_MARKER_PASS = 0x1,
VISOR_USES_SOLIDS_PASS = 0x2,
};
struct Visor_Entry
{
int unsigned flags;
char *save_name;
char *long_name;
Visor_Handler *handler;
};
Visor_Entry visors[] =
{
// NOTE(casey): Entity generic
{VISOR_USES_MARKER_PASS, "entity_position", "entity position", visor_entity_position},
// NOTE(casey): Grass planting
{VISOR_USES_MARKER_PASS, "grass_planting_distribution", "grass planting distribution", visor_grass_planting_distribution},
{VISOR_USES_MARKER_PASS, "grass_planting_boundaries", "grass planting boundaries", visor_grass_planting_boundaries},
{VISOR_USES_SOLIDS_PASS, "grass_planting_area", "grass planting area", visor_grass_planting_area},
{VISOR_USES_SOLIDS_PASS, "grass_planting_distance_curve", "grass distance curve", visor_grass_planting_distance_curve},
{VISOR_USES_SOLIDS_PASS, "grass_planting_scale_curve", "grass scale curve", visor_grass_planting_scale_curve},
// NOTE(IC): Light probes
{VISOR_USES_SOLIDS_PASS, "light_probe_radius", "light probe radius", visor_light_probe_radius},
};
//
//
//
static int compare_names(const void *b1, const void *b2)
{
// TODO(casey): WTF doesn't qsort pass a user parameter?
Lister_Panel_Settings &s = world_panel->lister_panel->settings[world_panel->lister_panel->current_settings_index];
Entity *e1 = *(Entity **)b1;
Entity *e2 = *(Entity **)b2;
if(s.show_selected_first)
{
assert(selected_hash);
bool e1_selected = selected_hash->find(e1);
bool e2_selected = selected_hash->find(e2);
if(e1_selected && !e2_selected)
{
return -1;
}
else if(!e1_selected && e2_selected)
{
return 1;
}
}
if(s.show_groups_first)
{
if((e1->portable_type == &ptype_Group) && (e2->portable_type != &ptype_Group))
{
return -1;
}
else if((e1->portable_type != &ptype_Group) && (e2->portable_type == &ptype_Group))
{
return 1;
}
}
if (e1->entity_name == NULL)
{
if (e2->entity_name == NULL)
{
return 0;
}
return 1;
}
else if (e2->entity_name == NULL)
{
return -1;
}
assert(e1->entity_name && e2->entity_name);
return stricmp(e1->entity_name, e2->entity_name);
}
static void draw_element(Panel_Layout &layout, Entity *e, Hash_Table *expanded_hash, Lister_Panel_Settings ¤t_settings)
{
assert(selected_hash);
layout.row(1);
if(expanded_hash)
{
bool original_expanded = (expanded_hash->find(e) != 0);
bool expanded = original_expanded;
layout.expansion_button(&expanded);
if(original_expanded != expanded)
{
if(expanded)
{
expanded_hash->add(e, e);
}
else
{
expanded_hash->remove(e);
}
}
}
char const *portable_type_name = e->portable_type->name;
bool highlight = false;
bool is_a_group = false;
if(e->portable_type == &ptype_Group)
{
Entity_Group *group = (Entity_Group *)e;
highlight = !(group->runtime_only_flags & ER_MY_LAYER_IS_INACTIVE);
is_a_group = true;
if(group->is_a_layer)
{
portable_type_name = "Layer";
}
}
else
{
highlight = (selected_hash->find(e) != 0);
}
Panel_Name entity_pname;
if(e->entity_name)
{
entity_pname = pnamef("%s (%s)", e->entity_name, portable_type_name);
}
else
{
entity_pname = pnamef("#%d (%s)", e->portable_id, portable_type_name);
}
entity_pname.description = "";
if (layout.push_button(entity_pname, highlight))
{
Input_Devices *input = gamelib_render->input_devices;
bool ctrl_held = input->get_keyboard_button_state(Keyboard::KEY_CTRL);
bool shift_held = input->get_keyboard_button_state(Keyboard::KEY_SHIFT);
bool alt_held = input->get_keyboard_button_state(Keyboard::KEY_ALT);
if(is_a_group)
{
Entity_Group *group = (Entity_Group *)e;
// TODO(casey): Can we get rid of the flag_group_as_dirty
// stuff now? The comment indicates it was in there for
// the group-zero case, but in fact it must never have
// properly handled that case, because that was the bug I
// had to fix that time with detached things not being set
// to visible, etc...
flag_group_as_dirty(group->entity_manager, group->portable_id); // Force elements rebuild; @speed. Was necessary since if we set someone's group to 0 (or whatever), the group doesn't know it yet, and we still treat him as if he were in the group!
group->update_elements();
if(ctrl_held)
{
// Put everything in the selection into the current group.
Entity_Manager *manager = open_entity_manager();
Auto_Array <Entity *> selected;
get_selection_entities(&selected);
Entity *other;
Array_Foreach(&selected, other)
{
flag_group_as_dirty(manager, other->group_id);
other->group_id = group->portable_id;
note_change("Changed Layer", other);
}
if(selected.items)
{
flag_group_as_dirty(manager, group->portable_id);
}
}
else if(alt_held)
{
begin_selection_change();
if (!shift_held) set_selection(0);
group->update_elements();
int id;
Array_Foreach(&group->elements, id)
{
add_to_selection(id);
}
end_selection_change();
}
else
{
change_group_visibility(group, !group->current_group_visibility);
}
}
else
{
if(shift_held)
{
toggle_selection(e->portable_id);
}
else if(ctrl_held)
{
Auto_Array <Entity *> temp;
temp.add(e);
Bounding_Box box;
Vector3 focus = get_center(&box, &temp);
set_camera_to_view_box(box);
}
else
{
set_selection(e->portable_id);
}
}
}
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
if(current_settings.special_button[special_index])
{
Special_Filter_Entry &entry = special_filters[special_index];
int handler_result = NOT_APPLICABLE;
entry.handler(entry.param, e, false, false, &handler_result);
if(handler_result == NOT_APPLICABLE)
{
layout.nub(pname(entry.short_name));
}
else
{
if(layout.push_button(pname(entry.short_name, entry.short_name, "", entry.positive_name), handler_result))
{
note_operation_name(handler_result ? entry.negated_name : entry.positive_name);
int ignored;
entry.handler(entry.param, e, true, !handler_result, &ignored);
}
}
}
}}
}
static void draw_element_recursive(Panel_Layout &layout, Entity *e, Hash_Table &viewing_present, Hash_Table &expanded, Lister_Panel_Settings ¤t_settings)
{
Auto_Array<Entity *> sub_entities;
if(e->portable_type == &ptype_Group)
{
Entity_Group *group = (Entity_Group *)e;
Foreach(int id, group->elements)
{
Entity *sub_e = group->entity_manager->find(id);
if(sub_e && viewing_present.find(sub_e))
{
sub_entities.add(sub_e);
}
}
}
draw_element(layout, e, sub_entities.items ? &expanded : 0, current_settings);
if(sub_entities.items && (expanded.find(e)))
{
layout.push_indent();
// TODO(casey): Sort groups first when set
qsort(sub_entities.data, sub_entities.items, sizeof(sub_entities.data[0]), compare_names);
Foreach(Entity *sub_e, sub_entities)
{
draw_element_recursive(layout, sub_e, viewing_present, expanded, current_settings);
}
layout.pop_indent();
}
}
static bool passes_filter(Lister_Panel_Settings &settings, Entity *e)
{
bool result = false;
if(e)
{
bool passes_type = settings.include_type[e->portable_type->serial_number_in_memory];
bool passes_special = true;
{for(int special_index = 0;
passes_special && (special_index < NV_ARRAY_SIZE(special_filters));
++special_index)
{
Special_Filter_Entry &special = special_filters[special_index];
assert(special_index < NV_ARRAY_SIZE(settings.special));
int filter_mode = settings.special[special_index];
switch(filter_mode)
{
case LISTER_FILTER_MODE_OFF:
{
// NOTE(casey): Ignored
} break;
case LISTER_FILTER_MODE_POSITIVE:
{
int handler_result = NOT_APPLICABLE;
special.handler(special.param, e, 0, false, &handler_result);
if(handler_result != NOT_APPLICABLE)
{
passes_special = (handler_result != 0);
}
} break;
case LISTER_FILTER_MODE_NEGATIVE:
{
int handler_result = NOT_APPLICABLE;
special.handler(special.param, e, 0, false, &handler_result);
if(handler_result != NOT_APPLICABLE)
{
passes_special = (handler_result == 0);
}
} break;
default:
{
assert(!"Unrecognized special filter mode");
} break;
}
}}
bool passes_distance = true;
if((settings.max_distance_from_cursor != PSEUDO_FLT_MAX) || (settings.max_distance_from_camera != PSEUDO_FLT_MAX) || (settings.max_distance_from_focus != PSEUDO_FLT_MAX))
{
Movement_Panel *mp = ::world_panel->movement_panel;
Vector3 sphere_center = get_sphere_center_in_worldspace(e);
float r = get_sphere_radius_in_worldspace(e);
float cursor_distance2 = distance_squared(sphere_center, mp->cursor_position);
float camera_distance2 = distance_squared(sphere_center, globals.camera_position);
float focus_distance2 = distance_squared(sphere_center, get_camera_focus());
passes_distance = ((cursor_distance2 < square(settings.max_distance_from_cursor + r)) && (camera_distance2 < square(settings.max_distance_from_camera + r)) && (focus_distance2 < square(settings.max_distance_from_focus + r)));
}
result = (passes_type && passes_special && passes_distance);
}
return(result);
}
static void set_all_types_to(Lister_Panel_Settings &settings, bool value)
{
{for(int type_index = 0;
type_index < shared_globals.portable_type_manager->types.items;
++type_index)
{
settings.include_type[type_index] = value;
}}
}
static void set_special(Lister_Panel_Settings &settings, Special_Filter *handler, int mode, bool button_on)
{
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
if(special_filters[special_index].handler == handler)
{
settings.special[special_index] = mode;
settings.special_button[special_index] = button_on;
}
}}
}
static void unpack_selection(Hash_Table &selected)
{
Entity_Manager *manager = open_entity_manager();
Foreach(int id, selected_portable_ids)
{
Entity *e = manager->find(id);
if(e)
{
selected.add(e, e);
}
}
}
Lister_Panel::Lister_Panel() : Panel(PANEL_TAG_WORLD), expanded(13729)
{
set_default_location(0, 1, 150, -6, 0, 1, 150, -6);
title = "Lister";
collapsed = true;
my_width = 300;
EDITOR_VAR(show_display_section, false);
EDITOR_VAR(show_included_types_section, false);
EDITOR_VAR(show_spatial_filters_section, false);
EDITOR_VAR(show_special_filters_section, false);
EDITOR_VAR(show_visors_section, false);
EDITOR_VAR(visor_brightness, Panel_Layout::get_default_overlay_brightness());
EDITOR_VAR(included_types_scroll, 0.0f);
EDITOR_VAR(special_filters_scroll, 0.0f);
EDITOR_VAR(visors_scroll, 0.0f);
EDITOR_VAR(entities_scroll, 0.0f);
EDITOR_VAR(current_settings_index, 0);
int type_count = shared_globals.portable_type_manager->types.items;
//
// NOTE(casey): Clear all settings to defaults
//
{for(int settings_index = 0;
settings_index < NV_ARRAY_SIZE(settings);
++settings_index)
{
Lister_Panel_Settings &this_settings = settings[settings_index];
sprintf(this_settings.long_name, "unused"/* , settings_index */);
sprintf(this_settings.short_name, "unu"/* , settings_index */);
sprintf(this_settings.description, "(no description)"/* , settings_index */);
this_settings.pull_groups_in = false;
this_settings.show_groups_first = false;
this_settings.show_selected_first = false;
this_settings.show_hierarchically = true;
this_settings.show_when_collapsed = false;
this_settings.type_mode = LISTER_TYPE_MODE_LIST;
this_settings.update_automatically = true;
this_settings.max_distance_from_cursor = PSEUDO_FLT_MAX;
this_settings.max_distance_from_camera = PSEUDO_FLT_MAX;
this_settings.max_distance_from_focus = PSEUDO_FLT_MAX;
assert(type_count <= NV_ARRAY_SIZE(this_settings.include_type));
{for(int type_index = 0;
type_index < type_count;
++type_index)
{
this_settings.include_type[type_index] = false;
}}
assert(NV_ARRAY_SIZE(special_filters) <= NV_ARRAY_SIZE(this_settings.special));
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
this_settings.special[special_index] = LISTER_FILTER_MODE_OFF;
this_settings.special_button[special_index] = false;
}}
assert(NV_ARRAY_SIZE(visors) <= NV_ARRAY_SIZE(this_settings.visors));
{for(int visor_index = 0;
visor_index < NV_ARRAY_SIZE(visors);
++visor_index)
{
Visor_Settings &v = this_settings.visors[visor_index];
v.enabled = false;
v.scale = 1.0f;
assert(NV_ARRAY_SIZE(v.color) == 8);
v.color[0] = argb_color(0, 0, 0, 1);
v.color[1] = argb_color(1, 0, 0, 1);
v.color[2] = argb_color(0, 1, 0, 1);
v.color[3] = argb_color(0, 0, 1, 1);
v.color[4] = argb_color(1, 1, 0, 1);
v.color[5] = argb_color(0, 1, 1, 1);
v.color[6] = argb_color(1, 0, 1, 1);
v.color[7] = argb_color(1, 1, 1, 1);
}}
}}
//
// NOTE(casey): Set a few slots to example filters that people can use right
// of the bat before they start customizing
//
{
Lister_Panel_Settings &s = settings[0];
sprintf(s.long_name, "layers");
sprintf(s.short_name, "lay");
sprintf(s.description, "emulates the old layer lister, but with proper scrolling");
s.pull_groups_in = false;
s.show_groups_first = false;
s.show_hierarchically = false;
s.show_when_collapsed = true;
s.include_type[ptype_Group.serial_number_in_memory] = true;
set_special(s, filter_groups_must_be_layers, LISTER_FILTER_MODE_POSITIVE, false);
set_special(s, filter_selected, LISTER_FILTER_MODE_OFF, true);
}
{
Lister_Panel_Settings &s = settings[1];
sprintf(s.long_name, "groups");
sprintf(s.short_name, "grp");
sprintf(s.description, "new-school hierarchical layer/group lister");
s.pull_groups_in = false;
s.show_groups_first = false;
s.show_hierarchically = true;
s.show_when_collapsed = true;
s.include_type[ptype_Group.serial_number_in_memory] = true;
set_special(s, filter_selected, LISTER_FILTER_MODE_OFF, true);
}
{
Lister_Panel_Settings &s = settings[2];
sprintf(s.long_name, "flat selected");
sprintf(s.short_name, "fsl");
sprintf(s.description, "shows selected entities as a flat list");
s.pull_groups_in = false;
s.show_groups_first = false;
s.show_hierarchically = false;
s.show_when_collapsed = true;
set_all_types_to(s, true);
set_special(s, filter_selected, LISTER_FILTER_MODE_POSITIVE, false);
set_special(s, filter_hidden, LISTER_FILTER_MODE_OFF, true);
}
{
Lister_Panel_Settings &s = settings[3];
sprintf(s.long_name, "selected");
sprintf(s.short_name, "sel");
sprintf(s.description, "shows selected entities within their hierarchies");
s.pull_groups_in = true;
s.show_groups_first = false;
s.show_hierarchically = true;
s.show_when_collapsed = true;
set_all_types_to(s, true);
set_special(s, filter_selected, LISTER_FILTER_MODE_POSITIVE, false);
set_special(s, filter_hidden, LISTER_FILTER_MODE_OFF, true);
}
[REDACTED]
{
Lister_Panel_Settings &s = settings[5];
sprintf(s.long_name, "by type");
sprintf(s.short_name, "typ");
sprintf(s.description, "flat list of entities of the current entity panel type");
s.pull_groups_in = false;
s.show_groups_first = false;
s.show_hierarchically = false;
s.show_when_collapsed = true;
s.type_mode = LISTER_TYPE_MODE_ENTITY_PANEL;
set_all_types_to(s, true);
set_special(s, filter_selected, LISTER_FILTER_MODE_OFF, true);
set_special(s, filter_hidden, LISTER_FILTER_MODE_OFF, true);
}
{
Lister_Panel_Settings &s = settings[6];
sprintf(s.long_name, "camera group");
sprintf(s.short_name, "cmg");
sprintf(s.description, "shows all groups near to the camera");
s.pull_groups_in = true;
s.show_groups_first = false;
s.show_hierarchically = true;
s.show_when_collapsed = true;
s.include_type[ptype_Group.serial_number_in_memory] = true;
s.max_distance_from_camera = 50.0f;
set_special(s, filter_selected, LISTER_FILTER_MODE_OFF, true);
set_special(s, filter_hidden, LISTER_FILTER_MODE_OFF, true);
}
{
Lister_Panel_Settings &s = settings[7];
sprintf(s.long_name, "camera all");
sprintf(s.short_name, "cma");
sprintf(s.description, "shows all entities near to the camera");
s.pull_groups_in = true;
s.show_groups_first = true;
s.show_hierarchically = true;
set_all_types_to(s, true);
s.max_distance_from_camera = 50.0f;
set_special(s, filter_selected, LISTER_FILTER_MODE_OFF, true);
set_special(s, filter_hidden, LISTER_FILTER_MODE_OFF, true);
}
//
// NOTE(casey): Now, overwrite everything with the user's preferences if they exist
//
{for(int settings_index = 0;
settings_index < NV_ARRAY_SIZE(settings);
++settings_index)
{
Lister_Panel_Settings &this_settings = settings[settings_index];
this_settings.long_name_reader = this_settings.long_name;
this_settings.short_name_reader = this_settings.short_name;
this_settings.description_reader = this_settings.description;
EDITOR_VAR_INDEXED(&this_settings.long_name_reader, "long_name", settings_index);
EDITOR_VAR_INDEXED(&this_settings.short_name_reader, "short_name", settings_index);
EDITOR_VAR_INDEXED(&this_settings.description_reader, "description", settings_index);
safe_copy_and_redirect(this_settings.long_name, this_settings.long_name_reader, sizeof(this_settings.long_name));
safe_copy_and_redirect(this_settings.short_name, this_settings.short_name_reader, sizeof(this_settings.short_name));
safe_copy_and_redirect(this_settings.description, this_settings.description_reader, sizeof(this_settings.description));
EDITOR_VAR_INDEXED(&this_settings.pull_groups_in, "pull_groups_in", settings_index);
EDITOR_VAR_INDEXED(&this_settings.show_groups_first, "show_groups_first", settings_index);
EDITOR_VAR_INDEXED(&this_settings.show_selected_first, "show_selected_first", settings_index);
EDITOR_VAR_INDEXED(&this_settings.show_hierarchically, "show_hierarchically", settings_index);
EDITOR_VAR_INDEXED(&this_settings.show_when_collapsed, "show_when_collapsed", settings_index);
EDITOR_VAR_INDEXED((int *)&this_settings.type_mode, "type_mode", settings_index);
EDITOR_VAR_INDEXED(&this_settings.update_automatically, "update_automatically", settings_index);
EDITOR_VAR_INDEXED(&this_settings.max_distance_from_cursor, "max_distance_from_cursor", settings_index);
EDITOR_VAR_INDEXED(&this_settings.max_distance_from_camera, "max_distance_from_camera", settings_index);
EDITOR_VAR_INDEXED(&this_settings.max_distance_from_focus, "max_distance_from_focus", settings_index);
{for(int type_index = 0;
type_index < type_count;
++type_index)
{
EDITOR_VAR_SUBNAME(&this_settings.include_type[type_index], "include_type", settings_index, (char *)shared_globals.portable_type_manager->types[type_index]->name);
}}
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
EDITOR_VAR_SUBNAME(&this_settings.special[special_index], "special", settings_index, special_filters[special_index].save_name);
EDITOR_VAR_SUBNAME(&this_settings.special_button[special_index], "special_button", settings_index, special_filters[special_index].save_name);
}}
{for(int visor_index = 0;
visor_index < NV_ARRAY_SIZE(visors);
++visor_index)
{
Visor_Settings &v = this_settings.visors[visor_index];
EDITOR_VAR_SUBNAME(&v.enabled, "visor_enabled", settings_index, visors[visor_index].save_name);
EDITOR_VAR_SUBNAME(&v.scale, "visor_scale", settings_index, visors[visor_index].save_name);
#if 0
// TODO(casey): Persist colors
EDITOR_VAR_SUBNAME(&v.color[0], "visor_color_0", settings_index, visors[visor_index].save_name);
EDITOR_VAR_SUBNAME(&v.color[1], "visor_color_1", settings_index, visors[visor_index].save_name);
EDITOR_VAR_SUBNAME(&v.color[2], "visor_color_2", settings_index, visors[visor_index].save_name);
EDITOR_VAR_SUBNAME(&v.color[3], "visor_color_3", settings_index, visors[visor_index].save_name);
EDITOR_VAR_SUBNAME(&v.color[4], "visor_color_4", settings_index, visors[visor_index].save_name);
EDITOR_VAR_SUBNAME(&v.color[5], "visor_color_5", settings_index, visors[visor_index].save_name);
EDITOR_VAR_SUBNAME(&v.color[6], "visor_color_6", settings_index, visors[visor_index].save_name);
EDITOR_VAR_SUBNAME(&v.color[7], "visor_color_7", settings_index, visors[visor_index].save_name);
#endif
}}
}}
EDITOR_STANDARD_PANEL_VARS;
}
Lister_Panel::~Lister_Panel()
{
}
void Lister_Panel::update()
{
Panel::update();
buttons_active = ::world_panel->buttons_active; // @Refactor: backwards data flow.
// NOTE(casey): Unpack selection to avoid n^2 lookups during tests
// TODO(casey): Why can't I just _clear_ the hash table?
if(selected_hash)
{
delete selected_hash;
}
selected_hash = new Hash_Table(13729);
assert(selected_hash);
unpack_selection(*selected_hash);
Lister_Panel_Settings ¤t_settings = settings[current_settings_index];
if(current_settings.type_mode == LISTER_TYPE_MODE_ENTITY_PANEL)
{
Entity_Panel *ep = ::world_panel->entity_panel;
set_all_types_to(current_settings, false);
assert(ep->current_type >= 0);
assert(ep->current_type < NV_ARRAY_SIZE(current_settings.include_type));
current_settings.include_type[ep->current_type] = true;
}
if(current_settings.type_mode == LISTER_TYPE_MODE_SELECTION)
{
set_all_types_to(current_settings, false);
}
if(current_settings.update_automatically)
{
Entity_Manager *manager = open_entity_manager();
viewing_ids.reset();
Foreach(Entity *e, manager->all_entities)
{
if(current_settings.type_mode == LISTER_TYPE_MODE_SELECTION)
{
if(selected_hash->find(e))
{
current_settings.include_type[e->portable_type->serial_number_in_memory] = true;
}
}
if(passes_filter(current_settings, e))
{
viewing_ids.add(e->portable_id);
}
}
}
}
void Lister_Panel::draw_3d_im_stuff(const Viewpoint * vp)
{
Lister_Panel_Settings ¤t_settings = settings[current_settings_index];
// NOTE(casey): Build the lists of active visors by type of visor pass
Auto_Array<int> marker_visors;
Auto_Array<int> solids_visors;
{for(int visor_index = 0;
visor_index < NV_ARRAY_SIZE(visors);
++visor_index)
{
if(current_settings.visors[visor_index].enabled)
{
if(visors[visor_index].flags & VISOR_USES_MARKER_PASS)
{
marker_visors.add(visor_index);
}
if(visors[visor_index].flags & VISOR_USES_SOLIDS_PASS)
{
solids_visors.add(visor_index);
}
}
}}
// NOTE(casey): Draw all visors that do textured marker triangles
if(marker_visors.items)
{
Resource_Ptr<Texture_Map> map(acquire_texture(0, "ui_circle_marker"), 0);
setup_standard_soft_z(vp, true, visor_brightness);
Device_Context::set_tex_parameter("texture_map", &*map);
Device_Context::im_begin();
Entity_Manager *manager = open_entity_manager();
Foreach(int id, viewing_ids)
{
Entity *e = manager->find(id);
if(e)
{
Foreach(int visor_index, marker_visors)
{
visors[visor_index].handler(current_settings.visors[visor_index], e);
}
}
}
Device_Context::im_flush();
}
// NOTE(casey): Draw all visors that do untextured triangles
if(solids_visors.items)
{
setup_standard_soft_z(vp, /* textured= */false);
Device_Context::im_begin();
Entity_Manager *manager = open_entity_manager();
Foreach(int id, viewing_ids)
{
Entity *e = manager->find(id);
if(e)
{
Foreach(int visor_index, solids_visors)
{
visors[visor_index].handler(current_settings.visors[visor_index], e);
}
}
}
Device_Context::im_flush();
}
}
void Lister_Panel::draw_2d_stuff()
{
Input_Devices *input = gamelib_render->input_devices;
bool ctrl_held = input->get_keyboard_button_state(Keyboard::KEY_CTRL);
if(collapsed)
{
my_width = 300;
}
else
{
my_width = 600;
}
rendering_2d_right_handed();
Panel_Layout layout(this, x, y, my_width, true);
Lister_Panel_Settings ¤t_settings = settings[current_settings_index];
layout.window_title(pname(current_settings.long_name));
int disp_index = 0;
{for(int settings_index = 0;
settings_index < NV_ARRAY_SIZE(settings);
++settings_index)
{
Lister_Panel_Settings &s = settings[settings_index];
if(!collapsed || s.show_when_collapsed)
{
if((disp_index % 6) == 0)
{
layout.row(6);
}
if(layout.push_button(pname(s.long_name, s.short_name, "", s.description), (current_settings_index == settings_index)))
{
if(ctrl_held)
{
s = current_settings;
}
else
{
current_settings_index = settings_index;
}
}
++disp_index;
}
}}
if(!collapsed)
{
if(layout.section_title(pname("display"), &show_display_section))
{
layout.row(1, pname("long name"));
layout.text_edit(sizeof(current_settings.long_name), current_settings.long_name);
layout.row(1, pname("short name"));
layout.text_edit(sizeof(current_settings.short_name), current_settings.short_name);
layout.row(1, pname("description"));
layout.text_edit(sizeof(current_settings.description), current_settings.description);
layout.row(4, pname("list options"));
layout.bool_button(pname("pull groups", "pgp", "", "any selected entity automatically includes all its parent groups in the listing"), ¤t_settings.pull_groups_in);
layout.bool_button(pname("groups first", "gpf", "", "list groups first, then entities, instead of everything being in alphabetical order"), ¤t_settings.show_groups_first);
layout.bool_button(pname("sel first", "sfr", "", "list selected items first, then everything else"), ¤t_settings.show_selected_first);
layout.bool_button(pname("hierarchical", "har", "", "list in hierarchical format, instead of flat"), ¤t_settings.show_hierarchically);
layout.row(2, pname("misc options"));
layout.bool_button(pname("show when collapsed", "swc", "", "show a button for this filter in the collapsed panel"), ¤t_settings.show_when_collapsed);
layout.bool_button(pname("auto update", "aup", "", "automatically update the contents of the lister as edits are made to the world"), ¤t_settings.update_automatically);
}
if(layout.section_title(pname("included types"), &show_included_types_section))
{
layout.row(3, pname("type from"));
layout.mutex_button(pname("list", "lst", "", "set the type inclusion manually from a list of all possible types"), (int *)¤t_settings.type_mode, LISTER_TYPE_MODE_LIST);
layout.mutex_button(pname("selection", "tfs", "", "set the type inclusion automatically to the set of types in the current selection"), (int *)¤t_settings.type_mode, LISTER_TYPE_MODE_SELECTION);
layout.mutex_button(pname("entity inst", "tfi", "", "set the type inclusion automatically from the current instatiation type of the entity panel"), (int *)¤t_settings.type_mode, LISTER_TYPE_MODE_ENTITY_PANEL);
if(current_settings.type_mode == LISTER_TYPE_MODE_LIST)
{
layout.row(2, pname("preset", "pre", "", "quickly enable or disable a preset list of types"));
if(layout.push_button(pname("none", "nne", "", "include no types")))
{
set_all_types_to(current_settings, false);
}
if(layout.push_button(pname("all", "all", "", "include all types")))
{
set_all_types_to(current_settings, true);
}
layout.begin_scrollable_section(&included_types_scroll);
{for(int type_index = 0;
type_index < shared_globals.portable_type_manager->types.items;
++type_index)
{
if((type_index % 2) == 0)
{
layout.row(2);
}
layout.bool_button(pname((char *)shared_globals.portable_type_manager->types[type_index]->name), ¤t_settings.include_type[type_index]);
}}
layout.end_scrollable_section();
}
}
if(layout.section_title(pname("spatial filters"), &show_spatial_filters_section))
{
layout.float_mutex_control(pname("cursor dist", "crd", "", "sets the maximum distance from the cursor an entity can be before it is excluded from the listing"), ¤t_settings.max_distance_from_cursor, "10", 10.0f, "25", 25.0f, "50", 50.0f, "inf", PSEUDO_FLT_MAX);
layout.float_mutex_control(pname("camera dist", "cmd", "", "sets the maximum distance from the camera an entity can be before it is excluded from the listing"), ¤t_settings.max_distance_from_camera, "10", 10.0f, "25", 25.0f, "50", 50.0f, "inf", PSEUDO_FLT_MAX);
layout.float_mutex_control(pname("focus dist", "foc", "", "sets the maximum distance from the camera focus an entity can be before it is excluded from the listing"), ¤t_settings.max_distance_from_focus, "10", 10.0f, "25", 25.0f, "50", 50.0f, "inf", PSEUDO_FLT_MAX);
}
if(layout.section_title(pname("parameter filters and operations"), &show_special_filters_section))
{
layout.row(2, pname("preset", "pre", "", "quickly enable or disable a preset list of filters"));
if(layout.push_button(pname("no filters", "nfl", "", "apply no parameter filters")))
{
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
current_settings.special[special_index] = LISTER_FILTER_MODE_OFF;
}}
}
if(layout.push_button(pname("no operations", "nop", "", "show no operation columns")))
{
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
current_settings.special_button[special_index] = false;
}}
}
layout.begin_scrollable_section(&special_filters_scroll);
layout.begin_row_template();
layout.row_template_variable_column();
layout.row_template_fixed_column(layout.default_nub_width);
layout.row_template_variable_column();
layout.row_template_fixed_column(layout.default_nub_width);
layout.end_and_set_row_template();
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
if((special_index % 2) == 0)
{
layout.row();
}
Special_Filter_Entry &entry = special_filters[special_index];
int &value = current_settings.special[special_index];
if(layout.push_button((value == LISTER_FILTER_MODE_NEGATIVE) ? pname(entry.negated_name) : pname(entry.positive_name), value) != LISTER_FILTER_MODE_OFF)
{
value = ((value + 1) % LISTER_FILTER_MODE_COUNT);
}
layout.bool_button(pname(entry.short_name), ¤t_settings.special_button[special_index]);
}}
layout.clear_row_template();
layout.end_scrollable_section();
}
if(layout.section_title(pname("visors"), &show_visors_section))
{
layout.overlay_brightness_control(&visor_brightness);
layout.row(1, pname("preset", "pre", "", "quickly enable or disable a preset list of visors"));
if(layout.push_button(pname("none", "nne", "", "show no visors")))
{
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
current_settings.visors[special_index].enabled = false;
}}
}
layout.begin_scrollable_section(&visors_scroll);
{for(int visor_index = 0;
visor_index < NV_ARRAY_SIZE(visors);
++visor_index)
{
if((visor_index % 2) == 0)
{
layout.row(2);
}
Visor_Entry &entry = visors[visor_index];
layout.bool_button(pname(entry.long_name), ¤t_settings.visors[visor_index].enabled);
}}
layout.end_scrollable_section();
}
}
//
//
//
Entity_Manager *manager = open_entity_manager();
Hash_Table viewing_present(13729);
Auto_Array<Entity *> top_level;
// NOTE(casey): Unpack listed entities to avoid n^2 lookups during hierarchy walk,
// and determine who is at the top level
int special_states[NV_ARRAY_SIZE(current_settings.special)] = {0};
Foreach(int id, viewing_ids)
{
Entity *e = manager->find(id);
while(e && !viewing_present.find(e))
{
viewing_present.add(e, e);
if(!current_settings.show_hierarchically || (e->group_id == 0))
{
top_level.add(e);
}
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
Special_Filter_Entry &entry = special_filters[special_index];
int handler_result = NOT_APPLICABLE;
entry.handler(entry.param, e, 0, false, &handler_result);
if(handler_result != NOT_APPLICABLE)
{
special_states[special_index] |= (handler_result ? 2 : 1);
}
}}
if(current_settings.pull_groups_in && e->group_id)
{
e = manager->find(e->group_id);
}
else
{
e = 0;
}
}
}
layout.section_title(pname("listing"));
#if 0
layout.row(4, pname("expand", "exp", "", "quickly expand or collapse multiple entries"));
layout.push_button(pname("none", "nne", "", "collapse everything"));
layout.push_button(pname("layers", "lay", "", "expand all layer entities"));
layout.push_button(pname("groups", "grp", "", "expand all group entities"));
layout.push_button(pname("all", "all", "", "expand everything"));
#endif
layout.begin_row_template();
layout.row_template_variable_column();
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
if(current_settings.special_button[special_index])
{
Special_Filter_Entry &entry = special_filters[special_index];
layout.row_template_fixed_column(layout.default_nub_width);
}
}}
layout.row_template_fixed_column(layout.default_scroll_bar_width);
layout.end_and_set_row_template();
layout.row(0, pname("entity name", "entity name", "", ""));
{for(int special_index = 0;
special_index < NV_ARRAY_SIZE(special_filters);
++special_index)
{
if(current_settings.special_button[special_index])
{
Special_Filter_Entry &entry = special_filters[special_index];
bool current_state = (special_states[special_index] == 2);
if(layout.push_button(pname(entry.short_name, entry.short_name, "", entry.positive_name), current_state))
{
note_operation_name(current_state ? entry.negated_name : entry.positive_name);
Foreach(int id, viewing_ids)
{
Entity *e = manager->find(id);
if(e)
{
int ignored;
entry.handler(entry.param, e, true, !current_state, &ignored);
}
}
}
}
}}
layout.nub(pname(""));
layout.begin_scrollable_section(&entities_scroll, collapsed ? 600 : 300);
// NOTE(casey): If walking hierarchically, make sure we move everything
// to the top level that would not be reached by the in-order walk
if(current_settings.show_hierarchically)
{
Foreach(int id, viewing_ids)
{
Entity *e = manager->find(id);
if(e && (e->group_id != 0))
{
Entity *group = manager->find(e->group_id);
if(!group || !viewing_present.find(group))
{
top_level.add(e);
}
}
}
}
qsort(top_level.data, top_level.items, sizeof(top_level.data[0]), compare_names);
Foreach(Entity *e, top_level)
{
if(current_settings.show_hierarchically)
{
draw_element_recursive(layout, e, viewing_present, expanded, current_settings);
}
else
{
draw_element(layout, e, 0, current_settings);
}
}
layout.end_scrollable_section();
layout.clear_row_template();
layout.complete();
}
#endif // ALLOW_DEVELOPER