got basic ui elmental interaction working, + alignment of anchor
This commit is contained in:
parent
1e5773e486
commit
035c726a71
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -13,4 +13,4 @@
|
||||
"cwd": "${workspaceFolder}/build"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -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
26
.vscode/tasks.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
|
@ -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 }
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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]
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
}
|
||||
|
@ -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 )
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
96
code/ui.layout.odin
Normal 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 )
|
||||
}
|
||||
}
|
442
code/ui.odin
442
code/ui.odin
@ -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,
|
||||
}
|
||||
|
@ -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
20
scripts/helpers/ini.ps1
Normal 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
3
scripts/setup_shell.ps1
Normal file
@ -0,0 +1,3 @@
|
||||
set-alias -Name 'build' -Value '.\build.ps1'
|
||||
set-alias -Name 'buildclean' -Value '.\clean.ps1'
|
||||
|
Loading…
Reference in New Issue
Block a user