From 035c726a71800cbeee97d3620c2ce2c357711655 Mon Sep 17 00:00:00 2001
From: Ed_ <edwardgz@gmail.com>
Date: Sat, 2 Mar 2024 10:24:09 -0500
Subject: [PATCH] got basic ui elmental interaction working, + alignment of
 anchor

---
 .vscode/launch.json         |   2 +-
 .vscode/settings.json       |   3 +-
 .vscode/tasks.json          |  26 +++
 code/__Imgui_raddbg/ui.odin |   2 +-
 code/api.odin               |  11 +-
 code/collision.odin         |   5 +
 code/colors.odin            |   4 +
 code/env.odin               |   5 +-
 code/girme_stack.odin       |  10 +-
 code/grime.odin             |   9 +-
 code/grime_array.odin       |  10 +-
 code/grime_hashmap_zpl.odin |   4 +
 code/grime_linked_list.odin |  53 +++--
 code/host/host.odin         |   4 +-
 code/input.odin             |   8 +
 code/math.odin              |  24 ++
 code/space.odin             |  17 +-
 code/tick_render.odin       |  47 +++-
 code/tick_update.odin       |  64 +++++-
 code/ui.layout.odin         |  96 ++++++++
 code/ui.odin                | 442 +++++++++++++++++++++++++-----------
 scripts/build.ps1           |  55 ++++-
 scripts/helpers/ini.ps1     |  20 ++
 scripts/setup_shell.ps1     |   3 +
 24 files changed, 722 insertions(+), 202 deletions(-)
 create mode 100644 .vscode/tasks.json
 create mode 100644 code/ui.layout.odin
 create mode 100644 scripts/helpers/ini.ps1
 create mode 100644 scripts/setup_shell.ps1

diff --git a/.vscode/launch.json b/.vscode/launch.json
index eeee589..7506802 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -13,4 +13,4 @@
 			"cwd": "${workspaceFolder}/build"
 		}
 	]
-}
\ No newline at end of file
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index cb9a190..92f5eae 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,5 +5,6 @@
 		"**/thirdparty" : false,
 	},
 	"godot_tools.scene_file_config": "c:\\projects\\SectrPrototype\\code",
-	"autoHide.autoHidePanel": false
+	"autoHide.autoHidePanel": false,
+	"autoHide.autoHideSideBar": false
 }
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..7fcae09
--- /dev/null
+++ b/.vscode/tasks.json
@@ -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"
+            }
+        }
+    ]
+}
\ No newline at end of file
diff --git a/code/__Imgui_raddbg/ui.odin b/code/__Imgui_raddbg/ui.odin
index e5e4905..22cf6dd 100644
--- a/code/__Imgui_raddbg/ui.odin
+++ b/code/__Imgui_raddbg/ui.odin
@@ -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,
diff --git a/code/api.odin b/code/api.odin
index 9b2200c..baa822b 100644
--- a/code/api.odin
+++ b/code/api.odin
@@ -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
diff --git a/code/collision.odin b/code/collision.odin
index d6fba76..90b6bc9 100644
--- a/code/collision.odin
+++ b/code/collision.odin
@@ -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 )
diff --git a/code/colors.odin b/code/colors.odin
index 5f87530..177d317 100644
--- a/code/colors.odin
+++ b/code/colors.odin
@@ -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 }
diff --git a/code/env.odin b/code/env.odin
index d996645..101f1a5 100644
--- a/code/env.odin
+++ b/code/env.odin
@@ -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,
 }
diff --git a/code/girme_stack.odin b/code/girme_stack.odin
index 12c0a3f..eaaf282 100644
--- a/code/girme_stack.odin
+++ b/code/girme_stack.odin
@@ -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]
 }
diff --git a/code/grime.odin b/code/grime.odin
index 171f6e9..63d803b 100644
--- a/code/grime.odin
+++ b/code/grime.odin
@@ -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,
 }
diff --git a/code/grime_array.odin b/code/grime_array.odin
index de16409..437b79d 100644
--- a/code/grime_array.odin
+++ b/code/grime_array.odin
@@ -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
 }
diff --git a/code/grime_hashmap_zpl.odin b/code/grime_hashmap_zpl.odin
index bdaa8e7..51151cb 100644
--- a/code/grime_hashmap_zpl.odin
+++ b/code/grime_hashmap_zpl.odin
@@ -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 )
diff --git a/code/grime_linked_list.odin b/code/grime_linked_list.odin
index f11d589..0a296e8 100644
--- a/code/grime_linked_list.odin
+++ b/code/grime_linked_list.odin
@@ -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 )
+}
diff --git a/code/host/host.odin b/code/host/host.odin
index 0e53b8f..2ea88d4 100644
--- a/code/host/host.odin
+++ b/code/host/host.odin
@@ -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 )
diff --git a/code/input.odin b/code/input.odin
index d3da8d8..da9b595 100644
--- a/code/input.odin
+++ b/code/input.odin
@@ -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,
diff --git a/code/math.odin b/code/math.odin
index 69151bf..77f25c8 100644
--- a/code/math.odin
+++ b/code/math.odin
@@ -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
+}
diff --git a/code/space.odin b/code/space.odin
index 4a38521..ee849ff 100644
--- a/code/space.odin
+++ b/code/space.odin
@@ -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 {
diff --git a/code/tick_render.odin b/code/tick_render.odin
index 7e6ffea..444f552 100644
--- a/code/tick_render.odin
+++ b/code/tick_render.odin
@@ -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()
 }
diff --git a/code/tick_update.odin b/code/tick_update.odin
index 872b73e..27d2ae5 100644
--- a/code/tick_update.odin
+++ b/code/tick_update.odin
@@ -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
 
diff --git a/code/ui.layout.odin b/code/ui.layout.odin
new file mode 100644
index 0000000..0c79e4b
--- /dev/null
+++ b/code/ui.layout.odin
@@ -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 )
+	}
+}
diff --git a/code/ui.odin b/code/ui.odin
index 972b095..78b9d3b 100644
--- a/code/ui.odin
+++ b/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,
 }
diff --git a/scripts/build.ps1 b/scripts/build.ps1
index 45007b9..cf69420 100644
--- a/scripts/build.ps1
+++ b/scripts/build.ps1
@@ -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
diff --git a/scripts/helpers/ini.ps1 b/scripts/helpers/ini.ps1
new file mode 100644
index 0000000..f4e1130
--- /dev/null
+++ b/scripts/helpers/ini.ps1
@@ -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
+}
diff --git a/scripts/setup_shell.ps1 b/scripts/setup_shell.ps1
new file mode 100644
index 0000000..918c91f
--- /dev/null
+++ b/scripts/setup_shell.ps1
@@ -0,0 +1,3 @@
+set-alias -Name 'build'      -Value '.\build.ps1'
+set-alias -Name 'buildclean' -Value '.\clean.ps1'
+