Ed_
595de438af
I wasn't expecting it to be this to bad to support it... Also: * I renamed some of the files to group better with the virtual codebase view
410 lines
12 KiB
Odin
410 lines
12 KiB
Odin
package sectr
|
|
|
|
import lalg "core:math/linalg"
|
|
|
|
UI_Widget :: struct {
|
|
using box : ^UI_Box,
|
|
using signal : UI_Signal,
|
|
}
|
|
|
|
ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
|
|
{
|
|
widget.box = ui_box_make( flags, label )
|
|
widget.signal = ui_signal_from_box( widget.box )
|
|
return
|
|
}
|
|
|
|
ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget)
|
|
{
|
|
btn_flags := UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus }
|
|
btn.box = ui_box_make( btn_flags | flags, label )
|
|
btn.signal = ui_signal_from_box( btn.box )
|
|
return
|
|
}
|
|
|
|
#region("Horizontal Box")
|
|
/*
|
|
Horizontal Boxes automatically manage a collection of widgets and
|
|
attempt to slot them adjacent to each other along the x-axis.
|
|
|
|
The user must provide the direction that the hbox will append entries.
|
|
How the widgets will be scaled will be based on the individual entires style flags.
|
|
|
|
All the usual behaviors that the style and box flags do apply when managed by the box widget.
|
|
Whether or not the horizontal box will scale the widget's width is if:
|
|
fixed size or "scale by ratio" flags are not used for the width.
|
|
The hbox will use the anchor's (range2) ratio.x value to determine the "stretch ratio".
|
|
|
|
Keep in mind the stretch ratio is only respected if no size.min.x value is violated for each of the widgets.
|
|
*/
|
|
|
|
// Horizontal Widget
|
|
UI_HBox :: struct {
|
|
using widget : UI_Widget,
|
|
direction : UI_LayoutDirectionX,
|
|
}
|
|
|
|
// Boilerplate creation
|
|
ui_hbox_begin :: proc( direction : UI_LayoutDirectionX, label : string, flags : UI_BoxFlags = {} ) -> (hbox : UI_HBox) {
|
|
// profile(#procedure)
|
|
hbox.direction = direction
|
|
hbox.box = ui_box_make( flags, label )
|
|
hbox.signal = ui_signal_from_box(hbox.box)
|
|
return
|
|
}
|
|
|
|
// Auto-layout children
|
|
ui_hbox_end :: proc( hbox : UI_HBox, width_ref : ^f32 = nil, compute_layout := true )
|
|
{
|
|
// profile(#procedure)
|
|
if compute_layout do ui_box_compute_layout(hbox.box)
|
|
ui_layout_children_horizontally( hbox.box, hbox.direction, width_ref )
|
|
}
|
|
|
|
@(deferred_out = ui_hbox_end_auto)
|
|
ui_hbox :: #force_inline proc( direction : UI_LayoutDirectionX, label : string, flags : UI_BoxFlags = {} ) -> (hbox : UI_HBox) {
|
|
hbox = ui_hbox_begin(direction, label, flags)
|
|
ui_parent_push(hbox.box)
|
|
return
|
|
}
|
|
|
|
// Auto-layout children and pop parent from parent stack
|
|
ui_hbox_end_auto :: proc( hbox : UI_HBox ) {
|
|
ui_hbox_end(hbox)
|
|
ui_parent_pop()
|
|
}
|
|
#endregion("Horizontal Box")
|
|
|
|
#region("Resizable")
|
|
// Parameterized widget def for ui_resizable_handles
|
|
UI_Resizable :: struct {
|
|
using widget : UI_Widget,
|
|
handle_width : f32,
|
|
color_non_default : Color,
|
|
color_default : Color,
|
|
left : bool,
|
|
right : bool,
|
|
top : bool,
|
|
bottom : bool,
|
|
corner_tr : bool,
|
|
corner_tl : bool,
|
|
corner_br : bool,
|
|
corner_bl : bool,
|
|
compute_layout : bool
|
|
}
|
|
|
|
ui_resizable_begin :: proc( label : string, flags : UI_BoxFlags = {},
|
|
handle_width : f32 = 15,
|
|
handle_color_non_default : Color = Color_ResizeHandle,
|
|
handle_color_default : Color = Color_Transparent,
|
|
left := true,
|
|
right := true,
|
|
top := true,
|
|
bottom := true,
|
|
corner_tr := true,
|
|
corner_tl := true,
|
|
corner_br := true,
|
|
corner_bl := true,
|
|
compute_layout := true ) -> (resizable : UI_Resizable)
|
|
{
|
|
resizable.box = ui_box_make(flags, label)
|
|
resizable.signal = ui_signal_from_box(resizable.box)
|
|
|
|
resizable.handle_width = handle_width
|
|
resizable.color_non_default = handle_color_non_default
|
|
resizable.color_default = handle_color_default
|
|
resizable.left = left
|
|
resizable.right = right
|
|
resizable.top = top
|
|
resizable.bottom = bottom
|
|
resizable.corner_tr = corner_tr
|
|
resizable.corner_tl = corner_tl
|
|
resizable.corner_br = corner_br
|
|
resizable.corner_bl = corner_bl
|
|
resizable.compute_layout = compute_layout
|
|
return
|
|
}
|
|
|
|
ui_resizable_end :: proc( resizable : ^UI_Resizable, pos, size : ^Vec2 ) {
|
|
using resizable
|
|
ui_resizable_handles( & widget, pos, size,
|
|
handle_width,
|
|
color_non_default,
|
|
color_default,
|
|
left,
|
|
right,
|
|
top,
|
|
bottom,
|
|
corner_tr,
|
|
corner_tl,
|
|
corner_br,
|
|
corner_bl,
|
|
compute_layout)
|
|
}
|
|
|
|
ui_resizable_begin_auto :: proc() {
|
|
|
|
}
|
|
|
|
ui_resizable_end_auto :: proc() {
|
|
|
|
}
|
|
|
|
// Adds resizable handles to a widget
|
|
// TODO(Ed): Add centered resize support (use center alignment on shift-click)
|
|
ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
|
|
handle_width : f32 = 15,
|
|
handle_color_non_default : Color = Color_ResizeHandle,
|
|
handle_color_default : Color = Color_Transparent,
|
|
left := true,
|
|
right := true,
|
|
top := true,
|
|
bottom := true,
|
|
corner_tr := true,
|
|
corner_tl := true,
|
|
corner_br := true,
|
|
corner_bl := true,
|
|
compute_layout := true)
|
|
{
|
|
profile(#procedure)
|
|
handle_left : UI_Widget
|
|
handle_right : UI_Widget
|
|
handle_top : UI_Widget
|
|
handle_bottom : UI_Widget
|
|
handle_corner_tr : UI_Widget
|
|
handle_corner_tl : UI_Widget
|
|
handle_corner_br : UI_Widget
|
|
handle_corner_bl : UI_Widget
|
|
|
|
ui_parent(parent)
|
|
flags := UI_BoxFlags { .Mouse_Clickable, .Focusable }
|
|
|
|
layout_bar_width := UI_Layout {
|
|
flags = { .Fixed_Width },
|
|
alignment = {1, 1},
|
|
margins = { handle_width, handle_width, 0, 0 },
|
|
size = range2({handle_width, 0}, {}),
|
|
}
|
|
style_bar := UI_Style {
|
|
bg_color = Color_ResizeHandle,
|
|
corner_radii = { 5, 0, 0, 0 }
|
|
}
|
|
theme_bar := to_ui_style_combo(style_bar)
|
|
theme_bar.default.bg_color = handle_color_default
|
|
theme_bar.default.corner_radii[0] = 0
|
|
ui_layout(layout_bar_width)
|
|
ui_style(theme_bar)
|
|
layout_bar_height := layout_bar_width
|
|
layout_bar_height.flags = {.Fixed_Height}
|
|
layout_bar_height.size.min = {0, handle_width}
|
|
layout_bar_height.margins = { 0, 0, handle_width, handle_width }
|
|
layout_bar_height.alignment = {0, 0}
|
|
|
|
context.user_ptr = & parent.label
|
|
name :: proc( ) -> StrRunesPair {
|
|
parent_label := (transmute(^string) context.user_ptr) ^
|
|
return str_intern(str_fmt_tmp("%v: %v", ))
|
|
}
|
|
|
|
if left do handle_left = ui_widget("Settings Menu: Resize Left Handle", flags )
|
|
if right {
|
|
handle_right = ui_widget("Settings Menu: Resize Right Handle", flags)
|
|
handle_right.layout.anchor.left = 1
|
|
handle_right.layout.alignment = { 0, 1 }
|
|
}
|
|
|
|
ui_layout(layout_bar_height)
|
|
ui_style_ref().default.bg_color = handle_color_default
|
|
if top do handle_top = ui_widget("Settings Menu: Resize Top Border", flags )
|
|
if bottom {
|
|
handle_bottom = ui_widget("Settings Menu: Resize Bottom Border", flags)
|
|
using handle_bottom.layout
|
|
anchor.top = 1
|
|
alignment = { 0, 1 }
|
|
}
|
|
|
|
layout_corner := UI_Layout {
|
|
flags = { .Fixed_Width, .Fixed_Height },
|
|
alignment = {1, 0},
|
|
size = range2({handle_width, handle_width}, {}),
|
|
}
|
|
style_corner := UI_Style {
|
|
bg_color = Color_ResizeHandle,
|
|
corner_radii = { 5, 0, 0, 0 },
|
|
}
|
|
ui_theme(layout_corner, style_corner)
|
|
ui_style_ref().default.bg_color = handle_color_default
|
|
if corner_tl do handle_corner_tl = ui_widget("Settings Menu: Corner TL", flags)
|
|
if corner_tr {
|
|
handle_corner_tr = ui_widget("Settings Menu: Corner TR", flags)
|
|
handle_corner_tr.layout.anchor = range2({1, 0}, {})
|
|
handle_corner_tr.layout.alignment = {0, 0}
|
|
}
|
|
|
|
if corner_bl {
|
|
handle_corner_bl = ui_widget("Settings Menu: Corner BL", flags)
|
|
handle_corner_bl.layout.anchor = range2({}, {0, 1})
|
|
handle_corner_bl.layout.alignment = { 1, 1 }
|
|
}
|
|
if corner_br {
|
|
handle_corner_br = ui_widget("Settings Menu: Corner BR", flags)
|
|
handle_corner_br.layout.anchor = range2({1, 0}, {0, 1})
|
|
handle_corner_br.layout.alignment = {0, 1}
|
|
}
|
|
|
|
process_handle_drag :: #force_inline proc ( handle : ^UI_Widget,
|
|
direction : Vec2,
|
|
size_delta : Vec2,
|
|
target_alignment : Vec2,
|
|
pos : ^Vec2,
|
|
size : ^Vec2,
|
|
alignment : ^Vec2, )
|
|
{
|
|
ui := get_state().ui_context
|
|
if ui.last_pressed_key != handle.key { return }
|
|
|
|
size_delta := size_delta
|
|
pos_adjust := size^ * (alignment^ - target_alignment)
|
|
|
|
@static was_dragging := false
|
|
|
|
using handle
|
|
if active
|
|
{
|
|
size^ += size_delta * direction
|
|
if pressed {
|
|
pos^ -= pos_adjust
|
|
}
|
|
else {
|
|
alignment^ = target_alignment
|
|
}
|
|
was_dragging = true
|
|
}
|
|
else if released && was_dragging
|
|
{
|
|
pos^ += pos_adjust
|
|
alignment^ = target_alignment
|
|
was_dragging = false
|
|
}
|
|
}
|
|
|
|
delta := get_state().input.mouse.delta
|
|
alignment := & parent.layout.alignment
|
|
|
|
if compute_layout do ui_box_compute_layout_children(parent)
|
|
|
|
if right do process_handle_drag( & handle_right, { 1, 0 }, delta, {0, 0}, pos, size, alignment )
|
|
if left do process_handle_drag( & handle_left, { -1, 0 }, delta, {1, 0}, pos, size, alignment )
|
|
if top do process_handle_drag( & handle_top, { 0, 1 }, delta, {0, 0}, pos, size, alignment )
|
|
if bottom do process_handle_drag( & handle_bottom, { 0, -1 }, delta, {0, 1}, pos, size, alignment )
|
|
if corner_tr do process_handle_drag( & handle_corner_tr, { 1, 1 }, delta, {0, 0}, pos, size, alignment )
|
|
if corner_tl do process_handle_drag( & handle_corner_tl, { -1, 1 }, delta, {1, 0}, pos, size, alignment )
|
|
if corner_br do process_handle_drag( & handle_corner_br, { 1, -1 }, delta, {0, 1}, pos, size, alignment )
|
|
if corner_bl do process_handle_drag( & handle_corner_bl, { -1, -1 }, delta, {1, 1}, pos, size, alignment )
|
|
}
|
|
#endregion("Resizable")
|
|
|
|
ui_spacer :: proc( label : string ) -> (widget : UI_Widget) {
|
|
widget.box = ui_box_make( {.Mouse_Clickable}, label )
|
|
widget.signal = ui_signal_from_box( widget.box )
|
|
|
|
widget.style.bg_color = Color_Transparent
|
|
return
|
|
}
|
|
|
|
#region("Text")
|
|
|
|
ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {} ) -> UI_Widget
|
|
{
|
|
// profile(#procedure)
|
|
state := get_state(); using state
|
|
|
|
box := ui_box_make( flags, label )
|
|
signal := ui_signal_from_box( box )
|
|
|
|
box.text = content
|
|
return { box, signal }
|
|
}
|
|
|
|
ui_text_spaces :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
|
|
{
|
|
// profile(#procedure)
|
|
state := get_state(); using state
|
|
|
|
// TODO(Ed) : Move this somwhere in state.
|
|
space_str := str_intern( " " )
|
|
|
|
box := ui_box_make( flags, label )
|
|
signal := ui_signal_from_box( box )
|
|
|
|
box.text = space_str
|
|
return { box, signal }
|
|
}
|
|
|
|
ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
|
|
{
|
|
// profile(#procedure)
|
|
state := get_state(); using state
|
|
|
|
// TODO(Ed) : Move this somwhere in state.
|
|
tab_str := str_intern( "\t" )
|
|
|
|
box := ui_box_make( flags, label )
|
|
signal := ui_signal_from_box( box )
|
|
|
|
box.text = tab_str
|
|
return { box, signal }
|
|
}
|
|
#endregion("Text")
|
|
|
|
#region("Vertical Box")
|
|
/*
|
|
Vertical Boxes automatically manage a collection of widgets and
|
|
attempt to slot them adjacent to each other along the y-axis.
|
|
|
|
The user must provide the direction that the vbox will append entries.
|
|
How the widgets will be scaled will be based on the individual entires style flags.
|
|
|
|
All the usual behaviors that the style and box flags do apply when managed by the box widget.
|
|
Whether or not the horizontal box will scale the widget's width is if:
|
|
fixed size or "scale by ratio" flags are not used for the width.
|
|
The hbox will use the anchor's (range2) ratio.y value to determine the "stretch ratio".
|
|
|
|
Keep in mind the stretch ratio is only respected if no size.min.y value is violated for each of the widgets.
|
|
*/
|
|
|
|
UI_VBox :: struct {
|
|
using widget : UI_Widget,
|
|
direction : UI_LayoutDirectionY,
|
|
}
|
|
|
|
// Boilerplate creation
|
|
ui_vbox_begin :: proc( direction : UI_LayoutDirectionY, label : string, flags : UI_BoxFlags = {} ) -> (vbox : UI_VBox) {
|
|
// profile(#procedure)
|
|
vbox.direction = direction
|
|
vbox.box = ui_box_make( flags, label )
|
|
vbox.signal = ui_signal_from_box( vbox.box )
|
|
return
|
|
}
|
|
|
|
// Auto-layout children
|
|
ui_vbox_end :: proc( vbox : UI_VBox, height_ref : ^f32 = nil, compute_layout := true ) {
|
|
// profile(#procedure)
|
|
if compute_layout do ui_box_compute_layout(vbox)
|
|
ui_layout_children_vertically( vbox.box, vbox.direction, height_ref )
|
|
}
|
|
|
|
// Auto-layout children and pop parent from parent stack
|
|
ui_vbox_end_pop_parent :: proc( vbox : UI_VBox ) {
|
|
ui_parent_pop()
|
|
ui_vbox_end(vbox)
|
|
}
|
|
|
|
@(deferred_out = ui_vbox_end_pop_parent)
|
|
ui_vbox :: #force_inline proc( direction : UI_LayoutDirectionY, label : string, flags : UI_BoxFlags = {} ) -> (vbox : UI_VBox) {
|
|
vbox = ui_vbox_begin(direction, label, flags)
|
|
ui_parent_push(vbox.widget)
|
|
return
|
|
}
|
|
#endregion("Vertical Box")
|