vertex_pos2_uv2 :: struct { pos : Vector2; uv : Vector2; } vertex_pos2_uv2_col4 :: struct { pos : Vector2; uv : Vector2; col : Vector4; } UI_TYPE :: enum u8 { ROOT; LABEL; ICON; FONTICON; TEXTINPUT; BUTTON; DROPDOWN; TOGGLE; LABELEDINPUT; WINDOW; CONTAINER; SCROLLBOX; LIST; SPACING; PROGRESSBAR; SCROLLBAR; BLOCK; } UIKey :: string; UIAnchor :: enum u8 { NONE; TOP_LEFT; TOP_RIGHT; BOTTOM_RIGHT; BOTTOM_LEFT; CENTER; } UILayout :: enum u8 { NONE; LTR; RTL; TTB; BTT; } UIOffAxisLayout :: enum u8 { NONE; MIDDLE; } UISizing :: enum u8 { NONE; FIT; GROW; } UIColourScheme :: struct { normal : Vector4; hover : Vector4; active : Vector4; background : Vector4; } UIContext :: struct { roots : [..]*Rect; stack : [..]*Rect; rects : Table(string, *Rect); anchor : UIAnchor; layout : UILayout; sizing_x : UISizing; sizing_y : UISizing; offaxis_layout : UIOffAxisLayout; font : *Font; margin : s32 = 2; border_size : s32 = 12; padding : s32 = 4; colours : UIColourScheme; show_debug : bool; } ui_context : UIContext; UIAction :: struct { left_pressed : bool; left_released : bool; right_pressed : bool; right_released : bool; hovering : bool; scroll : float; } ContainerFlags :: enum_flags u8 { SCROLL; } Rect :: struct { type : UI_TYPE; key : UIKey; anchor : UIAnchor; layout : UILayout; sizing_x : UISizing; sizing_y : UISizing; colours : UIColourScheme; offaxis_layout : UIOffAxisLayout; pos : Vector2; last_frame_pos : Vector2; size : Vector2; last_frame_size : Vector2; last_frame_max_size : Vector2; min_size : Vector2; max_size : Vector2; cursor : Vector2; margin : s32; border_size : s32; padding : s32; first : *Rect; next : *Rect; parent : *Rect; dragging : bool; resizing : bool; active : bool; hot : bool; offset_y : s64; flags : ContainerFlags; interacted : bool; last_frame : u64; } Label :: struct { #as using rect : Rect; rect.type = .LABEL; font : *Font; font_colour : Vector4; text : string; text_size : Vector2; max_descent : float; } Icon :: struct { #as using rect : Rect; rect.type = .ICON; texture : *Texture; } FontIcon :: struct { #as using rect : Rect; rect.type = .FONTICON; font : *Font; name : string; colour : Vector4; } Button :: struct { #as using rect : Rect; type = .BUTTON; normal_frame : *Texture; hot_frame : *Texture; active_frame : *Texture; icon : *Texture; font : *Font; font_colour : Vector4; text : string; } TextInput :: struct { #as using rect : Rect; type = .TEXTINPUT; frame : *Texture; font : *Font; font_colour : Vector4; text : [..]u32; max_width : float; max_descent : float; textedit_state : STB_TexteditState; cursor_time : float; } Container :: struct { #as using rect : Rect; type = .CONTAINER; gradient : bool; colour_left : Vector4; colour_right : Vector4; manual_max_x : bool; manual_max_y : bool; } Spacing :: struct { #as using rect : Rect; type = .SPACING; } ProgressBar :: struct { #as using rect : Rect; type = .PROGRESSBAR; background : Vector4; foreground : Vector4; progress : float; } ScrollBar :: struct { #as using rect : Rect; type = .SCROLLBAR; scrollbar_size : float; scrollbar_y : float; scrolling_by_mouse : bool; } normal_frame : Texture; hot_frame : Texture; active_frame : Texture; dog : Texture; ui_init :: () { init_texture(*normal_frame, "panel-border-000.png"); init_texture(*hot_frame, "panel-transparent-center-000.png"); init_texture(*active_frame, "panel-000.png"); init_texture(*dog, "dog.png"); } ui_new_frame :: () { array_reset(*ui_context.roots); array_reset(*ui_context.stack); } ui_end_frame :: () { ui_grow_roots(); for ui_context.roots { ui_layout(it); } ui_max_sizes(); for ui_context.roots { ui_line_break(it); } for ui_context.rects { if _frame > it.last_frame { remove it; } it.last_frame_pos = it.pos; it.last_frame_size = it.size; it.last_frame_max_size = it.max_size; } array_reset(*input_transitions); _frame += 1; } ui_append_to_parent :: (rect: *Rect) { rect.first = null; rect.next = null; rect.parent = null; if ui_context.stack.count > 0 { rect.parent = peek(ui_context.stack); if rect.parent.first == null { rect.parent.first = rect; } else { to_append : *Rect = rect.parent.first; while to_append.next { to_append = to_append.next; } to_append.next = rect; } } } ui_render :: () { for ui_context.roots render_ui(it); } ui_begin :: (s: string) { text, key := ui_decompose_and_generate_id(null, s); success, ptr := table_find(*ui_context.rects, key); root : *Rect = xx ptr; if !success { new_root := New(Rect); new_root.key = key; table_set(*ui_context.rects, new_root.key, new_root); root = xx << table_find_pointer(*ui_context.rects, new_root.key); root.anchor = ui_context.anchor; root.layout = ui_context.layout; root.sizing_x = ui_context.sizing_x; root.sizing_y = ui_context.sizing_y; root.colours = ui_context.colours; } root.last_frame = _frame; root.cursor = .{}; if root.anchor == .BOTTOM_LEFT { #if OS == .ANDROID { x, y, width, height, success := get_dimensions(android_app.window, true); root.pos = .{0.0, xx (height - android_app.contentRect.bottom)}; } else { root.pos = .{0.0, 0.0}; } } else if root.anchor == .TOP_LEFT { root.pos = .{0.0, xx window_height}; } root.first = null; root.next = null; root.parent = null; array_add(*ui_context.roots, << table_find_pointer(*ui_context.rects, text)); array_add(*ui_context.stack, << table_find_pointer(*ui_context.rects, text)); } ui_end :: () { root := cast(*Rect, pop(*ui_context.stack)); root.min_size = v2(2 * root.margin + 2 * root.padding); content_size := ui_content_size(root); root.size = root.min_size + content_size; root.max_size = root.size; } ui_draw_root :: (using rect: *Rect) { // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); if anchor == .TOP_LEFT && layout == .TTB { p := Vector2.{rect.pos.x + margin + padding, rect.pos.y + margin + padding - rect.size.y}; render_panel(null, p, rect.size - 2 * v2(margin), colours.background, colours.background, border_size); } else { render_panel(null, rect.pos + v2(margin) + v2(padding), rect.size - 2 * v2(margin), colours.background, colours.background, border_size); } // Restore modified GL state // restore_opengl_state(*opengl_state); } global_max_width : float = 100.0; ui_label :: (s: string = "", colour := Vector4.{1.0, 1.0, 1.0, 1.0}) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); label : *Label = xx ptr; if !success { new_label := New(Label); new_label.key = key; table_set(*ui_context.rects, new_label.key, new_label); label = xx << table_find_pointer(*ui_context.rects, new_label.key); label.anchor = ui_context.anchor; label.font = ui_context.font; label.text = text; label.colours = ui_context.colours; } label.offaxis_layout = ui_context.offaxis_layout; label.text = text; label.font_colour = colour; label.margin = ui_context.margin; label.padding = ui_context.padding; ui_size_label(label, 0.0); label.last_frame = _frame; ui_append_to_parent(label); } ui_size_label :: (label: *Label, max_width: float) { text_utf32 : [..]u32; defer array_free(text_utf32); StringAt : u64; while StringAt < xx label.text.count { Decode : kbts_decode = kbts_DecodeUtf8(label.text.data + StringAt, xx label.text.count - StringAt); StringAt += Decode.SourceCharactersConsumed; if Decode.Valid { array_add(*text_utf32, Decode.Codepoint); } } label_size : Vector2; max_descent : float; idx : s32; while idx < label.text.count { word_size, word_descent, read := next_word_size(label.font, text_utf32, idx, max_width); if max_width > 0.0 && label_size.x + word_size.x > max_width { break; } label_size.x += word_size.x; label_size.y = max(label_size.y, word_size.y); max_descent = max(max_descent, word_descent); idx += read; } //label_size := Vector2.{xx Simp.prepare_text(label.font, label.text), xx label.font.character_height}; label.min_size = v2(2 * label.margin + 2 * label.padding) + Vector2.{label_size.x, label_size.y + max_descent}; label.size = label.min_size; label.max_size = label.size; label.max_descent = max_descent; //label.lines = lines; label.text_size = label_size; } ui_draw_label :: (using label: *Label) { // render_pos : Vector2; // if parent.anchor == .TOP_LEFT { // render_pos = .{rect.pos.x + margin + padding, rect.pos.y + margin + padding - rect.size.y + parent.size.y}; // } else { // render_pos = pos + v2(margin) + v2(padding); // } // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); if ui_context.show_debug { render_rectangle(pos, size); render_rectangle(pos + v2(margin), size - 2 * v2(margin)); render_rectangle(pos + v2(margin) + v2(padding), size - 2 * v2(margin) - 2 * v2(padding)); } render_panel(null, pos + v2(margin), size - 2 * v2(margin), colours.background, colours.background, border_size); // Restore modified GL state // restore_opengl_state(*opengl_state); //n : s32 = xx label.text.count; line_height := font.face.size.metrics.height >> 6; midline : Vector2 = pos + Vector2.{cast(float, margin + padding), cast(float, margin + padding) + max_descent}; //0.0/*xx -(font.face.size.metrics.height >> 6 / 2.0)*/}; text_utf32 : [..]u32; defer array_free(text_utf32); StringAt : u64; while StringAt < xx text.count { Decode : kbts_decode = kbts_DecodeUtf8(text.data + StringAt, xx text.count - StringAt); StringAt += Decode.SourceCharactersConsumed; if Decode.Valid { array_add(*text_utf32, Decode.Codepoint); } } this_size : Vector2; idx : s32; row_start : s32; row_end : s32; while idx < label.text.count { word_size, word_max_descent, read := next_word_size(label.font, text_utf32, idx, label.max_size.x); if this_size.x + word_size.x > label.max_size.x { render_text(font, .{row_end - row_start, text_utf32.data + row_start}, this_size, midline, colour = font_colour); row_start = row_end; midline.y += label.font.face.size.metrics.height >> 6; } this_size.x += word_size.x; this_size.y = max(this_size.y, word_size.y); idx += read; row_end += read; } if row_start < row_end { render_text(font, .{row_end - row_start, text_utf32.data + row_start}, midline, this_size, colour = font_colour); } //label_size := Vector2.{xx Simp.prepare_text(font, text), xx font.character_height}; //Simp.draw_prepared_text(font, xx midline.x, xx (midline.y - label_size.y / 2.0), font_colour); //draw_pos : Vector2 = last_frame_pos + v2(cast(float, margin + padding)) + .{0.0, text_size.y}; // for line : lines { // render_text(font, line.text, .{draw_pos.x, draw_pos.y - line.size.y + line.max_descent}, colour = font_colour); // draw_pos.y -= line.size.y; // } // label_size, max_ascent, max_descent := calculate_string_draw_size(label.font, label.text, true); } ui_icon :: (s: string, texture: *Texture, size: Vector2) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); icon : *Icon = xx ptr; if !success { new_icon := New(Icon); new_icon.key = key; table_set(*ui_context.rects, new_icon.key, new_icon); icon = xx << table_find_pointer(*ui_context.rects, new_icon.key); icon.colours = ui_context.colours; icon.texture = texture; } icon.offaxis_layout = ui_context.offaxis_layout; icon.margin = ui_context.margin; icon.padding = ui_context.padding; icon.min_size = size; icon.size = icon.min_size + v2(2 * icon.margin + 2 * icon.padding); icon.max_size = icon.size; icon.last_frame = _frame; ui_append_to_parent(icon); } ui_draw_icon :: (using icon: *Icon) { // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); if ui_context.show_debug { render_rectangle(pos, size); render_rectangle(pos + v2(margin), size - 2 * v2(margin)); render_rectangle(pos + v2(margin) + v2(padding), size - 2 * v2(margin) - 2 * v2(padding)); } render_texture(texture, pos + v2(margin) + v2(padding), min_size); // Restore modified GL state // restore_opengl_state(*opengl_state); } ui_font_icon :: (font: *Font, s: string, size: Vector2, colour: Vector4 = .{1, 1, 1, 1}) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); icon : *FontIcon = xx ptr; if !success { new_icon := New(FontIcon); new_icon.key = key; table_set(*ui_context.rects, new_icon.key, new_icon); icon = xx << table_find_pointer(*ui_context.rects, new_icon.key); icon.colours = ui_context.colours; icon.font = font; } icon.colour = colour; icon.offaxis_layout = ui_context.offaxis_layout; icon.name = s; icon.margin = ui_context.margin; icon.padding = ui_context.padding; icon.min_size = size; icon.size = icon.min_size + v2(2 * icon.margin + 2 * icon.padding); icon.max_size = icon.size; icon.last_frame = _frame; ui_append_to_parent(icon); } ui_draw_font_icon :: (using font_icon: *FontIcon) { midline : Vector2 = pos + Vector2.{cast(float, margin + border_size + padding), xx (size.y / 2.0)}; // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); if ui_context.show_debug { render_rectangle(pos, size); render_rectangle(pos + v2(margin), size - 2 * v2(margin)); render_rectangle(pos + v2(margin) + v2(padding), size - 2 * v2(margin) - 2 * v2(padding)); } // Restore modified GL state // restore_opengl_state(*opengl_state); } ui_button :: (s: string = "", icon_texture: *Texture = null, font_colour := Vector4.{1, 1, 1, 1}, draw_frame := true) -> bool { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); button : *Button = xx ptr; if !success { new_button := New(Button); new_button.key = key; table_set(*ui_context.rects, new_button.key, new_button); button = xx << table_find_pointer(*ui_context.rects, new_button.key); button.anchor = ui_context.anchor; button.sizing_x = ui_context.sizing_x; button.sizing_y = ui_context.sizing_y; button.font = ui_context.font; button.text = text; if draw_frame { button.normal_frame = *normal_frame; button.hot_frame = *hot_frame; button.active_frame = *active_frame; } } button.colours = ui_context.colours; button.font_colour = font_colour; button.offaxis_layout = ui_context.offaxis_layout; button.margin = ui_context.margin; button.border_size = ui_context.border_size; button.padding = ui_context.padding; button.min_size = v2(2 * button.margin + 2 * button.border_size + 2 * button.padding); text_utf32 : [..]u32; defer array_free(text_utf32); StringAt : u64; while StringAt < xx text.count { Decode : kbts_decode = kbts_DecodeUtf8(text.data + StringAt, xx text.count - StringAt); StringAt += Decode.SourceCharactersConsumed; if Decode.Valid { array_add(*text_utf32, Decode.Codepoint); } } idx : s32; while idx < text.count { label_size, max_descent, read := next_word_size(button.font, text_utf32, idx, button.max_size.x); idx += read; } // if icon_texture { // button.icon = icon_texture; // button.min_size.y += max(cast(float, button.icon.height), label_size.y); // button.min_size.x += button.icon.width; // button.min_size.x += label_size.x; // } else { // button.min_size += label_size; // } button.size = button.min_size; button.max_size = button.size; button.last_frame = _frame; button.interacted = false; ui_append_to_parent(button); action := ui_action(button); if action.hovering { button.hot = true; if action.left_pressed { button.active = true; } if action.left_released { button.active = false; return true; } button.interacted = true; } else { button.hot = false; button.active = false; next := button.first; while next { if next.interacted { button.interacted = true; } next = next.next; } } return false; } ui_draw_button :: (using button: *Button) { // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); if active { render_panel(active_frame, pos + v2(margin), size - 2 * v2(margin), colours.active, colours.active, border_size); } else if hot { render_panel(hot_frame, pos + v2(margin), size - 2 * v2(margin), colours.hover, colours.hover, border_size); } else { render_panel(normal_frame, pos + v2(margin), size - 2 * v2(margin), colours.normal, colours.normal, border_size); } midline : Vector2 = pos + Vector2.{cast(float, margin + border_size + padding), xx (size.y / 2.0)}; // if button.icon { // render_texture(*sprite, midline - Vector2.{0.0, sprite.scale.y / 2.0}); // midline += Vector2.{sprite.scale.x, 0.0}; // } if ui_context.show_debug { render_rectangle(pos, size); render_rectangle(pos + v2(margin), size - 2 * v2(margin)); render_rectangle(pos + v2(margin) + v2(border_size), size - 2 * v2(margin) - 2 * v2(border_size)); render_rectangle(pos + v2(margin) + v2(border_size) + v2(padding), size - 2 * v2(margin) - 2 * v2(border_size) - 2 * v2(padding)); } // Restore modified GL state // restore_opengl_state(*opengl_state); // label_size := Vector2.{xx Simp.prepare_text(button.font, button.text), xx button.font.character_height}; // Simp.draw_prepared_text(button.font, xx midline.x, xx (midline.y - label_size.y / 2.0), font_colour); } ui_scrollbar :: (s: string = "", icon_texture: *Texture = null, font_colour := Vector4.{1, 1, 1, 1}, draw_frame := true) -> bool { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); scrollbar : *ScrollBar = xx ptr; if !success { new_scrollbar := New(ScrollBar); new_scrollbar.key = key; table_set(*ui_context.rects, new_scrollbar.key, new_scrollbar); scrollbar = xx << table_find_pointer(*ui_context.rects, new_scrollbar.key); scrollbar.anchor = ui_context.anchor; scrollbar.sizing_x = ui_context.sizing_x; scrollbar.sizing_y = ui_context.sizing_y; } scrollbar.colours = ui_context.colours; scrollbar.offaxis_layout = ui_context.offaxis_layout; scrollbar.margin = ui_context.margin; scrollbar.border_size = ui_context.border_size; scrollbar.padding = ui_context.padding; scrollbar.min_size = v2(2 * scrollbar.margin + 2 * scrollbar.padding); scrollbar.size = scrollbar.min_size; scrollbar.max_size = scrollbar.size; scrollbar.last_frame = _frame; scrollbar.interacted = false; ui_append_to_parent(scrollbar); action := ui_action(scrollbar); if action.hovering { if action.left_pressed { x, y : float; SDL_GetMouseState(*x, *y); y = window_height - y; if xx y > scrollbar.scrollbar_y && xx y < scrollbar.scrollbar_y + scrollbar.scrollbar_size { scrollbar.hot = true; scrollbar.scrolling_by_mouse = true; } } if action.left_released == true { scrollbar.scrolling_by_mouse = false; } scrollbar.interacted = true; } else { if action.left_released == true { scrollbar.scrolling_by_mouse = false; } scrollbar.hot = false; scrollbar.active = false; next := scrollbar.first; while next { if next.interacted { scrollbar.interacted = true; } next = next.next; } } if scrollbar.scrolling_by_mouse { if !input_state.left_mouse { scrollbar.scrolling_by_mouse = false; } container := scrollbar.parent; container.offset_y = xx (container.offset_y - action.scroll); if container.flags & .SCROLL { if container.offset_y > xx (container.last_frame_size.y - container.max_size.y) container.offset_y = xx (container.last_frame_size.y - container.max_size.y); if container.offset_y < 0 container.offset_y = 0; } } return false; } ui_draw_scroll_bar :: (using scroll_bar: *ScrollBar) { // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); scrollbar := cast(*ScrollBar, next); y := parent.pos.y + (parent.size.y - parent.max_size.y); size_y := parent.max_size.y - parent.margin; scrollbar_offset_n := cast(float, parent.offset_y) / parent.size.y; scrollbar_offset := scrollbar_offset_n * size_y; render_panel(null, .{parent.pos.x + parent.size.x - 30 - 2 * parent.margin, y}, .{30, size_y}, .{0.94, 0.94, 0.94, 0.3}, .{0.94, 0.94, 0.94, 0.3}, 0); render_panel(null, .{parent.pos.x + parent.size.x - 30 - 2 * parent.margin, scrollbar_y}, .{30, scrollbar_size}, .{0.94, 0.94, 0.94, 0.6}, .{0.94, 0.94, 0.94, 0.6}, 0); // Restore modified GL state // restore_opengl_state(*opengl_state); } ui_text_input :: (s: string, font_colour := Vector4.{1, 1, 1, 1}) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); text_input : *TextInput = xx ptr; if !success { new_text_input := New(TextInput); new_text_input.key = key; table_set(*ui_context.rects, new_text_input.key, new_text_input); text_input = xx << table_find_pointer(*ui_context.rects, new_text_input.key); text_input.margin = ui_context.margin; text_input.border_size = ui_context.border_size; text_input.padding = ui_context.padding; //text_input.frame = get_texture("panel-border-000"); text_input.font = ui_context.font; stb_textedit_initialize_state(*text_input.textedit_state, 0); } ui_append_to_parent(text_input); text_size : Vector2; max_descent : float; i : s32; while i < text_input.text.count { row : StbTexteditRow; STB_TEXTEDIT_LAYOUTROW(*row, text_input, i); is_new_line : bool = text_input.text[i + row.num_chars - 1] == #char "\n"; text_size.x = max(text_size.x, row.x1); text_size.y += text_input.font.face.size.metrics.height >> 6; // if is_new_line && text_input.text.count <= i + row.num_chars { // text_size.y += text_input.font.face.size.metrics.height >> 6; // } i += xx row.num_chars; } text_input.min_size = v2(2 * text_input.margin + 2 * text_input.border_size + 2 * text_input.padding) + max(text_size, .{xx text_input.font.face.size.metrics.max_advance >> 6, xx text_input.font.face.size.metrics.height >> 6}); text_input.size = text_input.min_size; text_input.max_size = text_input.size; text_input.anchor = ui_context.anchor; text_input.colours = ui_context.colours; text_input.font_colour = font_colour; text_input.last_frame = _frame; text_input.cursor = .{}; text_input.interacted = false; action := ui_action(text_input); if action.hovering { text_input.hot = true; if action.left_pressed { text_input.active = true; } text_input.interacted = true; } else { text_input.hot = false; } } ui_draw_text_input :: (using text_input: *TextInput) { // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); // if virtual_input.show_ui_debug_frames { // render_rectangle(pos, size); // render_rectangle(pos + v2(margin), size - 2 * v2(margin)); // render_rectangle(pos + v2(margin) + v2(padding), size - 2 * v2(margin) - 2 * v2(padding)); // } if active { render_panel(null, pos + v2(margin), size - 2 * v2(margin), colours.active, colours.active, border_size); } else if hot { render_panel(null, pos + v2(margin), size - 2 * v2(margin), colours.hover, colours.hover, border_size); } else { render_panel(null, pos + v2(margin), size - 2 * v2(margin), colours.normal, colours.normal, border_size); } // Restore modified GL state // restore_opengl_state(*opengl_state); cursor_pos : Vector2 = pos + .{0.0, size.y - font.face.size.metrics.height >> 6 - 2 * (margin + border_size + padding)}; Rectangle :: struct { pos : Vector2; size : Vector2; } if text_input.text.count > 0 { draw_pos : Vector2 = pos + .{0.0, size.y - font.face.size.metrics.height >> 6 - (margin + border_size + padding)} + v2(margin) + v2(border_size) + v2(padding); i : s32; select_pos : Vector2 = pos + .{0.0, size.y - font.face.size.metrics.height >> 6 - 2 * (margin + border_size + padding)}; while i < text_input.text.count { row : StbTexteditRow; STB_TEXTEDIT_LAYOUTROW(*row, text_input, i); is_new_line : bool = text_input.text[i + row.num_chars - 1] == #char "\n"; select_rectangle : Rectangle; log("Cursor: %", textedit_state.cursor); if i + row.num_chars >= textedit_state.cursor && !(is_new_line && textedit_state.cursor == i + row.num_chars) { j : s32; while grapheme := i + j < textedit_state.cursor { size, max_descent, read := next_grapheme_size(font, text, i + j, 0.0); cursor_pos.x += size.x; j += read; } } else { cursor_pos.y -= font.face.size.metrics.height >> 6; } render_text(font, .{row.num_chars, text_input.text.data + i}, .{draw_pos.x, draw_pos.y + row.ymin}, text_input.size,colour = font_colour); if textedit_state.select_start < textedit_state.select_end { if !(textedit_state.select_end < i || textedit_state.select_start > i + row.num_chars) { select_size : Vector2 = .{0.0, xx font.face.size.metrics.height >> 6}; if textedit_state.select_start < i && textedit_state.select_end > i + row.num_chars { select_pos.x = 0.0; select_size.x = row.x1 - row.x0; } if textedit_state.select_start >= i && textedit_state.select_end > i + row.num_chars { j : s32; while grapheme := i + j < textedit_state.select_start && i + j < i + row.num_chars { size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); if segment_type & .GRAPHEME { select_pos.x += size.x; j += read; } else { select_pos.y -= font.face.size.metrics.height >> 6; } } select_size.x = row.x1 - select_pos.x; } if textedit_state.select_start < i && textedit_state.select_end <= i + row.num_chars { select_pos.x = 0.0; j : s32; while grapheme := i + j < textedit_state.select_end && i + j < i + row.num_chars { size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); if segment_type & .GRAPHEME { select_size.x += size.x; j += read; } } } if textedit_state.select_start >= i && textedit_state.select_end <= i + row.num_chars { j : s32; while grapheme := i + j < textedit_state.select_start && i + j < i + row.num_chars { size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); if segment_type & .GRAPHEME { select_pos.x += size.x; j += read; } } while grapheme := i + j < textedit_state.select_end && i + j < i + row.num_chars { size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); if segment_type & .GRAPHEME { select_size.x += size.x; j += read; } } } render_filled_rectangle(select_pos + v2(margin) + v2(border_size) + v2(padding), select_size, .{0.8, 0.2, 0.55, 0.4}); } } else if textedit_state.select_start > textedit_state.select_end { if textedit_state.select_start > i && textedit_state.select_end < i + row.num_chars { select_size : Vector2 = .{0.0, xx font.face.size.metrics.height >> 6}; if textedit_state.select_end < i && textedit_state.select_start > i + row.num_chars { select_pos.x = 0.0; select_size.x = row.x1 - row.x0; } if textedit_state.select_end >= i && textedit_state.select_start > i + row.num_chars { j : s32; while grapheme := i + j < textedit_state.select_end && i + j < i + row.num_chars { size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); if segment_type & .GRAPHEME { select_pos.x += size.x; j += read; } else { select_pos.y -= font.face.size.metrics.height >> 6; } } select_size.x = row.x1 - select_pos.x; } if textedit_state.select_end < i && textedit_state.select_start <= i + row.num_chars { select_pos.x = 0.0; j : s32; while grapheme := i + j < textedit_state.select_start && i + j < i + row.num_chars { size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); if segment_type & .GRAPHEME { select_size.x += size.x; j += read; } } } if textedit_state.select_end >= i && textedit_state.select_start <= i + row.num_chars { j : s32; while grapheme := i + j < textedit_state.select_end && i + j < i + row.num_chars { size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); if segment_type & .GRAPHEME { select_pos.x += size.x; j += read; } } while grapheme := i + j < textedit_state.select_start && i + j < i + row.num_chars { size, max_descent, read, segment_type := next_segment_size(font, text, i + j, 0.0, .GRAPHEME | .LINE_HARD); if segment_type & .GRAPHEME { select_size.x += size.x; j += read; } } } render_filled_rectangle(select_pos + v2(margin) + v2(border_size) + v2(padding), select_size, .{0.8, 0.2, 0.55, 0.4}); } } draw_pos.y -= font.face.size.metrics.height >> 6; select_pos.y -= font.face.size.metrics.height >> 6; i += xx row.num_chars; } } // if textedit_state.select_end != textedit_state.select_start { // if textedit_state.select_end > textedit_state.select_start { // for select_rectangles { // render_filled_rectangle(it.pos + v2(margin) + v2(border_size) + v2(padding), it.size, .{0.8, 0.2, 0.55, 0.4}); // } // } // } if cursor_time < 0.5 { render_filled_rectangle(cursor_pos + v2(margin) + v2(border_size) + v2(padding), .{5.0, xx font.face.size.metrics.height >> 6}); } else if cursor_time > 1.0 { cursor_time = 0.0; } cursor_time += dt; } ui_begin_container :: (s: string, gradient := false, colour_left := Vector4.{}, colour_right := Vector4.{}, max_x: s32 = -1, max_y: s32 = -1, flags: ContainerFlags = 0) -> bool { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); container : *Container = xx ptr; if !success { new_container := New(Container); new_container.key = key; table_set(*ui_context.rects, new_container.key, new_container); container = xx << table_find_pointer(*ui_context.rects, new_container.key); container.anchor = ui_context.anchor; container.layout = ui_context.layout; container.sizing_x = ui_context.sizing_x; container.sizing_y = ui_context.sizing_y; container.colours = ui_context.colours; } container.flags = flags; container.offaxis_layout = ui_context.offaxis_layout; container.margin = ui_context.margin; container.padding = ui_context.padding; container.min_size = v2(2 * container.margin + 2 * container.padding); container.size = container.min_size; if max_x != -1 { container.max_size.x = xx max_x; container.manual_max_x = true; } if max_y != -1 { container.max_size.y = xx max_y; container.manual_max_y = true; } container.last_frame = _frame; container.interacted = false; container.cursor = v2(container.margin + container.padding); container.gradient = gradient; container.colour_left = colour_left; container.colour_right = colour_right; ui_append_to_parent(container); array_add(*ui_context.stack, container); if container.flags & .SCROLL ui_scrollbar("scrollbar"); return true; } ui_end_container :: () { container : *Container = xx pop(*ui_context.stack); content_size := ui_content_size(container); container.size = container.min_size + content_size; if container.anchor == .TOP_LEFT { container.cursor += .{0, content_size.y};// - 4 * container.padding}; } action := ui_action(container); if action.hovering { if container.flags & .SCROLL { container.offset_y = xx (container.offset_y - action.scroll); if container.offset_y > xx (container.last_frame_size.y - container.max_size.y) container.offset_y = xx (container.last_frame_size.y - container.max_size.y); if container.offset_y < 0 container.offset_y = 0; } } } ui_draw_container :: (using container: *Container) { // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); if gradient { render_panel(null, pos + v2(margin), size - 2 * v2(margin), colour_left, colour_right, border_size); } else { render_panel(null, pos + v2(margin), size - 2 * v2(margin), colours.background, colours.background, border_size); } if ui_context.show_debug { render_rectangle(pos, size); render_rectangle(pos + v2(margin), size - 2 * v2(margin)); render_rectangle(pos + v2(margin) + v2(padding), size - 2 * v2(margin) - 2 * v2(padding)); } // Restore modified GL state // restore_opengl_state(*opengl_state); } ui_spacing :: (s: string) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); spacing : *Spacing = xx ptr; if !success { new_spacing := New(Spacing); new_spacing.key = key; table_set(*ui_context.rects, new_spacing.key, new_spacing); spacing = xx << table_find_pointer(*ui_context.rects, new_spacing.key); spacing.anchor = ui_context.anchor; spacing.layout = ui_context.layout; spacing.sizing_x = ui_context.sizing_x; spacing.sizing_y = ui_context.sizing_y; spacing.colours = ui_context.colours; } spacing.offaxis_layout = ui_context.offaxis_layout; spacing.margin = ui_context.margin; spacing.padding = ui_context.padding; spacing.min_size = v2(2 * spacing.margin + 2 * spacing.padding); spacing.size = spacing.min_size; spacing.last_frame = _frame; spacing.interacted = false; spacing.cursor = v2(spacing.margin + spacing.padding); ui_append_to_parent(spacing); } ui_progress_bar :: (s: string = "", progress: float, size: Vector2, background := Vector4.{1, 1, 1, 1}, foreground := Vector4.{1, 1, 1, 1}) { text, key := ui_decompose_and_generate_id(peek(ui_context.stack), s); success, ptr := table_find(*ui_context.rects, key); bar : *ProgressBar = xx ptr; if !success { new_bar := New(ProgressBar); new_bar.key = key; table_set(*ui_context.rects, new_bar.key, new_bar); bar = xx << table_find_pointer(*ui_context.rects, new_bar.key); bar.anchor = ui_context.anchor; bar.sizing_x = ui_context.sizing_x; bar.sizing_y = ui_context.sizing_y; } bar.progress = progress; bar.background = background; bar.foreground = foreground; bar.colours = ui_context.colours; bar.offaxis_layout = ui_context.offaxis_layout; bar.margin = ui_context.margin; bar.border_size = ui_context.border_size; bar.padding = ui_context.padding; bar.min_size = size; bar.size = v2(2 * bar.margin + 2 * bar.border_size + 2 * bar.padding) + bar.min_size; bar.last_frame = _frame; bar.interacted = false; ui_append_to_parent(bar); } ui_draw_progress_bar :: (using bar: *ProgressBar) { // Backup GL state // opengl_state : OpenGLState; // save_opengl_state(*opengl_state); render_panel(null, pos + v2(margin), min_size, background, background, border_size, 10.0); render_panel(null, pos + v2(margin), .{min_size.x * progress, min_size.y}, foreground, foreground, border_size, 10.0); if ui_context.show_debug { render_rectangle(pos, size); render_rectangle(pos + v2(margin), size - 2 * v2(margin)); render_rectangle(pos + v2(margin) + v2(padding), size - 2 * v2(margin) - 2 * v2(padding)); } // Restore modified GL state // restore_opengl_state(*opengl_state); } ui_decompose_and_generate_id :: (parent: *Rect, s: string) -> string, UIKey { key : UIKey; text : string; if contains(s, "###") { components := split(s, "###"); key = components[1]; text = components[0]; } else if contains(s, "##") { components := split(s, "##"); key = s; text = components[0]; } else { key = s; text = s; } if parent key = join(key, parent.key, separator = "-"); return text, key; } ui_action :: (rect : *Rect) -> UIAction { action : UIAction; should_consume : bool; x, y : float; SDL_GetMouseState(*x, *y); mouse := Vector2.{xx x, xx (window_height - y)}; isinrect := is_in_rect(rect, mouse); if isinrect { if rect.type == .TEXTINPUT { //print("%\n", input_transitions); } action.hovering = true; for transition: input_transitions { if transition.left_down { action.left_pressed = true; should_consume = true; } if transition.left_up { action.left_released = true; should_consume = true; } if transition.right_down { action.right_pressed = true; should_consume = true; } if transition.right_up { action.right_released = true; should_consume = true; } if rect.type == .CONTAINER { container := cast(*Container, rect); if container.flags & .SCROLL && abs(transition.mouse_wheel) > 0 { action.scroll += transition.mouse_wheel; should_consume = true; } if OS == .ANDROID && container.flags & .SCROLL && abs(transition.motion_delta.y) > 0 { action.scroll -= transition.motion_delta.y; should_consume = true; } } if rect.type == .SCROLLBAR { if abs(transition.motion_delta.y) > 0 { action.scroll -= transition.motion_delta.y; should_consume = true; } } } } else { for transition : input_transitions { if rect.type == { case .SCROLLBAR; if abs(transition.motion_delta.y) > 0 { action.scroll -= transition.motion_delta.y; } } } } for transition : input_transitions { if rect.type == { case .TEXTINPUT; text_input := cast(*TextInput, rect); if text_input.active { if transition.char > 0 { stb_textedit_key(text_input, *text_input.textedit_state, transition.char); text_input.cursor_time = 0.0; } } } } if should_consume array_reset(*input_transitions); return action; } ui_content_size :: (rect: *Rect) -> Vector2 { size : Vector2; next := rect.first; while next { if next.type != .SCROLLBAR { size_to_add := /*ifx next.type == .LABEL then next.last_frame_size else */next.size; if rect.layout == { case .TTB; size.x = max(size.x, /*rect.min_size.x + */ size_to_add.x); size.y += size_to_add.y; case .LTR; size.x += size_to_add.x; size.y = max(size.y, /*rect.min_size.y +*/ size_to_add.y); case .BTT; size.x = max(size.x, /*rect.min_size.x +*/ size_to_add.x); size.y += size_to_add.y; } } if next.interacted { rect.interacted = true; } next = next.next; } return size; } ui_grow_roots :: () { top_left : *Rect; bottom_left : *Rect; for ui_context.roots { if it.anchor == .TOP_LEFT top_left = it; else if it.anchor == .BOTTOM_LEFT bottom_left = it; } if bottom_left { bottom_left.max_size = bottom_left.size; top_left.max_size = Vector2.{xx window_width, xx window_height - bottom_left.max_size.y}; if bottom_left.sizing_x == .GROW { bottom_left.size.x = xx window_width; bottom_left.max_size.x = bottom_left.size.x; } } if top_left { // if top_left.sizing_y == .GROW { // if bottom_left.sizing_y != .GROW { // //top_left.size.y = window_height - bottom_left.size.y; // } // } if top_left.sizing_x == .GROW { top_left.size.x = xx window_width; top_left.max_size.x = top_left.size.x; } ui_grow_containers(top_left); } } ui_grow_containers :: (rect: *Rect) { remaining_width : s32 = xx rect.size.x - 2 * rect.margin - 2 * rect.padding; remaining_height : s32 = xx rect.size.y - 2 * rect.margin - 2 * rect.padding; if rect.type == .CONTAINER { container := cast(*Container, rect); if container.flags & .SCROLL { remaining_width -= 50; } } if rect.layout == .TTB { next := rect.first; num_growable_children : int; while next { remaining_height -= xx next.size.y; if next.type == .CONTAINER || next.type == .SPACING { if next.sizing_y == .GROW { num_growable_children += 1; } } next = next.next; } if num_growable_children > 0 { while remaining_height > 0 { smallest := rect.first.size.y; second := FLOAT32_INFINITY; height_to_add := remaining_height; next := rect.first; while next { if next.type == .CONTAINER || next.type == .SPACING { if next.sizing_y == .GROW { if next.size.y < smallest { second = smallest; smallest = next.size.y; } if next.size.y > smallest { second = min(second, next.size.y); height_to_add = xx (second - smallest); } } } next = next.next; } height_to_add = min(height_to_add, xx (cast(float) remaining_height / num_growable_children)); next = rect.first; while next { if next.type == .CONTAINER || next.type == .SPACING { if next.sizing_y == .GROW { if next.size.y == smallest { next.size.y += height_to_add; remaining_height -= height_to_add; } } } next = next.next; } } } next = rect.first; while next { if next.type == .CONTAINER || next.type == .SPACING { if next.sizing_x == .GROW { next.size.x += remaining_width - next.size.x; } } next = next.next; } } else if rect.layout == .LTR { next := rect.first; num_growable_children : int; while next { remaining_width -= xx next.size.x; if next.type == .CONTAINER || next.type == .SPACING { if next.sizing_x == .GROW { num_growable_children += 1; } } next = next.next; } if num_growable_children > 0 { while remaining_width > 0 { smallest := rect.first.size.x; second := FLOAT32_INFINITY; width_to_add := remaining_width; next := rect.first; while next { if next.type == .CONTAINER || next.type == .SPACING { if next.sizing_x == .GROW { if next.size.x < smallest { second = smallest; smallest = next.size.x; } if next.size.x > smallest { second = min(second, next.size.x); width_to_add = xx (second - smallest); } } } next = next.next; } width_to_add = min(width_to_add, xx (cast(float) remaining_width / num_growable_children)); next = rect.first; while next { if next.type == .CONTAINER || next.type == .SPACING { if next.sizing_x == .GROW { if next.size.x == smallest { next.size.x += width_to_add; remaining_width -= width_to_add; } } if next.sizing_y == .GROW { next.size.y += remaining_height - next.size.y; } } next = next.next; } } } next = rect.first; while next { if next.type == .CONTAINER || next.type == .SPACING { if next.sizing_y == .GROW { next.size.y += remaining_height - next.size.x; } } next = next.next; } } next := rect.first; while next { ui_grow_containers(next); next = next.next; } } ui_max_sizes :: () { top_left : *Rect; bottom_left : *Rect; for ui_context.roots { if it.anchor == .TOP_LEFT top_left = it; else if it.anchor == .BOTTOM_LEFT bottom_left = it; } if bottom_left { next := bottom_left.first; while next { if next.type == .CONTAINER ui_max_size_containers(next); next = next.next; } } if top_left { next := top_left.first; while next { if next.type == .CONTAINER ui_max_size_containers(next); if next.type == .TEXTINPUT { text_input := cast(*TextInput, next); text_input.max_size.x = top_left.max_size.x; return; } next = next.next; } } } ui_max_size_containers :: (rect: *Rect) { if rect.type != .CONTAINER return; container := cast(*Container, rect); if !container.manual_max_x { rect.max_size.x = min(rect.parent.max_size.x, rect.size.x); } if !container.manual_max_y { rect.max_size.y = min(rect.parent.max_size.y - (rect.parent.pos.y - (rect.pos.y + rect.size.y)), rect.size.y); } //rect.max_size = min(Vector2.{rect.parent.max_size.x, rect.parent.max_size.y - (rect.parent.pos.y - (rect.pos.y + rect.size.y))}, rect.size); next := rect.first; while next { if rect.flags & .SCROLL && next.type == .SCROLLBAR { scrollbar := cast(*ScrollBar, next); y := rect.pos.y + (rect.size.y - rect.max_size.y); size_y := rect.max_size.y - rect.margin; scrollbar.pos = .{rect.pos.x + rect.size.x - 30 - 2 * rect.margin, y}; scrollbar.size = .{30, size_y}; scrollbar.max_size = scrollbar.size; scrollbar_size := rect.max_size.y / rect.size.y * size_y; scrollbar_offset_n := cast(float, rect.offset_y) / rect.size.y; scrollbar_offset := scrollbar_offset_n * size_y; scrollbar_y := y + size_y - scrollbar_size - scrollbar_offset; scrollbar.scrollbar_y = scrollbar_y; scrollbar.scrollbar_size = scrollbar_size; } else if next.type == .TEXTINPUT { text_input := cast(*TextInput, next); text_input.max_size.x = rect.max_size.x; } ui_max_size_containers(next); next = next.next; } } ui_line_break :: (rect: *Rect) { next : *Rect = rect.first; while next { if next.type == .LABEL { ui_size_label(xx next, rect.max_size.x); } ui_line_break(next); next = next.next; } } ui_layout :: (rect: *Rect) { next : *Rect = rect.first; original_cursor : Vector2 = rect.cursor; while next { if next.type == .WINDOW { ui_layout(next); next = next.next; continue; } if next.type == .SCROLLBAR { ui_layout(next); next = next.next; continue; } if rect.type == { case .ROOT; #through; case .LABEL; #through; case .ICON; #through; case .TEXTINPUT; #through; case .CONTAINER; #through; case .BUTTON; if rect.anchor == .BOTTOM_LEFT { next.pos = rect.pos + rect.cursor + Vector2.{0, xx rect.offset_y}; if rect.layout == { case .LTR; rect.cursor.x += next.size.x; if next.offaxis_layout == .MIDDLE { next.pos.y = rect.pos.y + rect.cursor.y + (rect.size.y / 2.0) - (next.size.y / 2.0); } case .BTT; rect.cursor.y += next.size.y; if next.offaxis_layout == .MIDDLE { next.pos.x = rect.pos.x + rect.cursor.x + (rect.size.x / 2.0) - (next.size.x / 2.0); } } } else if rect.anchor == .TOP_LEFT { size_to_add := ifx next.type == .LABEL then next.last_frame_size else next.size; next.pos = rect.pos + rect.cursor - .{0.0, size_to_add.y} + Vector2.{0, xx rect.offset_y}; if rect.layout == { case .LTR; if rect.type == .CONTAINER && rect.flags & .SCROLL { rect.cursor.x += next.max_size.x; } else { rect.cursor.x += next.size.x; } if next.offaxis_layout == .MIDDLE { next.pos.y = rect.pos.y + rect.cursor.y - (rect.size.y / 2.0) - (next.size.y / 2.0); } case .RTL; case .TTB; if rect.type == .CONTAINER && rect.flags & .SCROLL { rect.cursor.y -= next.max_size.y; } else { rect.cursor.y -= size_to_add.y; } if next.offaxis_layout == .MIDDLE { next.pos.x = rect.pos.x + rect.cursor.x + (rect.size.x / 2.0) - (next.size.x / 2.0); } case .BTT; } } } ui_layout(next); next = next.next; } } render_panel :: (texture: *Texture, pos: Vector2, size: Vector2, colour_left: Vector4, colour_right: Vector4, border_size: s32, radius := 15.0) { glEnable(GL_BLEND); use(*panel_shader); vao : GLuint; glGenVertexArrays(1, *vao); glBindVertexArray(vao); whole_pos : Vector2; whole_size : Vector2; whole_pos.x = cast(float, cast(s64, pos.x)); whole_pos.y = cast(float, cast(s64, pos.y)); whole_size.x = cast(float, cast(s64, size.x)); whole_size.y = cast(float, cast(s64, size.y)); vertices := rounded_rectangle_vertices(whole_size.x, whole_size.y, radius, colour_left, colour_right); for * vertices it.pos += whole_pos; vertex_vbo : GLuint; glGenBuffers(1, *vertex_vbo); glBindBuffer(GL_ARRAY_BUFFER, vertex_vbo); glBufferData(GL_ARRAY_BUFFER, size_of(type_of(vertices[0])) * vertices.count, vertices.data, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(vertex_pos2_uv2_col4), xx offset_of(vertex_pos2_uv2_col4, #code pos)); if texture { glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, size_of(vertex_pos2_uv2_col4), xx offset_of(vertex_pos2_uv2_col4, #code uv)); } glEnableVertexAttribArray(2); glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, size_of(vertex_pos2_uv2_col4), xx offset_of(vertex_pos2_uv2_col4, #code col)); model : Matrix4 = identity_of(Matrix4); view : Matrix4 = identity_of(Matrix4); proj : Matrix4 = orthographic_gl(0.0, xx window_width, 0.0, xx window_height, -1.0, 1.0); if texture { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture.id); } glUniformMatrix4fv(glGetUniformLocation(panel_shader.id, "uni_model"), 1, GL_TRUE, cast(*float) *model); glUniformMatrix4fv(glGetUniformLocation(panel_shader.id, "uni_view"), 1, GL_TRUE, cast(*float) *view); glUniformMatrix4fv(glGetUniformLocation(panel_shader.id, "uni_proj"), 1, GL_TRUE, cast(*float) *proj); glUniform1i(glGetUniformLocation(panel_shader.id, "ninepatchborder"), border_size); glUniform2f(glGetUniformLocation(panel_shader.id, "scale"), size.x, size.y); if texture { glUniform1i(glGetUniformLocation(panel_shader.id, "texturewidth\0"), texture.width); glUniform1i(glGetUniformLocation(panel_shader.id, "textureheight\0"), texture.height); } glDrawArrays(GL_TRIANGLES, 0, xx vertices.count); glBindVertexArray(0); glDeleteBuffers(1, *vertex_vbo); glDeleteVertexArrays(1, *vao); array_reset(*vertices); } render_texture :: (using texture: *Texture, pos: Vector2, size: Vector2) { if !texture { return; } glEnable(GL_BLEND); vao : GLuint; glGenVertexArrays(1, *vao); glBindVertexArray(vao); use(*sprite_shader); vertices := Vector4.[ .{0.0, 0.0, 0.0, 0.0}, .{1.0, 1.0, 1.0, 1.0}, .{0.0, 1.0, 0.0, 1.0}, .{0.0, 0.0, 0.0, 0.0}, .{1.0, 0.0, 1.0, 0.0}, .{1.0, 1.0, 1.0, 1.0}, ]; vertex_vbo : GLuint; glGenBuffers(1, *vertex_vbo); glBindBuffer(GL_ARRAY_BUFFER, vertex_vbo); glBufferData(GL_ARRAY_BUFFER, size_of(type_of(vertices[0])) * vertices.count, vertices.data, GL_STATIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(type_of(vertices[0])), xx (0 * size_of(type_of(vertices[0].x)))); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, size_of(type_of(vertices[0])), xx (2 * size_of(type_of(vertices[0].x)))); glEnableVertexAttribArray(1); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, id); model : Matrix4 = identity_of(Matrix4); model = translate(model, .{pos.x, pos.y, 0.0}); model = scale(model, .{size.x, size.y, 1.0}); view : Matrix4 = identity_of(Matrix4); proj : Matrix4 = orthographic_gl(0.0, xx window_width, 0.0, xx window_height, -1.0, 1.0); glUniformMatrix4fv(glGetUniformLocation(sprite_shader.id, "uni_model"), 1, GL_TRUE, cast(*float) *model); glUniformMatrix4fv(glGetUniformLocation(sprite_shader.id, "uni_view"), 1, GL_TRUE, cast(*float) *view); glUniformMatrix4fv(glGetUniformLocation(sprite_shader.id, "uni_proj"), 1, GL_TRUE, cast(*float) *proj); glDrawArrays(GL_TRIANGLES, 0, xx vertices.count); glBindVertexArray(0); glDeleteBuffers(1, *vertex_vbo); glDeleteVertexArrays(1, *vao); } rounded_rectangle_vertices :: (width: float, height: float, radius: float, _colour_left: Vector4, _colour_right: Vector4) -> [..]vertex_pos2_uv2_col4 { vertices : [..]vertex_pos2_uv2_col4; colour_left := srgb_to_linear(_colour_left); colour_right := srgb_to_linear(_colour_right); // NE origin : Vector2; v : Vector2; i := 0; while i < 90 { rad := i * PI / 180.0; origin = Vector2.{width - radius, height - radius}; v = origin; uv := Vector2.{v.x / width, v.y / height}; col := lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = origin + .{cos(rad) * radius, sin(rad) * radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); rad = (i + 10) * PI / 180.0; v = origin + .{cos(rad) * radius, sin(rad) * radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); i += 10; } // NW i = 90; while i < 180 { rad := i * PI / 180.0; origin = Vector2.{radius, height - radius}; v = origin; uv := Vector2.{v.x / width, v.y / height}; col := lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = origin + .{cos(rad) * radius, sin(rad) * radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); rad = (i + 10) * PI / 180.0; v = origin + .{cos(rad) * radius, sin(rad) * radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); i += 10; } // SW i = 180; while i < 270 { rad := i * PI / 180.0; origin = Vector2.{radius, radius}; v = origin; uv := Vector2.{v.x / width, v.y / height}; col := lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = origin + .{cos(rad) * radius, sin(rad) * radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); rad = (i + 10) * PI / 180.0; v = origin + .{cos(rad) * radius, sin(rad) * radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); i += 10; } // SE i = 270; while i < 360 { rad := i * PI / 180.0; origin := Vector2.{width - radius, radius}; v = origin; uv := Vector2.{v.x / width, v.y / height}; col := lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = origin + .{cos(rad) * radius, sin(rad) * radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); rad = (i + 10) * PI / 180.0; v = origin + .{cos(rad) * radius, sin(rad) * radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); i += 10; } // N v = .{radius, height - radius}; uv := Vector2.{v.x / width, v.y / height}; col := lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{radius, height}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, height}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{radius, height}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); // O v = .{radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{radius, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{0.0, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{0.0, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{0.0, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); // S v = .{radius, 0.0}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, 0.0}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, 0.0}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); // E v = .{width - radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); // Center panel v = .{radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{radius, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); v = .{width - radius, height - radius}; uv = .{v.x / width, v.y / height}; col = lerp(colour_left, colour_right, uv.x); array_add(*vertices, .{v, uv, col}); return vertices; } is_in_rect :: (rect: *Rect, mouse: Vector2) -> bool { mouseinscreen := Vector2.{mouse.x, mouse.y}; b := whbox_to_box(get_intersection_for_container(rect)); bottom_left : Vector2 = rect.pos + (rect.last_frame_size - rect.last_frame_max_size) + v2(rect.margin); top_right : Vector2 = bottom_left + rect.max_size - v2(rect.margin); if (mouseinscreen.x < xx b.bottom_left.x) return false; if (mouseinscreen.y < xx b.bottom_left.y) return false; if (mouseinscreen.x > xx b.top_right.x) return false; if (mouseinscreen.y > xx b.top_right.y) return false; return true; } is_in_rect :: (pos: Vector2, size: Vector2, test: Vector2) -> bool { top_left : Vector2 = pos; bottom_right : Vector2 = pos + size; if (test.x < top_left.x) return false; if (test.y < top_left.y) return false; if (test.x > bottom_right.x) return false; if (test.y > bottom_right.y) return false; return true; } get_intersection_for_container :: (rect: *Rect) -> whbox { scissor := box.{.{0, 0}, .{window_width, window_height}}; while rect { lower_left : vec2i; if rect.type == .ROOT && rect.anchor == .TOP_LEFT { lower_left = .{xx rect.last_frame_pos.x, xx ((window_height - rect.last_frame_pos.y) + (window_height - rect.last_frame_max_size.y))}; } else if rect.type == .CONTAINER && rect.flags & .SCROLL { lower_left = .{xx rect.last_frame_pos.x, xx ((rect.last_frame_pos.y) + (rect.last_frame_size.y - rect.last_frame_max_size.y))}; } else if rect.anchor == .TOP_LEFT { lower_left = .{xx rect.last_frame_pos.x, xx ((rect.last_frame_pos.y) + (rect.last_frame_size.y - rect.last_frame_max_size.y))}; //lower_left = .{xx rect.pos.x, xx (rect.pos.y + (rect.size.y - rect.max_size.y))}; } else { lower_left = .{xx rect.last_frame_pos.x, xx (rect.last_frame_pos.y + (rect.last_frame_size.y - rect.last_frame_max_size.y))}; } rect_box := box.{lower_left, lower_left + vec2i.{xx rect.last_frame_max_size.x, xx rect.last_frame_max_size.y}}; scissor = intersect(scissor, rect_box); rect = rect.parent; } return box_to_whbox(scissor); } render_ui :: (rect: *Rect) { last_scissor_box : [4]GLint; scissor_enabled : bool; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box.data); scissor_enabled = xx glIsEnabled(GL_SCISSOR_TEST); //glEnable(GL_SCISSOR_TEST); scissor := get_intersection_for_container(rect); if rect.anchor == .TOP_LEFT { glScissor(xx scissor.bottom_left.x, xx scissor.bottom_left.y, xx scissor.size.x, xx scissor.size.y); } if rect.type == { case .ROOT; ui_draw_root(xx rect); case .LABEL; ui_draw_label(xx rect); case .ICON; ui_draw_icon(xx rect); case .FONTICON; ui_draw_font_icon(xx rect); case .BUTTON; ui_draw_button(xx rect); // case .DROPDOWN; // ui_draw_dropdown(cast(*Dropdown) rect); // case .TOGGLE; // ui_draw_toggle(cast(*Toggle) rect); // case .WINDOW; // ui_draw_window(cast(*Window) rect); case .CONTAINER; ui_draw_container(xx rect); // case .SCROLLBOX; // ui_draw_scrollbox(cast(*Scrollbox) rect); // case .LIST; // ui_draw_list(cast(*List) rect); case .PROGRESSBAR; ui_draw_progress_bar(xx rect); case .SCROLLBAR; ui_draw_scroll_bar(xx rect); case .TEXTINPUT; ui_draw_text_input(xx rect); } next : *Rect = rect.first; while next { render_ui(next); next = next.next; } glScissor(last_scissor_box[0], last_scissor_box[1], xx last_scissor_box[2], xx last_scissor_box[3]); if scissor_enabled glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); } render_rectangle :: (pos: Vector2, size: Vector2, window_space: bool = true) { positions: [4]Vector2 = Vector2.[ .{pos.x, pos.y}, .{pos.x + size.x, pos.y}, .{pos.x + size.x, pos.y + size.y}, .{pos.x, pos.y + size.y}, ]; vao : GLuint; glGenVertexArrays(1, *vao); glBindVertexArray(vao); vbo : GLuint; glGenBuffers(1, *vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, size_of(type_of(positions)), positions.data, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(Vector2), null); model : Matrix4 = identity_of(Matrix4); view : Matrix4 = identity_of(Matrix4); proj : Matrix4 = orthographic_gl(0.0, xx window_width, 0.0, xx window_height, -1.0, 1.0); use(*line_shader); glBindVertexArray(vao); glUniformMatrix4fv(glGetUniformLocation(line_shader.id, "uni_model"), 1, GL_TRUE, cast(*float) *model); glUniformMatrix4fv(glGetUniformLocation(line_shader.id, "uni_view"), 1, GL_TRUE, cast(*float) *view); glUniformMatrix4fv(glGetUniformLocation(line_shader.id, "uni_proj"), 1, GL_TRUE, cast(*float) *proj); glUniform4f(glGetUniformLocation(line_shader.id, "uni_colour"), 1, 1, 1, 1); glDrawArrays(GL_LINE_LOOP, 0, xx positions.count); glBindVertexArray(0); glDeleteBuffers(1, *vbo); glDeleteVertexArrays(1, *vao); } render_filled_rectangle :: (pos: Vector2, size: Vector2, colour := Vector4.{1, 1, 1, 1}, window_space: bool = true) { positions: [4]Vector2 = Vector2.[ .{pos.x + size.x, pos.y}, .{pos.x, pos.y}, .{pos.x + size.x, pos.y + size.y}, .{pos.x, pos.y + size.y}, ]; vao : GLuint; glGenVertexArrays(1, *vao); glBindVertexArray(vao); vbo : GLuint; glGenBuffers(1, *vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, size_of(type_of(positions)), positions.data, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, size_of(Vector2), null); model : Matrix4 = identity_of(Matrix4); view : Matrix4 = identity_of(Matrix4); proj : Matrix4 = orthographic_gl(0.0, xx window_width, 0.0, xx window_height, -1.0, 1.0); use(*line_shader); glBindVertexArray(vao); glUniformMatrix4fv(glGetUniformLocation(line_shader.id, "uni_model"), 1, GL_TRUE, cast(*float) *model); glUniformMatrix4fv(glGetUniformLocation(line_shader.id, "uni_view"), 1, GL_TRUE, cast(*float) *view); glUniformMatrix4fv(glGetUniformLocation(line_shader.id, "uni_proj"), 1, GL_TRUE, cast(*float) *proj); glUniform4f(glGetUniformLocation(line_shader.id, "uni_colour"), colour.x, colour.y, colour.z, colour.w); glDrawArrays(GL_TRIANGLE_STRIP, 0, xx positions.count); glBindVertexArray(0); glDeleteBuffers(1, *vbo); glDeleteVertexArrays(1, *vao); } #scope_file _frame : u64;