got basic ui elmental interaction working, + alignment of anchor

This commit is contained in:
Edward R. Gonzalez 2024-03-02 10:24:09 -05:00
parent 1e5773e486
commit 035c726a71
24 changed files with 722 additions and 202 deletions

2
.vscode/launch.json vendored
View File

@ -13,4 +13,4 @@
"cwd": "${workspaceFolder}/build"
}
]
}
}

View File

@ -5,5 +5,6 @@
"**/thirdparty" : false,
},
"godot_tools.scene_file_config": "c:\\projects\\SectrPrototype\\code",
"autoHide.autoHidePanel": false
"autoHide.autoHidePanel": false,
"autoHide.autoHideSideBar": false
}

26
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"tasks": [
{
"label": "Build Sectr",
"type": "shell",
"command": "pwsh.exe",
"args": [
"-NoProfile",
"-ExecutionPolicy",
"Unrestricted",
"-File",
"${workspaceFolder}/scripts/build.ps1"
],
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"focus": false,
"reveal": "always",
"panel": "shared"
}
}
]
}

View File

@ -170,7 +170,7 @@ UI_ScrollPt :: struct {
UI_ScrollPt2 :: [2]UI_ScrollPt
UI_Signal :: struct {
box : UI_Box,
box : ^ UI_Box,
cursor_pos : Vec2,
drag_delta : Vec2,

View File

@ -73,7 +73,10 @@ startup :: proc( live_mem : virtual.Arena, snapshot_mem : []u8, host_logger : ^
// rl.Odin_SetMalloc( RL_MALLOC )
rl.SetConfigFlags( { rl.ConfigFlag.WINDOW_RESIZABLE /*, rl.ConfigFlag.WINDOW_TOPMOST*/ } )
rl.SetConfigFlags( {
rl.ConfigFlag.WINDOW_RESIZABLE,
rl.ConfigFlag.WINDOW_TOPMOST,
})
// Rough setup of window with rl stuff
window_width : i32 = 1000
@ -177,7 +180,7 @@ reload :: proc( live_mem : virtual.Arena, snapshot_mem : []u8, host_logger : ^ L
snapshot = snapshot_mem
// This is no longer necessary as we have proper base address setting
when false
when true
{
persistent_slice := slice_ptr( block.base, Memory_Persistent_Size )
transient_slice := slice_ptr( memory_after( persistent_slice), Memory_Trans_Temp_Szie )
@ -216,11 +219,13 @@ swap :: proc( a, b : ^ $Type ) -> ( ^ Type, ^ Type ) {
}
@export
tick :: proc( delta_time : f64 ) -> b32
tick :: proc( delta_time : f64, delta_ns : Duration ) -> b32
{
context.allocator = transient_allocator()
context.temp_allocator = temp_allocator()
get_state().frametime_delta_ns = delta_ns
result := update( delta_time )
render()
return result

View File

@ -2,6 +2,11 @@ package sectr
import "core:math/linalg"
pos_within_range2 :: proc( pos : Vec2, range : Range2 ) -> b32 {
within_x := pos.x > range.p0.x && pos.x < range.p1.x
within_y := pos.y < range.p0.y && pos.y > range.p1.y
return b32(within_x && within_y)
}
box_is_within :: proc( box : ^ Box2, pos : Vec2 ) -> b32 {
bounds := box_get_bounds( box )

View File

@ -3,12 +3,16 @@ package sectr
import rl "vendor:raylib"
Color :: rl.Color
Color_Blue :: rl.BLUE
Color_Green :: rl.GREEN
Color_Red :: rl.RED
Color_White :: rl.WHITE
Color_Transparent :: Color { 0, 0, 0, 0 }
Color_BG :: Color { 41, 41, 45, 255 }
Color_BG_TextBox :: Color { 32, 32, 32, 255 }
Color_BG_TextBox_Green :: Color { 102, 102, 110, 255 }
Color_Frame_Disabled :: Color { 22, 22, 22, 120 }
Color_Frame_Hover :: Color { 122, 122, 125, 255 }
Color_Frame_Select :: Color { 188, 188, 188, 255 }
Color_GreyRed :: Color { 220, 100, 100, 125 }

View File

@ -112,6 +112,8 @@ State :: struct {
engine_refresh_hz : i32,
engine_refresh_target : i32,
frametime_delta_ns : Duration,
font_firacode : FontID,
font_squidgy_slimes : FontID,
font_rec_mono_semicasual_reg : FontID,
@ -178,6 +180,7 @@ DebugData :: struct {
mouse_vis : b32,
last_mouse_pos : Vec2,
frame_1_on_top : b32,
zoom_target : f32,
frame_2_created : b32,
}

View File

@ -21,6 +21,12 @@ stack_pop :: proc( using stack : ^ Stack( $ Type, $ Size ) ) {
}
}
stack_peek :: proc( using stack : ^ Stack( $ Type, $ Size ) ) -> ^ Type {
return & items[idx]
stack_peek_ref :: proc( using stack : ^ Stack( $ Type, $ Size ) ) -> ^ Type {
last := max( 0, idx - 1 )
return & items[last]
}
stack_peek :: proc ( using stack : ^ Stack( $ Type, $ Size ) ) -> Type {
last := max( 0, idx - 1 )
return items[last]
}

View File

@ -55,7 +55,13 @@ import "core:path/filepath"
file_name_from_path :: filepath.short_stem
import str "core:strings"
str_builder_to_string :: str.to_string
import "core:time"
Duration :: time.Duration
import "core:unicode"
is_white_space :: unicode.is_white_space
import "core:unicode/utf8"
runes_to_string :: utf8.runes_to_string
string_to_runes :: utf8.string_to_runes
OS_Type :: type_of(ODIN_OS)
@ -71,9 +77,10 @@ is_power_of_two :: proc {
}
to_runes :: proc {
utf8.string_to_runes,
string_to_runes,
}
to_string :: proc {
runes_to_string,
str_builder_to_string,
}

View File

@ -226,9 +226,13 @@ array_set_capacity :: proc( using self : ^ Array( $ Type ), new_capacity : u64 )
return AllocatorError.None
}
raw_data, result_code := alloc( cast(int) new_capacity * size_of(Type), allocator = allocator )
ensure( result_code == AllocatorError.None, "Failed to allocate for new array capacity" )
data = cast( [^] Type ) raw_data
new_data, result_code := alloc( cast(int) new_capacity * size_of(Type), allocator = allocator )
if result_code != AllocatorError.None {
ensure( false, "Failed to allocate for new array capacity" )
return result_code
}
free( raw_data(data) )
data = cast( [^] Type ) new_data
capacity = new_capacity
return result_code
}

View File

@ -110,6 +110,10 @@ zpl_hmap_grow :: proc( using self : ^ HMapZPL( $ Type ) ) -> AllocatorError {
zpl_hmap_rehash :: proc( ht : ^ HMapZPL( $ Type ), new_num : u64 ) -> AllocatorError
{
// For now the prototype should never allow this to happen.
// We use this almost exclusively in persistent memory and its not setup for
// dealing with reallocations in a conservative manner.
ensure( false, "ZPL HMAP IS REHASHING" )
last_added_index : i64
new_ht, init_result := zpl_hmap_init_reserve( Type, ht.hashes.allocator, new_num )

View File

@ -30,42 +30,45 @@ DLL_NodeFL :: struct ( $ Type : typeid ) {
first, last : ^ Type,
}
type_is_node :: #force_inline proc "contextless" ( $ Type : typeid ) -> b32
type_is_node :: #force_inline proc "contextless" ( $ Type : typeid ) -> bool
{
// elem_type := type_elem_type(Type)
return type_has_field( type_elem_type(Type), "prev" ) && type_has_field( type_elem_type(Type), "next" )
}
dll_insert_raw :: proc "contextless" ( null, first, last, position, new : ^ DLL_Node( $ Type ) )
dll_full_insert_raw :: proc "contextless" ( null : ^($ Type), parent, pos, node : ^ Type )
{
// Empty Case
if first == null {
first = new
last = new
new.next = null
new.prev = null
if parent.first == null {
parent.first = node
parent.last = node
node.next = null
node.prev = null
}
else if position == null {
else if pos == null {
// Position is not set, insert at beginning
new.next = first
first.prev = new
first = new
new.prev = null
node.next = parent.first
parent.first.prev = node
parent.first = node
node.prev = null
}
else if position == last {
else if pos == parent.last {
// Positin is set to last, insert at end
last.next = new
new.prev = last
last = new
new.next = null
parent.last.next = node
node.prev = parent.last
parent.last = node
node.next = null
}
else {
// Insert around position
if position.next != null {
position.next.prev = new
else
{
if pos.next != null {
pos.next.prev = node
}
new.next = position.next
position.next = new
new.prev = position
node.next = pos.next
pos.next = node
node.prev = pos
}
}
dll_full_push_back :: proc "contextless" ( null : ^($ Type), parent, node : ^ Type ) {
dll_full_insert_raw( null, parent, parent.last, node )
}

View File

@ -210,7 +210,7 @@ sync_sectr_api :: proc( sectr_api : ^ sectr.ModuleAPI, memory : ^ VMemChunk, log
// Wait for pdb to unlock (linker may still be writting)
for ; file_is_locked( Path_Sectr_Debug_Symbols ) && file_is_locked( Path_Sectr_Live_Module ); {}
thread_sleep( Millisecond * 50 )
thread_sleep( Millisecond * 100 )
sectr_api ^ = load_sectr_api( version_id )
verify( sectr_api.lib_version != 0, "Failed to hot-reload the sectr module" )
@ -285,7 +285,7 @@ main :: proc()
// Hot-Reload
sync_sectr_api( & sectr_api, & memory, & logger )
running = sectr_api.tick( duration_seconds( delta_ns ) )
running = sectr_api.tick( duration_seconds( delta_ns ), delta_ns )
sectr_api.clean_temp()
delta_ns = time.tick_lap_time( & start_tick )

View File

@ -17,10 +17,18 @@ btn_pressed :: proc( btn : DigitalBtn ) -> b32 {
return btn.ended_down && btn.half_transitions > 0
}
btn_released :: proc ( btn : DigitalBtn ) -> b32 {
return btn.ended_down == false && btn.half_transitions > 0
}
pressed :: proc {
btn_pressed,
}
released :: proc {
btn_released,
}
MaxKeyboardKeys :: 256
KeyboardKey :: enum u32 {
null = 0x00,

View File

@ -1,5 +1,12 @@
package sectr
Axis2 :: enum i32 {
Invalid = -1,
X = 0,
Y = 1,
Count,
}
is_power_of_two_u32 :: proc( value : u32 ) -> b32
{
return value != 0 && ( value & ( value - 1 )) == 0
@ -12,3 +19,20 @@ Vec3 :: linalg.Vector3f32
Vec2i :: [2]i32
Vec3i :: [3]i32
Range2 :: struct #raw_union{
using min_max : struct {
min, max : Vec2
},
using pts : struct {
p0, p1 : Vec2
},
using xy : struct {
x0, y0 : f32,
x1, y1 : f32,
},
}
Rect :: struct {
top_left, bottom_right : Vec2
}

View File

@ -24,11 +24,13 @@ when ODIN_OS == OS_Type.Windows {
cm_to_pixels :: proc {
f32_cm_to_pixels,
vec2_cm_to_pixels,
range2_cm_to_pixels,
}
pixels_to_cm :: proc {
f32_pixels_to_cm,
vec2_pixels_to_cm,
range2_pixels_to_cm,
}
points_to_pixels :: proc {
@ -89,6 +91,18 @@ vec2_points_to_pixels :: proc(vpoints: Vec2) -> Vec2 {
return vpoints * DPT_PPCM * cm_per_pixel
}
range2_cm_to_pixels :: proc( range : Range2 ) -> Range2 {
screen_ppcm := get_state().app_window.ppcm
result := Range2 { pts = { range.min * screen_ppcm, range.max * screen_ppcm }}
return result
}
range2_pixels_to_cm :: proc( range : Range2 ) -> Range2 {
screen_ppcm := get_state().app_window.ppcm
cm_per_pixel := 1.0 / screen_ppcm
result := Range2 { pts = { range.min * cm_per_pixel, range.max * cm_per_pixel }}
return result
}
// vec2_points_to_cm :: proc( vpoints : Vec2 ) -> Vec2 {
@ -166,7 +180,8 @@ view_get_corners :: proc() -> BoundsCorners2 {
screen_to_world :: proc(pos: Vec2) -> Vec2 {
state := get_state(); using state
cam := & project.workspace.cam
return vec2_pixels_to_cm( cam.target + pos * (1 / cam.zoom) )
result := Vec2 { cam.target.x, -cam.target.y} + Vec2 { pos.x, -pos.y } * (1 / cam.zoom)
return result
}
screen_to_render :: proc(pos: Vec2) -> Vec2 {

View File

@ -39,7 +39,7 @@ render :: proc()
screen_corners := screen_get_corners()
position := screen_corners.top_right
position.x -= 200
position.x -= 800
position.y += debug.draw_debug_text_y
content := str_fmt_buffer( draw_text_scratch[:], format, ..args )
@ -61,11 +61,11 @@ render :: proc()
}
if debug.mouse_vis {
debug_text( "Position: %v", input.mouse.pos )
debug_text( "Mouse Position (Screen): %v", input.mouse.pos )
debug_text("Mouse Position (World): %v", screen_to_world(input.mouse.pos) )
cursor_pos := transmute(Vec2) state.app_window.extent + input.mouse.pos
rl.DrawCircleV( cursor_pos, 10, Color_White_A125 )
}
debug.draw_debug_text_y = 50
}
//endregion Render Screenspace
@ -76,21 +76,56 @@ render_mode_2d :: proc()
{
state := get_state(); using state
cam := & project.workspace.cam
win_extent := state.app_window.extent
rl.BeginMode2D( project.workspace.cam )
//region Imgui Render
ImguiRender:
{
ui := & state.project.workspace.ui
root := ui.root
if root.num_children == 0 {
break ImguiRender
}
current := root.first
for ; current != nil; {
parent := current.parent
style := current.style
computed := & current.computed
// TODO(Ed) : Render Borders
render_bounds := Range2 { pts = {
world_to_screen_pos(computed.bounds.min),
world_to_screen_pos(computed.bounds.max),
}}
rect := rl.Rectangle {
render_bounds.min.x,
render_bounds.min.y,
render_bounds.max.x - render_bounds.min.x,
render_bounds.max.y - render_bounds.min.y,
}
rl.DrawRectangleRec( rect, style.bg_color )
rl.DrawCircleV( render_bounds.p0, 5, Color_Red )
rl.DrawCircleV( render_bounds.p1, 5, Color_Blue )
current = ui_box_tranverse_next( current )
}
}
//endregion Imgui Render
debug_draw_text_world( "This is text in world space", { 0, 0 }, 16.0 )
debug_draw_text_world( "This is text in world space", { 0, 200 }, 16.0 )
if debug.mouse_vis {
// rl.DrawCircleV( screen_to_world(input.mouse.pos), 10, Color_GreyRed )
cursor_world_pos := screen_to_world(input.mouse.pos)
rl.DrawCircleV( world_to_screen_pos(cursor_world_pos), 5, Color_GreyRed )
}
rl.DrawCircleV( { 0, 0 }, 1, Color_White )
rl.EndMode2D()
}

View File

@ -171,21 +171,71 @@ update :: proc( delta_time : f64 ) -> b32
}
}
}
//endregion
//endregion Camera Manual Nav
//region Imgui Tick
{
// Creates the root box node, set its as the first parent.
ui_graph_build( & state.project.workspace.ui )
ui_style({ bg_color = Color_BG_TextBox })
ui_set_layout({ size = { 200, 200 }})
frame_style_flags : UI_StyleFlags = {
.Fixed_Position_X, .Fixed_Position_Y,
.Fixed_Width, .Fixed_Height,
}
frame_style_default := UI_Style {
flags = frame_style_flags,
bg_color = Color_BG_TextBox,
}
frame_style_disabled := UI_Style {
flags = frame_style_flags,
bg_color = Color_Frame_Disabled,
}
frame_style_hovered := UI_Style {
flags = frame_style_flags,
bg_color = Color_Frame_Hover,
}
frame_style_select := UI_Style {
flags = frame_style_flags,
bg_color = Color_Frame_Select,
}
frame_theme := UI_StyleTheme { styles = {
frame_style_default,
frame_style_disabled,
frame_style_hovered,
frame_style_select,
}}
ui_style_theme( frame_theme )
first_flags : UI_BoxFlags = { .Mouse_Clickable, .Focusable, .Click_To_Focus }
ui_box_make( first_flags, "FIRST BOX BOIS" )
first_layout := UI_Layout {
anchor = {},
// alignment = { 0.0, 0.0 },
alignment = { 0.5, 0.5 },
// alignment = { 1.0, 1.0 },
pos = { 0, 0 },
size = { 200, 200 },
}
ui_set_layout( first_layout )
// First Demo
when false
{
first_flags : UI_BoxFlags = { .Mouse_Clickable, .Focusable, .Click_To_Focus }
first_box := ui_box_make( first_flags, "FIRST BOX BOIS" )
signal := ui_signal_from_box( first_box )
if signal.left_clicked || debug.frame_2_created {
second_layout := first_layout
second_layout.pos = { 250, 0 }
ui_set_layout( second_layout )
second_box := ui_box_make( first_flags, "SECOND BOX BOIS" )
signal := ui_signal_from_box( second_box )
debug.frame_2_created = true
}
}
}
// endregion
//endregion Imgui Tick
debug.last_mouse_pos = input.mouse.pos

96
code/ui.layout.odin Normal file
View File

@ -0,0 +1,96 @@
package sectr
ui_compute_layout :: proc()
{
state := get_state()
root := state.project.workspace.ui.root
{
computed := & root.computed
bounds := & computed.bounds
style := root.style
layout := & style.layout
bounds.min = layout.pos
bounds.max = layout.size
computed.content = bounds^
computed.padding = {}
}
current := root.first
for ; current != nil;
{
parent := current.parent
parent_content := parent.computed.content
computed := & current.computed
style := current.style
layout := & style.layout
margins := Range2 { pts = {
parent_content.p0 + { layout.margins.left, layout.margins.top },
parent_content.p1 - { layout.margins.right, layout.margins.bottom },
}}
anchor := & layout.anchor
pos : Vec2
if UI_StyleFlag.Fixed_Position_X in style.flags {
pos.x = layout.pos.x
pos.x -= margins.p0.x * anchor.x0
pos.x += margins.p0.x * anchor.x1
}
if UI_StyleFlag.Fixed_Position_Y in style.flags {
pos.y = layout.pos.y
pos.y -= margins.p1.y * anchor.y0
pos.y += margins.p1.y * anchor.y1
}
size : Vec2
if UI_StyleFlag.Fixed_Width in style.flags {
size.x = layout.size.x
}
else {
// TODO(Ed) : Not sure what todo here...
}
if UI_StyleFlag.Fixed_Height in style.flags {
size.y = layout.size.y
}
else {
// TODO(Ed) : Not sure what todo here...
}
half_size := size * 0.5
size_bounds := Range2 { pts = {
Vec2 {},
{ size.x, -size.y },
}}
aligned_bounds := Range2 { pts = {
size_bounds.p0 + size * { -layout.alignment.x, layout.alignment.y },
size_bounds.p1 - size * { layout.alignment.x, -layout.alignment.y },
}}
bounds := & computed.bounds
(bounds^) = aligned_bounds
(bounds^) = { pts = {
pos + aligned_bounds.p0,
pos + aligned_bounds.p1,
}}
border_offset := Vec2 { layout.border_width, layout.border_width }
padding := & computed.padding
(padding^) = { pts = {
bounds.p0 + border_offset,
bounds.p1 - border_offset,
}}
content := & computed.content
(content^) = { pts = {
bounds.p0 + { layout.padding.left, layout.padding.top },
bounds.p1 - { layout.padding.right, layout.padding.bottom },
}}
current = ui_box_tranverse_next( current )
}
}

View File

@ -1,12 +1,8 @@
package sectr
import "base:runtime"
// TODO(Ed) : This is in Raddbg base_types.h, consider moving outside of UI.
Axis2 :: enum i32 {
Invalid = -1,
X = 0,
Y = 1,
Count,
}
Corner :: enum i32 {
Invalid = -1,
@ -21,23 +17,6 @@ Corner :: enum i32 {
Count = 4,
}
Range2 :: struct #raw_union{
using _ : struct {
min, max : Vec2
},
using _ : struct {
p0, p1 : Vec2
},
using _ : struct {
x0, y0 : f32,
x1, y1 : f32,
},
}
Rect :: struct {
top_left, bottom_right : Vec2
}
Side :: enum i32 {
Invalid = -1,
Min = 0,
@ -74,35 +53,36 @@ UI_AnchorPresets :: enum u32 {
}
UI_BoxFlag :: enum u64 {
Disabled,
Focusable,
Mouse_Clickable,
Keyboard_Clickable,
Click_To_Focus,
Fixed_With,
Fixed_Height,
Scroll_X,
Scroll_Y,
Text_Wrap,
Pan_X,
Pan_Y,
Count,
}
UI_BoxFlags :: bit_set[UI_BoxFlag; u64]
UI_BoxFlag_Scroll :: UI_BoxFlags { .Scroll_X, .Scroll_Y }
// The UI_Box's actual positioning and sizing
// There is an excess of rectangles here for debug puproses.
UI_Computed :: struct {
bounds : Range2,
border : Range2,
margin : Range2,
padding : Range2,
content : Range2,
}
UI_LayoutSide :: struct #raw_union {
using _ : struct {
top, bottom : UI_Scalar2,
left, right : UI_Scalar2,
top, bottom : UI_Scalar,
left, right : UI_Scalar,
}
}
@ -110,6 +90,18 @@ UI_Cursor :: struct {
placeholder : int,
}
UI_FramePassKind :: enum {
Generate,
Compute,
Logical,
}
UI_InteractState :: struct {
hot_time : f32,
active_time : f32,
disabled_time : f32,
}
UI_Key :: distinct u64
UI_Scalar :: f32
@ -128,8 +120,12 @@ UI_Scalar2 :: [Axis2.Count]UI_Scalar
// Desiered constraints on the UI_Box.
UI_Layout :: struct {
// TODO(Ed) : Should layout have its own flags (separate from the style flags)
// flags : UI_LayoutFlags
// TODO(Ed) : Make sure this is all we need to represent an anchor.
anchor : Range2,
anchor : Range2,
alignment : Vec2,
border_width : UI_Scalar,
@ -138,19 +134,62 @@ UI_Layout :: struct {
corner_radii : [Corner.Count]f32,
// TODO(Ed) : Add support for this
size_to_content : b32,
size : Vec2,
// Position in relative coordinate space.
// If the box's flags has Fixed_Position, then this will be its aboslute position in the relative coordinate space
pos : Vec2,
// TODO(Ed) : Should everything no matter what its parent is use a WS_Pos instead of a raw vector pos?
// If the box is a child of the root parent, its automatically in world space and thus will use the tile_pos.
tile_pos : WS_Pos,
// TODO(Ed) : Add support for size_to_content?
// size_to_content : b32,
size : Vec2,
}
UI_BoxState :: enum {
Disabled,
UI_Signal :: struct {
box : ^ UI_Box,
cursor_pos : Vec2,
drag_delta : Vec2,
scroll : Vec2,
left_clicked : b8,
right_clicked : b8,
double_clicked : b8,
keyboard_clicked : b8,
pressed : b8,
released : b8,
dragging : b8,
hovering : b8,
cursor_over : b8,
commit : b8,
}
UI_StyleFlag :: enum u32 {
Fixed_Position_X,
Fixed_Position_Y,
Fixed_Width,
Fixed_Height,
Text_Wrap,
Count,
}
UI_StyleFlags :: bit_set[UI_StyleFlag; u32]
UI_StylePreset :: enum u32 {
Default,
Disabled,
Hovered,
Focused,
Count,
}
UI_Style :: struct {
flags : UI_StyleFlags,
bg_color : Color,
border_color : Color,
@ -168,6 +207,13 @@ UI_Style :: struct {
transition_time : f32,
}
UI_StyleTheme :: struct #raw_union {
array : [UI_StylePreset.Count] UI_Style,
using styles : struct {
default, disabled, hovered, focused : UI_Style,
}
}
UI_TextAlign :: enum u32 {
Left,
Center,
@ -175,45 +221,35 @@ UI_TextAlign :: enum u32 {
Count
}
UI_InteractState :: struct {
hot_time : f32,
active_time : f32,
disabled_time : f32,
}
UI_Box :: struct {
// Cache ID
key : UI_Key,
label : string,
// Regenerated per frame.
using _ : DLL_NodeFull( UI_Box ), // first, last, prev, next
using links : DLL_NodeFull( UI_Box ), // first, last, prev, next
parent : ^ UI_Box,
num_children : i32,
flags : UI_BoxFlags,
computed : UI_Computed,
style : UI_Style,
theme : UI_StyleTheme,
style : ^ UI_Style,
// Persistent Data
// hash_links : DLL_Node_PN( ^ UI_Box), // This isn't necessary if not using RJF hash table.
style_delta : f32,
// prev_computed : UI_Computed,
// prev_style : UI_Style,v
mouse : UI_InteractState,
keyboard : UI_InteractState,
}
// UI_BoxFlags_Stack_Size :: 512
UI_Layout_Stack_Size :: 512
UI_Style_Stack_Size :: 512
UI_Parent_Stack_Size :: 1024
UI_Built_Boxes_Array_Size :: Kilobyte * 8
UI_FramePassKind :: enum {
Generate,
Compute,
Logical,
}
UI_State :: struct {
// TODO(Ed) : Use these
build_arenas : [2]Arena,
@ -225,16 +261,19 @@ UI_State :: struct {
prev_cache : ^ HMapZPL( UI_Box ),
curr_cache : ^ HMapZPL( UI_Box ),
root : ^ UI_Box,
null_box : ^ UI_Box, // Ryan had this, I don't know why yet.
root : ^ UI_Box,
// Do we need to recompute the layout?
layout_dirty : b32,
// TODO(Ed) : Look into using a build arena like Ryan does for these possibly (and thus have a linked-list stack)
style_stack : Stack( UI_Style, UI_Style_Stack_Size ),
parent_stack : Stack( ^ UI_Box, UI_Parent_Stack_Size ),
theme_stack : Stack( UI_StyleTheme, UI_Style_Stack_Size ),
parent_stack : Stack( ^ UI_Box, UI_Parent_Stack_Size ),
// flag_stack : Stack( UI_BoxFlags, UI_BoxFlags_Stack_Size ),
hot : UI_Key,
active_mouse : [MouseBtn.count] UI_Key,
active : UI_Key,
clipboard_copy : UI_Key,
last_clicked : UI_Key,
@ -242,21 +281,9 @@ UI_State :: struct {
drag_start_mouse : Vec2,
// drag_state_arena : ^ Arena,
// drag_state data : string,
}
ui_key_from_string :: proc( value : string ) -> UI_Key {
key := cast(UI_Key) crc32( transmute([]byte) value )
return key
}
ui_box_equal :: proc( a, b : ^ UI_Box ) -> b32 {
BoxSize :: size_of(UI_Box)
result : b32 = true
result &= a.key == b.key // We assume for now the label is the same as the key, if not something is terribly wrong.
result &= a.flags == b.flags
return result
last_pressed_key : [MouseBtn.count] UI_Key,
last_pressed_key_us : [MouseBtn.count] f32,
}
ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator )
@ -287,53 +314,13 @@ ui_reload :: proc( ui : ^ UI_State, cache_allocator : Allocator )
ui_shutdown :: proc() {
}
ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
{
get_state().ui_context = ui
using get_state().ui_context
ui_box_equal :: proc( a, b : ^ UI_Box ) -> b32 {
BoxSize :: size_of(UI_Box)
swap( & curr_cache, & prev_cache )
root = ui_box_make( {}, "root#001" )
ui_parent_push(root)
log("BUILD GRAPH BEGIN")
}
// TODO(Ed) :: Is this even needed?
ui_graph_build_end :: proc()
{
ui_parent_pop()
// Regenerate the computed layout if dirty
// ui_compute_layout()
get_state().ui_context = nil
log("BUILD GRAPH END")
}
@(deferred_none = ui_graph_build_end)
ui_graph_build :: proc( ui : ^ UI_State ) {
ui_graph_build_begin( ui )
}
ui_parent_push :: proc( ui : ^ UI_Box ) {
stack := & get_state().ui_context.parent_stack
stack_push( & get_state().ui_context.parent_stack, ui )
}
ui_parent_pop :: proc() {
// If size_to_content is set, we need to compute the layout now.
// Check to make sure that the parent's children are the same for this frame,
// if its not we need to mark the layout as dirty.
stack_pop( & get_state().ui_context.parent_stack )
}
@(deferred_none = ui_parent_pop)
ui_parent :: proc( ui : ^ UI_Box) {
ui_parent_push( ui )
result : b32 = true
result &= a.key == b.key // We assume for now the label is the same as the key, if not something is terribly wrong.
result &= a.flags == b.flags
return result
}
ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
@ -362,6 +349,7 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
curr_box = set_result
}
// TODO(Ed) : Review this when we learn layouts more...
if prev_box != nil {
layout_dirty &= ! ui_box_equal( curr_box, prev_box )
}
@ -370,45 +358,225 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
}
curr_box.flags = flags
curr_box.style = ( stack_peek( & style_stack ) ^ )
curr_box.parent = ( stack_peek( & parent_stack ) ^ )
curr_box.theme = stack_peek( & theme_stack )
curr_box.parent = stack_peek( & parent_stack )
// Clear old links
curr_box.parent = nil
curr_box.links = {}
curr_box.num_children = 0
// If there is a parent, setup the relevant references
if curr_box.parent != nil
parent := stack_peek( & parent_stack )
if parent != nil
{
// dbl_linked_list_push_back( box.parent, nil, box )
curr_box.parent.last = curr_box
if curr_box.parent.first == nil {
curr_box.parent.first = curr_box
}
dll_full_push_back( null_box, parent, curr_box )
parent.num_children += 1
curr_box.parent = parent
}
return curr_box
}
ui_set_layout :: proc ( layout : UI_Layout ) {
log("LAYOUT SET")
ui_box_tranverse_next :: proc( box : ^ UI_Box ) -> (^ UI_Box) {
// If current has children, do them first
if box.first != nil {
return box.first
}
if box.next == nil
{
// There is no more adjacent nodes
if box.parent != nil {
// Lift back up to parent, and set it to its next.
return box.parent.next
}
}
return box.next
}
ui_compute_layout :: proc() {
// TODO(Ed) : This generates the bounds for each box.
ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
{
get_state().ui_context = ui
using get_state().ui_context
curr_cache, prev_cache = swap( curr_cache, prev_cache )
if ui.active == UI_Key(0) {
ui.hot = UI_Key(0)
}
root = ui_box_make( {}, "root#001" )
root.style = & root.theme.default
ui_parent_push(root)
}
ui_layout_set_size :: proc( size : Vec2 ) {
// TODO(Ed) :: Is this even needed?
ui_graph_build_end :: proc()
{
ui_parent_pop() // Should be ui_context.root
// Regenerate the computed layout if dirty
ui_compute_layout()
get_state().ui_context = nil
}
ui_style_push :: proc( preset : UI_Style ) {
log("STYLE PUSH")
stack_push( & get_state().ui_context.style_stack, preset )
@(deferred_none = ui_graph_build_end)
ui_graph_build :: proc( ui : ^ UI_State ) {
ui_graph_build_begin( ui )
}
ui_style_pop :: proc() {
log("STYLE POP")
stack_pop( & get_state().ui_context.style_stack )
ui_key_from_string :: proc( value : string ) -> UI_Key {
key := cast(UI_Key) crc32( transmute([]byte) value )
return key
}
@(deferred_none = ui_style_pop)
ui_style :: proc( preset : UI_Style ) {
ui_style_push( preset )
ui_parent_push :: proc( ui : ^ UI_Box ) {
stack := & get_state().ui_context.parent_stack
stack_push( & get_state().ui_context.parent_stack, ui )
}
ui_parent_pop :: proc() {
// If size_to_content is set, we need to compute the layout now.
// Check to make sure that the parent's children are the same for this frame,
// if its not we need to mark the layout as dirty.
stack_pop( & get_state().ui_context.parent_stack )
}
@(deferred_none = ui_parent_pop)
ui_parent :: proc( ui : ^ UI_Box) {
ui_parent_push( ui )
}
ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
{
ui := get_state().ui_context
input := get_state().input
signal := UI_Signal { box = box }
if ui == & get_state().project.workspace.ui {
signal.cursor_pos = screen_to_world( input.mouse.pos )
}
else {
signal.cursor_pos = input.mouse.pos
}
signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds )
left_pressed := pressed( input.mouse.left )
left_released := released( input.mouse.left )
mouse_clickable := UI_BoxFlag.Mouse_Clickable in box.flags
keyboard_clickable := UI_BoxFlag.Keyboard_Clickable in box.flags
if mouse_clickable && signal.cursor_over && left_pressed
{
ui.hot = box.key
ui.active = box.key
ui.active_mouse[MouseBtn.Left] = box.key
ui.drag_start_mouse = signal.cursor_pos
ui.last_pressed_key = box.key
signal.pressed = true
// TODO(Ed) : Support double-click detection
}
if mouse_clickable && signal.cursor_over && left_released
{
ui.active = UI_Key(0)
ui.active_mouse[MouseBtn.Left] = UI_Key(0)
signal.released = true
signal.left_clicked = true
ui.last_clicked = box.key
}
if mouse_clickable && ! signal.cursor_over && left_released {
ui.hot = UI_Key(0)
ui.active = UI_Key(0)
ui.active_mouse[MouseBtn.Left] = UI_Key(0)
signal.released = true
signal.left_clicked = false
}
if keyboard_clickable
{
// TODO(Ed) : Add keyboard interaction support
}
// TODO(Ed) : Add scrolling support
if UI_BoxFlag.Scroll_X in box.flags {
}
if UI_BoxFlag.Scroll_Y in box.flags {
}
// TODO(Ed) : Add panning support
if UI_BoxFlag.Pan_X in box.flags {
}
if UI_BoxFlag.Pan_Y in box.flags {
}
if signal.cursor_over &&
ui.hot == UI_Key(0) || ui.hot == box.key &&
ui.active == UI_Key(0) || ui.active == box.key
{
ui.hot = box.key
}
style_preset := UI_StylePreset.Default
if box.key == ui.hot {
style_preset = UI_StylePreset.Hovered
}
if box.key == ui.active {
style_preset = UI_StylePreset.Focused
}
if UI_BoxFlag.Disabled in box.flags {
style_preset = UI_StylePreset.Disabled
}
box.style = & box.theme.array[style_preset]
return signal
}
ui_style_ref :: proc( box_state : UI_StylePreset ) -> (^ UI_Style) {
return & stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state]
}
ui_style_set :: proc ( style : UI_Style, box_state : UI_StylePreset ) {
stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] = style
}
ui_style_set_layout :: proc ( layout : UI_Layout, preset : UI_StylePreset ) {
stack_peek_ref( & get_state().ui_context.theme_stack ).array[preset].layout = layout
}
ui_style_theme_push :: proc( preset : UI_StyleTheme ) {
stack_push( & get_state().ui_context.theme_stack, preset )
}
ui_style_theme_pop :: proc() {
stack_pop( & get_state().ui_context.theme_stack )
}
@(deferred_none = ui_style_theme_pop)
ui_style_theme :: proc( preset : UI_StyleTheme ) {
ui_style_theme_push( preset )
}
ui_style_theme_set_layout :: proc ( layout : UI_Layout ) {
for & preset in stack_peek_ref( & get_state().ui_context.theme_stack ).array {
preset.layout = layout
}
}
ui_set_layout :: proc {
ui_style_set_layout,
ui_style_theme_set_layout,
}

View File

@ -1,7 +1,13 @@
cls
write-host "Build.ps1"
$incremental_checks = Join-Path $PSScriptRoot 'helpers/incremental_checks.ps1'
. $incremental_checks
write-host 'incremental_checks.ps1 imported'
$ini_parser = join-path $PSScriptRoot 'helpers/ini.ps1'
. $ini_parser
write-host 'ini.ps1 imported'
$path_root = git rev-parse --show-toplevel
$path_code = join-path $path_root 'code'
@ -10,10 +16,26 @@ $path_scripts = join-path $path_root 'scripts'
$path_thirdparty = join-path $path_root 'thirdparty'
$path_odin = join-path $path_thirdparty 'odin'
if ( $IsWindows ) {
if ( -not( test-path $path_build) ) {
new-item -ItemType Directory -Path $path_build
}
$path_system_details = join-path $path_build 'system_details.ini'
if ( test-path $path_system_details ) {
$iniContent = Get-IniContent $path_system_details
$CoreCount_Physical = $iniContent["CPU"]["PhysicalCores"]
$CoreCount_Logical = $iniContent["CPU"]["LogicalCores"]
}
elseif ( $IsWindows ) {
$CPU_Info = Get-CimInstance ClassName Win32_Processor | Select-Object -Property NumberOfCores, NumberOfLogicalProcessors
$CoreCount_Physical, $CoreCount_Logical = $CPU_Info.NumberOfCores, $CPU_Info.NumberOfLogicalProcessors
new-item -path $path_system_details -ItemType File
"[CPU]" | Out-File $path_system_details
"PhysicalCores=$CoreCount_Physical" | Out-File $path_system_details -Append
"LogicalCores=$CoreCount_Logical" | Out-File $path_system_details -Append
}
write-host "Core Count - Physical: $CoreCount_Physical Logical: $CoreCount_Logical"
# Odin Compiler Flags
@ -71,8 +93,17 @@ $msvc_link_default_base_address = 0x180000000
push-location $path_root
$update_deps = join-path $path_scripts 'update_deps.ps1'
$odin_compiler = join-path $path_odin 'odin.exe'
if ( -not( test-path 'build') ) {
new-item -ItemType Directory -Path 'build'
function Invoke-WithColorCodedOutput { param( [scriptblock] $command )
& $command 2>&1 | ForEach-Object {
# Write-Host "Type: $($_.GetType().FullName)" # Add this line for debugging
$color = 'White' # Default text color
switch ($_) {
{ $_ -imatch "error" } { $color = 'Red'; break }
{ $_ -imatch "warning" } { $color = 'Yellow'; break }
}
Write-Host "`t$_" -ForegroundColor $color
}
}
function build-prototype
@ -123,19 +154,20 @@ push-location $path_root
$build_args += $flag_output_path + $module_dll
$build_args += ($flag_collection + $pkg_collection_thirdparty)
$build_args += $flag_use_separate_modules
$build_args += $flag_thread_count + $CoreCount_Physical
$build_args += $flag_optimize_none
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += ($flag_extra_linker_flags + $linker_args )
$build_args += $flag_subsystem + 'windows'
# $build_args += $flag_show_system_calls
# $build_args += $flag_show_timings
$build_args += $flag_show_timings
$build_args += ($flag_extra_linker_flags + $linker_args )
if ( Test-Path $module_dll) {
$module_dll_pre_build_hash = get-filehash -path $module_dll -Algorithm MD5
}
& $odin_compiler $build_args
Invoke-WithColorCodedOutput -command { & $odin_compiler $build_args }
if ( Test-Path $module_dll ) {
$module_dll_post_build_hash = get-filehash -path $module_dll -Algorithm MD5
@ -172,6 +204,7 @@ push-location $path_root
return
}
write-host 'Building Host Module'
$linker_args = ""
$linker_args += ( $flag_msvc_link_disable_dynamic_base + ' ' )
@ -185,14 +218,12 @@ push-location $path_root
$build_args += $flag_optimize_none
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += ($flag_extra_linker_flags + $linker_args )
$build_args += $flag_subsystem + 'windows'
$build_args += ($flag_extra_linker_flags + $linker_args )
$build_args += $flag_show_timings
# $build_args += $flag_show_system_call
# $build_args += $flag_show_timings
write-host 'Building Host Module'
& $odin_compiler $build_args
write-host
Invoke-WithColorCodedOutput { & $odin_compiler $build_args }
}
build-host
@ -200,3 +231,5 @@ push-location $path_root
}
build-prototype
pop-location # path_root
exit 0

20
scripts/helpers/ini.ps1 Normal file
View File

@ -0,0 +1,20 @@
# This is meant to be used with build.ps1, and is not a standalone script.
function Get-IniContent { param([ string]$filePath )
$ini = @{}
$currentSection = $null
switch -regex -file $filePath
{
"^\[(.+)\]$" {
$currentSection = $matches[1].Trim()
$ini[$currentSection] = @{}
}
"^(.+?)\s*=\s*(.*)" {
$key, $value = $matches[1].Trim(), $matches[2].Trim()
if ($null -ne $currentSection) {
$ini[$currentSection][$key] = $value
}
}
}
return $ini
}

3
scripts/setup_shell.ps1 Normal file
View File

@ -0,0 +1,3 @@
set-alias -Name 'build' -Value '.\build.ps1'
set-alias -Name 'buildclean' -Value '.\clean.ps1'