exposing clicks to custom api

master
Allen Webster 2016-06-10 19:46:30 -04:00
parent 5c108dcbcb
commit 180b4016d1
8 changed files with 294 additions and 164 deletions

View File

@ -178,6 +178,7 @@ enum Command_ID{
cmdid_seek_right,
cmdid_center_view,
cmdid_left_adjust_view,
cmdid_word_complete,
@ -312,6 +313,9 @@ struct View_Summary{
float line_height;
int unwrapped_lines;
int show_whitespace;
i32_Rect file_region;
GUI_Scroll_Vars scroll_vars;
};
inline View_Summary
view_summary_zero(){

View File

@ -30,6 +30,7 @@
#define GET_USER_INPUT_SIG(n) User_Input n(Application_Links *app, unsigned int get_type, unsigned int abort_type)
#define GET_COMMAND_INPUT_SIG(n) User_Input n(Application_Links *app)
#define GET_EVENT_MESSAGE_SIG(n) Event_Message n(Application_Links *app)
#define GET_MOUSE_STATE_SIG(n) Mouse_State n(Application_Links *app)
#define START_QUERY_BAR_SIG(n) int n(Application_Links *app, Query_Bar *bar, unsigned int flags)
#define END_QUERY_BAR_SIG(n) void n(Application_Links *app, Query_Bar *bar, unsigned int flags)
#define PRINT_MESSAGE_SIG(n) void n(Application_Links *app, char *string, int len)
@ -71,6 +72,7 @@ extern "C"{
typedef GET_USER_INPUT_SIG(Get_User_Input_Function);
typedef GET_COMMAND_INPUT_SIG(Get_Command_Input_Function);
typedef GET_EVENT_MESSAGE_SIG(Get_Event_Message_Function);
typedef GET_MOUSE_STATE_SIG(Get_Mouse_State_Function);
typedef START_QUERY_BAR_SIG(Start_Query_Bar_Function);
typedef END_QUERY_BAR_SIG(End_Query_Bar_Function);
typedef PRINT_MESSAGE_SIG(Print_Message_Function);
@ -115,6 +117,7 @@ struct Application_Links{
Get_User_Input_Function *get_user_input;
Get_Command_Input_Function *get_command_input;
Get_Event_Message_Function *get_event_message;
Get_Mouse_State_Function *get_mouse_state;
Start_Query_Bar_Function *start_query_bar;
End_Query_Bar_Function *end_query_bar;
Print_Message_Function *print_message;

View File

@ -317,6 +317,12 @@ default_keys(Bind_Helper *context){
// It is possible to override this binding for individual keys.
bind_vanilla_keys(context, write_character);
// NOTE(allen|a4.0.7): You can now bind left and right clicks.
// They only trigger on mouse presses. Modifiers do work
// so control+click shift+click etc can now have special meanings.
bind(context, key_mouse_left, MDFR_NONE, click_set_cursor);
bind(context, key_mouse_right, MDFR_NONE, click_set_mark);
bind(context, key_left, MDFR_NONE, move_left);
bind(context, key_right, MDFR_NONE, move_right);
bind(context, key_del, MDFR_NONE, delete_char);
@ -345,6 +351,7 @@ default_keys(Bind_Helper *context){
bind(context, 'c', MDFR_CTRL, cmdid_copy);
bind(context, 'd', MDFR_CTRL, delete_range);
bind(context, 'e', MDFR_CTRL, cmdid_center_view);
bind(context, 'E', MDFR_CTRL, cmdid_left_adjust_view);
bind(context, 'f', MDFR_CTRL, search);
bind(context, 'g', MDFR_CTRL, goto_line);
bind(context, 'h', MDFR_CTRL, cmdid_history_backward);

View File

@ -113,6 +113,63 @@ CUSTOM_COMMAND_SIG(delete_range){
// Basic Navigation
//
int
get_relative_xy(View_Summary *view, int x, int y, float *x_out, float *y_out){
int result = false;
i32_Rect region = view->file_region;
int max_x = (region.x1 - region.x0);
int max_y = (region.y1 - region.y0);
GUI_Scroll_Vars scroll_vars = view->scroll_vars;
int rx = x - region.x0;
int ry = y - region.y0;
if (ry >= 0){
if (rx >= 0 && rx < max_x && ry >= 0 && ry < max_y){
result = 1;
}
}
*x_out = (float)rx + scroll_vars.scroll_x;
*y_out = (float)ry + scroll_vars.scroll_y;
return(result);
}
CUSTOM_COMMAND_SIG(click_set_cursor){
View_Summary view = app->get_active_view(app);
// TODO(allen): Need a better system for
// weeding out views in a hidden state.
if (view.locked_buffer_id != 0){
Mouse_State mouse = app->get_mouse_state(app);
float rx = 0, ry = 0;
if (get_relative_xy(&view, mouse.x, mouse.y, &rx, &ry)){
app->view_set_cursor(app, &view,
seek_xy(rx, ry, true,
view.unwrapped_lines),
true);
}
}
}
CUSTOM_COMMAND_SIG(click_set_mark){
View_Summary view = app->get_active_view(app);
if (view.locked_buffer_id != 0){
Mouse_State mouse = app->get_mouse_state(app);
float rx = 0, ry = 0;
if (get_relative_xy(&view, mouse.x, mouse.y, &rx, &ry)){
app->view_set_mark(app, &view,
seek_xy(rx, ry, true,
view.unwrapped_lines)
);
}
}
}
inline void
move_vertical(Application_Links *app, float line_multiplier){
View_Summary view = app->get_active_view(app);

317
4ed.cpp
View File

@ -57,6 +57,108 @@ struct Command_Data{
Partition part;
};
enum Input_Types{
Input_AnyKey,
Input_Esc,
Input_MouseMove,
Input_MouseLeftButton,
Input_MouseRightButton,
Input_MouseWheel,
Input_Count
};
struct Consumption_Record{
b32 consumed;
char consumer[32];
};
struct Available_Input{
Key_Summary *keys;
Mouse_State *mouse;
Consumption_Record records[Input_Count];
};
Available_Input
init_available_input(Key_Summary *keys, Mouse_State *mouse){
Available_Input result = {0};
result.keys = keys;
result.mouse = mouse;
return(result);
}
Key_Summary
direct_get_key_data(Available_Input *available){
Key_Summary result = *available->keys;
return(result);
}
Mouse_State
direct_get_mouse_state(Available_Input *available){
Mouse_State result = *available->mouse;
return(result);
}
Key_Summary
get_key_data(Available_Input *available){
Key_Summary result = {0};
if (!available->records[Input_AnyKey].consumed){
result = *available->keys;
}
else if (!available->records[Input_Esc].consumed){
i32 i = 0;
i32 count = available->keys->count;
Key_Event_Data key = {0};
for (i = 0; i < count; ++i){
key = get_single_key(available->keys, i);
if (key.keycode == key_esc){
result.count = 1;
result.keys[0] = key;
break;
}
}
}
return(result);
}
Mouse_State
get_mouse_state(Available_Input *available){
Mouse_State mouse = *available->mouse;
if (available->records[Input_MouseLeftButton].consumed){
mouse.l = 0;
mouse.press_l = 0;
mouse.release_l = 0;
}
if (available->records[Input_MouseRightButton].consumed){
mouse.r = 0;
mouse.press_r = 0;
mouse.release_r = 0;
}
if (available->records[Input_MouseWheel].consumed){
mouse.wheel = 0;
}
return(mouse);
}
void
consume_input(Available_Input *available, i32 input_type, char *consumer){
Consumption_Record *record = &available->records[input_type];
record->consumed = 1;
if (consumer){
String str = make_fixed_width_string(record->consumer);
copy(&str, consumer);
terminate_with_null(&str);
}
else{
record->consumer[0] = 0;
}
}
struct App_Vars{
Models models;
// TODO(allen): This wants to live in
@ -71,6 +173,8 @@ struct App_Vars{
Complete_State complete_state;
Command_Data command_data;
Available_Input available_input;
};
enum Coroutine_Type{
@ -376,6 +480,47 @@ COMMAND_DECL(center_view){
view->recent->scroll.target_y = y;
}
COMMAND_DECL(left_adjust_view){
REQ_READABLE_VIEW(view);
REQ_FILE(file, view);
f32 x;
if (view->file_data.unwrapped_lines){
x = view->recent->cursor.unwrapped_x;
}
else{
x = view->recent->cursor.wrapped_x;
}
x = clamp_bottom(0.f, x - 30.f);
view->recent->scroll.target_x = x;
}
COMMAND_DECL(set_cursor){
REQ_READABLE_VIEW(view);
REQ_FILE(file, view);
USE_VARS(vars);
i32_Rect file_region = view->file_region;
Mouse_State mouse = direct_get_mouse_state(&vars->available_input);
f32 max_y = view_file_height(view);
f32 max_x = view_file_width(view);
GUI_Scroll_Vars scroll_vars = view->gui_target.scroll_updated;
f32 rx = (f32)(mouse.x - file_region.x0);
f32 ry = (f32)(mouse.y - file_region.y0);
if (ry >= 0){
if (rx >= 0 && rx < max_x && ry >= 0 && ry < max_y){
view_cursor_move(view,
rx + scroll_vars.scroll_x,
ry + scroll_vars.scroll_y,
1);
}
}
}
COMMAND_DECL(word_complete){
USE_MODELS(models);
USE_VARS(vars);
@ -677,6 +822,7 @@ COMMAND_DECL(paste_next){
}
}
// TODO(allen): FIX THIS FUCKIN SHIT!
COMMAND_DECL(undo){
USE_MODELS(models);
REQ_OPEN_VIEW(view);
@ -1617,6 +1763,9 @@ fill_view_summary(View_Summary *view, View *vptr, Live_Views *live_set, Working_
view->mark = view_compute_cursor_from_pos(vptr, vptr->recent->mark);
view->cursor = vptr->recent->cursor;
view->preferred_x = vptr->recent->preferred_x;
view->file_region = vptr->file_region;
view->scroll_vars = *vptr->current_scroll;
}
}
}
@ -2082,6 +2231,13 @@ extern "C"{
return(result);
}
GET_MOUSE_STATE_SIG(external_get_mouse_state){
Command_Data *cmd = (Command_Data*)app->cmd_context;
App_Vars *vars = cmd->vars;
Mouse_State mouse = direct_get_mouse_state(&vars->available_input);
return(mouse);
}
GET_EVENT_MESSAGE_SIG(external_get_event_message){
Event_Message message = {0};
System_Functions *system = (System_Functions*)app->system_links;
@ -2249,6 +2405,7 @@ app_links_init(System_Functions *system, Application_Links *app_links, void *dat
app_links->get_user_input = external_get_user_input;
app_links->get_command_input = external_get_command_input;
app_links->get_event_message = external_get_event_message;
app_links->get_mouse_state = external_get_mouse_state;
app_links->start_query_bar = external_start_query_bar;
app_links->end_query_bar = external_end_query_bar;
@ -2304,6 +2461,7 @@ setup_command_table(){
SET(seek_right);
SET(center_view);
SET(left_adjust_view);
SET(word_complete);
@ -3156,96 +3314,6 @@ update_cli_handle_with_file(System_Functions *system, Models *models,
return(result);
}
enum Input_Types{
Input_AnyKey,
Input_Esc,
Input_MouseMove,
Input_MouseLeftButton,
Input_MouseRightButton,
Input_MouseWheel,
Input_Count
};
struct Consumption_Record{
b32 consumed;
char consumer[32];
};
struct Available_Input{
Key_Summary *keys;
Mouse_State *mouse;
Consumption_Record records[Input_Count];
};
Available_Input
init_available_input(Key_Summary *keys, Mouse_State *mouse){
Available_Input result = {0};
result.keys = keys;
result.mouse = mouse;
return(result);
}
Key_Summary
get_key_data(Available_Input *available){
Key_Summary result = {0};
if (!available->records[Input_AnyKey].consumed){
result = *available->keys;
}
else if (!available->records[Input_Esc].consumed){
i32 i = 0;
i32 count = available->keys->count;
Key_Event_Data key = {0};
for (i = 0; i < count; ++i){
key = get_single_key(available->keys, i);
if (key.keycode == key_esc){
result.count = 1;
result.keys[0] = key;
break;
}
}
}
return(result);
}
Mouse_State
get_mouse_state(Available_Input *available){
Mouse_State mouse = *available->mouse;
if (available->records[Input_MouseLeftButton].consumed){
mouse.l = 0;
mouse.press_l = 0;
mouse.release_l = 0;
}
if (available->records[Input_MouseRightButton].consumed){
mouse.r = 0;
mouse.press_r = 0;
mouse.release_r = 0;
}
if (available->records[Input_MouseWheel].consumed){
mouse.wheel = 0;
}
return(mouse);
}
void
consume_input(Available_Input *available, i32 input_type, char *consumer){
Consumption_Record *record = &available->records[input_type];
record->consumed = 1;
if (consumer){
String str = make_fixed_width_string(record->consumer);
copy(&str, consumer);
terminate_with_null(&str);
}
else{
record->consumer[0] = 0;
}
}
App_Step_Sig(app_step){
Application_Step_Result app_result = *result;
app_result.animating = 0;
@ -3567,9 +3635,7 @@ App_Step_Sig(app_step){
Coroutine *command_coroutine = models->command_coroutine;
View *view = cmd->view;
for (i32 i = 0;
i < 128 && command_coroutine;
++i){
for (i32 i = 0; i < 128 && command_coroutine; ++i){
User_Input user_in = {0};
user_in.abort = 1;
@ -3603,12 +3669,12 @@ App_Step_Sig(app_step){
}
// NOTE(allen): pass events to debug
Available_Input available_input = init_available_input(&key_summary, &input->mouse);
vars->available_input = init_available_input(&key_summary, &input->mouse);
#if FRED_INTERNAL
{
Debug_Data *debug = &models->debug;
Key_Summary key_data = get_key_data(&available_input);
Key_Summary key_data = get_key_data(&vars->available_input);
Debug_Input_Event *events = debug->input_events;
@ -3643,7 +3709,7 @@ App_Step_Sig(app_step){
get_flags |= abort_flags;
if ((get_flags & EventOnAnyKey) || (get_flags & EventOnEsc)){
Key_Summary key_data = get_key_data(&available_input);
Key_Summary key_data = get_key_data(&vars->available_input);
for (i32 key_i = 0; key_i < key_data.count; ++key_i){
Key_Event_Data key = get_single_key(&key_data, key_i);
@ -3671,14 +3737,14 @@ App_Step_Sig(app_step){
if (EventOnAnyKey & get_flags){
pass_in = 1;
consume_input(&available_input, Input_AnyKey,
consume_input(&vars->available_input, Input_AnyKey,
"command coroutine");
}
if (key.keycode == key_esc){
if (EventOnEsc & get_flags){
pass_in = 1;
}
consume_input(&available_input, Input_Esc,
consume_input(&vars->available_input, Input_Esc,
"command coroutine");
}
@ -3718,7 +3784,7 @@ App_Step_Sig(app_step){
}
if (get_flags & EventOnMouseMove){
pass_in = 1;
consume_input(&available_input, Input_MouseMove,
consume_input(&vars->available_input, Input_MouseMove,
"command coroutine");
}
@ -3728,7 +3794,7 @@ App_Step_Sig(app_step){
}
if (get_flags & EventOnLeftButton){
pass_in = 1;
consume_input(&available_input, Input_MouseLeftButton,
consume_input(&vars->available_input, Input_MouseLeftButton,
"command coroutine");
}
}
@ -3739,7 +3805,7 @@ App_Step_Sig(app_step){
}
if (get_flags & EventOnRightButton){
pass_in = 1;
consume_input(&available_input, Input_MouseRightButton,
consume_input(&vars->available_input, Input_MouseRightButton,
"command coroutine");
}
}
@ -3750,7 +3816,7 @@ App_Step_Sig(app_step){
}
if (get_flags & EventOnWheel){
pass_in = 1;
consume_input(&available_input, Input_MouseWheel,
consume_input(&vars->available_input, Input_MouseWheel,
"command coroutine");
}
}
@ -3785,9 +3851,9 @@ App_Step_Sig(app_step){
active_input.mouse.x = input->mouse.x;
active_input.mouse.y = input->mouse.y;
active_input.keys = get_key_data(&available_input);
active_input.keys = get_key_data(&vars->available_input);
Mouse_State mouse_state = get_mouse_state(&available_input);
Mouse_State mouse_state = get_mouse_state(&vars->available_input);
{
Panel *panel = 0, *used_panels = 0;
@ -3809,11 +3875,11 @@ App_Step_Sig(app_step){
app_result.animating = 1;
}
if (result.consume_keys){
consume_input(&available_input, Input_AnyKey,
consume_input(&vars->available_input, Input_AnyKey,
"file view step");
}
if (result.consume_keys || result.consume_esc){
consume_input(&available_input, Input_Esc,
consume_input(&vars->available_input, Input_Esc,
"file view step");
}
@ -3824,23 +3890,23 @@ App_Step_Sig(app_step){
summary.mouse = mouse_state;
}
GUI_Scroll_Vars *vars = view->current_scroll;
GUI_Scroll_Vars *scroll_vars = view->current_scroll;
Input_Process_Result ip_result =
do_step_file_view(system, view, panel->inner, active,
&summary, *vars, view->scroll_region);
&summary, *scroll_vars, view->scroll_region);
if (ip_result.is_animating){
app_result.animating = 1;
}
if (ip_result.consumed_l){
consume_input(&available_input, Input_MouseLeftButton,
consume_input(&vars->available_input, Input_MouseLeftButton,
"file view step");
}
if (ip_result.consumed_r){
consume_input(&available_input, Input_MouseRightButton,
consume_input(&vars->available_input, Input_MouseRightButton,
"file view step");
}
Assert(view->current_scroll == vars);
*vars = ip_result.vars;
Assert(view->current_scroll == scroll_vars);
*scroll_vars = ip_result.vars;
view->scroll_region = ip_result.region;
}
}
@ -3848,9 +3914,20 @@ App_Step_Sig(app_step){
update_command_data(vars, cmd);
// NOTE(allen): post scroll vars back to the view's gui targets
{
Panel *panel = 0, *used_panels = 0;
used_panels = &models->layout.used_sentinel;
for (dll_items(panel, used_panels)){
Assert(panel->view);
view_end_cursor_scroll_updates(panel->view);
}
}
// NOTE(allen): command execution
{
Key_Summary key_data = get_key_data(&available_input);
Key_Summary key_data = get_key_data(&vars->available_input);
b32 hit_something = 0;
b32 hit_esc = 0;
@ -3908,11 +3985,11 @@ App_Step_Sig(app_step){
}
if (hit_something){
consume_input(&available_input, Input_AnyKey,
consume_input(&vars->available_input, Input_AnyKey,
"command dispatcher");
}
if (hit_esc){
consume_input(&available_input, Input_Esc,
consume_input(&vars->available_input, Input_Esc,
"command dispatcher");
}
}
@ -3920,14 +3997,13 @@ App_Step_Sig(app_step){
update_command_data(vars, cmd);
// NOTE(allen): pass consumption data to debug
#if FRED_INTERNAL
{
Debug_Data *debug = &models->debug;
i32 count = debug->this_frame_count;
Consumption_Record *record = 0;
record = &available_input.records[Input_MouseLeftButton];
record = &vars->available_input.records[Input_MouseLeftButton];
if (record->consumed && record->consumer[0] != 0){
Debug_Input_Event *event = debug->input_events;
for (i32 i = 0; i < count; ++i, ++event){
@ -3938,7 +4014,7 @@ App_Step_Sig(app_step){
}
}
record = &available_input.records[Input_MouseRightButton];
record = &vars->available_input.records[Input_MouseRightButton];
if (record->consumed && record->consumer[0] != 0){
Debug_Input_Event *event = debug->input_events;
for (i32 i = 0; i < count; ++i, ++event){
@ -3949,7 +4025,7 @@ App_Step_Sig(app_step){
}
}
record = &available_input.records[Input_Esc];
record = &vars->available_input.records[Input_Esc];
if (record->consumed && record->consumer[0] != 0){
Debug_Input_Event *event = debug->input_events;
for (i32 i = 0; i < count; ++i, ++event){
@ -3960,7 +4036,7 @@ App_Step_Sig(app_step){
}
}
record = &available_input.records[Input_AnyKey];
record = &vars->available_input.records[Input_AnyKey];
if (record->consumed){
Debug_Input_Event *event = debug->input_events;
for (i32 i = 0; i < count; ++i, ++event){
@ -3970,7 +4046,6 @@ App_Step_Sig(app_step){
}
}
}
#endif
// NOTE(allen): initialize message
if (input->first_step){

View File

@ -1454,11 +1454,11 @@ view_compute_cursor(View *view, Buffer_Seek seek){
break;
case buffer_seek_wrapped_xy:
result = view_compute_cursor_from_wrapped_xy(view, seek.x, seek.y);
result = view_compute_cursor_from_wrapped_xy(view, seek.x, seek.y, seek.round_down);
break;
case buffer_seek_unwrapped_xy:
result = view_compute_cursor_from_unwrapped_xy(view, seek.x, seek.y);
result = view_compute_cursor_from_unwrapped_xy(view, seek.x, seek.y, seek.round_down);
break;
case buffer_seek_line_char:
@ -3613,31 +3613,10 @@ file_step(View *view, i32_Rect region, Input_Summary *user_input, b32 is_active,
i32 is_animating = 0;
Editing_File *file = view->file_data.file;
if (file && !file->is_loading){
f32 max_visible_y = view_file_height(view);
f32 max_x = view_file_width(view);
GUI_Scroll_Vars scroll_vars = *view->current_scroll;
if (file->state.paste_effect.tick_down > 0){
--file->state.paste_effect.tick_down;
is_animating = 1;
}
if (user_input->mouse.press_l && is_active){
f32 rx = (f32)(user_input->mouse.x - region.x0);
f32 ry = (f32)(user_input->mouse.y - region.y0);
if (ry >= 0){
if (rx >= 0 && rx < max_x && ry >= 0 && ry < max_visible_y){
*consumed_l = true;
view_cursor_move(view,
rx + scroll_vars.scroll_x,
ry + scroll_vars.scroll_y,
1);
view->mode = view_mode_zero();
}
}
}
}
return(is_animating);
@ -4022,6 +4001,18 @@ struct View_Step_Result{
b32 consume_esc;
};
inline void
gui_show_mouse(GUI_Target *target, String *string, i32 mx, i32 my){
string->size = 0;
append(string, "mouse: (");
append_int_to_str(string, mx);
append(string, ",");
append_int_to_str(string, my);
append(string, ")");
gui_do_text_field(target, *string, string_zero());
}
internal View_Step_Result
step_file_view(System_Functions *system, View *view, View *active_view, Input_Summary input){
View_Step_Result result = {0};
@ -4689,19 +4680,7 @@ step_file_view(System_Functions *system, View *view, View *active_view, Input_Su
{
Debug_Data *debug = &view->persistent.models->debug;
{
int mx = input.mouse.x;
int my = input.mouse.y;
string.size = 0;
append(&string, "mouse: (");
append_int_to_str(&string, mx);
append(&string, ",");
append_int_to_str(&string, my);
append(&string, ")");
}
gui_do_text_field(target, string, empty_str);
gui_show_mouse(target, &string, input.mouse.x, input.mouse.y);
Debug_Input_Event *input_event = debug->input_events;
for (i32 i = 0;
@ -4885,6 +4864,8 @@ step_file_view(System_Functions *system, View *view, View *active_view, Input_Su
}
else{
gui_show_mouse(target, &string, input.mouse.x, input.mouse.y);
#define SHOW_GUI_BLANK(n) show_gui_line(target, &string, n, 0, "", 0)
#define SHOW_GUI_LINE(n, str) show_gui_line(target, &string, n, 0, " " str, 0)
#define SHOW_GUI_STRING(n, h, str, mes) show_gui_line(target, &string, n, h, " " str " ", mes)

View File

@ -1,11 +1,11 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 25.02.2016
*
* File editing view for 4coder
*
*/
* Mr. 4th Dimention - Allen Webster
*
* 25.02.2016
*
* File editing view for 4coder
*
*/
// TOP

View File

@ -48,6 +48,7 @@ int View_Set_Buffer(Application_Links *app, View_Summary *view, int buffer_id)
User_Input Get_User_Input(Application_Links *app, unsigned int get_type, unsigned int abort_type)
User_Input Get_Command_Input(Application_Links *app)
Event_Message Get_Event_Message(Application_Links *app)
Mouse_State Get_Mouse_State(Application_Links *app)
// Queries and information display
int Start_Query_Bar(Application_Links *app, Query_Bar *bar, unsigned int flags)
@ -60,3 +61,5 @@ GUI* Get_GUI(Application_Links *app, int view_id)
void Change_Theme(Application_Links *app, char *name, int len)
void Change_Font(Application_Links *app, char *name, int len)
void Set_Theme_Colors(Application_Links *app, Theme_Color *colors, int count)