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' +