/* * Mr. 4th Dimention - Allen Webster * * 20.02.2016 * * GUI system for 4coder * */ // TOP struct Query_Slot{ Query_Slot *next; Query_Bar *query_bar; }; struct Query_Set{ Query_Slot slots[8]; Query_Slot *free_slot; Query_Slot *used_slot; }; internal void init_query_set(Query_Set *set){ Query_Slot *slot = set->slots; int i; set->free_slot = slot; set->used_slot = 0; for (i = 0; i+1 < ArrayCount(set->slots); ++i, ++slot){ slot->next = slot + 1; } } internal Query_Slot* alloc_query_slot(Query_Set *set){ Query_Slot *slot = set->free_slot; if (slot != 0){ set->free_slot = slot->next; slot->next = set->used_slot; set->used_slot = slot; } return(slot); } internal void free_query_slot(Query_Set *set, Query_Bar *match_bar){ Query_Slot *slot = 0, *prev = 0; for (slot = set->used_slot; slot != 0; slot = slot->next){ if (slot->query_bar == match_bar) break; prev = slot; } if (slot){ if (prev){ prev->next = slot->next; } else{ set->used_slot = slot->next; } slot->next = set->free_slot; set->free_slot = slot; } } #if 0 enum GUI_Piece_Type{ gui_type_text_input, gui_type_number_input, gui_type_label, gui_type_slider }; struct GUI_Piece_Header{ i32 type; i32 padding; }; // TODO(allen): Inline string for prompt? struct GUI_Piece_Text_Input{ String *dest; f32_Rect rect; String prompt; }; struct GUI_Piece_Number_Input{ i32 *dest; f32_Rect rect; String prompt; }; struct GUI_Piece_Label{ f32_Rect rect; String text; }; struct GUI_Piece_Slider{ i32 *dest; f32_Rect rect; i32 max; }; struct GUI_Layout_Engine{ i32_Rect region; i32 x, y; }; struct GUI_Target{ Partition push_buffer; GUI_Layout_Engine layout; }; internal void refresh_gui(GUI_Target *target, i32_Rect region){ target->push_buffer.pos = 0; target->layout.region = region; target->layout.x = 0; target->layout.y = 0; } internal void push_gui_item(GUI_Target *target, GUI_Piece_Header header, void *item, i32 size){ GUI_Piece_Header *ptr; i32 total_size; Assert(sizeof(header) == 8); total_size = sizeof(header) + size; total_size = ((total_size + 7) & ~7); ptr = (GUI_Piece_Header*)push_block(&target->push_buffer, size); if (ptr){ *ptr = header; memcpy(ptr + 1, item, size); } else{ Assert(!"bad situation"); } } internal void push_gui_text_in(GUI_Target *target, String prompt, String *dest){ GUI_Piece_Header header = {}; GUI_Piece_Text_Input item = {}; header.type = gui_type_text_input; item.dest = dest; item.rect = gui_layout(target); // ?? what do we need here? item.prompt = prompt; push_gui_item(target, header, &item, sizeof(item)); } internal void push_gui_number_in(GUI_Target *target, String prompt, i32 *dest){ GUI_Piece_Header header = {}; GUI_Piece_Number_Input item = {}; header.type = gui_type_number_input; item.dest = dest; item.rect = gui_layout(target); // ?? what do we need here? item.prompt = prompt; push_gui_item(target, header, &item, sizeof(item)); } internal void push_gui_label(GUI_Target *target, String text){ GUI_Piece_Header header = {}; GUI_Piece_Label item = {}; header.type = gui_type_label; item.rect = gui_layout(target); // ?? what do we need here? item.text = text; push_gui_item(target, header, &item, sizeof(item)); } #endif struct Single_Line_Input_Step{ b8 hit_newline; b8 hit_ctrl_newline; b8 hit_a_character; b8 hit_backspace; b8 hit_esc; b8 made_a_change; b8 did_command; b8 no_file_match; }; enum Single_Line_Input_Type{ SINGLE_LINE_STRING, SINGLE_LINE_FILE }; struct Single_Line_Mode{ Single_Line_Input_Type type; String *string; Hot_Directory *hot_directory; b32 fast_folder_select; b32 try_to_match; b32 case_sensitive; }; internal Single_Line_Input_Step app_single_line_input_core(System_Functions *system, Working_Set *working_set, Key_Event_Data key, Single_Line_Mode mode){ Single_Line_Input_Step result = {}; if (key.keycode == key_back){ result.hit_backspace = 1; if (mode.string->size > 0){ result.made_a_change = 1; --mode.string->size; switch (mode.type){ case SINGLE_LINE_STRING: mode.string->str[mode.string->size] = 0; break; case SINGLE_LINE_FILE: { char end_character = mode.string->str[mode.string->size]; if (char_is_slash(end_character)){ mode.string->size = reverse_seek_slash(*mode.string) + 1; mode.string->str[mode.string->size] = 0; hot_directory_set(system, mode.hot_directory, *mode.string, working_set); } else{ mode.string->str[mode.string->size] = 0; } }break; } } } else if (key.character == '\n' || key.character == '\t'){ result.made_a_change = 1; if (key.modifiers[MDFR_CONTROL_INDEX] || key.modifiers[MDFR_ALT_INDEX]){ result.hit_ctrl_newline = 1; } else{ result.hit_newline = 1; if (mode.fast_folder_select){ Hot_Directory_Match match; char front_name_space[256]; String front_name = make_fixed_width_string(front_name_space); get_front_of_directory(&front_name, *mode.string); match = hot_directory_first_match(mode.hot_directory, front_name, 1, 1, mode.case_sensitive); if (mode.try_to_match && !match.filename.str){ match = hot_directory_first_match(mode.hot_directory, front_name, 1, 0, mode.case_sensitive); } if (match.filename.str){ if (match.is_folder){ set_last_folder(mode.string, match.filename, mode.hot_directory->slash); hot_directory_set(system, mode.hot_directory, *mode.string, working_set); result.hit_newline = 0; } else{ if (mode.try_to_match){ mode.string->size = reverse_seek_slash(*mode.string) + 1; append(mode.string, match.filename); } } } else{ if (mode.try_to_match){ result.no_file_match = 1; } } } } } else if (key.keycode == key_esc){ result.hit_esc = 1; result.made_a_change = 1; } else if (key.character){ result.hit_a_character = 1; if (!key.modifiers[MDFR_CONTROL_INDEX] && !key.modifiers[MDFR_ALT_INDEX]){ if (mode.string->size+1 < mode.string->memory_size){ u8 new_character = (u8)key.character; mode.string->str[mode.string->size] = new_character; mode.string->size++; mode.string->str[mode.string->size] = 0; if (mode.type == SINGLE_LINE_FILE && char_is_slash(new_character)){ hot_directory_set(system, mode.hot_directory, *mode.string, working_set); } result.made_a_change = 1; } } else{ result.did_command = 1; result.made_a_change = 1; } } return result; } inline Single_Line_Input_Step app_single_line_input_step(System_Functions *system, Key_Event_Data key, String *string){ Single_Line_Mode mode = {}; mode.type = SINGLE_LINE_STRING; mode.string = string; return app_single_line_input_core(system, 0, key, mode); } inline Single_Line_Input_Step app_single_file_input_step(System_Functions *system, Working_Set *working_set, Key_Event_Data key, String *string, Hot_Directory *hot_directory, b32 fast_folder_select, b32 try_to_match, b32 case_sensitive){ Single_Line_Mode mode = {}; mode.type = SINGLE_LINE_FILE; mode.string = string; mode.hot_directory = hot_directory; mode.fast_folder_select = fast_folder_select; mode.try_to_match = try_to_match; mode.case_sensitive = case_sensitive; return app_single_line_input_core(system, working_set, key, mode); } inline Single_Line_Input_Step app_single_number_input_step(System_Functions *system, Key_Event_Data key, String *string){ Single_Line_Input_Step result = {}; Single_Line_Mode mode = {}; mode.type = SINGLE_LINE_STRING; mode.string = string; char c = (char)key.character; if (c == 0 || c == '\n' || char_is_numeric(c)) result = app_single_line_input_core(system, 0, key, mode); return result; } struct Widget_ID{ i32 id; i32 sub_id0; i32 sub_id1; i32 sub_id2; }; inline b32 widget_match(Widget_ID s1, Widget_ID s2){ return (s1.id == s2.id && s1.sub_id0 == s2.sub_id0 && s1.sub_id1 == s2.sub_id1 && s1.sub_id2 == s2.sub_id2); } struct UI_State{ Render_Target *target; Style *style; Font_Set *font_set; Mouse_State *mouse; Key_Summary *keys; Working_Set *working_set; i16 font_id; Widget_ID selected, hover, hot; b32 activate_me; b32 redraw; b32 input_stage; i32 sub_id1_change; f32 height, view_y; }; inline Widget_ID make_id(UI_State *state, i32 id){ Widget_ID r = state->selected; r.id = id; return r; } inline Widget_ID make_sub0(UI_State *state, i32 id){ Widget_ID r = state->selected; r.sub_id0 = id; return r; } inline Widget_ID make_sub1(UI_State *state, i32 id){ Widget_ID r = state->selected; r.sub_id1 = id; return r; } inline Widget_ID make_sub2(UI_State *state, i32 id){ Widget_ID r = state->selected; r.sub_id2 = id; return r; } inline b32 is_selected(UI_State *state, Widget_ID id){ return widget_match(state->selected, id); } inline b32 is_hot(UI_State *state, Widget_ID id){ return widget_match(state->hot, id); } inline b32 is_hover(UI_State *state, Widget_ID id){ return widget_match(state->hover, id); } struct UI_Layout{ i32 row_count; i32 row_item_width; i32 row_max_item_height; i32_Rect rect; i32 x, y; }; struct UI_Layout_Restore{ UI_Layout layout; UI_Layout *dest; }; inline void begin_layout(UI_Layout *layout, i32_Rect rect){ layout->rect = rect; layout->x = rect.x0; layout->y = rect.y0; layout->row_count = 0; layout->row_max_item_height = 0; } inline void begin_row(UI_Layout *layout, i32 count){ layout->row_count = count; layout->row_item_width = (layout->rect.x1 - layout->x) / count; } inline i32_Rect layout_rect(UI_Layout *layout, i32 height){ i32_Rect rect; rect.x0 = layout->x; rect.y0 = layout->y; rect.x1 = rect.x0; rect.y1 = rect.y0 + height; if (layout->row_count > 0){ --layout->row_count; rect.x1 = rect.x0 + layout->row_item_width; layout->x += layout->row_item_width; layout->row_max_item_height = Max(height, layout->row_max_item_height); } if (layout->row_count == 0){ rect.x1 = layout->rect.x1; layout->row_max_item_height = Max(height, layout->row_max_item_height); layout->y += layout->row_max_item_height; layout->x = layout->rect.x0; layout->row_max_item_height = 0; } return rect; } inline UI_Layout_Restore begin_sub_layout(UI_Layout *layout, i32_Rect area){ UI_Layout_Restore restore; restore.layout = *layout; restore.dest = layout; begin_layout(layout, area); return restore; } inline void end_sub_layout(UI_Layout_Restore restore){ *restore.dest = restore.layout; } struct UI_Style{ u32 dark, dim, bright; u32 base, pop1, pop2; }; internal UI_Style get_ui_style(Style *style){ UI_Style ui_style; ui_style.dark = style->main.back_color; ui_style.dim = style->main.margin_color; ui_style.bright = style->main.margin_active_color; ui_style.base = style->main.default_color; ui_style.pop1 = style->main.file_info_style.pop1_color; ui_style.pop2 = style->main.file_info_style.pop2_color; return ui_style; } internal UI_Style get_ui_style_upper(Style *style){ UI_Style ui_style; ui_style.dark = style->main.margin_color; ui_style.dim = style->main.margin_hover_color; ui_style.bright = style->main.margin_active_color; ui_style.base = style->main.default_color; ui_style.pop1 = style->main.file_info_style.pop1_color; ui_style.pop2 = style->main.file_info_style.pop2_color; return ui_style; } inline void get_colors(UI_State *state, u32 *back, u32 *fore, Widget_ID wid, UI_Style style){ bool32 hover = is_hover(state, wid); bool32 hot = is_hot(state, wid); i32 level = hot + hover; switch (level){ case 2: *back = style.bright; *fore = style.dark; break; case 1: *back = style.dim; *fore = style.bright; break; case 0: *back = style.dark; *fore = style.bright; break; } } inline void get_pop_color(UI_State *state, u32 *pop, Widget_ID wid, UI_Style style){ bool32 hover = is_hover(state, wid); bool32 hot = is_hot(state, wid); i32 level = hot + hover; switch (level){ case 2: *pop = style.pop1; break; case 1: *pop = style.pop1; break; case 0: *pop = style.pop1; break; } } internal UI_State ui_state_init(UI_State *state_in, Render_Target *target, Input_Summary *user_input, Style *style, i16 font_id, Font_Set *font_set, Working_Set *working_set, b32 input_stage){ UI_State state = {}; state.target = target; state.style = style; state.font_set = font_set; state.font_id = font_id; state.working_set = working_set; if (user_input){ state.mouse = &user_input->mouse; state.keys = &user_input->keys; } state.selected = state_in->selected; state.hot = state_in->hot; if (input_stage) state.hover = {}; else state.hover = state_in->hover; state.redraw = 0; state.activate_me = 0; state.input_stage = input_stage; state.height = state_in->height; state.view_y = state_in->view_y; return state; } inline b32 ui_state_match(UI_State a, UI_State b){ return (widget_match(a.selected, b.selected) && widget_match(a.hot, b.hot) && widget_match(a.hover, b.hover)); } internal b32 ui_finish_frame(UI_State *persist_state, UI_State *state, UI_Layout *layout, i32_Rect rect, b32 do_wheel, b32 *did_activation){ b32 result = 0; f32 h = layout->y + persist_state->view_y - rect.y0; f32 max_y = h - (rect.y1 - rect.y0); persist_state->height = h; persist_state->view_y = state->view_y; if (state->input_stage){ Mouse_State *mouse = state->mouse; Font_Set *font_set = state->font_set; if (mouse->wheel != 0 && do_wheel){ i32 height = get_font_info(font_set, state->font_id)->height; persist_state->view_y += mouse->wheel*height; result = 1; } if (mouse->release_l && widget_match(state->hot, state->hover)){ if (did_activation) *did_activation = 1; if (state->activate_me){ state->selected = state->hot; } } if (!mouse->l && !mouse->r){ state->hot = {}; } if (!ui_state_match(*persist_state, *state) || state->redraw){ result = 1; } *persist_state = *state; } if (persist_state->view_y >= max_y) persist_state->view_y = max_y; if (persist_state->view_y < 0) persist_state->view_y = 0; return result; } internal b32 ui_do_button_input(UI_State *state, i32_Rect rect, Widget_ID id, bool32 activate, bool32 *right = 0){ b32 result = 0; Mouse_State *mouse = state->mouse; b32 hover = hit_check(mouse->x, mouse->y, rect); if (hover){ state->hover = id; if (activate) state->activate_me = 1; if (mouse->press_l || (mouse->press_r && right)) state->hot = id; if (mouse->l && mouse->r) state->hot = {}; } bool32 is_match = is_hot(state, id); if (mouse->release_l && is_match){ if (hover) result = 1; state->redraw = 1; } if (right && mouse->release_r && is_match){ if (hover) *right = 1; state->redraw = 1; } return result; } internal bool32 ui_do_subdivided_button_input(UI_State *state, i32_Rect rect, i32 parts, Widget_ID id, bool32 activate, i32 *indx_out, bool32 *right = 0){ bool32 result = 0; real32 x0, x1; i32_Rect sub_rect; Widget_ID sub_widg = id; real32 sub_width = (rect.x1 - rect.x0) / (real32)parts; sub_rect.y0 = rect.y0; sub_rect.y1 = rect.y1; x1 = (real32)rect.x0; for (i32 i = 0; i < parts; ++i){ x0 = x1; x1 = x1 + sub_width; sub_rect.x0 = TRUNC32(x0); sub_rect.x1 = TRUNC32(x1); sub_widg.sub_id2 = i; if (ui_do_button_input(state, sub_rect, sub_widg, activate, right)){ *indx_out = i; break; } } return result; } internal real32 ui_do_vscroll_input(UI_State *state, i32_Rect top, i32_Rect bottom, i32_Rect slider, Widget_ID id, real32 val, real32 step_amount, real32 smin, real32 smax, real32 vmin, real32 vmax){ Mouse_State *mouse = state->mouse; i32 mx = mouse->x; i32 my = mouse->y; if (hit_check(mx, my, top)){ state->hover = id; state->hover.sub_id2 = 1; } if (hit_check(mx, my, bottom)){ state->hover = id; state->hover.sub_id2 = 2; } if (hit_check(mx, my, slider)){ state->hover = id; state->hover.sub_id2 = 3; } if (mouse->press_l) state->hot = state->hover; if (id.id == state->hot.id){ if (mouse->release_l){ Widget_ID wid1, wid2; wid1 = wid2 = id; wid1.sub_id2 = 1; wid2.sub_id2 = 2; if (state->hot.sub_id2 == 1 && is_hover(state, wid1)) val -= step_amount; if (state->hot.sub_id2 == 2 && is_hover(state, wid2)) val += step_amount; state->redraw = 1; } if (state->hot.sub_id2 == 3){ f32 S, L; S = (f32)mouse->y - (slider.y1 - slider.y0) / 2; if (S < smin) S = smin; if (S > smax) S = smax; L = unlerp(smin, S, smax); val = lerp(vmin, L, vmax); state->redraw = 1; } } return val; } internal b32 ui_do_text_field_input(UI_State *state, String *str){ b32 result = 0; Key_Summary *keys = state->keys; for (i32 key_i = 0; key_i < keys->count; ++key_i){ Key_Event_Data key = get_single_key(keys, key_i); char c = (char)key.character; if (char_is_basic(c) && str->size < str->memory_size-1){ str->str[str->size++] = c; str->str[str->size] = 0; } else if (c == '\n'){ result = 1; } else if (key.keycode == key_back && str->size > 0){ str->str[--str->size] = 0; } } return result; } internal b32 ui_do_file_field_input(System_Functions *system, UI_State *state, Hot_Directory *hot_dir, b32 try_to_match, b32 case_sensitive){ Key_Event_Data key; Single_Line_Input_Step step; String *str = &hot_dir->string; Key_Summary *keys = state->keys; i32 key_i; b32 result = 0; terminate_with_null(str); for (key_i = 0; key_i < keys->count; ++key_i){ key = get_single_key(keys, key_i); step = app_single_file_input_step(system, state->working_set, key, str, hot_dir, 1, try_to_match, case_sensitive); if ((step.hit_newline || step.hit_ctrl_newline) && !step.no_file_match) result = 1; } return result; } internal b32 ui_do_line_field_input(System_Functions *system, UI_State *state, String *string){ b32 result = 0; Key_Summary *keys = state->keys; for (i32 key_i = 0; key_i < keys->count; ++key_i){ Key_Event_Data key = get_single_key(keys, key_i); terminate_with_null(string); Single_Line_Input_Step step = app_single_line_input_step(system, key, string); if (step.hit_newline || step.hit_ctrl_newline) result = 1; } return result; } internal b32 ui_do_slider_input(UI_State *state, i32_Rect rect, Widget_ID wid, real32 min, real32 max, real32 *v){ b32 result = 0; ui_do_button_input(state, rect, wid, 0); Mouse_State *mouse = state->mouse; if (is_hot(state, wid)){ result = 1; *v = unlerp(min, (f32)mouse->x, max); state->redraw = 1; } return result; } internal bool32 do_text_field(Widget_ID wid, UI_State *state, UI_Layout *layout, String prompt, String dest){ b32 result = 0; i32 character_h = get_font_info(state->font_set, state->font_id)->height; i32_Rect rect = layout_rect(layout, character_h); if (state->input_stage){ ui_do_button_input(state, rect, wid, 1); if (is_selected(state, wid)){ if (ui_do_text_field_input(state, &dest)){ result = 1; } } } else{ Render_Target *target = state->target; UI_Style ui_style = get_ui_style_upper(state->style); u32 back, fore, prompt_pop; get_colors(state, &back, &fore, wid, ui_style); get_pop_color(state, &prompt_pop, wid, ui_style); draw_rectangle(target, rect, back); i32 x = draw_string(target, state->font_id, prompt, rect.x0, rect.y0 + 1, prompt_pop); draw_string(target, state->font_id, dest, x, rect.y0 + 1, ui_style.base); } return result; } internal b32 do_button(i32 id, UI_State *state, UI_Layout *layout, char *text, i32 height_mult, b32 is_toggle = 0, b32 on = 0){ b32 result = 0; i16 font_id = state->font_id; i32 character_h = get_font_info(state->font_set, font_id)->height; i32_Rect btn_rect = layout_rect(layout, character_h * height_mult); if (height_mult > 1) btn_rect = get_inner_rect(btn_rect, 2); else{ btn_rect.x0 += 2; btn_rect.x1 -= 2; } Widget_ID wid = make_id(state, id); if (state->input_stage){ if (ui_do_button_input(state, btn_rect, wid, 0)){ result = 1; } } else{ Render_Target *target = state->target; UI_Style ui_style = get_ui_style(state->style); u32 back, fore, outline; outline = ui_style.bright; get_colors(state, &back, &fore, wid, ui_style); draw_rectangle(target, btn_rect, back); draw_rectangle_outline(target, btn_rect, outline); real32 text_width = font_string_width(target, font_id, text); i32 box_width = btn_rect.x1 - btn_rect.x0; i32 box_height = btn_rect.y1 - btn_rect.y0; i32 x_pos = TRUNC32(btn_rect.x0 + (box_width - text_width)*.5f); draw_string(target, font_id, text, x_pos, btn_rect.y0 + (box_height - character_h) / 2, fore); if (is_toggle){ i32_Rect on_box = get_inner_rect(btn_rect, character_h/2); on_box.x1 = on_box.x0 + (on_box.y1 - on_box.y0); if (on) draw_rectangle(target, on_box, fore); else draw_rectangle(target, on_box, back); draw_rectangle_outline(target, on_box, fore); } } return result; } internal b32 do_undo_slider(Widget_ID wid, UI_State *state, UI_Layout *layout, i32 max, i32 v, Undo_Data *undo, i32 *out){ b32 result = 0; i16 font_id = state->font_id; i32 character_h = get_font_info(state->font_set, font_id)->height; i32_Rect containing_rect = layout_rect(layout, character_h); i32_Rect click_rect; click_rect.x0 = containing_rect.x0 + character_h - 1; click_rect.x1 = containing_rect.x1 - character_h + 1; click_rect.y0 = containing_rect.y0 + 2; click_rect.y1 = containing_rect.y1 - 2; if (state->input_stage){ real32 l; if (ui_do_slider_input(state, click_rect, wid, (real32)click_rect.x0, (real32)click_rect.x1, &l)){ real32 v_new = lerp(0.f, l, (real32)max); v = ROUND32(v_new); result = 1; if (out) *out = v; } } else{ Render_Target *target = state->target; if (max > 0){ UI_Style ui_style = get_ui_style_upper(state->style); real32 L = unlerp(0.f, (real32)v, (real32)max); i32 x = FLOOR32(lerp((real32)click_rect.x0, L, (real32)click_rect.x1)); i32 bar_top = ((click_rect.y0 + click_rect.y1) >> 1) - 1; i32 bar_bottom = bar_top + 2; bool32 show_bar = 1; real32 tick_step = (click_rect.x1 - click_rect.x0) / (real32)max; bool32 show_ticks = 1; if (tick_step <= 5.f) show_ticks = 0; if (undo == 0){ if (show_bar){ i32_Rect slider_rect; slider_rect.x0 = click_rect.x0; slider_rect.x1 = x; slider_rect.y0 = bar_top; slider_rect.y1 = bar_bottom; draw_rectangle(target, slider_rect, ui_style.dim); slider_rect.x0 = x; slider_rect.x1 = click_rect.x1; draw_rectangle(target, slider_rect, ui_style.pop1); } if (show_ticks){ f32_Rect tick; tick.x0 = (real32)click_rect.x0 - 1; tick.x1 = (real32)click_rect.x0 + 1; tick.y0 = (real32)bar_top - 3; tick.y1 = (real32)bar_bottom + 3; for (i32 i = 0; i < v; ++i){ draw_rectangle(target, tick, ui_style.dim); tick.x0 += tick_step; tick.x1 += tick_step; } for (i32 i = v; i <= max; ++i){ draw_rectangle(target, tick, ui_style.pop1); tick.x0 += tick_step; tick.x1 += tick_step; } } } else{ if (show_bar){ i32_Rect slider_rect; slider_rect.x0 = click_rect.x0; slider_rect.y0 = bar_top; slider_rect.y1 = bar_bottom; Edit_Step *history = undo->history.edits; i32 block_count = undo->history_block_count; Edit_Step *step = history; for (i32 i = 0; i < block_count; ++i){ u32 color; if (step->type == ED_REDO || step->type == ED_UNDO) color = ui_style.pop1; else color = ui_style.dim; real32 L; if (i + 1 == block_count){ L = 1.f; }else{ step = history + step->next_block; L = unlerp(0.f, (real32)(step - history), (real32)max); } if (L > 1.f) L = 1.f; i32 x = FLOOR32(lerp((real32)click_rect.x0, L, (real32)click_rect.x1)); slider_rect.x1 = x; draw_rectangle(target, slider_rect, color); slider_rect.x0 = slider_rect.x1; if (L == 1.f) break; } } if (show_ticks){ f32_Rect tick; tick.x0 = (real32)click_rect.x0 - 1; tick.x1 = (real32)click_rect.x0 + 1; tick.y0 = (real32)bar_top - 3; tick.y1 = (real32)bar_bottom + 3; Edit_Step *history = undo->history.edits; u32 color = ui_style.dim; for (i32 i = 0; i <= max; ++i){ if (i != max){ if (history[i].type == ED_REDO) color = ui_style.pop1; else if (history[i].type == ED_UNDO || history[i].type == ED_NORMAL) color = ui_style.pop2; else color = ui_style.dim; } draw_rectangle(target, tick, color); tick.x0 += tick_step; tick.x1 += tick_step; } } } i32_Rect slider_handle; slider_handle.x0 = x - 2; slider_handle.x1 = x + 2; slider_handle.y0 = click_rect.y0; slider_handle.y1 = click_rect.y1; draw_rectangle(target, slider_handle, ui_style.bright); } } return result; } internal void do_label(UI_State *state, UI_Layout *layout, char *text, int text_size, f32 height = 2.f){ Style *style = state->style; i16 font_id = state->font_id; i32 line_height = get_font_info(state->font_set, font_id)->height; i32_Rect label = layout_rect(layout, FLOOR32(line_height * height)); if (!state->input_stage){ Render_Target *target = state->target; u32 back = style->main.margin_color; u32 fore = style->main.default_color; draw_rectangle(target, label, back); i32 height = label.y1 - label.y0; String textstr = make_string(text, text_size); draw_string(target, font_id, textstr, label.x0, label.y0 + (height - line_height)/2, fore); } } inline void do_label(UI_State *state, UI_Layout *layout, String text, f32 height = 2.f){ do_label(state, layout, text.str, text.size, height); } internal void do_scroll_bar(UI_State *state, i32_Rect rect){ i32 id = 1; i32 w = (rect.x1 - rect.x0); i32 h = (rect.y1 - rect.y0); i32_Rect top_arrow, bottom_arrow; top_arrow.x0 = rect.x0; top_arrow.x1 = rect.x1; top_arrow.y0 = rect.y0; top_arrow.y1 = top_arrow.y0 + w; bottom_arrow.x0 = rect.x0; bottom_arrow.x1 = rect.x1; bottom_arrow.y1 = rect.y1; bottom_arrow.y0 = bottom_arrow.y1 - w; f32 space_h = (f32)(bottom_arrow.y0 - top_arrow.y1); if (space_h <= w) return; i32 slider_h = w; f32 view_hmin = 0; f32 view_hmax = state->height - h; f32 L = unlerp(view_hmin, state->view_y, view_hmax); f32 slider_hmin = (f32)top_arrow.y1; f32 slider_hmax = (f32)bottom_arrow.y0 - slider_h; f32 S = lerp(slider_hmin, L, slider_hmax); i32_Rect slider; slider.x0 = rect.x0; slider.x1 = rect.x1; slider.y0 = FLOOR32(S); slider.y1 = slider.y0 + slider_h; Widget_ID wid = make_id(state, id); if (state->input_stage){ state->view_y = ui_do_vscroll_input(state, top_arrow, bottom_arrow, slider, wid, state->view_y, (f32)(get_font_info(state->font_set, state->font_id)->height), slider_hmin, slider_hmax, view_hmin, view_hmax); } else{ Render_Target *target = state->target; f32 x0, y0, x1, y1, x2, y2; f32 w_1_2 = w*.5f; f32 w_1_3 = w*.333333f; f32 w_2_3 = w*.666667f; UI_Style ui_style = get_ui_style(state->style); u32 outline, back, fore; outline = ui_style.bright; wid.sub_id2 = 0; x0 = (w_1_2 + top_arrow.x0); x1 = (w_1_3 + top_arrow.x0); x2 = (w_2_3 + top_arrow.x0); ++wid.sub_id2; y0 = (w_1_3 + top_arrow.y0); y1 = (w_2_3 + top_arrow.y0); y2 = (w_2_3 + top_arrow.y0); get_colors(state, &back, &fore, wid, ui_style); draw_rectangle(target, top_arrow, back); draw_rectangle_outline(target, top_arrow, outline); ++wid.sub_id2; y0 = (w_2_3 + bottom_arrow.y0); y1 = (w_1_3 + bottom_arrow.y0); y2 = (w_1_3 + bottom_arrow.y0); get_colors(state, &back, &fore, wid, ui_style); draw_rectangle(target, bottom_arrow, back); draw_rectangle_outline(target, bottom_arrow, outline); ++wid.sub_id2; get_colors(state, &back, &fore, wid, ui_style); draw_rectangle(target, slider, back); draw_rectangle_outline(target, slider, outline); draw_rectangle_outline(target, rect, outline); } } internal void draw_gradient_slider(Render_Target *target, Vec4 base, i32 channel, i32 steps, f32 top, f32_Rect slider, b32 hsla){ Vec4 low, high; f32 *lowv, *highv; f32 x; f32 next_x; f32 x_step; f32 v_step; f32 m; x = (real32)slider.x0; x_step = (real32)(slider.x1 - slider.x0) / steps; v_step = top / steps; m = 1.f / top; lowv = &low.v[channel]; highv = &high.v[channel]; if (hsla){ for (i32 i = 0; i < steps; ++i){ low = high = base; *lowv = (i * v_step); *highv = *lowv + v_step; *lowv *= m; *highv *= m; low = hsla_to_rgba(low); high = hsla_to_rgba(high); next_x = x + x_step; draw_gradient_2corner_clipped( target, x, slider.y0, next_x, slider.y1, low, high); x = next_x; } } else{ for (i32 i = 0; i < steps; ++i){ low = high = base; *lowv = (i * v_step); *highv = *lowv + v_step; *lowv *= m; *highv *= m; next_x = x + x_step; draw_gradient_2corner_clipped( target, x, slider.y0, next_x, slider.y1, low, high); x = next_x; } } } inline void draw_hsl_slider(Render_Target *target, Vec4 base, i32 channel, i32 steps, f32 top, f32_Rect slider){ draw_gradient_slider(target, base, channel, steps, top, slider, 1); } inline void draw_rgb_slider(Render_Target *target, Vec4 base, i32 channel, i32 steps, f32 top, f32_Rect slider){ draw_gradient_slider(target, base, channel, steps, top, slider, 0); } internal b32 do_main_file_box(System_Functions *system, UI_State *state, UI_Layout *layout, Hot_Directory *hot_directory, b32 try_to_match, b32 case_sensitive, char *end){ b32 result = 0; Style *style = state->style; String *string = &hot_directory->string; i16 font_id = state->font_id; i32 line_height = get_font_info(state->font_set, font_id)->height; i32_Rect box = layout_rect(layout, line_height + 2); if (state->input_stage){ if (ui_do_file_field_input(system, state, hot_directory, try_to_match, case_sensitive)){ result = 1; } } else{ Render_Target *target = state->target; u32 back = style->main.margin_color; u32 fore = style->main.default_color; u32 special = style->main.special_character_color; draw_rectangle(target, box, back); i32 x = box.x0; x = draw_string(target, font_id, string->str, x, box.y0, fore); if (end) draw_string(target, font_id, end, x, box.y0, special); } layout->y = box.y1; return result; } internal b32 do_main_string_box(System_Functions *system, UI_State *state, UI_Layout *layout, String *string){ b32 result = 0; Style *style = state->style; i16 font_id = state->font_id; i32 line_height = get_font_info(state->font_set, font_id)->height; i32_Rect box = layout_rect(layout, line_height + 2); if (state->input_stage){ if (ui_do_line_field_input(system, state, string)){ result = 1; } } else{ Render_Target *target = state->target; u32 back = style->main.margin_color; u32 fore = style->main.default_color; draw_rectangle(target, box, back); i32 x = box.x0; x = draw_string(target, font_id, string->str, x, box.y0, fore); } layout->y = box.y1; return result; } internal b32 do_list_option(i32 id, UI_State *state, UI_Layout *layout, String text){ b32 result = 0; Style *style = state->style; i16 font_id = state->font_id; i32 character_h = get_font_info(state->font_set, font_id)->height; i32_Rect box = layout_rect(layout, character_h*2); Widget_ID wid = make_id(state, id); if (state->input_stage){ if (ui_do_button_input(state, box, wid, 0)){ result = 1; } } else{ Render_Target *target = state->target; i32_Rect inner = get_inner_rect(box, 3); u32 back, outline, fore, pop; back = style->main.back_color; fore = style->main.default_color; pop = style->main.file_info_style.pop2_color; if (is_hover(state, wid)) outline = style->main.margin_active_color; else outline = style->main.margin_color; draw_rectangle(target, inner, back); i32 x = inner.x0, y = box.y0 + character_h/2; x = draw_string(target, font_id, text, x, y, fore); draw_margin(target, box, inner, outline); } layout->y = box.y1; return result; } internal b32 do_checkbox_list_option(i32 id, UI_State *state, UI_Layout *layout, String text, b32 is_on){ b32 result = 0; Style *style = state->style; i16 font_id = state->font_id; i32 character_h = get_font_info(state->font_set, font_id)->height; i32_Rect box = layout_rect(layout, character_h*2); Widget_ID wid = make_id(state, id); if (state->input_stage){ if (ui_do_button_input(state, box, wid, 0)){ result = 1; } } else{ Render_Target *target = state->target; i32_Rect inner = get_inner_rect(box, 3); u32 back, outline, fore, pop, box_color; back = style->main.back_color; fore = style->main.default_color; pop = style->main.file_info_style.pop2_color; if (is_hover(state, wid)) outline = style->main.margin_active_color; else outline = style->main.margin_color; box_color = style->main.margin_active_color; draw_rectangle(target, inner, back); i32_Rect square; square = get_inner_rect(inner, character_h/3); square.x1 = square.x0 + (square.y1 - square.y0); if (is_on) draw_rectangle(target, square, box_color); else draw_margin(target, square, 1, box_color); i32 x = square.x1 + 3; i32 y = box.y0 + character_h/2; x = draw_string(target, font_id, text, x, y, fore); draw_margin(target, box, inner, outline); } layout->y = box.y1; return result; } internal b32 do_file_option(i32 id, UI_State *state, UI_Layout *layout, String filename, b32 is_folder, String extra, char slash){ b32 result = 0; Style *style = state->style; i16 font_id = state->font_id; i32 character_h = get_font_info(state->font_set, font_id)->height; char slash_buf[2] = { slash, 0 }; i32_Rect box = layout_rect(layout, character_h*2); Widget_ID wid = make_id(state, id); if (state->input_stage){ if (ui_do_button_input(state, box, wid, 0)){ result = 1; } } else{ Render_Target *target = state->target; i32_Rect inner = get_inner_rect(box, 3); u32 back, outline, fore, pop; back = style->main.back_color; fore = style->main.default_color; pop = style->main.file_info_style.pop2_color; if (is_hover(state, wid)) outline = style->main.margin_active_color; else outline = style->main.margin_color; draw_rectangle(target, inner, back); i32 x = inner.x0, y = box.y0 + character_h/2; x = draw_string(target, font_id, filename, x, y, fore); if (is_folder) x = draw_string(target, font_id, slash_buf, x, y, fore); draw_string(target, font_id, extra, x, y, pop); draw_margin(target, box, inner, outline); } layout->y = box.y1; return result; } internal b32 do_file_list_box(System_Functions *system, UI_State *state, UI_Layout *layout, Hot_Directory *hot_dir, b32 has_filter, b32 try_to_match, b32 case_sensitive, b32 *new_dir, b32 *selected, char *end){ b32 result = 0; File_List *files = &hot_dir->file_list; if (do_main_file_box(system, state, layout, hot_dir, try_to_match, case_sensitive, end)){ *selected = 1; terminate_with_null(&hot_dir->string); } else{ persist String p4c_extension = make_lit_string("p4c"); persist String message_loaded = make_lit_string(" LOADED"); persist String message_unsaved = make_lit_string(" LOADED *"); persist String message_unsynced = make_lit_string(" LOADED !"); persist String message_nothing = {}; char front_name_space[256]; String front_name = make_fixed_width_string(front_name_space); get_front_of_directory(&front_name, hot_dir->string); Absolutes absolutes; get_absolutes(front_name, &absolutes, 1, 1); char full_path_[256]; String full_path = make_fixed_width_string(full_path_); get_path_of_directory(&full_path, hot_dir->string); i32 restore_size = full_path.size; i32 i; File_Info *info, *end; end = files->infos + files->count; for (info = files->infos, i = 0; info != end; ++info, ++i){ String filename = info->filename; append(&full_path, filename); terminate_with_null(&full_path); Editing_File *file = working_set_contains(state->working_set, full_path); full_path.size = restore_size; b8 is_folder = (info->folder != 0); b8 ext_match = (match(file_extension(filename), p4c_extension) != 0); b8 name_match = (filename_match(front_name, &absolutes, filename, case_sensitive) != 0); b8 is_loaded = (file != 0 && file_is_ready(file)); String message = message_nothing; if (is_loaded){ switch (buffer_get_sync(file)){ case SYNC_GOOD: message = message_loaded; break; case SYNC_BEHIND_OS: message = message_unsynced; break; case SYNC_UNSAVED: message = message_unsaved; break; } } if ((is_folder || !has_filter || ext_match) && name_match){ if (do_file_option(100+i, state, layout, filename, is_folder, message, system->slash)){ result = 1; hot_directory_clean_end(hot_dir); append(&hot_dir->string, filename); if (is_folder){ *new_dir = 1; append(&hot_dir->string, system->slash); } else{ *selected = 1; } terminate_with_null(&hot_dir->string); } } } } return result; } internal b32 do_live_file_list_box(System_Functions *system, UI_State *state, UI_Layout *layout, Working_Set *working_set, String *string, b32 *selected){ b32 result = 0; if (do_main_string_box(system, state, layout, string)){ *selected = 1; terminate_with_null(string); } else{ persist String message_unsaved = make_lit_string(" *"); persist String message_unsynced = make_lit_string(" !"); persist String message_nothing = {}; Absolutes absolutes; get_absolutes(*string, &absolutes, 1, 1); i32 count = working_set->file_index_count; Editing_File *files = working_set->files; for (i32 i = 0; i < count; ++i){ Editing_File *file = files + i; if (!file->state.is_dummy){ String message = message_nothing; switch (buffer_get_sync(file)){ case SYNC_BEHIND_OS: message = message_unsynced; break; case SYNC_UNSAVED: message = message_unsaved; break; } if (filename_match(*string, &absolutes, file->name.live_name, 1)){ if (do_file_option(100+i, state, layout, file->name.live_name, 0, message, system->slash)){ result = 1; *selected = 1; copy(string, file->name.source_path); terminate_with_null(string); } } } } } return result; } struct Super_Color{ Vec4 hsla; Vec4 rgba; u32 *out; }; internal Super_Color super_color_create(u32 packed){ Super_Color result = {}; result.rgba = unpack_color4(packed); result.hsla = rgba_to_hsla(result.rgba); return result; } internal void super_color_post_hsla(Super_Color *color, Vec4 hsla){ color->hsla = hsla; if (hsla.h == 1.f) hsla.h = 0.f; color->rgba = hsla_to_rgba(hsla); *color->out = pack_color4(color->rgba); } internal void super_color_post_rgba(Super_Color *color, Vec4 rgba){ color->rgba = rgba; color->hsla = rgba_to_hsla(rgba); *color->out = pack_color4(rgba); } internal void super_color_post_packed(Super_Color *color, u32 packed){ color->rgba = unpack_color4(packed); color->hsla = rgba_to_hsla(color->rgba); *color->out = packed; } u32 super_color_clear_masks[] = {0xFF00FFFF, 0xFFFF00FF, 0xFFFFFF00}; u32 super_color_shifts[] = {16, 8, 0}; internal u32 super_color_post_byte(Super_Color *color, i32 channel, u8 byte){ u32 packed = *color->out; packed &= super_color_clear_masks[channel]; packed |= (byte << super_color_shifts[channel]); super_color_post_packed(color, packed); return packed; } struct Color_Highlight{ i32 ids[4]; }; struct Library_UI{ UI_State *state; UI_Layout *layout; Font_Set *fonts; Style_Library *styles; Hot_Directory *hot_directory; }; struct Color_UI{ UI_State *state; UI_Layout *layout; Font_Set *fonts; Style_Font *global_font; f32 hex_advance; u32 *palette; i32 palette_size; Color_Highlight highlight; Super_Color color; b32 has_hover_color; Super_Color hover_color; }; enum Channel_Field_Type{ CF_DEC, CF_HEX }; internal void do_single_slider(i32 sub_id, Color_UI *ui, i32 channel, b32 is_rgba, i32 grad_steps, f32 top, f32_Rect slider, f32 v_handle, i32_Rect rect){ f32_Rect click_box = slider; click_box.y0 -= v_handle; if (ui->state->input_stage){ real32 v; if (ui_do_slider_input(ui->state, i32R(click_box), make_sub1(ui->state, sub_id), slider.x0, slider.x1, &v)){ Vec4 new_color; if (is_rgba) new_color = ui->color.rgba; else new_color = ui->color.hsla; new_color.v[channel] = clamp(0.f, v, 1.f); if (is_rgba) super_color_post_rgba(&ui->color, new_color); else super_color_post_hsla(&ui->color, new_color); } } else{ Render_Target *target = ui->state->target; Vec4 color; real32 x; if (is_rgba){ color = ui->color.rgba; draw_rgb_slider(target, V4(0,0,0,1.f), channel, 10, 100.f, slider); } else{ i32 steps; real32 top; if (channel == 0){ steps = 45; top = 360.f; } else{ steps = 10; top = 100.f; } color = ui->color.hsla; draw_hsl_slider(target, color, channel, steps, top, slider); } x = lerp(slider.x0, color.v[channel], slider.x1); draw_rectangle( target, f32R(x, slider.y0, x + 1, slider.y1), 0xff000000); draw_rectangle( target, f32R(x-2, click_box.y0, x+3, slider.y0-4), 0xff777777); } } internal void do_hsl_sliders(Color_UI *ui, i32_Rect rect){ real32 bar_width = (real32)(rect.x1 - rect.x0 - 20); if (bar_width > 45){ f32_Rect slider; real32 y; i32 sub_id; real32 v_full_space = 30.f; real32 v_half_space = 15.f; real32 v_quarter_space = 12.f; real32 v_handle = 9.f; slider.x0 = rect.x0 + 10.f; slider.x1 = slider.x0 + bar_width; sub_id = 0; i32 step_count[] = {45, 10, 10}; real32 tops[] = {360.f, 100.f, 100.f}; y = rect.y0 + v_quarter_space; for (i32 i = 0; i < 3; ++i){ ++sub_id; slider.y0 = y; slider.y1 = slider.y0 + v_half_space; do_single_slider(sub_id, ui, i, 0, step_count[i], tops[i], slider, v_handle, rect); y += v_full_space; } } } internal void fill_buffer_color_channel(char *buffer, u8 x, Channel_Field_Type ftype){ if (ftype == CF_DEC){ u8 x0; x0 = x / 10; buffer[2] = (x - (10*x0)) + '0'; x = x0; x0 = x / 10; buffer[1] = (x - (10*x0)) + '0'; x = x0; x0 = x / 10; buffer[0] = (x - (10*x0)) + '0'; } else{ u8 n; n = x & 0xF; buffer[1] = int_to_hexchar(n); x >>= 4; n = x & 0xF; buffer[0] = int_to_hexchar(n); } } internal b32 do_channel_field(i32 sub_id, Color_UI *ui, u8 *channel, Channel_Field_Type ftype, i32 y, u32 color, u32 back, i32 x0, i32 x1){ b32 result = 0; i16 font_id = ui->state->font_id; i32 line_height = get_font_info(ui->state->font_set, font_id)->height; i32_Rect hit_region; hit_region.x0 = x0; hit_region.x1 = x1; hit_region.y0 = y; hit_region.y1 = y + line_height; i32 digit_count; if (ftype == CF_DEC) digit_count = 3; else digit_count = 2; Render_Target *target = ui->state->target; if (ui->state->input_stage){ i32 indx; ui_do_subdivided_button_input(ui->state, hit_region, digit_count, make_sub1(ui->state, sub_id), 1, &indx); } else{ if (ui->state->hover.sub_id1 == sub_id && ui->state->selected.sub_id1 != sub_id){ draw_rectangle(target, hit_region, back); } } char string_buffer[4]; string_buffer[digit_count] = 0; fill_buffer_color_channel(string_buffer, *channel, ftype); if (ui->state->selected.sub_id1 == sub_id){ i32 indx = ui->state->selected.sub_id2; if (ui->state->input_stage){ Key_Summary *keys = ui->state->keys; for (i32 key_i = 0; key_i < keys->count; ++key_i){ Key_Event_Data key = get_single_key(keys, key_i); if (key.keycode == key_right){ ++indx; if (indx > digit_count-1) indx = 0; } if (key.keycode == key_left){ --indx; if (indx < 0) indx = digit_count-1; } i32 new_value = *channel; if (key.keycode == key_up || key.keycode == key_down){ i32 place = digit_count-1-indx; i32 base = (ftype == CF_DEC)?10:0x10; i32 step_amount = 1; while (place > 0){ step_amount *= base; --place; } if (key.keycode == key_down){ step_amount = 0 - step_amount; } new_value += step_amount; } u8 c = (u8)key.character; bool32 is_good = (ftype == CF_DEC)?char_is_numeric(c):char_is_hex(c); if (is_good){ string_buffer[indx] = c; if (ftype == CF_DEC) new_value = str_to_int(make_string(string_buffer, 3)); else new_value = hexstr_to_int(make_string(string_buffer, 2)); ++indx; if (indx > digit_count-1) indx = 0; } if (c == '\n'){ switch (sub_id){ case 1: case 2: case 4: case 5: ui->state->sub_id1_change = sub_id + 3; break; case 7: case 8: ui->state->sub_id1_change = sub_id - 6; break; } } if (new_value != *channel){ if (new_value > 255){ *channel = 255; } else if (new_value < 0){ *channel = 0; } else{ *channel = (u8)new_value; } fill_buffer_color_channel(string_buffer, *channel, ftype); result = 1; } ui->state->selected.sub_id2 = indx; } } else{ f32_Rect r = f32R(hit_region); r.x0 += indx*ui->hex_advance+1; r.x1 = r.x0+ui->hex_advance+1; draw_rectangle(target, r, back); } } if (!ui->state->input_stage) draw_string_mono(target, font_id, string_buffer, (real32)x0 + 1, (real32)y, ui->hex_advance, color); return result; } internal void do_rgb_sliders(Color_UI *ui, i32_Rect rect){ i32 dec_x0, dec_x1; dec_x0 = rect.x0 + 10; dec_x1 = TRUNC32(dec_x0 + ui->hex_advance*3 + 2); i32 hex_x0, hex_x1; hex_x0 = dec_x1 + 10; hex_x1 = TRUNC32(hex_x0 + ui->hex_advance*2 + 2); rect.x0 = hex_x1; real32 bar_width = (real32)(rect.x1 - rect.x0 - 20); f32_Rect slider; f32 y; i32 sub_id; u8 channel; real32 v_full_space = 30.f; real32 v_half_space = 15.f; real32 v_quarter_space = 12.f; real32 v_handle = 9.f; u32 packed_color = *ui->color.out; y = rect.y0 + v_quarter_space; slider.x0 = rect.x0 + 10.f; slider.x1 = slider.x0 + bar_width; sub_id = 0; persist i32 shifts[3] = { 16, 8, 0 }; persist u32 fore_colors[3] = { 0xFFFF0000, 0xFF00FF00, 0xFF1919FF }; persist u32 back_colors[3] = { 0xFF222222, 0xFF222222, 0xFF131313 }; for (i32 i = 0; i < 3; ++i){ i32 shift = shifts[i]; u32 fore = fore_colors[i]; u32 back = back_colors[i]; ++sub_id; channel = (packed_color >> shift) & 0xFF; if (do_channel_field(sub_id, ui, &channel, CF_DEC, (i32)y, fore, back, dec_x0, dec_x1)) super_color_post_byte(&ui->color, i, channel); ++sub_id; channel = (packed_color >> shift) & 0xFF; if (do_channel_field(sub_id, ui, &channel, CF_HEX, (i32)y, fore, back, hex_x0, hex_x1)) super_color_post_byte(&ui->color, i, channel); ++sub_id; slider.y0 = y; slider.y1 = slider.y0 + v_half_space; if (bar_width > 45.f) do_single_slider(sub_id, ui, i, 1, 10, 100.f, slider, v_handle, rect); y += v_full_space; } } struct Blob_Layout{ i32_Rect rect; i32 x, y; i32 size, space; }; internal void begin_layout(Blob_Layout *layout, i32_Rect rect){ layout->rect = rect; layout->x = rect.x0 + 10; layout->y = rect.y0; layout->size = 20; layout->space = 5; } internal void do_blob(Color_UI *ui, Blob_Layout *layout, u32 color, bool32 *set_me, i32 sub_id){ i32_Rect rect = layout->rect; f32_Rect blob; blob.x0 = (real32)layout->x; blob.y0 = (real32)layout->y; blob.x1 = blob.x0 + layout->size; blob.y1 = blob.y0 + layout->size; layout->y += layout->size + layout->space; if (layout->y + layout->size + layout->space*2 > rect.y1){ layout->y = rect.y0; layout->x += layout->size + layout->space; } if (ui->state->input_stage){ bool32 right = 0; if (ui_do_button_input(ui->state, i32R(blob), make_sub1(ui->state, sub_id), 0, &right)){ super_color_post_packed(&ui->color, color); } else if (right) *set_me = 1; } else{ Render_Target *target = ui->state->target; draw_rectangle(target, blob, color); persist u32 silver = 0xFFa0a0a0; draw_rectangle_outline(target, blob, silver); } } inline void do_blob(Color_UI *ui, Blob_Layout *layout, u32 *color, bool32 *set_me){ i32 sub_id = (i32)((char*)color - (char*)ui->state->style); do_blob(ui, layout, *color, set_me, sub_id); } internal void do_v_divide(Color_UI *ui, Blob_Layout *layout, i32 width){ i32_Rect rect = layout->rect; if (layout->y > rect.y0){ layout->x += layout->size + layout->space; } layout->x += width; layout->y = rect.y0; } internal void do_palette(Color_UI *ui, i32_Rect rect){ Style *style = ui->state->style; Blob_Layout layout; begin_layout(&layout, rect); bool32 set_me; do_blob(ui, &layout, &style->main.back_color, &set_me); do_blob(ui, &layout, &style->main.margin_color, &set_me); do_blob(ui, &layout, &style->main.margin_active_color, &set_me); do_blob(ui, &layout, &style->main.cursor_color, &set_me); do_blob(ui, &layout, &style->main.at_cursor_color, &set_me); do_blob(ui, &layout, &style->main.mark_color, &set_me); do_blob(ui, &layout, &style->main.highlight_color, &set_me); do_blob(ui, &layout, &style->main.at_highlight_color, &set_me); do_blob(ui, &layout, &style->main.default_color, &set_me); do_blob(ui, &layout, &style->main.comment_color, &set_me); do_blob(ui, &layout, &style->main.keyword_color, &set_me); do_blob(ui, &layout, &style->main.str_constant_color, &set_me); do_blob(ui, &layout, &style->main.char_constant_color, &set_me); do_blob(ui, &layout, &style->main.int_constant_color, &set_me); do_blob(ui, &layout, &style->main.float_constant_color, &set_me); do_blob(ui, &layout, &style->main.bool_constant_color, &set_me); do_blob(ui, &layout, &style->main.include_color, &set_me); do_blob(ui, &layout, &style->main.preproc_color, &set_me); do_blob(ui, &layout, &style->main.special_character_color, &set_me); do_blob(ui, &layout, &style->main.highlight_junk_color, &set_me); do_blob(ui, &layout, &style->main.highlight_white_color, &set_me); do_blob(ui, &layout, &style->main.paste_color, &set_me); do_blob(ui, &layout, &style->main.file_info_style.bar_color, &set_me); do_blob(ui, &layout, &style->main.file_info_style.base_color, &set_me); do_blob(ui, &layout, &style->main.file_info_style.pop1_color, &set_me); do_blob(ui, &layout, &style->main.file_info_style.pop2_color, &set_me); do_v_divide(ui, &layout, 20); if (!ui->state->input_stage){ Render_Target *target = ui->state->target; draw_string(target, ui->state->font_id, "Global Palette: right click to save color", layout.x, layout.rect.y0, style->main.default_color); } layout.rect.y0 += layout.size + layout.space; layout.y = layout.rect.y0; i32 palette_size = ui->palette_size + 1000; u32 *color = ui->palette; for (i32 i = 1000; i < palette_size; ++i, ++color){ set_me = 0; do_blob(ui, &layout, *color, &set_me, i); if (set_me){ *color = *ui->color.out; ui->state->redraw = 1; } } } internal void do_sub_button(i32 id, Color_UI *ui, char *text){ i16 font_id = ui->state->font_id; i32 line_height = get_font_info(ui->state->font_set, font_id)->height; i32_Rect rect = layout_rect(ui->layout, line_height + 2); if (ui->state->input_stage){ ui_do_button_input(ui->state, rect, make_sub0(ui->state, id), 1); } else{ Render_Target *target = ui->state->target; u32 back_color, text_color; text_color = 0xFFDDDDDD; if (ui->state->selected.sub_id0 == id){ back_color = 0xFF444444; } else if (ui->state->hover.sub_id0 == id){ back_color = 0xFF222222; } else{ back_color = 0xFF111111; } draw_rectangle(target, rect, back_color); draw_string(target, font_id, text, rect.x0, rect.y0 + 1, text_color); } } internal void do_color_adjuster(Color_UI *ui, u32 *color, u32 text_color, u32 back_color, char *name){ i32 id = raw_ptr_dif(color, ui->state->style); i16 font_id = ui->state->font_id; i32 character_h = get_font_info(ui->state->font_set, font_id)->height; u32 text = 0, back = 0; i32_Rect bar = layout_rect(ui->layout, character_h); if (ui->state->input_stage){ if (ui_do_button_input(ui->state, bar, make_id(ui->state, id), 1)){ ui->has_hover_color = 1; ui->hover_color = super_color_create(*color); } } else{ Render_Target *target = ui->state->target; u32 text_hover = 0xFF101010; u32 back_hover = 0xFF999999; if (ui->state->selected.id != id && ui->state->hover.id == id){ text = text_hover; back = back_hover; } else{ text = text_color; back = back_color; } draw_rectangle(target, bar, back); i32 end_pos = draw_string(target, font_id, name, bar.x0, bar.y0, text); real32 x_spacing = ui->hex_advance; i32_Rect temp_rect = bar; temp_rect.x0 = temp_rect.x1 - CEIL32(x_spacing * 9.f); if (temp_rect.x0 >= end_pos + x_spacing){ u32 n = *color; char full_hex_string[] = "0x000000"; for (i32 i = 7; i >= 2; --i){ i32 m = (n & 0xF); n >>= 4; full_hex_string[i] = int_to_hexchar(m); } draw_string_mono(target, font_id, full_hex_string, (f32)temp_rect.x0, (f32)bar.y0, x_spacing, text); } for (i32 i = 0; i < ArrayCount(ui->highlight.ids); ++i){ if (ui->highlight.ids[i] == id){ draw_rectangle_outline(target, f32R(bar), text_color); break; } } } if (ui->state->selected.id == id){ Render_Target *target = ui->state->target; i32_Rect expanded = layout_rect(ui->layout, 115 + (character_h + 2)); UI_Layout_Restore restore = begin_sub_layout(ui->layout, expanded); ui->color.out = color; if (ui->state->input_stage){ if (ui->state->selected.sub_id0 == 0){ ui->state->selected.sub_id0 = 1; } } else{ draw_rectangle(target, expanded, 0xff000000); } begin_row(ui->layout, 3); do_sub_button(1, ui, "HSL"); do_sub_button(2, ui, "RGB"); do_sub_button(3, ui, "Palette"); i32_Rect sub_rect; sub_rect = expanded; sub_rect.y0 += 10 + character_h; switch (ui->state->selected.sub_id0){ case 1: do_hsl_sliders(ui, sub_rect); break; case 2: do_rgb_sliders(ui, sub_rect); break; case 3: do_palette(ui, sub_rect); break; } end_sub_layout(restore); } } internal void do_style_name(Color_UI *ui){ i32 id = -3; i16 font_id = ui->state->font_id; i32 line_height = get_font_info(ui->state->font_set, font_id)->height; i32_Rect srect = layout_rect(ui->layout, line_height); Widget_ID wid = make_id(ui->state, id); b32 selected = is_selected(ui->state, wid); if (ui->state->input_stage){ if (!selected){ ui_do_button_input(ui->state, srect, wid, 1); } else{ Style *style = ui->state->style; if (ui_do_text_field_input(ui->state, &style->name)){ ui->state->selected = {}; } } } else{ Render_Target *target = ui->state->target; Style *style = ui->state->style; u32 back, fore_text, fore_label; if (selected){ back = 0xFF000000; fore_label = 0xFF808080; fore_text = 0xFFFFFFFF; } else if (is_hover(ui->state, wid)){ back = 0xFF999999; fore_text = fore_label = 0xFF101010; } else{ back = style->main.back_color; fore_text = fore_label = style->main.default_color; } draw_rectangle(target, srect, back); i32 x = srect.x0; x = draw_string(target, font_id, "NAME: ", x, srect.y0, fore_label); x = draw_string(target, font_id, style->name.str, x, srect.y0, fore_text); } } internal b32 do_font_option(Color_UI *ui, i16 font_id){ b32 result = 0; Font_Info *info = get_font_info(ui->state->font_set, font_id); i32 sub_id = (i32)(i64)(info); i32_Rect orect = layout_rect(ui->layout, info->height); Widget_ID wid = make_sub0(ui->state, sub_id); if (ui->state->input_stage){ if (ui_do_button_input(ui->state, orect, wid, 0)){ result = 1; } } else{ Render_Target *target = ui->state->target; u32 back, fore; if (is_hover(ui->state, wid)){ back = 0xFF999999; fore = 0xFF101010; } else{ back = 0xFF000000; fore = 0xFFFFFFFF; } draw_rectangle(target, orect, back); i32 x = orect.x0; x = draw_string(target, font_id, "->", x, orect.y0, fore); draw_string(target, font_id, info->name.str, x, orect.y0, fore); } return result; } internal void do_font_switch(Color_UI *ui){ i32 id = -2; Render_Target *target = ui->state->target; Font_Set *font_set = ui->state->font_set; i16 font_id = ui->state->font_id; Font_Info *info = get_font_info(font_set, font_id); i32 character_h = info->height; i32_Rect srect = layout_rect(ui->layout, character_h); Widget_ID wid = make_id(ui->state, id); if (ui->state->input_stage){ ui_do_button_input(ui->state, srect, wid, 1); } else{ Style *style = ui->state->style; u32 back, fore; if (is_hover(ui->state, wid) && !is_selected(ui->state, wid)){ back = 0xFF999999; fore = 0xFF101010; } else{ back = style->main.back_color; fore = style->main.default_color; } draw_rectangle(target, srect, back); i32 x = srect.x0; x = draw_string(target, font_id, "FONT: ", x, srect.y0, fore); x = draw_string(target, font_id, info->name.str, x, srect.y0, fore); } if (is_selected(ui->state, wid)){ srect = layout_rect(ui->layout, character_h/2); if (!ui->state->input_stage) draw_rectangle(target, srect, 0xFF000000); i32 count = font_set->count + 1; for (i16 i = 1; i < count; ++i){ if (i == font_id) continue; if (do_font_option(ui, i)){ ui->global_font->font_id = i; ui->global_font->font_changed = 1; } } srect = layout_rect(ui->layout, character_h/2); if (!ui->state->input_stage) draw_rectangle(target, srect, 0xFF000000); } } internal b32 do_style_preview(Library_UI *ui, Style *style, i32 toggle = -1){ b32 result = 0; i32 id; if (style == ui->state->style) id = 2; else id = raw_ptr_dif(style, ui->styles->styles) + 100; i16 font_id = ui->state->font_id; Font_Info *info = get_font_info(ui->state->font_set, font_id); i32_Rect prect = layout_rect(ui->layout, info->height*3 + 6); Widget_ID wid = make_id(ui->state, id); if (ui->state->input_stage){ if (ui_do_button_input(ui->state, prect, wid, 0)){ result = 1; } } else{ Render_Target *target = ui->state->target; u32 margin_color = style->main.margin_color; if (is_hover(ui->state, wid)){ margin_color = style->main.margin_active_color; } i32_Rect inner; if (toggle != -1){ i32_Rect toggle_box = prect; toggle_box.x1 = toggle_box.x0 + info->height*2 + 6; prect.x0 = toggle_box.x1; inner = get_inner_rect(toggle_box, 3); draw_margin(target, toggle_box, inner, margin_color); draw_rectangle(target, inner, style->main.back_color); i32 d; d = info->height/2; i32_Rect b; b.x0 = (inner.x1 + inner.x0)/2 - d; b.y0 = (inner.y1 + inner.y0)/2 - d; b.x1 = b.x0 + info->height; b.y1 = b.y0 + info->height; if (toggle) draw_rectangle(target, b, margin_color); else draw_rectangle_outline(target, b, margin_color); } inner = get_inner_rect(prect, 3); draw_margin(target, prect, inner, margin_color); draw_rectangle(target, inner, style->main.back_color); i32 text_y = inner.y0; i32 text_x = inner.x0; text_x = draw_string(target, font_id, style->name.str, text_x, text_y, style->main.default_color); i32 font_x = (i32)(inner.x1 - font_string_width(target, font_id, info->name.str)); if (font_x > text_x + 10) draw_string(target, font_id, info->name.str, font_x, text_y, style->main.default_color); text_x = inner.x0; text_y += info->height; text_x = draw_string(target, font_id, "if ", text_x, text_y, style->main.keyword_color); text_x = draw_string(target, font_id, "(x < ", text_x, text_y, style->main.default_color); text_x = draw_string(target, font_id, "0", text_x, text_y, style->main.int_constant_color); text_x = draw_string(target, font_id, ") { x = ", text_x, text_y, style->main.default_color); text_x = draw_string(target, font_id, "0", text_x, text_y, style->main.int_constant_color); text_x = draw_string(target, font_id, "; } ", text_x, text_y, style->main.default_color); text_x = draw_string(target, font_id, "// comment", text_x, text_y, style->main.comment_color); text_x = inner.x0; text_y += info->height; text_x = draw_string(target, font_id, "[] () {}; * -> +-/ <>= ! && || % ^", text_x, text_y, style->main.default_color); } ui->layout->y = prect.y1; return result; } // BOTTOM