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;
