Gut raylib usage from the codebase.
Going to either fully commit to sokol or if it fails, rolling the platform layer myself.
This commit is contained in:
		| @@ -3,11 +3,10 @@ package sectr | ||||
| // Scratch space | ||||
|  | ||||
| import sokol_gfx "thirdparty:sokol/gfx" | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| DebugData :: struct { | ||||
| 	square_size : i32, | ||||
| 	square_pos  : rl.Vector2, | ||||
| 	square_pos  : Vec2, | ||||
|  | ||||
| 	debug_text_vis    : b32, | ||||
| 	draw_debug_text_y : f32, | ||||
| @@ -35,10 +34,6 @@ DebugData :: struct { | ||||
| 	lorem_content : []byte, | ||||
| 	lorem_parse   : PWS_ParseResult, | ||||
|  | ||||
| 	// Test 3d Viewport | ||||
| 	cam_vp      : rl.Camera3D, | ||||
| 	viewport_rt : rl.RenderTexture, | ||||
|  | ||||
| 	gfx_clear_demo_pass_action : sokol_gfx.Pass_Action, | ||||
| 	gfx_tri_demo_state : struct { | ||||
| 		pipeline    : sokol_gfx.Pipeline, | ||||
|   | ||||
| @@ -6,8 +6,6 @@ import "core:mem" | ||||
| import "core:mem/virtual" | ||||
| import "core:os" | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| Str_App_State := "App State" | ||||
|  | ||||
| #region("Memory") | ||||
| @@ -167,6 +165,7 @@ AppWindow :: struct { | ||||
| 	extent    : Extents2, // Window half-size | ||||
| 	dpi_scale : f32,      // Dots per inch scale (provided by raylib via glfw) | ||||
| 	ppcm      : f32,      // Dots per centimetre | ||||
| 	resized   : b32,      // Extent changed this frame | ||||
| } | ||||
|  | ||||
| FontData :: struct { | ||||
| @@ -241,9 +240,14 @@ State :: struct { | ||||
| 	font_rec_mono_semicasual_reg : FontID, | ||||
| 	default_font                 : FontID, | ||||
|  | ||||
|  | ||||
| 	// Context tracking | ||||
| 	// These are used as implicit contextual states when doing immediate mode interfaces | ||||
| 	// or for event callbacks that need their context assigned | ||||
|  | ||||
| 	// There are two potential UI contextes for this prototype so far, | ||||
| 	// the screen-space UI and the current workspace UI. | ||||
| 	// This is used so that the ui api doesn't need to have the user pass the context every single time. | ||||
| 	// This is used so that the ui api doesn't need to have the user pass the context through every proc. | ||||
| 	ui_context          : ^UI_State, | ||||
| 	ui_floating_context : ^UI_FloatingManager, | ||||
|  | ||||
|   | ||||
| @@ -116,12 +116,12 @@ theme_table_row :: proc( is_even : bool ) -> UI_Theme | ||||
| 	if ! loaded | ||||
| 	{ | ||||
| 		app_color := app_color_theme() | ||||
| 		table_bg : Color | ||||
| 		table_bg : RGBA8 | ||||
| 		if is_even { | ||||
| 			table_bg = app_color.table_even_bg_color | ||||
| 			table_bg = app_color.table_even_bg | ||||
| 		} | ||||
| 		else { | ||||
| 			table_bg = app_color.table_odd_bg_color | ||||
| 			table_bg = app_color.table_odd_bg | ||||
| 		} | ||||
| 		layout := UI_Layout { | ||||
| 			flags          = {}, | ||||
|   | ||||
| @@ -1,32 +1,29 @@ | ||||
| package sectr | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
| RGBA8       :: struct { r, g, b, a : u8 } | ||||
| Color_Blue  :: RGBA8 {  90,  90, 230, 255 } | ||||
| Color_Red   :: RGBA8 { 230,  90,  90, 255 } | ||||
| Color_White :: RGBA8 { 255, 255, 255, 255 } | ||||
|  | ||||
| Color       :: rl.Color | ||||
| Color_Blue  :: rl.BLUE | ||||
| // Color_Green :: rl.GREEN | ||||
| Color_Red   :: rl.RED | ||||
| Color_White :: rl.WHITE | ||||
| Color_Transparent          :: RGBA8 {   0,   0,   0,   0 } | ||||
| Color_BG                   :: RGBA8 {  55,  55,  60, 255 } | ||||
| Color_BG_TextBox           :: RGBA8 {  32,  32,  32, 180 } | ||||
| Color_BG_Panel             :: RGBA8 {  32,  32,  32, 255 } | ||||
| Color_BG_Panel_Translucent :: RGBA8 {  32,  32,  32, 220 } | ||||
| Color_BG_TextBox_Green     :: RGBA8 { 102, 102, 110, 255 } | ||||
| Color_Frame_Disabled       :: RGBA8 {  22,  22,  22, 120 } | ||||
| Color_Frame_Hover          :: RGBA8 { 122, 122, 125, 200 } | ||||
| Color_Frame_Select         :: RGBA8 { 188, 188, 188, 220 } | ||||
| Color_GreyRed              :: RGBA8 { 220, 100, 100,  50 } | ||||
| Color_White_A125           :: RGBA8 { 255, 255, 255, 165 } | ||||
| Color_Black                :: RGBA8 {   0,   0,   0, 255 } | ||||
| Color_Green                :: RGBA8 {   0, 180,   0, 255 } | ||||
| Color_ResizeHandle         :: RGBA8 {  80,  80,  90, 180 } | ||||
|  | ||||
| Color_Transparent          :: Color {   0,   0,   0,   0 } | ||||
| Color_BG                   :: Color {  55,  55,  60, 255 } | ||||
| Color_BG_TextBox           :: Color {  32,  32,  32, 180 } | ||||
| Color_BG_Panel             :: Color {  32,  32,  32, 255 } | ||||
| Color_BG_Panel_Translucent :: Color {  32,  32,  32, 220 } | ||||
| Color_BG_TextBox_Green     :: Color { 102, 102, 110, 255 } | ||||
| Color_Frame_Disabled       :: Color {  22,  22,  22, 120 } | ||||
| Color_Frame_Hover          :: Color { 122, 122, 125, 200 } | ||||
| Color_Frame_Select         :: Color { 188, 188, 188, 220 } | ||||
| Color_GreyRed              :: Color { 220, 100, 100, 50 } | ||||
| Color_White_A125           :: Color { 255, 255, 255, 165 } | ||||
| Color_Black                :: Color { 0, 0, 0, 255 } | ||||
| Color_Green                :: Color { 0, 180, 0, 255 } | ||||
| Color_ResizeHandle         :: Color { 80, 80, 90, 180 } | ||||
| RGBA8_3D_BG :: RGBA8 { 188, 182 , 170, 255 } | ||||
|  | ||||
| Color_3D_BG :: Color { 188, 182 , 170, 255 } | ||||
|  | ||||
| Color_Debug_UI_Padding_Bounds :: Color {  40, 195, 170, 160 } | ||||
| Color_Debug_UI_Content_Bounds :: Color { 170, 120, 240, 160 } | ||||
| RGBA8_Debug_UI_Padding_Bounds :: RGBA8 {  40, 195, 170, 160 } | ||||
| RGBA8_Debug_UI_Content_Bounds :: RGBA8 { 170, 120, 240, 160 } | ||||
|  | ||||
| // TODO(Ed): The entire rendering pass should be post-processed by a tone curve configurable for the user | ||||
| // This is how you properly support any tonality of light or dark themes and not have it be base don the monitors raw output. | ||||
| @@ -51,8 +48,8 @@ AppColorTheme :: struct { | ||||
| 	resize_hndl_hot, | ||||
| 	resize_hndl_active, | ||||
|  | ||||
| 	table_even_bg_color, | ||||
| 	table_odd_bg_color, | ||||
| 	table_even_bg, | ||||
| 	table_odd_bg, | ||||
|  | ||||
| 	text_default, | ||||
| 	text_hot, | ||||
| @@ -66,116 +63,116 @@ AppColorTheme :: struct { | ||||
|  | ||||
| 	window_panel_bg, | ||||
| 	window_panel_border \ | ||||
| 	: Color | ||||
| 	: RGBA8 | ||||
| } | ||||
|  | ||||
| App_Thm_Dark :: AppColorTheme { | ||||
| 	light_limit = Color {185, 185, 185, 255}, | ||||
| 	dark_limit  = Color { 6, 6, 6, 255}, | ||||
| 	light_limit = RGBA8 {185, 185, 185, 255}, | ||||
| 	dark_limit  = RGBA8 { 6, 6, 6, 255}, | ||||
|  | ||||
| 	bg = Color {16, 16, 16, 255}, | ||||
| 	bg = RGBA8 {16, 16, 16, 255}, | ||||
|  | ||||
| 	border_default = Color { 54, 54, 54, 255}, | ||||
| 	border_default = RGBA8 { 54, 54, 54, 255}, | ||||
|  | ||||
| 	btn_bg_default = Color {  32,  32,  32, 255}, | ||||
| 	btn_bg_hot     = Color {  80,  80, 100, 255}, | ||||
| 	btn_bg_active  = Color { 100, 130, 180, 255}, | ||||
| 	btn_bg_default = RGBA8 {  32,  32,  32, 255}, | ||||
| 	btn_bg_hot     = RGBA8 {  80,  80, 100, 255}, | ||||
| 	btn_bg_active  = RGBA8 { 100, 130, 180, 255}, | ||||
|  | ||||
| 	input_box_bg        = Color { 20, 20, 20, 255}, | ||||
| 	input_box_bg_hot    = Color { 25, 25, 25, 255}, | ||||
| 	input_box_bg_active = Color { 15, 15, 15, 255}, | ||||
| 	input_box_bg        = RGBA8 { 20, 20, 20, 255}, | ||||
| 	input_box_bg_hot    = RGBA8 { 25, 25, 25, 255}, | ||||
| 	input_box_bg_active = RGBA8 { 15, 15, 15, 255}, | ||||
|  | ||||
| 	resize_hndl_default = Color_Transparent, | ||||
| 	resize_hndl_hot     = Color { 72, 72, 72, 90}, | ||||
| 	resize_hndl_active  = Color { 88, 88, 88, 90}, | ||||
| 	resize_hndl_hot     = RGBA8 { 72, 72, 72, 90}, | ||||
| 	resize_hndl_active  = RGBA8 { 88, 88, 88, 90}, | ||||
|  | ||||
| 	table_even_bg_color = Color { 35, 35, 35, 255}, | ||||
| 	table_odd_bg_color  = Color { 30, 30, 30, 255}, | ||||
| 	table_even_bg = RGBA8 { 35, 35, 35, 255}, | ||||
| 	table_odd_bg  = RGBA8 { 30, 30, 30, 255}, | ||||
|  | ||||
| 	text_default = Color {140, 137, 135, 255}, | ||||
| 	text_hot     = Color {210, 210, 210, 255}, | ||||
| 	text_active  = Color {255, 255, 255, 255}, | ||||
| 	text_default = RGBA8 {140, 137, 135, 255}, | ||||
| 	text_hot     = RGBA8 {210, 210, 210, 255}, | ||||
| 	text_active  = RGBA8 {255, 255, 255, 255}, | ||||
|  | ||||
| 	translucent_panel = Color { 30, 30, 30, 50}, | ||||
| 	translucent_panel = RGBA8 { 30, 30, 30, 50}, | ||||
|  | ||||
| 	window_bar_border       = Color{74, 74, 74, 255}, | ||||
| 	window_bar_bg           = Color{32, 32, 32, 255}, | ||||
| 	window_btn_close_bg_hot = Color{65, 45, 45, 255}, | ||||
| 	window_bar_border       = RGBA8{74, 74, 74, 255}, | ||||
| 	window_bar_bg           = RGBA8{32, 32, 32, 255}, | ||||
| 	window_btn_close_bg_hot = RGBA8{65, 45, 45, 255}, | ||||
|  | ||||
| 	window_panel_bg     = Color{ 20, 20, 20, 50}, | ||||
| 	window_panel_border = Color{ 84, 84, 84, 255}, | ||||
| 	window_panel_bg     = RGBA8{ 20, 20, 20, 50}, | ||||
| 	window_panel_border = RGBA8{ 84, 84, 84, 255}, | ||||
| } | ||||
|  | ||||
| App_Thm_Dusk :: AppColorTheme { | ||||
| 	light_limit = Color {125, 125, 125, 255}, | ||||
| 	dark_limit  = Color { 10, 10, 10, 255}, | ||||
| 	light_limit = RGBA8 {125, 125, 125, 255}, | ||||
| 	dark_limit  = RGBA8 { 10, 10, 10, 255}, | ||||
|  | ||||
| 	bg = Color {33, 33, 33, 255}, | ||||
| 	bg = RGBA8 {33, 33, 33, 255}, | ||||
|  | ||||
| 	border_default = Color { 64, 64, 64, 255}, | ||||
| 	border_default = RGBA8 { 64, 64, 64, 255}, | ||||
|  | ||||
| 	btn_bg_default = Color { 40,  40,  40, 255}, | ||||
| 	btn_bg_hot     = Color { 60,  60,  70, 255}, | ||||
| 	btn_bg_active  = Color { 90, 100, 130, 255}, | ||||
| 	btn_bg_default = RGBA8 { 40,  40,  40, 255}, | ||||
| 	btn_bg_hot     = RGBA8 { 60,  60,  70, 255}, | ||||
| 	btn_bg_active  = RGBA8 { 90, 100, 130, 255}, | ||||
|  | ||||
| 	input_box_bg        = Color { 20, 20, 20, 255}, | ||||
| 	input_box_bg_hot    = Color { 25, 25, 25, 255}, | ||||
| 	input_box_bg_active = Color { 15, 15, 15, 255}, | ||||
| 	input_box_bg        = RGBA8 { 20, 20, 20, 255}, | ||||
| 	input_box_bg_hot    = RGBA8 { 25, 25, 25, 255}, | ||||
| 	input_box_bg_active = RGBA8 { 15, 15, 15, 255}, | ||||
|  | ||||
| 	resize_hndl_default = Color_Transparent, | ||||
| 	resize_hndl_hot     = Color { 72, 72, 72, 90}, | ||||
| 	resize_hndl_active  = Color { 88, 88, 88, 90}, | ||||
| 	resize_hndl_hot     = RGBA8 { 72, 72, 72, 90}, | ||||
| 	resize_hndl_active  = RGBA8 { 88, 88, 88, 90}, | ||||
|  | ||||
| 	table_even_bg_color = Color { 35, 35, 35, 255}, | ||||
| 	table_odd_bg_color  = Color { 30, 30, 30, 255}, | ||||
| 	table_even_bg = RGBA8 { 35, 35, 35, 255}, | ||||
| 	table_odd_bg  = RGBA8 { 30, 30, 30, 255}, | ||||
|  | ||||
| 	text_default = Color {120, 117, 115, 255}, | ||||
| 	text_hot     = Color {180, 180, 180, 255}, | ||||
| 	text_active  = Color {240, 240, 240, 255}, | ||||
| 	text_default = RGBA8 {120, 117, 115, 255}, | ||||
| 	text_hot     = RGBA8 {180, 180, 180, 255}, | ||||
| 	text_active  = RGBA8 {240, 240, 240, 255}, | ||||
|  | ||||
| 	translucent_panel = Color { 10, 10, 10, 50}, | ||||
| 	translucent_panel = RGBA8 { 10, 10, 10, 50}, | ||||
|  | ||||
| 	window_bar_border       = Color { 64, 64, 64, 255}, // border_default | ||||
| 	window_bar_bg           = Color{35, 35, 35, 255}, | ||||
| 	window_btn_close_bg_hot = Color{45, 35, 35, 255}, | ||||
| 	window_bar_border       = RGBA8 { 64, 64, 64, 255}, // border_default | ||||
| 	window_bar_bg           = RGBA8{35, 35, 35, 255}, | ||||
| 	window_btn_close_bg_hot = RGBA8{45, 35, 35, 255}, | ||||
|  | ||||
| 	window_panel_bg     = Color { 10, 10, 10, 50}, // translucent_panel | ||||
| 	window_panel_border = Color{24, 24, 24, 255}, | ||||
| 	window_panel_bg     = RGBA8 { 10, 10, 10, 50}, // translucent_panel | ||||
| 	window_panel_border = RGBA8{24, 24, 24, 255}, | ||||
| } | ||||
|  | ||||
| App_Thm_Light :: AppColorTheme { | ||||
| 	light_limit = Color {195, 195, 195, 255}, | ||||
| 	dark_limit  = Color { 60,  60,  60, 255}, | ||||
| 	light_limit = RGBA8 {195, 195, 195, 255}, | ||||
| 	dark_limit  = RGBA8 { 60,  60,  60, 255}, | ||||
|  | ||||
| 	bg = Color {135, 135, 135, 255}, | ||||
| 	bg = RGBA8 {135, 135, 135, 255}, | ||||
|  | ||||
| 	border_default = Color { 174, 174, 174, 255}, | ||||
| 	border_default = RGBA8 { 174, 174, 174, 255}, | ||||
|  | ||||
| 	btn_bg_default = Color { 160, 160, 160, 255}, | ||||
| 	btn_bg_hot     = Color { 145, 145, 155, 255}, | ||||
| 	btn_bg_active  = Color { 124, 124, 136, 255}, | ||||
| 	btn_bg_default = RGBA8 { 160, 160, 160, 255}, | ||||
| 	btn_bg_hot     = RGBA8 { 145, 145, 155, 255}, | ||||
| 	btn_bg_active  = RGBA8 { 124, 124, 136, 255}, | ||||
|  | ||||
| 	input_box_bg        = Color {115, 115, 115, 255}, | ||||
| 	input_box_bg_hot    = Color {125, 125, 125, 255}, | ||||
| 	input_box_bg_active = Color {105, 105, 105, 255}, | ||||
| 	input_box_bg        = RGBA8 {115, 115, 115, 255}, | ||||
| 	input_box_bg_hot    = RGBA8 {125, 125, 125, 255}, | ||||
| 	input_box_bg_active = RGBA8 {105, 105, 105, 255}, | ||||
|  | ||||
| 	resize_hndl_default = Color_Transparent, | ||||
| 	resize_hndl_hot     = Color { 95, 95, 95, 90}, | ||||
| 	resize_hndl_active  = Color { 80, 80, 80, 90}, | ||||
| 	resize_hndl_hot     = RGBA8 { 95, 95, 95, 90}, | ||||
| 	resize_hndl_active  = RGBA8 { 80, 80, 80, 90}, | ||||
|  | ||||
| 	table_even_bg_color = Color {150, 150, 150, 255}, | ||||
| 	table_odd_bg_color  = Color {160, 160, 160, 255}, | ||||
| 	table_even_bg = RGBA8 {150, 150, 150, 255}, | ||||
| 	table_odd_bg  = RGBA8 {160, 160, 160, 255}, | ||||
|  | ||||
| 	text_default = Color { 55,  55,  55, 255}, | ||||
| 	text_hot     = Color { 85,  85,  85, 255}, | ||||
| 	text_active  = Color { 45,  45,  49, 255}, | ||||
| 	text_default = RGBA8 { 55,  55,  55, 255}, | ||||
| 	text_hot     = RGBA8 { 85,  85,  85, 255}, | ||||
| 	text_active  = RGBA8 { 45,  45,  49, 255}, | ||||
|  | ||||
| 	translucent_panel = Color { 110, 110, 110, 50}, | ||||
| 	translucent_panel = RGBA8 { 110, 110, 110, 50}, | ||||
|  | ||||
| 	window_bar_border       = Color{ 174, 174, 174, 255}, // border_default | ||||
| 	window_bar_bg           = Color{ 155, 155, 155, 255}, | ||||
| 	window_btn_close_bg_hot = Color{ 145, 135, 135, 255}, | ||||
| 	window_bar_border       = RGBA8{ 174, 174, 174, 255}, // border_default | ||||
| 	window_bar_bg           = RGBA8{ 155, 155, 155, 255}, | ||||
| 	window_btn_close_bg_hot = RGBA8{ 145, 135, 135, 255}, | ||||
|  | ||||
| 	window_panel_bg     = Color {135, 135, 135, 50}, // translucent_panel | ||||
| 	window_panel_border = Color{184, 184, 184, 255}, | ||||
| 	window_panel_bg     = RGBA8 {135, 135, 135, 50}, // translucent_panel | ||||
| 	window_panel_border = RGBA8{184, 184, 184, 255}, | ||||
| } | ||||
|   | ||||
| @@ -15,8 +15,6 @@ import sokol_app          "thirdparty:sokol/app" | ||||
| import sokol_gfx          "thirdparty:sokol/gfx" | ||||
| import sokol_app_gfx_glue "thirdparty:sokol/glue" | ||||
| 
 | ||||
| import rl "vendor:raylib" | ||||
| 
 | ||||
| Path_Assets       :: "../assets/" | ||||
| Path_Shaders      :: "../shaders/" | ||||
| Path_Input_Replay :: "scratch.sectr_replay" | ||||
| @@ -104,15 +102,17 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem | ||||
| 	} | ||||
| 
 | ||||
| 	// Setup input frame poll references | ||||
| 	input      = & input_data[1] | ||||
| 	input_prev = & input_data[0] | ||||
| 	for & input in input_data { | ||||
| 		using input | ||||
| 		error : AllocatorError | ||||
| 		keyboard_events.keys_pressed, error  = array_init_reserve(KeyCode, persistent_slab_allocator(), Kilo) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.keys_pressed array") | ||||
| 		keyboard_events.chars_pressed, error = array_init_reserve(rune, persistent_slab_allocator(), Kilo) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.chars_pressed array") | ||||
| 	{ | ||||
| 		input      = & input_data[1] | ||||
| 		input_prev = & input_data[0] | ||||
| 		for & input in input_data { | ||||
| 			using input | ||||
| 			error : AllocatorError | ||||
| 			keyboard_events.keys_pressed, error  = array_init_reserve(KeyCode, persistent_slab_allocator(), Kilo) | ||||
| 			ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.keys_pressed array") | ||||
| 			keyboard_events.chars_pressed, error = array_init_reserve(rune, persistent_slab_allocator(), Kilo) | ||||
| 			ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.chars_pressed array") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Configuration Load | ||||
| @@ -305,9 +305,9 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem | ||||
| 		{ | ||||
| 			using project.workspace | ||||
| 			cam = { | ||||
| 				target   = { 0, 0 }, | ||||
| 				offset   = transmute(Vec2) app_window.extent, | ||||
| 				rotation = 0, | ||||
| 				position = { 0, 0 }, | ||||
| 				view     = transmute(Vec2) app_window.extent, | ||||
| 				// rotation = 0, | ||||
| 				zoom     = 1.0, | ||||
| 			} | ||||
| 			// cam = { | ||||
| @@ -1,424 +0,0 @@ | ||||
| package sectr | ||||
|  | ||||
| import    "base:runtime" | ||||
| import  c "core:c/libc" | ||||
| import    "core:dynlib" | ||||
| import    "core:mem" | ||||
| import    "core:mem/virtual" | ||||
| import    "core:os" | ||||
| import    "core:slice" | ||||
| import    "core:strings" | ||||
| import    "core:time" | ||||
| import    "core:prof/spall" | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| when false { | ||||
| Path_Assets       :: "../assets/" | ||||
| Path_Shaders      :: "../shaders/" | ||||
| Path_Input_Replay :: "scratch.sectr_replay" | ||||
|  | ||||
| Persistent_Slab_DBG_Name := "Peristent Slab" | ||||
| Frame_Slab_DBG_Name      := "Frame Slab" | ||||
| Transient_Slab_DBG_Name  := "Transient Slab" | ||||
|  | ||||
| ModuleAPI :: struct { | ||||
| 	lib         : dynlib.Library, | ||||
| 	write_time  : FileTime, | ||||
| 	lib_version : i32, | ||||
|  | ||||
| 	startup     : type_of( startup ), | ||||
| 	shutdown    : type_of( sectr_shutdown ), | ||||
| 	reload      : type_of( reload ), | ||||
| 	tick        : type_of( tick ), | ||||
| 	clean_frame : type_of( clean_frame ), | ||||
| } | ||||
|  | ||||
| @export | ||||
| startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^Logger ) | ||||
| { | ||||
| 	spall.SCOPED_EVENT( & prof.ctx, & prof.buffer, #procedure ) | ||||
| 	Memory_App.profiler = prof | ||||
|  | ||||
| 	startup_tick := time.tick_now() | ||||
|  | ||||
| 	logger_init( & Memory_App.logger, "Sectr", host_logger.file_path, host_logger.file ) | ||||
| 	context.logger = to_odin_logger( & Memory_App.logger ) | ||||
|  | ||||
| 	// Setup memory for the first time | ||||
| 	{ | ||||
| 		using Memory_App; | ||||
| 		persistent   = persistent_mem | ||||
| 		frame        = frame_mem | ||||
| 		transient    = transient_mem | ||||
| 		files_buffer = files_buffer_mem | ||||
|  | ||||
| 		context.allocator      = transient_allocator() | ||||
| 		context.temp_allocator = transient_allocator() | ||||
| 		// TODO(Ed) : Put on the transient allocator a slab allocator (transient slab) | ||||
| 	} | ||||
|  | ||||
| 	state := new( State, persistent_allocator() ) | ||||
| 	Memory_App.state = state | ||||
| 	using state | ||||
|  | ||||
| 	// Setup Persistent Slabs & String Cache | ||||
| 	{ | ||||
| 		alignment := uint(mem.DEFAULT_ALIGNMENT) | ||||
|  | ||||
| 		policy_ptr := & default_slab_policy | ||||
| 		push( policy_ptr, SlabSizeClass {  128 * Kilobyte,   1 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {  256 * Kilobyte,   2 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {  512 * Kilobyte,   4 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    1 * Megabyte,  16 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    1 * Megabyte,  32 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    1 * Megabyte,  64 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    2 * Megabyte, 128 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    2 * Megabyte, 256 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    2 * Megabyte, 512 * Kilobyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    2 * Megabyte,   1 * Megabyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    2 * Megabyte,   2 * Megabyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    4 * Megabyte,   4 * Megabyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {    8 * Megabyte,   8 * Megabyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {   16 * Megabyte,  16 * Megabyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {   32 * Megabyte,  32 * Megabyte, alignment }) | ||||
| 		push( policy_ptr, SlabSizeClass {   64 * Megabyte,  64 * Megabyte, alignment }) | ||||
| 		// push( policy_ptr, SlabSizeClass { 128 * Megabyte, 128 * Megabyte, alignment }) | ||||
| 		// push( policy_ptr, SlabSizeClass { 256 * Megabyte, 256 * Megabyte, alignment }) | ||||
| 		// push( policy_ptr, SlabSizeClass { 512 * Megabyte, 512 * Megabyte, alignment }) | ||||
|  | ||||
| 		alloc_error : AllocatorError | ||||
| 		persistent_slab, alloc_error = slab_init( policy_ptr, allocator = persistent_allocator(), dbg_name = Persistent_Slab_DBG_Name ) | ||||
| 		verify( alloc_error == .None, "Failed to allocate the persistent slab" ) | ||||
|  | ||||
| 		transient_slab, alloc_error = slab_init( & default_slab_policy, allocator = transient_allocator(), dbg_name = Transient_Slab_DBG_Name ) | ||||
| 		verify( alloc_error == .None, "Failed to allocate transient slab" ) | ||||
|  | ||||
| 		transient_clear_time = 120 // Seconds, 2 Minutes | ||||
|  | ||||
| 		string_cache = str_cache_init() | ||||
| 	} | ||||
|  | ||||
| 	// Setup input frame poll references | ||||
| 	input      = & input_data[1] | ||||
| 	input_prev = & input_data[0] | ||||
| 	for & input in input_data { | ||||
| 		using input | ||||
| 		error : AllocatorError | ||||
| 		keyboard_events.keys_pressed, error  = array_init_reserve(KeyCode, persistent_slab_allocator(), Kilo) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.keys_pressed array") | ||||
| 		keyboard_events.chars_pressed, error = array_init_reserve(rune, persistent_slab_allocator(), Kilo) | ||||
| 		ensure(error == AllocatorError.None, "Failed to allocate input.keyboard_events.chars_pressed array") | ||||
| 	} | ||||
|  | ||||
| 	// Configuration Load | ||||
| 	// TODO(Ed): Make this actually load from an ini | ||||
| 	{ | ||||
| 		using config | ||||
| 		resolution_width  = 1000 | ||||
| 		resolution_height =  600 | ||||
| 		refresh_rate      =    0 | ||||
|  | ||||
| 		cam_min_zoom                 = 0.10 | ||||
| 		cam_max_zoom                 = 30.0 | ||||
| 		cam_zoom_mode                = .Smooth | ||||
| 		cam_zoom_smooth_snappiness   = 4.0 | ||||
| 		cam_zoom_sensitivity_digital = 0.2 | ||||
| 		cam_zoom_sensitivity_smooth  = 4.0 | ||||
|  | ||||
| 		engine_refresh_hz = 0 | ||||
|  | ||||
| 		timing_fps_moving_avg_alpha = 0.9 | ||||
|  | ||||
| 		ui_resize_border_width = 5 | ||||
|  | ||||
| 		color_theme = App_Thm_Dusk | ||||
| 	} | ||||
|  | ||||
| 	Desired_OS_Scheduler_MS :: 1 | ||||
| 	sleep_is_granular = set__scheduler_granularity( Desired_OS_Scheduler_MS ) | ||||
|  | ||||
| 	// Rough setup of window with rl stuff | ||||
| 	{ | ||||
| 		// rl.Odin_SetMalloc( RL_MALLOC ) | ||||
|  | ||||
| 		rl.SetConfigFlags( { | ||||
| 			rl.ConfigFlag.WINDOW_RESIZABLE, | ||||
| 			rl.ConfigFlag.WINDOW_TOPMOST, | ||||
| 		}) | ||||
|  | ||||
| 		window_width  : i32 = cast(i32) config.resolution_width | ||||
| 		window_height : i32 = cast(i32) config.resolution_height | ||||
| 		win_title     : cstring = "Sectr Prototype" | ||||
| 		rl.InitWindow( window_width, window_height, win_title ) | ||||
| 		log( "Raylib initialized and window opened" ) | ||||
|  | ||||
| 		window := & state.app_window | ||||
| 		window.extent.x = f32(window_width)  * 0.5 | ||||
| 		window.extent.y = f32(window_height) * 0.5 | ||||
|  | ||||
| 		// We do not support non-uniform DPI. | ||||
| 		window.dpi_scale = rl.GetWindowScaleDPI().x | ||||
| 		window.ppcm      = os_default_ppcm * window.dpi_scale | ||||
|  | ||||
| 		// Determining current monitor and setting the target frametime based on it.. | ||||
| 		monitor_id         = rl.GetCurrentMonitor() | ||||
| 		monitor_refresh_hz = rl.GetMonitorRefreshRate( monitor_id ) | ||||
|  | ||||
| 		if config.engine_refresh_hz == 0 { | ||||
| 			config.engine_refresh_hz = uint(monitor_refresh_hz) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Basic Font Setup | ||||
| 	{ | ||||
| 		font_provider_startup() | ||||
| 		// path_rec_mono_semicasual_reg := strings.concatenate( { Path_Assets, "RecMonoSemicasual-Regular-1.084.ttf" }) | ||||
| 		// font_rec_mono_semicasual_reg  = font_load( path_rec_mono_semicasual_reg, 24.0, "RecMonoSemiCasual_Regular" ) | ||||
|  | ||||
| 		// path_squidgy_slimes := strings.concatenate( { Path_Assets, "Squidgy Slimes.ttf" } ) | ||||
| 		// font_squidgy_slimes = font_load( path_squidgy_slimes, 24.0, "Squidgy_Slime" ) | ||||
|  | ||||
| 		path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" }, transient_allocator() ) | ||||
| 		font_firacode  = font_load( path_firacode, 24.0, "FiraCode" ) | ||||
| 		default_font = font_firacode | ||||
| 		log( "Default font loaded" ) | ||||
| 	} | ||||
|  | ||||
| 	// Setup the screen ui state | ||||
| 	{ | ||||
| 		ui_startup( & screen_ui.base, cache_allocator = persistent_slab_allocator() ) | ||||
| 		ui_floating_startup( & screen_ui.floating, persistent_slab_allocator(), 1 * Kilobyte, 1 * Kilobyte, "screen ui floating manager" ) | ||||
|  | ||||
| 		using screen_ui | ||||
| 		menu_bar.pos  = { -60, 0 } | ||||
| 		// menu_bar.pos  = Vec2(app_window.extent) * { -1, 1 } | ||||
| 		menu_bar.size = {140, 40} | ||||
|  | ||||
| 		settings_menu.min_size = {250, 200} | ||||
| 	} | ||||
|  | ||||
| 	// Demo project setup | ||||
| 	// TODO(Ed): This will eventually have to occur when the user either creates or loads a workspace. I don't know  | ||||
| 	{ | ||||
| 		using project | ||||
| 		path           = str_intern("./") | ||||
| 		name           = str_intern( "First Project" ) | ||||
| 		workspace.name = str_intern( "First Workspace" ) | ||||
| 		{ | ||||
| 			using project.workspace | ||||
| 			cam = { | ||||
| 				target   = { 0, 0 }, | ||||
| 				offset   = transmute(Vec2) app_window.extent, | ||||
| 				rotation = 0, | ||||
| 				zoom     = 1.0, | ||||
| 			} | ||||
| 			// cam = { | ||||
| 			// 	position   = { 0, 0, -100 }, | ||||
| 			// 	target     = { 0, 0, 0 }, | ||||
| 			// 	up         = { 0, 1, 0 }, | ||||
| 			// 	fovy       = 90, | ||||
| 			// 	projection = rl.CameraProjection.ORTHOGRAPHIC, | ||||
| 			// } | ||||
|  | ||||
| 			// Setup workspace UI state | ||||
| 			ui_startup( & workspace.ui, cache_allocator =  persistent_slab_allocator() ) | ||||
| 		} | ||||
|  | ||||
| 		debug.path_lorem = str_fmt("C:/projects/SectrPrototype/examples/Lorem Ipsum.txt", allocator = persistent_slab_allocator()) | ||||
|  | ||||
| 		alloc_error : AllocatorError; success : bool | ||||
| 		debug.lorem_content, success = os.read_entire_file( debug.path_lorem, persistent_slab_allocator() ) | ||||
|  | ||||
| 		debug.lorem_parse, alloc_error = pws_parser_parse( transmute(string) 	debug.lorem_content, persistent_slab_allocator() ) | ||||
| 		verify( alloc_error == .None, "Faield to parse due to allocation failure" ) | ||||
|  | ||||
| 		// Render texture test | ||||
| 		// debug.viewport_rt = rl.LoadRenderTexture( 1280, 720 ) | ||||
|  | ||||
| 		// debug.proto_text_shader = rl.LoadShader( "C:/projects/SectrPrototype/code/shaders/text_shader.vs", "C:/projects/SectrPrototype/code/shaders/text_shader.fs" ) | ||||
| 	} | ||||
|  | ||||
| 	startup_ms := duration_ms( time.tick_lap_time( & startup_tick)) | ||||
| 	log( str_fmt_tmp("Startup time: %v ms", startup_ms) ) | ||||
|  | ||||
| 	// Make sure to cleanup transient before continuing... | ||||
| 	// From here on, tarnsinet usage has to be done with care. | ||||
| 	// For most cases, the frame allocator should be more than enough. | ||||
| } | ||||
|  | ||||
| // For some reason odin's symbols conflict with native foreign symbols... | ||||
| @export | ||||
| sectr_shutdown :: proc() | ||||
| { | ||||
| 	context.logger = to_odin_logger( & Memory_App.logger ) | ||||
|  | ||||
| 	if Memory_App.persistent == nil { | ||||
| 		return | ||||
| 	} | ||||
| 	state := get_state() | ||||
|  | ||||
| 	// Replay | ||||
| 	{ | ||||
| 		file_close( Memory_App.replay.active_file ) | ||||
| 	} | ||||
|  | ||||
| 	font_provider_shutdown() | ||||
|  | ||||
| 	log("Module shutdown complete") | ||||
| } | ||||
|  | ||||
| @export | ||||
| reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger ) | ||||
| { | ||||
| 	spall.SCOPED_EVENT( & prof.ctx, & prof.buffer, #procedure ) | ||||
| 	Memory_App.profiler = prof | ||||
|  | ||||
| 	context.logger = to_odin_logger( & Memory_App.logger ) | ||||
| 	using Memory_App; | ||||
|  | ||||
| 	persistent   = persistent_mem | ||||
| 	frame        = frame_mem | ||||
| 	transient    = transient_mem | ||||
| 	files_buffer = files_buffer_mem | ||||
|  | ||||
| 	context.allocator      = persistent_allocator() | ||||
| 	context.temp_allocator = transient_allocator() | ||||
|  | ||||
| 	Memory_App.state = get_state() | ||||
| 	using state | ||||
|  | ||||
| 	// Procedure Addresses are not preserved on hot-reload. They must be restored for persistent data. | ||||
| 	// The only way to alleviate this is to either do custom handles to allocators | ||||
| 	// Or as done below, correct containers using allocators on reload. | ||||
| 	// Thankfully persistent dynamic allocations are rare, and thus we know exactly which ones they are. | ||||
|  | ||||
| 	slab_reload( persistent_slab, persistent_allocator() ) | ||||
|  | ||||
| 	hmap_chained_reload( font_provider_data.font_cache, persistent_allocator()) | ||||
|  | ||||
| 	slab_reload( string_cache.slab, persistent_allocator() ) | ||||
| 	zpl_hmap_reload( & string_cache.table, persistent_slab_allocator()) | ||||
|  | ||||
| 	slab_reload( frame_slab, frame_allocator()) | ||||
| 	slab_reload( transient_slab, transient_allocator()) | ||||
|  | ||||
| 	ui_reload( & get_state().project.workspace.ui, cache_allocator =  persistent_slab_allocator() ) | ||||
|  | ||||
| 	log("Module reloaded") | ||||
| } | ||||
|  | ||||
| @export | ||||
| tick :: proc( host_delta_time : f64, host_delta_ns : Duration ) -> b32 | ||||
| { | ||||
| 	profile( "Client Tick" ) | ||||
| 	context.logger = to_odin_logger( & Memory_App.logger ) | ||||
| 	state := get_state(); using state | ||||
|  | ||||
| 	should_close : b32 | ||||
|  | ||||
| 	client_tick := time.tick_now() | ||||
| 	{ | ||||
| 		profile("Work frame") | ||||
|  | ||||
| 		// Setup Frame Slab | ||||
| 		{ | ||||
| 			alloc_error : AllocatorError | ||||
| 			frame_slab, alloc_error = slab_init( & default_slab_policy, bucket_reserve_num = 0, | ||||
| 				allocator           = frame_allocator(), | ||||
| 				dbg_name            = Frame_Slab_DBG_Name, | ||||
| 				should_zero_buckets = true ) | ||||
| 			verify( alloc_error == .None, "Failed to allocate frame slab" ) | ||||
| 		} | ||||
|  | ||||
| 		context.allocator      = frame_slab_allocator() | ||||
| 		context.temp_allocator = transient_allocator() | ||||
|  | ||||
| 		rl.PollInputEvents() | ||||
|  | ||||
| 		debug.draw_ui_box_bounds_points = false | ||||
| 		debug.draw_UI_padding_bounds = false | ||||
| 		debug.draw_ui_content_bounds = false | ||||
|  | ||||
| 		// config.color_theme = App_Thm_Light | ||||
| 		// config.color_theme = App_Thm_Dusk | ||||
| 		config.color_theme = App_Thm_Dark | ||||
|  | ||||
| 		should_close = update( host_delta_time ) | ||||
| 		render() | ||||
|  | ||||
| 		rl.SwapScreenBuffer() | ||||
| 	} | ||||
|  | ||||
| 	// Timing | ||||
| 	{ | ||||
| 		// profile("Client tick timing processing") | ||||
| 		// config.engine_refresh_hz = uint(monitor_refresh_hz) | ||||
| 		// config.engine_refresh_hz = 6 | ||||
| 		frametime_target_ms          = 1.0 / f64(config.engine_refresh_hz) * S_To_MS | ||||
| 		sub_ms_granularity_required := frametime_target_ms <= Frametime_High_Perf_Threshold_MS | ||||
|  | ||||
| 		frametime_delta_ns      = time.tick_lap_time( & client_tick ) | ||||
| 		frametime_delta_ms      = duration_ms( frametime_delta_ns ) | ||||
| 		frametime_delta_seconds = duration_seconds( frametime_delta_ns ) | ||||
| 		frametime_elapsed_ms    = frametime_delta_ms + host_delta_time | ||||
|  | ||||
| 		if frametime_elapsed_ms < frametime_target_ms | ||||
| 		{ | ||||
| 			sleep_ms       := frametime_target_ms - frametime_elapsed_ms | ||||
| 			pre_sleep_tick := time.tick_now() | ||||
|  | ||||
| 			if sleep_ms > 0 { | ||||
| 				thread_sleep( cast(Duration) sleep_ms * MS_To_NS ) | ||||
| 				// thread__highres_wait( sleep_ms ) | ||||
| 			} | ||||
|  | ||||
| 			sleep_delta_ns := time.tick_lap_time( & pre_sleep_tick) | ||||
| 			sleep_delta_ms := duration_ms( sleep_delta_ns ) | ||||
|  | ||||
| 			if sleep_delta_ms < sleep_ms { | ||||
| 				// log( str_fmt_tmp("frametime sleep was off by: %v ms", sleep_delta_ms - sleep_ms )) | ||||
| 			} | ||||
|  | ||||
| 			frametime_elapsed_ms += sleep_delta_ms | ||||
| 			for ; frametime_elapsed_ms < frametime_target_ms; { | ||||
| 				sleep_delta_ns = time.tick_lap_time( & pre_sleep_tick) | ||||
| 				sleep_delta_ms = duration_ms( sleep_delta_ns ) | ||||
|  | ||||
| 				frametime_elapsed_ms += sleep_delta_ms | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		config.timing_fps_moving_avg_alpha = 0.99 | ||||
| 		frametime_avg_ms = mov_avg_exp( f64(config.timing_fps_moving_avg_alpha), frametime_elapsed_ms, frametime_avg_ms ) | ||||
| 		fps_avg          = 1 / (frametime_avg_ms * MS_To_S) | ||||
|  | ||||
| 		if frametime_elapsed_ms > 60.0 { | ||||
| 			context.allocator = transient_allocator() | ||||
| 			log( str_fmt("Big tick! %v ms", frametime_elapsed_ms), LogLevel.Warning ) | ||||
| 		} | ||||
| 	} | ||||
| 	return should_close | ||||
| } | ||||
|  | ||||
| @export | ||||
| clean_frame :: proc() | ||||
| { | ||||
| 	// profile( #procedure) | ||||
| 	state := get_state(); using state | ||||
| 	context.logger = to_odin_logger( & Memory_App.logger ) | ||||
|  | ||||
| 	free_all( frame_allocator() ) | ||||
|  | ||||
| 	transient_clear_elapsed += frametime_delta32() | ||||
| 	if transient_clear_elapsed >= transient_clear_time && ! transinet_clear_lock | ||||
| 	{ | ||||
| 		transient_clear_elapsed = 0 | ||||
| 		free_all( transient_allocator() ) | ||||
|  | ||||
| 		alloc_error : AllocatorError | ||||
| 		transient_slab, alloc_error = slab_init( & default_slab_policy, allocator = transient_allocator(), dbg_name = Transient_Slab_DBG_Name ) | ||||
| 		verify( alloc_error == .None, "Failed to allocate transient slab" ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| } // when false | ||||
| @@ -26,8 +26,9 @@ sokol_app_frame_callback :: proc "c" () { | ||||
|  | ||||
| 	window := & state.app_window | ||||
| 	if	int(window.extent.x) != int(sokol_width) || int(window.extent.y) != int(sokol_height) { | ||||
| 		window.extent.x = sokol_width | ||||
| 		window.extent.y = sokol_height | ||||
| 		window.resized = true | ||||
| 		window.extent.x = sokol_width  * 0.5 | ||||
| 		window.extent.y = sokol_height * 0.5 | ||||
| 		log("sokol_app: Event-based frame callback triggered (detected a resize") | ||||
| 	} | ||||
|  | ||||
| @@ -38,6 +39,8 @@ sokol_app_frame_callback :: proc "c" () { | ||||
| 	client_tick := time.tick_now() | ||||
| 	should_close |= tick_work_frame( sokol_delta_ms ) | ||||
| 	tick_frametime( & client_tick, sokol_delta_ms, sokol_delta_ns ) | ||||
|  | ||||
| 	window.resized = false | ||||
| } | ||||
|  | ||||
| sokol_app_cleanup_callback :: proc "c" () { | ||||
|   | ||||
| @@ -1,403 +0,0 @@ | ||||
| package sectr | ||||
|  | ||||
| import "core:fmt" | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| when false { | ||||
| range2_to_rl_rect :: #force_inline proc "contextless"( range : Range2 ) -> rl.Rectangle | ||||
| { | ||||
| 	rect := rl.Rectangle { | ||||
| 		range.min.x, | ||||
| 		range.max.y, | ||||
| 		abs(range.max.x - range.min.x), | ||||
| 		abs(range.max.y - range.min.y), | ||||
| 	} | ||||
| 	return rect | ||||
| } | ||||
|  | ||||
| draw_rectangle :: #force_inline proc "contextless" ( rect : rl.Rectangle, box : ^UI_RenderBoxInfo ) { | ||||
| 	using box | ||||
| 	if style.corner_radii[0] > 0 { | ||||
| 		rl.DrawRectangleRounded( rect, style.corner_radii[0], 9, style.bg_color ) | ||||
| 	} | ||||
| 	else { | ||||
| 		rl.DrawRectangleRec( rect, style.bg_color ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| draw_rectangle_lines :: #force_inline proc "contextless" ( rect : rl.Rectangle, box : ^UI_RenderBoxInfo, color : Color, thickness : f32 ) { | ||||
| 	using box | ||||
| 	if style.corner_radii[0] > 0 { | ||||
| 		rl.DrawRectangleRoundedLines( rect, style.corner_radii[0], 9, thickness, color ) | ||||
| 	} | ||||
| 	else { | ||||
| 		rl.DrawRectangleLinesEx( rect, thickness, color ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| render :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	state  := get_state(); using state | ||||
|  | ||||
| 	render_mode_3d() | ||||
|  | ||||
| 	rl.BeginDrawing() | ||||
| 	rl.ClearBackground( app_color_theme().bg ) | ||||
|  | ||||
| 	render_mode_2d_workspace() | ||||
| 	render_mode_screenspace() | ||||
|  | ||||
| 	rl.EndDrawing() | ||||
| } | ||||
|  | ||||
| // Experimental 3d viewport, not really the focus of this prototype | ||||
| // Until we can have a native or interpreted program render to it its not very useful. | ||||
| // Note(Ed): Other usecase could be 3d vis notes & math/graphical debug | ||||
| render_mode_3d :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
|  | ||||
| 	state := get_state(); using state | ||||
|  | ||||
| 	rl.BeginDrawing() | ||||
| 	rl.BeginTextureMode( debug.viewport_rt ) | ||||
| 	rl.BeginMode3D( debug.cam_vp ) | ||||
| 	rl.ClearBackground( Color_3D_BG ) | ||||
|  | ||||
| 	rl.EndMode3D() | ||||
| 	rl.EndTextureMode() | ||||
| 	rl.EndDrawing() | ||||
| } | ||||
|  | ||||
| // TODO(Ed): Eventually this needs to become a 'viewport within a UI' | ||||
| // This would allow the user to have more than one workspace open at the same time | ||||
| render_mode_2d_workspace :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	state  := get_state(); using state | ||||
| 	cam    := & project.workspace.cam | ||||
|  | ||||
| 	win_extent := state.app_window.extent | ||||
|  | ||||
| 	rl.BeginMode2D( project.workspace.cam ) | ||||
|  | ||||
| 	// Draw 3D Viewport | ||||
| 	when false | ||||
| 	{ | ||||
| 		viewport_size := Vec2 { 1280.0, 720.0 } | ||||
| 		vp_half_size := viewport_size * 0.5 | ||||
| 		viewport_box := range2( -vp_half_size, vp_half_size ) | ||||
| 		viewport_render := range2( | ||||
| 			ws_view_to_render_pos( viewport_box.min), | ||||
| 			ws_view_to_render_pos( viewport_box.max), | ||||
| 		) | ||||
| 		viewport_rect := range2_to_rl_rect( viewport_render ) | ||||
| 		rl.DrawTextureRec( debug.viewport_rt.texture, viewport_rect, -vp_half_size, Color_White ) | ||||
| 	} | ||||
|  | ||||
| 	// draw_text( "This is text in world space", { 0, 200 }, 16.0  ) | ||||
|  | ||||
| 	cam_zoom_ratio := 1.0 / cam.zoom | ||||
|  | ||||
| 	view_bounds := view_get_bounds() | ||||
| 	when false | ||||
| 	{ | ||||
| 		render_view := Range2 { pts = { | ||||
| 			ws_view_to_render_pos( view_bounds.min), | ||||
| 			ws_view_to_render_pos( view_bounds.max), | ||||
| 		}} | ||||
| 		view_rect := rl.Rectangle { | ||||
| 			render_view.min.x, | ||||
| 			render_view.max.y, | ||||
| 			abs(render_view.max.x - render_view.min.x), | ||||
| 			abs(render_view.max.y - render_view.min.y), | ||||
| 		} | ||||
| 		rl.DrawRectangleRounded( view_rect, 0.3, 9, { 255, 0, 0, 20 } ) | ||||
| 	} | ||||
|  | ||||
| 	ImguiRender: | ||||
| 	{ | ||||
| 		profile("Imgui Render") | ||||
| 		ui   := & state.project.workspace.ui | ||||
| 		root := ui.root | ||||
| 		if root == nil || root.num_children == 0 { | ||||
| 			break ImguiRender | ||||
| 		} | ||||
| 		state.ui_context = ui | ||||
|  | ||||
| 		current := root.first | ||||
| 		for & current in array_to_slice(ui.render_queue) | ||||
| 		{ | ||||
| 			profile("Box") | ||||
| 			style    := current.style | ||||
| 			computed := current.computed | ||||
|  | ||||
| 			if ! intersects_range2( view_bounds, computed.bounds ) { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			profile_begin("render space calc") | ||||
| 			render_bounds := range2( | ||||
| 				ws_view_to_render_pos(computed.bounds.min), | ||||
| 				ws_view_to_render_pos(computed.bounds.max), | ||||
| 			) | ||||
| 			render_padding := range2( | ||||
| 				ws_view_to_render_pos(computed.padding.min), | ||||
| 				ws_view_to_render_pos(computed.padding.max), | ||||
| 			) | ||||
| 			render_content := range2( | ||||
| 				ws_view_to_render_pos(computed.content.min), | ||||
| 				ws_view_to_render_pos(computed.content.max), | ||||
| 			) | ||||
|  | ||||
| 			rect_bounds  := range2_to_rl_rect( render_bounds ) | ||||
| 			rect_padding := range2_to_rl_rect( render_padding ) | ||||
| 			rect_content := range2_to_rl_rect( render_content ) | ||||
| 			profile_end() | ||||
|  | ||||
| 			profile_begin("raylib drawing") | ||||
| 			if style.bg_color.a != 0 | ||||
| 			{ | ||||
| 				draw_rectangle( rect_bounds, & current ) | ||||
| 			} | ||||
| 			if current.border_width > 0 { | ||||
| 				draw_rectangle_lines( rect_bounds, & current, style.border_color, current.border_width ) | ||||
| 			} | ||||
|  | ||||
| 			line_thickness := 1 * cam_zoom_ratio | ||||
| 			if debug.draw_UI_padding_bounds && equal_range2(computed.content, computed.padding) { | ||||
| 				draw_rectangle_lines( rect_padding, & current, Color_Debug_UI_Padding_Bounds, line_thickness ) | ||||
| 			} | ||||
| 			else if debug.draw_ui_content_bounds { | ||||
| 				draw_rectangle_lines( rect_content, & current, Color_Debug_UI_Content_Bounds, line_thickness ) | ||||
| 			} | ||||
|  | ||||
| 			point_radius := 3 * cam_zoom_ratio | ||||
|  | ||||
| 			// profile_begin("circles") | ||||
| 			if debug.draw_ui_box_bounds_points | ||||
| 			{ | ||||
| 				computed_size := computed.bounds.p1 - computed.bounds.p0 | ||||
| 				// center := Vec2 { | ||||
| 				// 	render_bounds.p0.x + computed_size.x * 0.5, | ||||
| 				// 	render_bounds.p0.y - computed_size.y * 0.5, | ||||
| 				// } | ||||
| 				// rl.DrawCircleV( center, point_radius, Color_White ) | ||||
|  | ||||
| 				rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red ) | ||||
| 				rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) | ||||
| 			} | ||||
| 			// profile_end() | ||||
|  | ||||
| 			profile_end() | ||||
|  | ||||
| 			if len(current.text.str) > 0 { | ||||
| 				ws_view_draw_text( current.text, ws_view_to_render_pos(computed.text_pos * {1, -1}), current.font_size, style.text_color ) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	//endregion Imgui Render | ||||
|  | ||||
|  | ||||
| 	if debug.mouse_vis { | ||||
| 		cursor_world_pos := screen_to_ws_view_pos(input.mouse.pos) | ||||
| 		rl.DrawCircleV( ws_view_to_render_pos(cursor_world_pos), 5, Color_GreyRed ) | ||||
| 	} | ||||
|  | ||||
| 	rl.DrawCircleV( { 0, 0 }, 1 * cam_zoom_ratio, Color_White ) | ||||
|  | ||||
| 	rl.EndMode2D() | ||||
| } | ||||
|  | ||||
| render_mode_screenspace :: proc () | ||||
| { | ||||
| 	profile("Render Screenspace") | ||||
|  | ||||
| 	state := get_state(); using state | ||||
| 	replay := & Memory_App.replay | ||||
| 	cam    := & project.workspace.cam | ||||
| 	win_extent := state.app_window.extent | ||||
|  | ||||
| 	render_screen_ui() | ||||
|  | ||||
| 	debug_text :: proc( format : string, args : ..any ) | ||||
| 	{ | ||||
| 		@static draw_text_scratch : [Kilobyte * 64]u8 | ||||
|  | ||||
| 		state := get_state(); using state | ||||
| 		if debug.draw_debug_text_y > 800 { | ||||
| 			debug.draw_debug_text_y = 0 | ||||
| 		} | ||||
|  | ||||
| 		cam            := & project.workspace.cam | ||||
| 		screen_corners := screen_get_corners() | ||||
|  | ||||
| 		position   := screen_corners.top_right | ||||
| 		position.x -= app_window.extent.x | ||||
| 		position.y -= debug.draw_debug_text_y | ||||
|  | ||||
| 		content := str_fmt_buffer( draw_text_scratch[:], format, ..args ) | ||||
| 		debug_draw_text( content, position, 12.0 ) | ||||
|  | ||||
| 		debug.draw_debug_text_y += 14 | ||||
| 	} | ||||
|  | ||||
| 	if debug.debug_text_vis | ||||
| 	{ | ||||
| 		fps_msg       := str_fmt( "FPS: %f", fps_avg) | ||||
| 		fps_msg_width := measure_text_size( fps_msg, default_font, 12.0, 0.0 ).x | ||||
| 		fps_msg_pos   := screen_get_corners().top_right - { fps_msg_width, 0 } - { 5, 5 } | ||||
| 		debug_draw_text( fps_msg, fps_msg_pos, 12.0, color = rl.GREEN ) | ||||
|  | ||||
| 		// debug_text( "Screen Width : %v", rl.GetScreenWidth () ) | ||||
| 		// debug_text( "Screen Height: %v", rl.GetScreenHeight() ) | ||||
| 		// debug_text( "frametime_target_ms       : %f ms", frametime_target_ms ) | ||||
| 		debug_text( "frametime                 : %f ms", frametime_delta_ms ) | ||||
| 		// debug_text( "frametime_last_elapsed_ms : %f ms", frametime_elapsed_ms ) | ||||
| 		if replay.mode == ReplayMode.Record { | ||||
| 			debug_text( "Recording Input") | ||||
| 		} | ||||
| 		if replay.mode == ReplayMode.Playback { | ||||
| 			debug_text( "Replaying Input") | ||||
| 		} | ||||
| 		// debug_text("Zoom Target: %v", project.workspace.zoom_target) | ||||
|  | ||||
| 		if debug.mouse_vis { | ||||
| 			debug_text("Mouse Vertical Wheel: %v", input.mouse.vertical_wheel ) | ||||
| 			debug_text("Mouse Delta                    : %v", input.mouse.delta ) | ||||
| 			debug_text("Mouse Position (Render)        : %v", input.mouse.raw_pos ) | ||||
| 			debug_text("Mouse Position (Screen)        : %v", input.mouse.pos ) | ||||
| 			debug_text("Mouse Position (Workspace View): %v", screen_to_ws_view_pos(input.mouse.pos) ) | ||||
| 			rl.DrawCircleV( input.mouse.raw_pos,                    10, Color_White_A125 ) | ||||
| 			rl.DrawCircleV( screen_to_render_pos(input.mouse.pos),  2, Color_BG ) | ||||
| 		} | ||||
|  | ||||
| 		ui := & project.workspace.ui | ||||
|  | ||||
| 		if true | ||||
| 		{ | ||||
| 			debug_text("Box Count (Workspace): %v", ui.built_box_count ) | ||||
|  | ||||
| 			hot_box    := ui_box_from_key( ui.curr_cache, ui.hot ) | ||||
| 			active_box := ui_box_from_key( ui.curr_cache, ui.active ) | ||||
| 			if hot_box != nil { | ||||
| 				debug_text("Worksapce Hot    Box   : %v", hot_box.label.str ) | ||||
| 				debug_text("Workspace Hot    Range2: %v", hot_box.computed.bounds.pts) | ||||
| 			} | ||||
| 			if active_box != nil{ | ||||
| 				debug_text("Workspace Active Box: %v", active_box.label.str ) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		ui = & screen_ui | ||||
|  | ||||
| 		if true | ||||
| 		{ | ||||
| 			debug_text("Box Count: %v", ui.built_box_count ) | ||||
|  | ||||
| 			hot_box    := ui_box_from_key( ui.curr_cache, ui.hot ) | ||||
| 			active_box := ui_box_from_key( ui.curr_cache, ui.active ) | ||||
| 			if hot_box != nil { | ||||
| 				debug_text("Hot    Box   : %v", hot_box.label.str ) | ||||
| 				debug_text("Hot    Range2: %v", hot_box.computed.bounds.pts) | ||||
| 			} | ||||
| 			if active_box != nil{ | ||||
| 				debug_text("Active Box: %v", active_box.label.str ) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		debug.draw_debug_text_y = 14 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // A non-zoomable static-view for ui | ||||
| // Only a scalar factor may be applied to the size of widgets & fonts | ||||
| // 'Window tiled' panels reside here | ||||
| render_screen_ui :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
|  | ||||
| 	using state := get_state() | ||||
|  | ||||
| 	//region App UI | ||||
| 	Render_App_UI: | ||||
| 	{ | ||||
| 		profile("App UI") | ||||
| 		ui := & state.screen_ui | ||||
| 		state.ui_context = ui | ||||
| 		root := ui.root | ||||
| 		if root.num_children == 0 { | ||||
| 			break Render_App_UI | ||||
| 		} | ||||
|  | ||||
| 		for & current in array_to_slice(ui.render_queue) | ||||
| 		{ | ||||
| 			profile("Box") | ||||
|  | ||||
| 			style    := current.style | ||||
| 			computed := & current.computed | ||||
|  | ||||
| 			profile_begin("Coordinate space conversion") | ||||
| 			render_bounds := range2( | ||||
| 				screen_to_render_pos(computed.bounds.min), | ||||
| 				screen_to_render_pos(computed.bounds.max), | ||||
| 			) | ||||
| 			render_padding := range2( | ||||
| 				screen_to_render_pos(computed.padding.min), | ||||
| 				screen_to_render_pos(computed.padding.max), | ||||
| 			) | ||||
| 			render_content := range2( | ||||
| 				screen_to_render_pos(computed.content.min), | ||||
| 				screen_to_render_pos(computed.content.max), | ||||
| 			) | ||||
| 			rect_bounds  := range2_to_rl_rect( render_bounds ) | ||||
| 			rect_padding := range2_to_rl_rect( render_padding ) | ||||
| 			rect_content := range2_to_rl_rect( render_content ) | ||||
| 			profile_end() | ||||
|  | ||||
| 			profile_begin("raylib drawing") | ||||
| 			if style.bg_color.a != 0 | ||||
| 			{ | ||||
| 				draw_rectangle( rect_bounds, & current ) | ||||
| 			} | ||||
| 			if current.border_width > 0 { | ||||
| 				draw_rectangle_lines( rect_bounds, & current, style.border_color, current.border_width ) | ||||
| 			} | ||||
|  | ||||
| 			line_thickness : f32 = 1 | ||||
|  | ||||
| 			if debug.draw_UI_padding_bounds && equal_range2(computed.content, computed.padding) { | ||||
| 				draw_rectangle_lines( rect_padding, & current, Color_Debug_UI_Padding_Bounds, line_thickness ) | ||||
| 			} | ||||
| 			else if debug.draw_ui_content_bounds { | ||||
| 				draw_rectangle_lines( rect_content, & current, Color_Debug_UI_Content_Bounds, line_thickness ) | ||||
| 			} | ||||
|  | ||||
| 			point_radius : f32 = 3 | ||||
|  | ||||
| 			if debug.draw_ui_box_bounds_points | ||||
| 			{ | ||||
| 				computed_size := computed.bounds.p1 - computed.bounds.p0 | ||||
| 				if false | ||||
| 				{ | ||||
| 					center := Vec2 { | ||||
| 						render_bounds.p0.x + computed_size.x * 0.5, | ||||
| 						render_bounds.p0.y - computed_size.y * 0.5, | ||||
| 					} | ||||
| 					rl.DrawCircleV( center, point_radius, Color_White ) | ||||
| 				} | ||||
| 				rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red ) | ||||
| 				rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) | ||||
| 			} | ||||
|  | ||||
| 			if len(current.text.str) > 0 && style.font.key != 0 { | ||||
| 				draw_text_screenspace( current.text, screen_to_render_pos(computed.text_pos), current.font_size, style.text_color ) | ||||
| 			} | ||||
| 			profile_end() | ||||
| 		} | ||||
| 	} | ||||
| 	//endregion App UI | ||||
| } | ||||
| } // when false | ||||
| @@ -22,7 +22,7 @@ render :: proc() | ||||
| 	} | ||||
|  | ||||
| 	// Triangle Demo | ||||
| 	if true | ||||
| 	if false | ||||
| 	{ | ||||
| 		using debug.gfx_tri_demo_state | ||||
| 		sokol_gfx.begin_pass(sokol_gfx.Pass { action = pass_action, swapchain = sokol_glue.swapchain() }) | ||||
| @@ -34,4 +34,14 @@ render :: proc() | ||||
| 		sokol_gfx.end_pass() | ||||
| 		sokol_gfx.commit() | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	// Batching Enqueue Boxes | ||||
| 	// Mixed with the batching enqueue for text | ||||
|  | ||||
|  | ||||
| 	//Begin | ||||
| 	// Flush boxs | ||||
| 	// flush text | ||||
| 	// End | ||||
| } | ||||
|   | ||||
| @@ -1,226 +0,0 @@ | ||||
| package sectr | ||||
|  | ||||
| import "core:math" | ||||
| import "core:strings" | ||||
| import "core:unicode/utf8" | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| when false { | ||||
| debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	state := get_state(); using state | ||||
|  | ||||
| 	if len( content ) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	runes, alloc_error := to_runes( content, frame_allocator() ) | ||||
| 	// runes, alloc_error := to_runes( content, context.temp_allocator ) | ||||
| 	// verify( alloc_error == AllocatorError.None, "Failed to temp allocate runes" ) | ||||
|  | ||||
| 	font := font | ||||
| 	if font.key == Font_Default.key { | ||||
| 	// if ( len(font) == 0 ) { | ||||
| 		font = default_font | ||||
| 	} | ||||
| 	pos := screen_to_render_pos(pos) | ||||
|  | ||||
| 	px_size := size | ||||
|  | ||||
| 	rl_font := to_rl_Font(font, px_size ) | ||||
| 	rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) | ||||
| 	rl.DrawTextCodepoints( rl_font, | ||||
| 		raw_data(runes), cast(i32) len(runes), | ||||
| 		position = transmute(rl.Vector2) pos, | ||||
| 		fontSize = px_size, | ||||
| 		spacing  = 0.0, | ||||
| 		tint     = color ); | ||||
| 	rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) | ||||
| } | ||||
|  | ||||
| draw_text_screenspace :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	state := get_state(); using state | ||||
|  | ||||
| 	if len( content.str ) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	font := font | ||||
| 	if  font.key == Font_Default.key { | ||||
| 		font = default_font | ||||
| 	} | ||||
| 	pos := pos | ||||
|  | ||||
| 	rl_font := to_rl_Font(font, size ) | ||||
| 	runes   := content.runes | ||||
|  | ||||
| 	rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) | ||||
| 	rl.DrawTextCodepoints( rl_font, | ||||
| 		raw_data(runes), cast(i32) len(runes), | ||||
| 		position = transmute(rl.Vector2) pos, | ||||
| 		fontSize = size, | ||||
| 		spacing  = 0.0, | ||||
| 		tint     = color ); | ||||
| 	rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) | ||||
| } | ||||
|  | ||||
| ws_view_draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	state := get_state(); using state | ||||
|  | ||||
| 	if len( content ) == 0 { | ||||
| 		return | ||||
| 	} | ||||
| 	runes, alloc_error := to_runes( content, frame_allocator() ) | ||||
| 	verify( alloc_error == AllocatorError.None, "Failed to temp allocate runes" ) | ||||
|  | ||||
| 	font := font | ||||
| 	if  font.key == Font_Default.key { | ||||
| 	// if len(font) == 0 { | ||||
| 		font = default_font | ||||
| 	} | ||||
| 	pos := ws_view_to_render_pos(pos) | ||||
|  | ||||
| 	px_size     := size | ||||
| 	zoom_adjust := px_size * project.workspace.cam.zoom | ||||
|  | ||||
| 	rl_font := to_rl_Font(font, zoom_adjust ) | ||||
| 	rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) | ||||
| 	rl.DrawTextCodepoints( rl_font, | ||||
| 		raw_data(runes), cast(i32) len(runes), | ||||
| 		position = transmute(rl.Vector2) pos, | ||||
| 		fontSize = px_size, | ||||
| 		spacing  = 0.0, | ||||
| 		tint     = color ); | ||||
| 	rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) | ||||
| } | ||||
|  | ||||
| when true | ||||
| { | ||||
| 	ws_view_draw_text_StrRunesPair :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) | ||||
| 	{ | ||||
| 		profile(#procedure) | ||||
| 		state := get_state(); using state | ||||
|  | ||||
| 		if len( content.str ) == 0 { | ||||
| 			return | ||||
| 		} | ||||
| 		font := font | ||||
| 		if  font.key == Font_Default.key { | ||||
| 			font = default_font | ||||
| 		} | ||||
| 		pos := ws_view_to_render_pos(pos) | ||||
|  | ||||
| 		px_size     := size | ||||
| 		zoom_adjust := px_size * project.workspace.cam.zoom | ||||
| 		rl_font     := to_rl_Font(font, zoom_adjust ) | ||||
| 		runes       := content.runes | ||||
|  | ||||
| 		profile_begin("raylib draw codepoints related") | ||||
| 		// rl.DrawTextCodepoints( rl_font, | ||||
| 		// 	raw_data(runes), cast(i32) len(runes), | ||||
| 		// 	position = transmute(rl.Vector2) pos, | ||||
| 		// 	fontSize = px_size, | ||||
| 		// 	spacing  = 0.0, | ||||
| 		// 	tint     = color ); | ||||
| 		rl.DrawTextEx(rl_font, | ||||
| 			strings.clone_to_cstring(content.str), | ||||
| 			position = transmute(rl.Vector2) pos, | ||||
| 			fontSize = px_size, | ||||
| 			spacing  = 0.0, | ||||
| 			tint     = color | ||||
| 		) | ||||
| 		profile_end() | ||||
| 	} | ||||
| } | ||||
| else | ||||
| { | ||||
| 	ws_view_draw_text_StrRunesPair :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) | ||||
| 	{ | ||||
|     profile(#procedure) | ||||
|     state := get_state(); using state | ||||
|  | ||||
| 		// We need an alternative way to draw text to the screen (the above is way to expensive) | ||||
| 		// Possibly need to watch handmade hero... | ||||
|  | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Raylib's equivalent doesn't take a length for the string (making it a pain in the ass) | ||||
| // So this is a 1:1 copy except it takes Odin strings | ||||
| measure_text_size_raylib :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 | ||||
| { | ||||
| 	// profile(#procedure) | ||||
| 	px_size := math.round( points_to_pixels( font_size ) ) | ||||
| 	rl_font := to_rl_Font( font, font_size ) | ||||
|  | ||||
| 	// This is a static var within raylib. We don't have getter access to it. | ||||
| 	// Note(Ed) : raylib font size is in pixels so this is also. | ||||
| 	@static text_line_spacing : f32 = 15 | ||||
|  | ||||
| 	text_size : Vec2 | ||||
|  | ||||
| 	if rl_font.texture.id == 0 || len(text) == 0 { | ||||
| 		return text_size | ||||
| 	} | ||||
|  | ||||
| 	temp_byte_counter : i32 = 0 // Used to count longer text line num chars | ||||
| 	byte_counter      : i32 = 0 | ||||
|  | ||||
| 	text_width      : f32 = 0.0 | ||||
| 	temp_text_width : f32 = 0.0 // Used to counter longer text line width | ||||
|  | ||||
| 	text_height := cast(f32) rl_font.baseSize | ||||
| 	scale_factor := px_size / text_height | ||||
|  | ||||
| 	letter : rune | ||||
| 	index  : i32 = 0 | ||||
|  | ||||
| 	for id : i32 = 0; id < i32(len(text)); | ||||
| 	{ | ||||
| 		byte_counter += 1 | ||||
|  | ||||
| 		next : i32 = 0 | ||||
|  | ||||
| 		ctext := cast(cstring) ( & raw_data( text )[id] ) | ||||
| 		letter = rl.GetCodepointNext( ctext, & next ) | ||||
| 		index  = rl.GetGlyphIndex( rl_font, letter ) | ||||
|  | ||||
| 		id += 1 | ||||
|  | ||||
| 		if letter != rune('\n') | ||||
| 		{ | ||||
| 			if rl_font.glyphs[index].advanceX != 0 { | ||||
| 				text_width += f32(rl_font.glyphs[index].advanceX) | ||||
| 			} | ||||
| 			else { | ||||
| 				text_width += rl_font.recs[index].width + f32(rl_font.glyphs[index].offsetX) | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if temp_text_width < text_width { | ||||
| 				temp_text_width = text_width | ||||
| 			} | ||||
| 			byte_counter = 0 | ||||
| 			text_width   = 0 | ||||
|  | ||||
| 			text_height += text_line_spacing | ||||
|  | ||||
| 			if temp_byte_counter < byte_counter { | ||||
| 				temp_byte_counter = byte_counter | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	if temp_text_width < text_width { | ||||
| 		temp_text_width = text_width | ||||
| 	} | ||||
| 	text_size.x = temp_text_width * scale_factor + f32(temp_byte_counter - 1) * spacing | ||||
| 	text_size.y = text_height * scale_factor | ||||
|  | ||||
| 	return text_size | ||||
| } | ||||
| } // when false | ||||
| @@ -6,8 +6,6 @@ import "core:math/linalg" | ||||
| import "core:os" | ||||
| import str "core:strings" | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| DebugActions :: struct { | ||||
| 	load_project   : b32, | ||||
| 	save_project   : b32, | ||||
| @@ -75,12 +73,9 @@ update :: proc( delta_time : f64 ) -> b32 | ||||
| 	workspace := & project.workspace | ||||
| 	cam       := & workspace.cam | ||||
|  | ||||
| 	if rl.IsWindowResized() { | ||||
| 		window := & state.app_window | ||||
| 		window.extent.x = f32(rl.GetScreenWidth())  * 0.5 | ||||
| 		window.extent.y = f32(rl.GetScreenHeight()) * 0.5 | ||||
|  | ||||
| 		project.workspace.cam.offset = transmute(Vec2) window.extent | ||||
| 	window := & state.app_window | ||||
| 	if window.resized { | ||||
| 		project.workspace.cam.view = transmute(Vec2) window.extent | ||||
| 	} | ||||
|  | ||||
| 	state.input, state.input_prev = swap( state.input, state.input_prev ) | ||||
| @@ -189,13 +184,13 @@ update :: proc( delta_time : f64 ) -> b32 | ||||
| 		  - cast(f32) i32(debug_actions.cam_move_up)   + cast(f32) i32(debug_actions.cam_move_down), | ||||
| 		} | ||||
| 		move_velocity *= digital_move_speed * f32(delta_time) | ||||
| 		cam.target    += move_velocity | ||||
| 		cam.position  += move_velocity | ||||
|  | ||||
| 		if debug_actions.cam_mouse_pan | ||||
| 		{ | ||||
| 			if is_within_screenspace(input.mouse.pos) { | ||||
| 				pan_velocity := input.mouse.delta * vec2(1, -1) * ( 1 / cam.zoom ) | ||||
| 				cam.target   -= pan_velocity | ||||
| 				cam.position -= pan_velocity | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| @@ -252,6 +247,7 @@ update :: proc( delta_time : f64 ) -> b32 | ||||
|  | ||||
| 	debug.last_mouse_pos = input.mouse.pos | ||||
|  | ||||
| 	should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose() | ||||
| 	// should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose() | ||||
| 	should_shutdown : b32 = false | ||||
| 	return should_shutdown | ||||
| } | ||||
|   | ||||
| @@ -1,2 +1,2 @@ | ||||
| package sectr | ||||
| 
 | ||||
| package sectr | ||||
							
								
								
									
										353
									
								
								code/sectr/font/fontstash.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										353
									
								
								code/sectr/font/fontstash.odin
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,353 @@ | ||||
| /* | ||||
| Yet another port of fontstash. | ||||
|  | ||||
| I decided t use this instead of the odin port as it already deviated from the original impl. | ||||
| So The code was small enough that I mine as well learn it by porting for my use case. | ||||
|  | ||||
| Original copyright for fonstash.h: | ||||
| ------------------------------------------------------------------------------ | ||||
|  Copyright (c) 2009-2013 Mikko Mononen memon@inside.org | ||||
|  | ||||
|  This software is provided 'as-is', without any express or implied | ||||
|  warranty.  In no event will the authors be held liable for any damages | ||||
|  arising from the use of this software. | ||||
|  Permission is granted to anyone to use this software for any purpose, | ||||
|  including commercial applications, and to alter it and redistribute it | ||||
|  freely, subject to the following restrictions: | ||||
|  1. The origin of this software must not be misrepresented; you must not | ||||
| 	claim that you wrote the original software. If you use this software | ||||
| 	in a product, an acknowledgment in the product documentation would be | ||||
| 	appreciated but is not required. | ||||
|  2. Altered source versions must be plainly marked as such, and must not be | ||||
| 	misrepresented as being the original software. | ||||
|  3. This notice may not be removed or altered from any source distribution. | ||||
| ------------------------------------------------------------------------------ | ||||
| */ | ||||
| package sectr | ||||
|  | ||||
| import stbtt "vendor:stb/truetype" | ||||
|  | ||||
| Range2_i16 :: struct #raw_union { | ||||
| 	using pts : Vec2_i16, | ||||
| 	using xy  : struct { | ||||
| 		x0, y0, x1, y1 : i16, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Vec2_i16 :: [2]i16 | ||||
|  | ||||
| FSTASH_Invalid :: -1 | ||||
|  | ||||
| FSTASH_Hash_Lut_Size    :: 256 | ||||
| FSTASH_Max_Fallbacks    :: 20 | ||||
| FSTASH_Max_States       :: 20 | ||||
| FSTASH_Vertex_Count     :: 1024 | ||||
| FSTASH_Init_Atlas_Nodes :: 256 | ||||
|  | ||||
| FSTASH_FontLuts      :: [FSTASH_Hash_Lut_Size]i32 | ||||
| FSTASH_FontFallbacks :: [FSTASH_Max_Fallbacks]i32 | ||||
|  | ||||
| FSTASH_HandleErrorProc :: #type proc( uptr : rawptr, error, val : i32 ) | ||||
|  | ||||
| FSTASH_RenderCreateProc :: #type proc( uptr : rawptr, width, height : i32 ) | ||||
| FSTASH_RenderResizeProc :: #type proc( uptr : rawptr, width, height : i32 ) | ||||
| FSTASH_RenderUpdateProc :: #type proc( uptr : rawptr, rect : ^i32, data : ^u8 ) | ||||
| FSTASH_RenderDrawProc   :: #type proc( uptr : rawptr, verts : ^f32, tcoords : ^f32, colors : ^i32, num_verts : i32 ) | ||||
| FSTASH_RenderDelete     :: #type proc( uptr : rawptr ) | ||||
|  | ||||
| FSTASH_AlignFlag :: enum u32  { | ||||
| 	Left, | ||||
| 	Center, | ||||
| 	Right, | ||||
| 	Top, | ||||
| 	Middle, | ||||
| 	Bottom, | ||||
| 	Baseline, | ||||
| } | ||||
| FSTASH_AlignFlags :: bit_set[ FSTASH_AlignFlag; u32 ] | ||||
|  | ||||
| // FONSflags | ||||
| FSTASH_QuadLocation :: enum u32 { | ||||
| 	Top_Left    = 1, | ||||
| 	Bottom_Left = 2, | ||||
| } | ||||
|  | ||||
| FSTASH_Atlas :: struct { | ||||
| 	dud : i32, | ||||
| } | ||||
|  | ||||
| FSTASH_AtlasNode :: struct { | ||||
| 	x, y, width : i16, | ||||
| } | ||||
|  | ||||
| FSTASH_ErrorCode :: enum u32 { | ||||
| 	Atlas_Full, | ||||
| 	Scratch_Full, | ||||
| 	States_Overflow, | ||||
| 	States_Underflow, | ||||
| } | ||||
|  | ||||
| FSTASH_Quad :: struct { | ||||
| 	x0, y0, s0, t0 : f32, | ||||
| 	x1, y1, s1, t1 : f32, | ||||
| } | ||||
|  | ||||
| FSTASH_Font :: struct { | ||||
| 	info      : stbtt.fontinfo, | ||||
| 	name      : string, | ||||
| 	data      : []byte, | ||||
| 	free_data : bool, | ||||
|  | ||||
| 	ascender    : f32, | ||||
| 	descender   : f32, | ||||
| 	line_height : f32, | ||||
|  | ||||
| 	glyphs        : Array(FSTASH_Glyph), | ||||
| 	lut           : FSTASH_FontLuts, | ||||
| 	fallbacks     : FSTASH_FontFallbacks, | ||||
| 	num_fallbacks : i32, | ||||
| } | ||||
|  | ||||
| FSTASH_Glyph :: struct { | ||||
| 	codepoint   : rune, | ||||
| 	index, next : i32, | ||||
| 	size, blur  : i16, | ||||
| 	x_advance   : i16, | ||||
| 	box         : Range2_i16, | ||||
| 	offset      : Vec2_i16, | ||||
| } | ||||
|  | ||||
| FSTASH_Params :: struct { | ||||
| 	width, height : i32, | ||||
| 	quad_location : FSTASH_QuadLocation, // (flags) | ||||
| 	render_create : FSTASH_RenderCreateProc, | ||||
| 	render_resize : FSTASH_RenderResizeProc, | ||||
| 	render_update : FSTASH_RenderUpdateProc, | ||||
| 	render_draw   : FSTASH_RenderDrawProc, | ||||
| 	render_delete : FSTASH_RenderDelete, | ||||
| } | ||||
|  | ||||
| FSTASH_State :: struct { | ||||
| 	font      : i32, | ||||
| 	alignment : i32, | ||||
| 	size      : f32, | ||||
| 	color     : [4]u8, | ||||
| 	blur      : f32, | ||||
| 	spacing   : f32, | ||||
| } | ||||
|  | ||||
| FSTASH_TextIter :: struct { | ||||
| 	x, y           : f32, | ||||
| 	next_x, next_y : f32, | ||||
| 	scale, spacing : f32, | ||||
|  | ||||
| 	isize, iblur : i16, | ||||
|  | ||||
| 	font : ^FSTASH_Font, | ||||
| 	prev_glyph_id : i32, | ||||
|  | ||||
| 	codepoint  : rune, | ||||
| 	utf8_state : rune, | ||||
|  | ||||
| 	str        : string, | ||||
| 	next       : string, | ||||
| 	end        : string, | ||||
| } | ||||
|  | ||||
| FSTASH_Context :: struct { | ||||
| 	params : FSTASH_Params, | ||||
|  | ||||
| // Atlas | ||||
| 	atlas           : Array(FSTASH_AtlasNode), | ||||
| 	texture_data    : []byte, | ||||
| 	width, height   : i32, | ||||
| // ---- | ||||
|  | ||||
| 	normalized_size : Vec2, | ||||
|  | ||||
| 	verts   : [FSTASH_Vertex_Count * 2]f32, | ||||
| 	tcoords : [FSTASH_Vertex_Count * 2]f32, | ||||
| 	colors  : [FSTASH_Vertex_Count    ]f32, | ||||
|  | ||||
| 	states  : [FSTASH_Max_States]FSTASH_State, | ||||
| 	num_states : i32, | ||||
|  | ||||
| 	handle_error : FSTASH_HandleErrorProc, | ||||
| 	error_uptr : rawptr, | ||||
| } | ||||
|  | ||||
| fstash_decode_utf8 :: proc( state : ^rune, codepoint : ^rune, to_decode : byte ) -> bool | ||||
| { | ||||
| 	UTF8_Accept :: 0 | ||||
| 	UTF8_Reject :: 1 | ||||
|  | ||||
| 	@static UTF8_Decode_Table := [?]u8 { | ||||
| 		// The first part of the table maps bytes to character classes that | ||||
| 		// to reduce the size of the transition table and create bitmasks. | ||||
| 		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  // 00..1F | ||||
| 		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  // 20..3F | ||||
| 		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  // 40..5F | ||||
| 		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  // 60..7F | ||||
| 		1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,  // 80..9F | ||||
| 		7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  // A0..BF | ||||
| 		8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  // C0..DF | ||||
| 		10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, // E0..FF | ||||
|  | ||||
| 		// The second part is a transition table that maps a combination | ||||
| 		// of a state of the automaton and a character class to a state. | ||||
| 		0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, | ||||
| 		12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, | ||||
| 		12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, | ||||
| 		12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, | ||||
| 		12,36,12,12,12,12,12,12,12,12,12,12, | ||||
| 	} | ||||
|  | ||||
| 	to_decode_rune := rune(to_decode) | ||||
| 	type           := UTF8_Decode_Table[to_decode_rune] | ||||
|  | ||||
| 	// Update codepoint otherwise initialize it. | ||||
| 	(codepoint^) = ((state^) != UTF8_Accept) ?          \ | ||||
| 			((to_decode_rune & 0x3F) | ((codepoint^) << 6)) \ | ||||
| 		: ((0xFF >> type) & (to_decode_rune)) | ||||
|  | ||||
| 	(state^) = cast(rune)(UTF8_Decode_Table[256 + (state^) * 16 + rune(type)]) | ||||
| 	return (state^) == UTF8_Accept | ||||
| } | ||||
|  | ||||
| fstash_atlas_delete :: proc ( ctx : ^FSTASH_Context ) { | ||||
| 	using ctx | ||||
| 	array_free( ctx.atlas ) | ||||
| } | ||||
|  | ||||
| fstash_atlas_expand :: proc( ctx : ^FSTASH_Context, width, height : i32 ) | ||||
| { | ||||
| 	if width > ctx.width { | ||||
| 		fstash_atlas_insert( ctx, ctx.atlas.num, ctx.width, 0, width - ctx.width ) | ||||
| 	} | ||||
|  | ||||
| 	ctx.width  = width | ||||
| 	ctx.height = height | ||||
| } | ||||
|  | ||||
| fstash_atlas_init :: proc( ctx : ^FSTASH_Context, width, height : i32, num_nodes : u32 = FSTASH_Init_Atlas_Nodes ) | ||||
| { | ||||
| 	error : AllocatorError | ||||
| 	ctx.atlas, error = array_init_reserve( FSTASH_AtlasNode, context.allocator, u64(num_nodes), dbg_name = "font atlas" ) | ||||
| 	ensure(error != AllocatorError.None, "Failed to allocate font atlas") | ||||
|  | ||||
| 	ctx.width  = width | ||||
| 	ctx.height = height | ||||
|  | ||||
| 	array_append( & ctx.atlas, FSTASH_AtlasNode{ width = i16(width)} ) | ||||
| } | ||||
|  | ||||
| fstash_atlas_insert :: proc( ctx : ^FSTASH_Context, id : u64, x, y, width : i32 ) -> (error : AllocatorError) | ||||
| { | ||||
| 	error = array_append_at( & ctx.atlas, FSTASH_AtlasNode{ i16(x), i16(y), i16(width) }, id ) | ||||
| 	return | ||||
| } | ||||
|  | ||||
| fstash_atlas_remove :: proc( ctx : ^FSTASH_Context, id : u64 ) | ||||
| { | ||||
| 	array_remove_at( ctx.atlas, id ) | ||||
| } | ||||
|  | ||||
| fstash_atlas_reset :: proc( ctx : ^FSTASH_Context, width, height : i32 ) | ||||
| { | ||||
| 	ctx.width  = width | ||||
| 	ctx.height = height | ||||
| 	array_clear( ctx.atlas ) | ||||
|  | ||||
| 	array_append( & ctx.atlas, FSTASH_AtlasNode{ width = i16(width)} ) | ||||
| } | ||||
|  | ||||
| fstash_atlas_add_skyline_level :: proc (ctx : ^FSTASH_Context, id : u64, x, y, width, height : i32 ) -> (error : AllocatorError) | ||||
| { | ||||
| 	insert :: fstash_atlas_insert | ||||
| 	remove :: fstash_atlas_remove | ||||
|  | ||||
| 	error = insert( ctx, id, x, y + height, width) | ||||
| 	if error != AllocatorError.None { | ||||
| 		ensure( false, "Failed to insert into atlas") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Delete skyline segments that fall under the shadow of the new segment. | ||||
| 	for sky_id := id; sky_id < ctx.atlas.num; sky_id += 1 | ||||
| 	{ | ||||
| 		curr := & ctx.atlas.data[sky_id    ] | ||||
| 		next := & ctx.atlas.data[sky_id + 1] | ||||
| 		if curr.x >= next.x + next.width do break | ||||
|  | ||||
| 		shrink := i16(next.x + next.width - curr.x) | ||||
| 		curr.x     += shrink | ||||
| 		curr.width -= shrink | ||||
|  | ||||
| 		if curr.width > 0 do break | ||||
|  | ||||
| 		remove(ctx, sky_id) | ||||
| 		sky_id -= 1 | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	// Merge same height skyline segments that are next to each other. | ||||
| 	for sky_id := id; sky_id < ctx.atlas.num - 1; | ||||
| 	{ | ||||
| 		curr := & ctx.atlas.data[sky_id    ] | ||||
| 		next := & ctx.atlas.data[sky_id + 1] | ||||
|  | ||||
| 		if curr.y == next.y { | ||||
| 			curr.width += next.width | ||||
| 			remove(ctx, sky_id + 1) | ||||
| 		} | ||||
| 		else { | ||||
| 			sky_id += 1 | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| fstash_atlas_rect_fits :: proc( ctx : ^FSTASH_Context, location, width, height : i32 ) -> (max_height : i32) | ||||
| { | ||||
| 	// Checks if there is enough space at the location of skyline span 'i', | ||||
| 	// and return the max height of all skyline spans under that at that location, | ||||
| 	// (think tetris block being dropped at that position). Or -1 if no space found. | ||||
| 	atlas := array_to_slice(ctx.atlas) | ||||
| 	node := atlas[location] | ||||
|  | ||||
| 	space_left : i32 | ||||
| 	if i32(node.x) + width > ctx.width { | ||||
| 		max_height = -1 | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	space_left = width; | ||||
|  | ||||
| 	y        := i32(node.y) | ||||
| 	location := location | ||||
| 	for ; space_left > 0; | ||||
| 	{ | ||||
| 		if u64(location) == ctx.atlas.num { | ||||
| 			max_height = -1 | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		node := atlas[location] | ||||
|  | ||||
| 		y := max(y, i32(node.y)) | ||||
| 		if y + height > ctx.height { | ||||
| 			max_height = -1 | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		space_left -= i32(node.width) | ||||
| 		location += 1 | ||||
| 	} | ||||
| 	max_height = y | ||||
| 	return | ||||
| } | ||||
|  | ||||
| fstash_atlas_add_rect :: proc( ctx : ^FSTASH_Context, ) | ||||
| { | ||||
| 	 | ||||
| } | ||||
| @@ -1,13 +1,5 @@ | ||||
| package sectr | ||||
|  | ||||
| import "core:fmt" | ||||
| import "core:math" | ||||
| import "core:mem" | ||||
| import "core:path/filepath" | ||||
| import "core:os" | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| Font_Largest_Px_Size :: 32 | ||||
|  | ||||
| Font_Size_Interval :: 2 | ||||
| @@ -35,158 +27,30 @@ FontTag :: struct { | ||||
| 	point_size : f32 | ||||
| } | ||||
|  | ||||
| FontGlyphsRender :: struct { | ||||
| 	size    : i32, | ||||
| 	count   : i32, | ||||
| 	padding : i32, | ||||
| 	texture : rl.Texture2D, | ||||
| 	recs    : [^]rl.Rectangle, // Characters rectangles in texture | ||||
| 	glyphs  : [^]rl.GlyphInfo, // Characters info data | ||||
| } | ||||
|  | ||||
| FontDef :: struct { | ||||
| 	path_file    : string, | ||||
|  | ||||
| 	// TODO(Ed) : you may have to store font data in the future if we render on demand | ||||
| 	// data         : []u8, | ||||
|  | ||||
| 	default_size : i32, | ||||
| 	size_table   : [Font_Largest_Px_Size / Font_Size_Interval] FontGlyphsRender, | ||||
| 	placeholder : int, | ||||
| } | ||||
|  | ||||
| FontProviderData :: struct { | ||||
| 	// font_cache : HMapZPL(FontDef), | ||||
| 	font_cache : HMapChainedPtr(FontDef), | ||||
| } | ||||
|  | ||||
| font_provider_startup :: proc() | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	state := get_state() | ||||
| 	font_provider_data := & get_state().font_provider_data; using font_provider_data | ||||
|  | ||||
| 	font_cache_alloc_error : AllocatorError | ||||
| 	font_cache, font_cache_alloc_error = hmap_chained_init(FontDef, hmap_closest_prime(1 * Kilo), persistent_allocator(), dbg_name = "font_cache" ) | ||||
| 	verify( font_cache_alloc_error == AllocatorError.None, "Failed to allocate font_cache" ) | ||||
|  | ||||
| 	log("font_cache created") | ||||
| 	log("font_provider initialized") | ||||
| } | ||||
|  | ||||
| font_provider_shutdown :: proc() | ||||
| { | ||||
| 	font_provider_data := & get_state().font_provider_data; using font_provider_data | ||||
|  | ||||
| 	for & entry in font_cache.lookup | ||||
| 	{ | ||||
| 		if entry == nil do continue | ||||
|  | ||||
| 		def := entry.value | ||||
| 		for & px_render in def.size_table { | ||||
| 			using px_render | ||||
| 			rl.UnloadFontData( glyphs, count ) | ||||
| 			rl.UnloadTexture ( texture ) | ||||
| 			rl.MemFree( recs ) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| font_load :: proc( path_file : string, | ||||
| font_load :: proc(ath_file : string, | ||||
| 	default_size : f32    = Font_Load_Use_Default_Size, | ||||
| 	desired_id   : string = Font_Load_Gen_ID | ||||
| ) -> FontID | ||||
| { | ||||
| 	profile(#procedure) | ||||
| 	log( str_fmt("Loading font: %v", path_file)) | ||||
| 	font_provider_data := & get_state().font_provider_data; using font_provider_data | ||||
|  | ||||
| 	font_data, read_succeded : = os.read_entire_file( path_file ) | ||||
| 	verify( b32(read_succeded), str_fmt("Failed to read font file for: %v", path_file) ) | ||||
| 	font_data_size := cast(i32) len(font_data) | ||||
|  | ||||
| 	desired_id := desired_id | ||||
| 	// Use file name as key | ||||
| 	if len(desired_id) == 0 { | ||||
| 		// NOTE(Ed): This should never be used except for laziness so I'll be throwing a warning everytime. | ||||
| 		log("desired_key not provided, using file name. Give it a proper name!", LogLevel.Warning) | ||||
| 		// desired_id = cast(FontID) file_name_from_path(path_file) | ||||
| 		desired_id = file_name_from_path(path_file) | ||||
| 	} | ||||
|  | ||||
| 	default_size := default_size | ||||
| 	if default_size == Font_Load_Use_Default_Size { | ||||
| 		default_size = Font_Default_Point_Size | ||||
| 	} | ||||
|  | ||||
| 	key            := cast(u64) crc32( transmute([]byte) desired_id ) | ||||
| 	def, set_error := hmap_chained_set(font_cache, key, FontDef{}) | ||||
| 	verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" ) | ||||
|  | ||||
| 	def.path_file    = path_file | ||||
| 	def.default_size = i32(points_to_pixels(default_size)) | ||||
|  | ||||
| 	// TODO(Ed): this is slow & eats quite a bit of memory early on. Setup a more on demand load for a specific size. | ||||
| 	// Also, we need to eventually switch to a SDF shader for rendering | ||||
|  | ||||
| 	// Render all sizes at once | ||||
| 	// Note(Ed) : We only generate textures for even multiples of the font. | ||||
| 	for font_size : i32 = Font_Size_Interval; font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval | ||||
| 	{ | ||||
| 		profile("font size render") | ||||
| 		id := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval) | ||||
|  | ||||
| 		px_render := & def.size_table[id - 1] | ||||
| 		using px_render | ||||
| 		size    = font_size | ||||
| 		count   = 95 // This is the default codepoint count from raylib when loading a font. | ||||
| 		padding = Font_TTF_Default_Chars_Padding | ||||
| 		glyphs  = rl.LoadFontData( raw_data(font_data), font_data_size, | ||||
| 			fontSize       = size, | ||||
| 			codepoints     = nil, | ||||
| 			codepointCount = count, | ||||
| 			type = rl.FontType.DEFAULT ) | ||||
| 		verify( glyphs != nil, str_fmt("Failed to load glyphs for font: %v at desired size: %v", desired_id, size ) ) | ||||
|  | ||||
| 		atlas  := rl.GenImageFontAtlas( glyphs, & recs, count, size, padding, i32(Font_Atlas_Packing_Method.Raylib_Basic) ) | ||||
| 		texture = rl.LoadTextureFromImage( atlas ) | ||||
|  | ||||
| 		// glyphs_slice := slice_ptr( glyphs, count ) | ||||
| 		// for glyph in glyphs_slice { | ||||
| 		// TODO(Ed) : See if above can properly reference | ||||
|  | ||||
| 		// NOTE(raylib): Update glyphs[i].image to use alpha, required to be used on image_draw_text() | ||||
| 		for glyph_id : i32 = 0; glyph_id < count; glyph_id += 1 { | ||||
| 			glyph := & glyphs[glyph_id] | ||||
|  | ||||
| 			rl.UnloadImage( glyph.image ) | ||||
| 			glyph.image = rl.ImageFromImage( atlas, recs[glyph_id] ) | ||||
| 		} | ||||
| 		rl.UnloadImage( atlas ) | ||||
| 	} | ||||
|  | ||||
| 	return { key, desired_id } | ||||
| } | ||||
|  | ||||
| Font_Use_Default_Size :: f32(0.0) | ||||
|  | ||||
| to_rl_Font :: proc( id : FontID, size := Font_Use_Default_Size ) -> rl.Font | ||||
| { | ||||
| 	font_provider_data := & get_state().font_provider_data; using font_provider_data | ||||
|  | ||||
| 	even_size := math.round(size * (1.0/f32(Font_Size_Interval))) * f32(Font_Size_Interval) | ||||
| 	size      := clamp( i32( even_size), 4, Font_Largest_Px_Size ) | ||||
| 	def       := hmap_chained_get( font_cache, id.key ) | ||||
| 	size       = size if size != i32(Font_Use_Default_Size) else def.default_size | ||||
|  | ||||
| 	id        := (size / Font_Size_Interval) + (size % Font_Size_Interval) | ||||
| 	px_render := & def.size_table[ id - 1 ] | ||||
|  | ||||
| 	rl_font : rl.Font | ||||
| 	rl_font.baseSize     = px_render.size | ||||
| 	rl_font.glyphCount   = px_render.count | ||||
| 	rl_font.glyphPadding = px_render.padding | ||||
| 	rl_font.glyphs       = px_render.glyphs | ||||
| 	rl_font.recs         = px_render.recs | ||||
| 	rl_font.texture      = px_render.texture | ||||
| 	return rl_font | ||||
| 	return {} | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,27 @@ | ||||
| package sectr | ||||
|  | ||||
| context_ext :: proc( $ Type : typeid ) -> (^Type) { | ||||
| context_usr :: #force_inline proc( $ Type : typeid ) -> (^Type) { | ||||
| 	return cast(^Type) context.user_ptr | ||||
| } | ||||
|  | ||||
| ContextExt :: struct { | ||||
| 	stack : StackFixed(rawptr, 1024), | ||||
| } | ||||
|  | ||||
| // Assign return value to context.user_ptr | ||||
| // context_ext_init :: proc() -> rawptr | ||||
| // { | ||||
|  | ||||
| // } | ||||
|  | ||||
| context_ext :: #force_inline proc() -> ^ContextExt { | ||||
| 	return cast(^ContextExt) context.user_ptr | ||||
| } | ||||
|  | ||||
| context_push :: proc( value : ^($Type) ) { | ||||
| 	push( & context_ext().stack, value ) | ||||
| } | ||||
|  | ||||
| context_pop :: proc( value : ^($Type) ) { | ||||
| 	pop( & context_ext().stack ) | ||||
| } | ||||
|   | ||||
| @@ -19,14 +19,14 @@ MemoryTracker :: struct { | ||||
|  | ||||
| Track_Memory :: false | ||||
|  | ||||
| tracker_msg_buffer : [Kilobyte * 16]u8 | ||||
| // tracker_msg_buffer : [Kilobyte * 16]u8 | ||||
|  | ||||
| memtracker_clear :: proc ( tracker : MemoryTracker ) { | ||||
| 	when ! Track_Memory { | ||||
| 		return | ||||
| 	} | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 	// temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	// context.temp_allocator = arena_allocator(& temp_arena) | ||||
|  | ||||
| 	logf("Clearing tracker: %v", tracker.name) | ||||
| 	memtracker_dump_entries(tracker); | ||||
| @@ -38,8 +38,8 @@ memtracker_init :: proc ( tracker : ^MemoryTracker, allocator : Allocator, num_e | ||||
| 	when ! Track_Memory { | ||||
| 		return | ||||
| 	} | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 	// temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	// context.temp_allocator = arena_allocator(& temp_arena) | ||||
|  | ||||
| 	tracker.name = name | ||||
|  | ||||
| @@ -56,8 +56,8 @@ memtracker_register :: proc( tracker : ^MemoryTracker, new_entry : MemoryTracker | ||||
| 		return | ||||
| 	} | ||||
| 	profile(#procedure) | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 	// temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	// context.temp_allocator = arena_allocator(& temp_arena) | ||||
|  | ||||
| 	if tracker.entries.num == tracker.entries.capacity { | ||||
| 		ensure(false, "Memory tracker entries array full, can no longer register any more allocations") | ||||
| @@ -110,8 +110,8 @@ memtracker_unregister :: proc( tracker : MemoryTracker, to_remove : MemoryTracke | ||||
| 		return | ||||
| 	} | ||||
| 	profile(#procedure) | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 	// temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	// context.temp_allocator = arena_allocator(& temp_arena) | ||||
|  | ||||
| 	entries := array_to_slice(tracker.entries) | ||||
| 	for idx in 0..< tracker.entries.num | ||||
| @@ -139,8 +139,8 @@ memtracker_check_for_collisions :: proc ( tracker : MemoryTracker ) | ||||
| 		return | ||||
| 	} | ||||
| 	profile(#procedure) | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 	// temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	// context.temp_allocator = arena_allocator(& temp_arena) | ||||
|  | ||||
| 	entries := array_to_slice(tracker.entries) | ||||
| 	for idx in 1 ..< tracker.entries.num { | ||||
| @@ -161,8 +161,8 @@ memtracker_dump_entries :: proc( tracker : MemoryTracker ) | ||||
| 	when ! Track_Memory { | ||||
| 		return | ||||
| 	} | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 	// temp_arena : Arena; arena_init(& temp_arena, tracker_msg_buffer[:]) | ||||
| 	// context.temp_allocator = arena_allocator(& temp_arena) | ||||
|  | ||||
| 	log( "Dumping Memory Tracker:") | ||||
| 	for idx in 0 ..< tracker.entries.num { | ||||
|   | ||||
							
								
								
									
										1
									
								
								code/sectr/input/input_sokol.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								code/sectr/input/input_sokol.odin
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| package sectr | ||||
| @@ -118,15 +118,22 @@ logger_interface :: proc( | ||||
| 	str_to_file_ln( logger.file, to_string(builder) ) | ||||
| } | ||||
| 
 | ||||
| // TODO(Ed): Use a fixed size block allocation for message formatting used by core_log | ||||
| // This will prevent stack overflows with the virtual arena debug logs at worst case and not need to do | ||||
| // some inline arena allocation on-site such as with the memory tracker | ||||
| 
 | ||||
| // This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators. | ||||
| Logger_Allocator_Buffer : [32 * Kilobyte]u8 | ||||
| 
 | ||||
| log :: proc( msg : string, level := LogLevel.Info, loc := #caller_location ) { | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) | ||||
| 	context.allocator      = arena_allocator(& temp_arena) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 
 | ||||
| 	core_log.log( level, msg, location = loc ) | ||||
| } | ||||
| 
 | ||||
| logf :: proc( fmt : string, args : ..any,  level := LogLevel.Info, loc := #caller_location  ) { | ||||
| 	// context.allocator = transient_allocator() | ||||
| 	temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) | ||||
| 	context.allocator      = arena_allocator(& temp_arena) | ||||
| 	context.temp_allocator = arena_allocator(& temp_arena) | ||||
| 
 | ||||
| 	core_log.logf( level, fmt, ..args, location = loc ) | ||||
| } | ||||
| @@ -134,7 +134,7 @@ pws_parser_lex :: proc ( text : string, allocator : Allocator ) -> ( PWS_LexResu | ||||
|  | ||||
| 	rune_type :: proc( codepoint : rune ) -> PWS_TokenType | ||||
| 	{ | ||||
| 		using self := context_ext( PWS_LexerData) | ||||
| 		using self := context_usr( PWS_LexerData) | ||||
|  | ||||
| 		switch codepoint | ||||
| 		{ | ||||
| @@ -177,7 +177,7 @@ pws_parser_lex :: proc ( text : string, allocator : Allocator ) -> ( PWS_LexResu | ||||
|  | ||||
| 	make_token :: proc ( byte_offset : int ) -> AllocatorError | ||||
| 	{ | ||||
| 		self := context_ext( PWS_LexerData); using self | ||||
| 		self := context_usr( PWS_LexerData); using self | ||||
|  | ||||
| 		if previous_rune == Rune_Carriage_Return && current_rune != Rune_Line_Feed { | ||||
| 			ensure(false, "Rouge Carriage Return") | ||||
| @@ -268,7 +268,7 @@ pws_parser_parse :: proc( text : string, allocator : Allocator ) -> ( PWS_ParseR | ||||
| 	//region Helper procs | ||||
| 	eat_line :: #force_inline proc() | ||||
| 	{ | ||||
| 		self := context_ext( PWS_ParseData); using self | ||||
| 		self := context_usr( PWS_ParseData); using self | ||||
| 		tok := cast( ^PWS_Token) head | ||||
|  | ||||
| 		line.type    = .Line | ||||
|   | ||||
| @@ -13,7 +13,7 @@ vec2_json_unmarshal :: proc( value : ^ json.Value ) -> Vec2 { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| color_json_unmarshal :: proc( value : ^ json.Value ) -> Color { | ||||
| color_json_unmarshal :: proc( value : ^ json.Value ) -> RGBA8 { | ||||
| 	json_color := value.(json.Array) | ||||
| 	r := u8(json_color[0].(json.Float)) | ||||
| 	g := u8(json_color[1].(json.Float)) | ||||
|   | ||||
| @@ -6,8 +6,6 @@ Ultimately the user's window ppcm (pixels-per-centimeter) determins how all virt | ||||
| */ | ||||
| package sectr | ||||
|  | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| // The points to pixels and pixels to points are our only reference to accurately converting | ||||
| // an object from world space to screen-space. | ||||
| // This prototype engine will have all its spacial unit base for distances in virtual pixels. | ||||
| @@ -97,7 +95,13 @@ range2_pixels_to_cm :: #force_inline proc "contextless"( range : Range2 ) -> Ran | ||||
|  | ||||
| //endregion | ||||
|  | ||||
| Camera :: rl.Camera2D | ||||
| Camera :: struct { | ||||
| 	view     : Extents2, | ||||
| 	position : Vec2, | ||||
| 	zoom     : f32, | ||||
| } | ||||
|  | ||||
| Camera_Default := Camera { zoom = 1 } | ||||
|  | ||||
| CameraZoomMode :: enum u32 { | ||||
| 	Digital, | ||||
| @@ -117,8 +121,8 @@ BoundsCorners2 :: struct { | ||||
| 	top_left, top_right, bottom_left, bottom_right: Vec2, | ||||
| } | ||||
|  | ||||
| Extents2  :: distinct Vec2 | ||||
| Extents2i :: distinct Vec2i | ||||
| Extents2  :: Vec2 | ||||
| Extents2i :: Vec2i | ||||
|  | ||||
| WS_Pos :: struct { | ||||
| 	tile_id : Vec2i, | ||||
| @@ -162,8 +166,8 @@ view_get_bounds :: #force_inline proc "contextless"() -> Range2 { | ||||
| 	cam            := & project.workspace.cam | ||||
| 	screen_extent  := state.app_window.extent | ||||
| 	cam_zoom_ratio := 1.0 / cam.zoom | ||||
| 	bottom_left  := Vec2 { cam.target.x, -cam.target.y } + Vec2 { -screen_extent.x, -screen_extent.y} * cam_zoom_ratio | ||||
| 	top_right    := Vec2 { cam.target.x, -cam.target.y } + Vec2 {  screen_extent.x,  screen_extent.y} * cam_zoom_ratio | ||||
| 	bottom_left  := Vec2 { cam.position.x, -cam.position.y } + Vec2 { -screen_extent.x, -screen_extent.y} * cam_zoom_ratio | ||||
| 	top_right    := Vec2 { cam.position.x, -cam.position.y } + Vec2 {  screen_extent.x,  screen_extent.y} * cam_zoom_ratio | ||||
| 	return range2( bottom_left, top_right ) | ||||
| } | ||||
|  | ||||
| @@ -173,10 +177,10 @@ view_get_corners :: #force_inline proc "contextless"() -> BoundsCorners2 { | ||||
| 	cam            := & project.workspace.cam | ||||
| 	cam_zoom_ratio := 1.0 / cam.zoom | ||||
| 	screen_extent  := state.app_window.extent * cam_zoom_ratio | ||||
| 	top_left     := cam.target + Vec2 { -screen_extent.x,  screen_extent.y } | ||||
| 	top_right    := cam.target + Vec2 {  screen_extent.x,  screen_extent.y } | ||||
| 	bottom_left  := cam.target + Vec2 { -screen_extent.x, -screen_extent.y } | ||||
| 	bottom_right := cam.target + Vec2 {  screen_extent.x, -screen_extent.y } | ||||
| 	top_left     := cam.position + Vec2 { -screen_extent.x,  screen_extent.y } | ||||
| 	top_right    := cam.position + Vec2 {  screen_extent.x,  screen_extent.y } | ||||
| 	bottom_left  := cam.position + Vec2 { -screen_extent.x, -screen_extent.y } | ||||
| 	bottom_right := cam.position + Vec2 {  screen_extent.x, -screen_extent.y } | ||||
| 	return { top_left, top_right, bottom_left, bottom_right } | ||||
| } | ||||
|  | ||||
| @@ -196,7 +200,7 @@ render_to_ws_view_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 { | ||||
| screen_to_ws_view_pos :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 { | ||||
| 	state := get_state(); using state | ||||
| 	cam   := & project.workspace.cam | ||||
| 	result := Vec2 { cam.target.x, -cam.target.y}  + Vec2 { pos.x, pos.y } * (1 / cam.zoom) | ||||
| 	result := Vec2 { cam.position.x, -cam.position.y}  + Vec2 { pos.x, pos.y } * (1 / cam.zoom) | ||||
| 	return result | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -62,9 +62,9 @@ UI_LayoutSide :: struct { | ||||
|  | ||||
| UI_LayoutFlag :: enum u32 { | ||||
|  | ||||
| 	// Will perform scissor pass on children to their parent's bounds | ||||
| 	// Will NOT perform scissor pass on children to their parent's bounds | ||||
| 	// (Specified in the parent) | ||||
| 	Clip_Children_To_Bounds, | ||||
| 	Dont_Clip_Children_To_bounds, | ||||
|  | ||||
| 	// Enforces the box will always remain in a specific position relative to the parent. | ||||
| 	// Overriding the anchors and margins. | ||||
|   | ||||
| @@ -86,6 +86,7 @@ UI_Parent_Stack_Size      :: 512 | ||||
| // UI_Built_Boxes_Array_Size :: 8 | ||||
| UI_Built_Boxes_Array_Size :: 128 * Kilobyte | ||||
|  | ||||
| // TODO(Ed): Rename to UI_Context | ||||
| UI_State :: struct { | ||||
| 	// TODO(Ed) : Use these | ||||
| 	// build_arenas : [2]Arena, | ||||
|   | ||||
| @@ -17,8 +17,8 @@ UI_StylePreset :: enum u32 { | ||||
| } | ||||
|  | ||||
| UI_Style :: struct { | ||||
| 	bg_color     : Color, | ||||
| 	border_color : Color, | ||||
| 	bg_color     : RGBA8, | ||||
| 	border_color : RGBA8, | ||||
|  | ||||
| 	// TODO(Ed): We cannot support individual corners unless we add it to raylib (or finally change the rendering backend) | ||||
| 	corner_radii : [Corner.Count]f32, | ||||
| @@ -33,7 +33,7 @@ UI_Style :: struct { | ||||
| 	// shader : UI_Shader, | ||||
|  | ||||
| 	font       : FontID, | ||||
| 	text_color : Color, | ||||
| 	text_color : RGBA8, | ||||
|  | ||||
| 	// TODO(Ed) : Support setting the cursor state | ||||
| 	cursor : UI_Cursor, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user