got basic ui elmental interaction working, + alignment of anchor
This commit is contained in:
		| @@ -170,7 +170,7 @@ UI_ScrollPt :: struct { | ||||
| UI_ScrollPt2 :: [2]UI_ScrollPt | ||||
|  | ||||
| UI_Signal :: struct { | ||||
| 	box : UI_Box, | ||||
| 	box : ^ UI_Box, | ||||
|  | ||||
| 	cursor_pos : Vec2, | ||||
| 	drag_delta : Vec2, | ||||
|   | ||||
| @@ -73,7 +73,10 @@ startup :: proc( live_mem : virtual.Arena, snapshot_mem : []u8, host_logger : ^ | ||||
|  | ||||
| 	// rl.Odin_SetMalloc( RL_MALLOC ) | ||||
|  | ||||
| 	rl.SetConfigFlags( { rl.ConfigFlag.WINDOW_RESIZABLE /*, rl.ConfigFlag.WINDOW_TOPMOST*/ } ) | ||||
| 	rl.SetConfigFlags( { | ||||
| 		rl.ConfigFlag.WINDOW_RESIZABLE, | ||||
| 		rl.ConfigFlag.WINDOW_TOPMOST, | ||||
| 	}) | ||||
|  | ||||
| 	// Rough setup of window with rl stuff | ||||
| 	window_width  : i32 = 1000 | ||||
| @@ -177,7 +180,7 @@ reload :: proc( live_mem : virtual.Arena, snapshot_mem : []u8, host_logger : ^ L | ||||
| 	snapshot = snapshot_mem | ||||
|  | ||||
| 	// This is no longer necessary as we have proper base address setting | ||||
| 	when false | ||||
| 	when true | ||||
| 	{ | ||||
| 		persistent_slice := slice_ptr( block.base, Memory_Persistent_Size ) | ||||
| 		transient_slice  := slice_ptr( memory_after( persistent_slice), Memory_Trans_Temp_Szie ) | ||||
| @@ -216,11 +219,13 @@ swap :: proc( a, b : ^ $Type ) -> ( ^ Type, ^ Type ) { | ||||
| } | ||||
|  | ||||
| @export | ||||
| tick :: proc( delta_time : f64 ) -> b32 | ||||
| tick :: proc( delta_time : f64, delta_ns : Duration ) -> b32 | ||||
| { | ||||
| 	context.allocator      = transient_allocator() | ||||
| 	context.temp_allocator = temp_allocator() | ||||
|  | ||||
| 	get_state().frametime_delta_ns = delta_ns | ||||
|  | ||||
| 	result := update( delta_time ) | ||||
| 	render() | ||||
| 	return result | ||||
|   | ||||
| @@ -2,6 +2,11 @@ package sectr | ||||
|  | ||||
| import "core:math/linalg" | ||||
|  | ||||
| pos_within_range2 :: proc( pos : Vec2, range : Range2 ) -> b32 { | ||||
| 	within_x := pos.x > range.p0.x && pos.x < range.p1.x | ||||
| 	within_y := pos.y < range.p0.y && pos.y > range.p1.y | ||||
| 	return b32(within_x && within_y) | ||||
| } | ||||
|  | ||||
| box_is_within :: proc( box : ^ Box2, pos : Vec2 ) -> b32 { | ||||
| 	bounds := box_get_bounds( box ) | ||||
|   | ||||
| @@ -3,12 +3,16 @@ package sectr | ||||
| import rl "vendor:raylib" | ||||
|  | ||||
| Color       :: rl.Color | ||||
| Color_Blue  :: rl.BLUE | ||||
| Color_Green :: rl.GREEN | ||||
| Color_Red   :: rl.RED | ||||
| Color_White :: rl.WHITE | ||||
|  | ||||
| Color_Transparent      :: Color {   0,   0,   0,   0 } | ||||
| Color_BG               :: Color {  41,  41,  45, 255 } | ||||
| Color_BG_TextBox       :: Color {  32,  32,  32, 255 } | ||||
| Color_BG_TextBox_Green :: Color { 102, 102, 110, 255 } | ||||
| Color_Frame_Disabled   :: Color {  22,  22,  22, 120 } | ||||
| Color_Frame_Hover      :: Color { 122, 122, 125, 255 } | ||||
| Color_Frame_Select     :: Color { 188, 188, 188, 255 } | ||||
| Color_GreyRed          :: Color { 220, 100, 100, 125 } | ||||
|   | ||||
| @@ -112,6 +112,8 @@ State :: struct { | ||||
| 	engine_refresh_hz     : i32, | ||||
| 	engine_refresh_target : i32, | ||||
|  | ||||
| 	frametime_delta_ns : Duration, | ||||
|  | ||||
| 	font_firacode                : FontID, | ||||
| 	font_squidgy_slimes          : FontID, | ||||
| 	font_rec_mono_semicasual_reg : FontID, | ||||
| @@ -178,6 +180,7 @@ DebugData :: struct { | ||||
| 	mouse_vis         : b32, | ||||
| 	last_mouse_pos    : Vec2, | ||||
|  | ||||
| 	frame_1_on_top : b32, | ||||
| 	zoom_target : f32, | ||||
|  | ||||
| 	frame_2_created : b32, | ||||
| } | ||||
|   | ||||
| @@ -21,6 +21,12 @@ stack_pop :: proc( using stack : ^ Stack( $ Type, $ Size ) ) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| stack_peek :: proc( using stack : ^ Stack( $ Type, $ Size ) ) -> ^ Type { | ||||
| 	return & items[idx] | ||||
| stack_peek_ref :: proc( using stack : ^ Stack( $ Type, $ Size ) ) -> ^ Type { | ||||
| 	last := max( 0, idx - 1 ) | ||||
| 	return & items[last] | ||||
| } | ||||
|  | ||||
| stack_peek :: proc ( using stack : ^ Stack( $ Type, $ Size ) ) -> Type { | ||||
| 	last := max( 0, idx - 1 ) | ||||
| 	return items[last] | ||||
| } | ||||
|   | ||||
| @@ -55,7 +55,13 @@ import "core:path/filepath" | ||||
| 	file_name_from_path :: filepath.short_stem | ||||
| import str "core:strings" | ||||
| 	str_builder_to_string  :: str.to_string | ||||
| import "core:time" | ||||
| 	Duration :: time.Duration | ||||
| import "core:unicode" | ||||
| 	is_white_space  :: unicode.is_white_space | ||||
| import "core:unicode/utf8" | ||||
| 	runes_to_string :: utf8.runes_to_string | ||||
| 	string_to_runes :: utf8.string_to_runes | ||||
|  | ||||
| OS_Type :: type_of(ODIN_OS) | ||||
|  | ||||
| @@ -71,9 +77,10 @@ is_power_of_two :: proc { | ||||
| } | ||||
|  | ||||
| to_runes :: proc { | ||||
| 	utf8.string_to_runes, | ||||
| 	string_to_runes, | ||||
| } | ||||
|  | ||||
| to_string :: proc { | ||||
| 	runes_to_string, | ||||
| 	str_builder_to_string, | ||||
| } | ||||
|   | ||||
| @@ -226,9 +226,13 @@ array_set_capacity :: proc( using self : ^ Array( $ Type ), new_capacity : u64 ) | ||||
| 		return AllocatorError.None | ||||
| 	} | ||||
|  | ||||
| 	raw_data, result_code := alloc( cast(int) new_capacity * size_of(Type), allocator = allocator ) | ||||
| 	ensure( result_code == AllocatorError.None, "Failed to allocate for new array capacity" ) | ||||
| 	data     = cast( [^] Type ) raw_data | ||||
| 	new_data, result_code := alloc( cast(int) new_capacity * size_of(Type), allocator = allocator ) | ||||
| 	if result_code != AllocatorError.None { | ||||
| 		ensure( false, "Failed to allocate for new array capacity" ) | ||||
| 		return result_code | ||||
| 	} | ||||
| 	free( raw_data(data) ) | ||||
| 	data     = cast( [^] Type ) new_data | ||||
| 	capacity = new_capacity | ||||
| 	return result_code | ||||
| } | ||||
|   | ||||
| @@ -110,6 +110,10 @@ zpl_hmap_grow :: proc( using self : ^ HMapZPL( $ Type ) ) -> AllocatorError { | ||||
|  | ||||
| zpl_hmap_rehash :: proc( ht : ^ HMapZPL( $ Type ), new_num : u64 ) -> AllocatorError | ||||
| { | ||||
| 	// For now the prototype should never allow this to happen. | ||||
| 	// We use this almost exclusively in persistent memory and its not setup for  | ||||
| 	// dealing with reallocations in a conservative manner. | ||||
| 	ensure( false, "ZPL HMAP IS REHASHING" ) | ||||
| 	last_added_index : i64 | ||||
|  | ||||
| 	new_ht, init_result := zpl_hmap_init_reserve( Type, ht.hashes.allocator, new_num ) | ||||
|   | ||||
| @@ -30,42 +30,45 @@ DLL_NodeFL :: struct ( $ Type : typeid ) { | ||||
| 	first, last : ^ Type, | ||||
| } | ||||
|  | ||||
| type_is_node :: #force_inline proc  "contextless" ( $ Type : typeid ) -> b32 | ||||
| type_is_node :: #force_inline proc  "contextless" ( $ Type : typeid ) -> bool | ||||
| { | ||||
| 	// elem_type := type_elem_type(Type) | ||||
| 	return type_has_field( type_elem_type(Type), "prev" ) && type_has_field( type_elem_type(Type), "next" ) | ||||
| } | ||||
|  | ||||
| dll_insert_raw ::  proc "contextless" ( null, first, last, position, new : ^ DLL_Node( $ Type ) ) | ||||
| dll_full_insert_raw ::  proc "contextless" ( null : ^($ Type), parent, pos, node : ^ Type ) | ||||
| { | ||||
| 	// Empty Case | ||||
| 	if first == null { | ||||
| 		first     = new | ||||
| 		last      = new | ||||
| 		new.next  = null | ||||
| 		new.prev  = null | ||||
| 	if parent.first == null { | ||||
| 		parent.first = node | ||||
| 		parent.last  = node | ||||
| 		node.next    = null | ||||
| 		node.prev    = null | ||||
| 	} | ||||
| 	else if position == null { | ||||
| 	else if pos == null { | ||||
| 		// Position is not set, insert at beginning | ||||
| 		new.next   = first | ||||
| 		first.prev = new | ||||
| 		first      = new | ||||
| 		new.prev   = null | ||||
| 		node.next         = parent.first | ||||
| 		parent.first.prev = node | ||||
| 		parent.first      = node | ||||
| 		node.prev         = null | ||||
| 	} | ||||
| 	else if position == last { | ||||
| 	else if pos == parent.last { | ||||
| 		// Positin is set to last, insert at end | ||||
| 		last.next = new | ||||
| 		new.prev  = last | ||||
| 		last      = new | ||||
| 		new.next  = null | ||||
| 		parent.last.next = node | ||||
| 		node.prev        = parent.last | ||||
| 		parent.last      = node | ||||
| 		node.next        = null | ||||
| 	} | ||||
| 	else { | ||||
| 		// Insert around position | ||||
| 		if position.next != null { | ||||
| 			position.next.prev = new | ||||
| 	else | ||||
| 	{ | ||||
| 		if pos.next != null { | ||||
| 			pos.next.prev = node | ||||
| 		} | ||||
| 		new.next      = position.next | ||||
| 		position.next = new | ||||
| 		new.prev      = position | ||||
| 		node.next = pos.next | ||||
| 		pos.next  = node | ||||
| 		node.prev = pos | ||||
| 	} | ||||
| } | ||||
|  | ||||
| dll_full_push_back :: proc "contextless" ( null : ^($ Type), parent, node : ^ Type ) { | ||||
| 	dll_full_insert_raw( null, parent, parent.last, node ) | ||||
| } | ||||
|   | ||||
| @@ -210,7 +210,7 @@ sync_sectr_api :: proc( sectr_api : ^ sectr.ModuleAPI, memory : ^ VMemChunk, log | ||||
|  | ||||
| 		// Wait for pdb to unlock (linker may still be writting) | ||||
| 		for ; file_is_locked( Path_Sectr_Debug_Symbols ) && file_is_locked( Path_Sectr_Live_Module ); {} | ||||
| 		thread_sleep( Millisecond * 50 ) | ||||
| 		thread_sleep( Millisecond * 100 ) | ||||
|  | ||||
| 		sectr_api ^ = load_sectr_api( version_id ) | ||||
| 		verify( sectr_api.lib_version != 0, "Failed to hot-reload the sectr module" ) | ||||
| @@ -285,7 +285,7 @@ main :: proc() | ||||
| 		// Hot-Reload | ||||
| 		sync_sectr_api( & sectr_api, & memory, & logger ) | ||||
|  | ||||
| 		running = sectr_api.tick( duration_seconds( delta_ns ) ) | ||||
| 		running = sectr_api.tick( duration_seconds( delta_ns ), delta_ns ) | ||||
| 		sectr_api.clean_temp() | ||||
|  | ||||
| 		delta_ns = time.tick_lap_time( & start_tick ) | ||||
|   | ||||
| @@ -17,10 +17,18 @@ btn_pressed :: proc( btn : DigitalBtn ) -> b32 { | ||||
| 	return btn.ended_down && btn.half_transitions > 0 | ||||
| } | ||||
|  | ||||
| btn_released :: proc ( btn : DigitalBtn ) -> b32 { | ||||
| 	return btn.ended_down == false && btn.half_transitions > 0 | ||||
| } | ||||
|  | ||||
| pressed :: proc { | ||||
| 	btn_pressed, | ||||
| } | ||||
|  | ||||
| released :: proc { | ||||
| 	btn_released, | ||||
| } | ||||
|  | ||||
| MaxKeyboardKeys :: 256 | ||||
| KeyboardKey :: enum u32 { | ||||
| 	null = 0x00, | ||||
|   | ||||
| @@ -1,5 +1,12 @@ | ||||
| package sectr | ||||
|  | ||||
| Axis2 :: enum i32 { | ||||
| 	Invalid = -1, | ||||
| 	X       = 0, | ||||
| 	Y       = 1, | ||||
| 	Count, | ||||
| } | ||||
|  | ||||
| is_power_of_two_u32 :: proc( value : u32 ) -> b32 | ||||
| { | ||||
| 	return value != 0 && ( value & ( value - 1 )) == 0 | ||||
| @@ -12,3 +19,20 @@ Vec3 :: linalg.Vector3f32 | ||||
|  | ||||
| Vec2i :: [2]i32 | ||||
| Vec3i :: [3]i32 | ||||
|  | ||||
| Range2 :: struct #raw_union{ | ||||
| 	using min_max : struct { | ||||
| 		min, max : Vec2 | ||||
| 	}, | ||||
| 	using pts : struct { | ||||
| 		p0, p1 : Vec2 | ||||
| 	}, | ||||
| 	using xy : struct { | ||||
| 		x0, y0 : f32, | ||||
| 		x1, y1 : f32, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| Rect :: struct { | ||||
| 	top_left, bottom_right : Vec2 | ||||
| } | ||||
|   | ||||
| @@ -24,11 +24,13 @@ when ODIN_OS == OS_Type.Windows { | ||||
| cm_to_pixels :: proc { | ||||
| 	f32_cm_to_pixels, | ||||
| 	vec2_cm_to_pixels, | ||||
| 	range2_cm_to_pixels, | ||||
| } | ||||
|  | ||||
| pixels_to_cm :: proc { | ||||
| 	f32_pixels_to_cm, | ||||
| 	vec2_pixels_to_cm, | ||||
| 	range2_pixels_to_cm, | ||||
| } | ||||
|  | ||||
| points_to_pixels :: proc { | ||||
| @@ -89,6 +91,18 @@ vec2_points_to_pixels :: proc(vpoints: Vec2) -> Vec2 { | ||||
| 	return vpoints * DPT_PPCM * cm_per_pixel | ||||
| } | ||||
|  | ||||
| range2_cm_to_pixels :: proc( range : Range2 ) -> Range2 { | ||||
| 	screen_ppcm := get_state().app_window.ppcm | ||||
| 	result := Range2 { pts = { range.min * screen_ppcm, range.max * screen_ppcm }} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| range2_pixels_to_cm :: proc( range : Range2 ) -> Range2 { | ||||
| 	screen_ppcm := get_state().app_window.ppcm | ||||
| 	cm_per_pixel := 1.0 / screen_ppcm | ||||
| 	result := Range2 { pts = { range.min * cm_per_pixel, range.max * cm_per_pixel }} | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| // vec2_points_to_cm :: proc( vpoints : Vec2 ) -> Vec2 { | ||||
|  | ||||
| @@ -166,7 +180,8 @@ view_get_corners :: proc() -> BoundsCorners2 { | ||||
| screen_to_world :: proc(pos: Vec2) -> Vec2 { | ||||
| 	state := get_state(); using state | ||||
| 	cam   := & project.workspace.cam | ||||
| 	return vec2_pixels_to_cm( cam.target + pos * (1 / cam.zoom) ) | ||||
| 	result := Vec2 { cam.target.x, -cam.target.y}  + Vec2 { pos.x, -pos.y } * (1 / cam.zoom) | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| screen_to_render :: proc(pos: Vec2) -> Vec2 { | ||||
|   | ||||
| @@ -39,7 +39,7 @@ render :: proc() | ||||
| 			screen_corners := screen_get_corners() | ||||
|  | ||||
| 			position   := screen_corners.top_right | ||||
| 			position.x -= 200 | ||||
| 			position.x -= 800 | ||||
| 			position.y += debug.draw_debug_text_y | ||||
|  | ||||
| 			content := str_fmt_buffer( draw_text_scratch[:], format, ..args ) | ||||
| @@ -61,11 +61,11 @@ render :: proc() | ||||
| 		} | ||||
|  | ||||
| 		if debug.mouse_vis { | ||||
| 			debug_text( "Position: %v", input.mouse.pos ) | ||||
| 			debug_text( "Mouse Position (Screen): %v", input.mouse.pos ) | ||||
| 			debug_text("Mouse Position (World): %v", screen_to_world(input.mouse.pos) ) | ||||
| 			cursor_pos :=  transmute(Vec2) state.app_window.extent + input.mouse.pos | ||||
| 			rl.DrawCircleV( cursor_pos, 10, Color_White_A125 ) | ||||
| 		} | ||||
|  | ||||
| 		debug.draw_debug_text_y = 50 | ||||
| 	} | ||||
| 	//endregion Render Screenspace | ||||
| @@ -76,21 +76,56 @@ render_mode_2d :: proc() | ||||
| { | ||||
| 	state  := get_state(); using state | ||||
| 	cam    := & project.workspace.cam | ||||
|  | ||||
| 	win_extent := state.app_window.extent | ||||
|  | ||||
| 	rl.BeginMode2D( project.workspace.cam ) | ||||
|  | ||||
| 	//region Imgui Render | ||||
| 	ImguiRender: | ||||
| 	{ | ||||
| 		ui   := & state.project.workspace.ui | ||||
| 		root := ui.root | ||||
| 		if root.num_children == 0 { | ||||
| 			break ImguiRender | ||||
| 		} | ||||
|  | ||||
| 		current := root.first | ||||
| 		for ; current != nil; { | ||||
| 			parent := current.parent | ||||
|  | ||||
| 			style    := current.style | ||||
| 			computed := & current.computed | ||||
|  | ||||
| 			// TODO(Ed) : Render Borders | ||||
|  | ||||
| 			render_bounds := Range2 { pts = { | ||||
| 				world_to_screen_pos(computed.bounds.min), | ||||
| 				world_to_screen_pos(computed.bounds.max), | ||||
| 			}} | ||||
|  | ||||
| 			rect := rl.Rectangle { | ||||
| 				render_bounds.min.x, | ||||
| 				render_bounds.min.y, | ||||
| 				render_bounds.max.x - render_bounds.min.x, | ||||
| 				render_bounds.max.y - render_bounds.min.y, | ||||
| 			} | ||||
| 			rl.DrawRectangleRec( rect, style.bg_color ) | ||||
| 			rl.DrawCircleV( render_bounds.p0, 5, Color_Red ) | ||||
| 			rl.DrawCircleV( render_bounds.p1, 5, Color_Blue ) | ||||
|  | ||||
| 			current = ui_box_tranverse_next( current ) | ||||
| 		} | ||||
| 	} | ||||
| 	//endregion Imgui Render | ||||
|  | ||||
| 	debug_draw_text_world( "This is text in world space", { 0, 0 }, 16.0  ) | ||||
| 	debug_draw_text_world( "This is text in world space", { 0, 200 }, 16.0  ) | ||||
|  | ||||
| 	if debug.mouse_vis { | ||||
| 		// rl.DrawCircleV(  screen_to_world(input.mouse.pos), 10, Color_GreyRed ) | ||||
| 		cursor_world_pos := screen_to_world(input.mouse.pos) | ||||
| 		rl.DrawCircleV( world_to_screen_pos(cursor_world_pos), 5, Color_GreyRed ) | ||||
| 	} | ||||
|  | ||||
| 	rl.DrawCircleV( { 0, 0 }, 1, Color_White ) | ||||
|  | ||||
| 	rl.EndMode2D() | ||||
| } | ||||
|   | ||||
| @@ -171,21 +171,71 @@ update :: proc( delta_time : f64 ) -> b32 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	//endregion | ||||
| 	//endregion Camera Manual Nav | ||||
|  | ||||
| 	//region Imgui Tick | ||||
|  | ||||
| 	{ | ||||
| 		// Creates the root box node, set its as the first parent. | ||||
| 		ui_graph_build( & state.project.workspace.ui ) | ||||
|  | ||||
| 		ui_style({ bg_color = Color_BG_TextBox }) | ||||
| 		ui_set_layout({ size = { 200, 200 }}) | ||||
| 		frame_style_flags : UI_StyleFlags = { | ||||
| 			.Fixed_Position_X, .Fixed_Position_Y, | ||||
| 			.Fixed_Width, .Fixed_Height, | ||||
| 		} | ||||
| 		frame_style_default := UI_Style { | ||||
| 			flags    = frame_style_flags, | ||||
| 			bg_color = Color_BG_TextBox, | ||||
| 		} | ||||
| 		frame_style_disabled := UI_Style { | ||||
| 			flags = frame_style_flags, | ||||
| 			bg_color = Color_Frame_Disabled, | ||||
| 		} | ||||
| 		frame_style_hovered := UI_Style { | ||||
| 			flags = frame_style_flags, | ||||
| 			bg_color = Color_Frame_Hover, | ||||
| 		} | ||||
| 		frame_style_select := UI_Style { | ||||
| 			flags = frame_style_flags, | ||||
| 			bg_color = Color_Frame_Select, | ||||
| 		} | ||||
| 		frame_theme := UI_StyleTheme { styles = { | ||||
| 			frame_style_default, | ||||
| 			frame_style_disabled, | ||||
| 			frame_style_hovered, | ||||
| 			frame_style_select, | ||||
| 		}} | ||||
| 		ui_style_theme( frame_theme ) | ||||
|  | ||||
| 		first_flags : UI_BoxFlags = { .Mouse_Clickable, .Focusable, .Click_To_Focus  } | ||||
| 		ui_box_make( first_flags, "FIRST BOX BOIS" ) | ||||
| 		first_layout := UI_Layout { | ||||
| 			anchor    = {}, | ||||
| 			// alignment = { 0.0, 0.0 }, | ||||
| 			alignment = { 0.5, 0.5 }, | ||||
| 			// alignment = { 1.0, 1.0 }, | ||||
| 			pos       = { 0, 0 }, | ||||
| 			size      = { 200, 200 }, | ||||
| 		} | ||||
| 		ui_set_layout( first_layout ) | ||||
|  | ||||
| 		// First Demo | ||||
| 		when false | ||||
| 		{ | ||||
| 			first_flags : UI_BoxFlags = { .Mouse_Clickable, .Focusable, .Click_To_Focus  } | ||||
| 			first_box := ui_box_make( first_flags, "FIRST BOX BOIS" ) | ||||
| 			signal    := ui_signal_from_box( first_box ) | ||||
|  | ||||
| 			if signal.left_clicked || debug.frame_2_created { | ||||
| 				second_layout := first_layout | ||||
| 				second_layout.pos = { 250, 0 } | ||||
| 				ui_set_layout( second_layout ) | ||||
|  | ||||
| 				second_box := ui_box_make( first_flags, "SECOND BOX BOIS" ) | ||||
| 				signal     := ui_signal_from_box( second_box ) | ||||
|  | ||||
| 				debug.frame_2_created = true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	// endregion | ||||
| 	//endregion Imgui Tick | ||||
|  | ||||
| 	debug.last_mouse_pos = input.mouse.pos | ||||
|  | ||||
|   | ||||
							
								
								
									
										96
									
								
								code/ui.layout.odin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								code/ui.layout.odin
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| package sectr | ||||
|  | ||||
| ui_compute_layout :: proc() | ||||
| { | ||||
| 	state := get_state() | ||||
|  | ||||
| 	root := state.project.workspace.ui.root | ||||
| 	{ | ||||
| 		computed := & root.computed | ||||
| 		bounds   := & computed.bounds | ||||
| 		style    := root.style | ||||
| 		layout   := & style.layout | ||||
|  | ||||
| 		bounds.min = layout.pos | ||||
| 		bounds.max = layout.size | ||||
|  | ||||
| 		computed.content = bounds^ | ||||
| 		computed.padding = {} | ||||
| 	} | ||||
|  | ||||
| 	current := root.first | ||||
| 	for ; current != nil; | ||||
| 	{ | ||||
| 		parent         := current.parent | ||||
| 		parent_content := parent.computed.content | ||||
| 		computed       := & current.computed | ||||
|  | ||||
| 		style  := current.style | ||||
| 		layout := & style.layout | ||||
|  | ||||
| 		margins := Range2 { pts = { | ||||
| 			parent_content.p0 + { layout.margins.left,  layout.margins.top }, | ||||
| 			parent_content.p1 - { layout.margins.right, layout.margins.bottom }, | ||||
| 		}} | ||||
|  | ||||
| 		anchor := & layout.anchor | ||||
| 		pos    : Vec2 | ||||
| 		if UI_StyleFlag.Fixed_Position_X in style.flags { | ||||
| 			pos.x  = layout.pos.x | ||||
| 			pos.x -= margins.p0.x * anchor.x0 | ||||
| 			pos.x += margins.p0.x * anchor.x1 | ||||
| 		} | ||||
| 		if UI_StyleFlag.Fixed_Position_Y in style.flags { | ||||
| 			pos.y  = layout.pos.y | ||||
| 			pos.y -= margins.p1.y * anchor.y0 | ||||
| 			pos.y += margins.p1.y * anchor.y1 | ||||
| 		} | ||||
|  | ||||
| 		size : Vec2 | ||||
| 		if UI_StyleFlag.Fixed_Width in style.flags { | ||||
| 			size.x = layout.size.x | ||||
| 		} | ||||
| 		else { | ||||
| 			// TODO(Ed) : Not sure what todo here... | ||||
| 		} | ||||
| 		if UI_StyleFlag.Fixed_Height in style.flags { | ||||
| 			size.y = layout.size.y | ||||
| 		} | ||||
| 		else { | ||||
| 			// TODO(Ed) : Not sure what todo here... | ||||
| 		} | ||||
|  | ||||
| 		half_size   := size * 0.5 | ||||
| 		size_bounds := Range2 { pts = { | ||||
| 			Vec2 {}, | ||||
| 			{ size.x, -size.y }, | ||||
| 		}} | ||||
|  | ||||
| 		aligned_bounds := Range2 { pts = { | ||||
| 			size_bounds.p0 + size * { -layout.alignment.x,  layout.alignment.y }, | ||||
| 			size_bounds.p1 - size * {  layout.alignment.x, -layout.alignment.y }, | ||||
| 		}} | ||||
|  | ||||
| 		bounds := & computed.bounds | ||||
| 		(bounds^) = aligned_bounds | ||||
| 		(bounds^) = { pts = { | ||||
| 			pos + aligned_bounds.p0, | ||||
| 			pos + aligned_bounds.p1, | ||||
| 		}} | ||||
|  | ||||
| 		border_offset := Vec2 { layout.border_width, layout.border_width } | ||||
| 		padding    := & computed.padding | ||||
| 		(padding^)  = { pts = { | ||||
| 			bounds.p0 + border_offset, | ||||
| 			bounds.p1 - border_offset, | ||||
| 		}} | ||||
|  | ||||
| 		content   := & computed.content | ||||
| 		(content^) = { pts = { | ||||
| 			bounds.p0 + { layout.padding.left,  layout.padding.top }, | ||||
| 			bounds.p1 - { layout.padding.right, layout.padding.bottom }, | ||||
| 		}} | ||||
|  | ||||
| 		current = ui_box_tranverse_next( current ) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										442
									
								
								code/ui.odin
									
									
									
									
									
								
							
							
						
						
									
										442
									
								
								code/ui.odin
									
									
									
									
									
								
							| @@ -1,12 +1,8 @@ | ||||
| package sectr | ||||
|  | ||||
| import "base:runtime" | ||||
|  | ||||
| // TODO(Ed) : This is in Raddbg base_types.h, consider moving outside of UI. | ||||
| Axis2 :: enum i32 { | ||||
| 	Invalid = -1, | ||||
| 	X       = 0, | ||||
| 	Y       = 1, | ||||
| 	Count, | ||||
| } | ||||
|  | ||||
| Corner :: enum i32 { | ||||
| 	Invalid = -1, | ||||
| @@ -21,23 +17,6 @@ Corner :: enum i32 { | ||||
| 	Count = 4, | ||||
| } | ||||
|  | ||||
| Range2 :: struct #raw_union{ | ||||
| 	using _ : struct { | ||||
| 		min, max : Vec2 | ||||
| 	}, | ||||
| 	using _ : struct { | ||||
| 		p0, p1 : Vec2 | ||||
| 	}, | ||||
| 	using _ : struct { | ||||
| 		x0, y0 : f32, | ||||
| 		x1, y1 : f32, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| Rect :: struct { | ||||
| 	top_left, bottom_right : Vec2 | ||||
| } | ||||
|  | ||||
| Side :: enum i32 { | ||||
| 	Invalid = -1, | ||||
| 	Min     = 0, | ||||
| @@ -74,35 +53,36 @@ UI_AnchorPresets :: enum u32 { | ||||
| } | ||||
|  | ||||
| UI_BoxFlag :: enum u64 { | ||||
| 	Disabled, | ||||
| 	Focusable, | ||||
|  | ||||
| 	Mouse_Clickable, | ||||
| 	Keyboard_Clickable, | ||||
| 	Click_To_Focus, | ||||
|  | ||||
| 	Fixed_With, | ||||
| 	Fixed_Height, | ||||
| 	Scroll_X, | ||||
| 	Scroll_Y, | ||||
|  | ||||
| 	Text_Wrap, | ||||
| 	Pan_X, | ||||
| 	Pan_Y, | ||||
|  | ||||
| 	Count, | ||||
| } | ||||
| UI_BoxFlags :: bit_set[UI_BoxFlag; u64] | ||||
| UI_BoxFlag_Scroll :: UI_BoxFlags { .Scroll_X, .Scroll_Y } | ||||
|  | ||||
| // The UI_Box's actual positioning and sizing | ||||
| // There is an excess of rectangles here for debug puproses. | ||||
| UI_Computed :: struct { | ||||
| 	bounds  : Range2, | ||||
| 	border  : Range2, | ||||
| 	margin  : Range2, | ||||
| 	padding : Range2, | ||||
| 	content : Range2, | ||||
| } | ||||
|  | ||||
| UI_LayoutSide :: struct #raw_union { | ||||
| 	using _ :  struct { | ||||
| 		top, bottom : UI_Scalar2, | ||||
| 		left, right : UI_Scalar2, | ||||
| 		top, bottom : UI_Scalar, | ||||
| 		left, right : UI_Scalar, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -110,6 +90,18 @@ UI_Cursor :: struct { | ||||
| 	placeholder : int, | ||||
| } | ||||
|  | ||||
| UI_FramePassKind :: enum { | ||||
| 	Generate, | ||||
| 	Compute, | ||||
| 	Logical, | ||||
| } | ||||
|  | ||||
| UI_InteractState :: struct { | ||||
| 	hot_time      : f32, | ||||
| 	active_time   : f32, | ||||
| 	disabled_time : f32, | ||||
| } | ||||
|  | ||||
| UI_Key :: distinct u64 | ||||
|  | ||||
| UI_Scalar :: f32 | ||||
| @@ -128,8 +120,12 @@ UI_Scalar2 :: [Axis2.Count]UI_Scalar | ||||
|  | ||||
| // Desiered constraints on the UI_Box. | ||||
| UI_Layout :: struct { | ||||
| 	// TODO(Ed) : Should layout have its own flags (separate from the style flags) | ||||
| 	// flags : UI_LayoutFlags | ||||
|  | ||||
| 	// TODO(Ed) : Make sure this is all we need to represent an anchor. | ||||
| 	anchor : Range2, | ||||
| 	anchor    : Range2, | ||||
| 	alignment : Vec2, | ||||
|  | ||||
| 	border_width : UI_Scalar, | ||||
|  | ||||
| @@ -138,19 +134,62 @@ UI_Layout :: struct { | ||||
|  | ||||
| 	corner_radii : [Corner.Count]f32, | ||||
|  | ||||
| 	// TODO(Ed) : Add support for this | ||||
| 	size_to_content : b32, | ||||
| 	size            : Vec2, | ||||
| 	// Position in relative coordinate space. | ||||
| 	// If the box's flags has Fixed_Position, then this will be its aboslute position in the relative coordinate space | ||||
| 	pos : Vec2, | ||||
| 	// TODO(Ed) : Should everything no matter what its parent is use a WS_Pos instead of a raw vector pos? | ||||
|  | ||||
| 	// If the box is a child of the root parent, its automatically in world space and thus will use the tile_pos. | ||||
| 	tile_pos : WS_Pos, | ||||
|  | ||||
| 	// TODO(Ed) : Add support for size_to_content? | ||||
| 	// size_to_content : b32, | ||||
| 	size : Vec2, | ||||
| } | ||||
|  | ||||
| UI_BoxState :: enum { | ||||
| 	Disabled, | ||||
| UI_Signal :: struct { | ||||
| 	box : ^ UI_Box, | ||||
|  | ||||
| 	cursor_pos : Vec2, | ||||
| 	drag_delta : Vec2, | ||||
| 	scroll     : Vec2, | ||||
|  | ||||
| 	left_clicked     : b8, | ||||
| 	right_clicked    : b8, | ||||
| 	double_clicked   : b8, | ||||
| 	keyboard_clicked : b8, | ||||
|  | ||||
| 	pressed     : b8, | ||||
| 	released    : b8, | ||||
| 	dragging    : b8, | ||||
| 	hovering    : b8, | ||||
| 	cursor_over : b8, | ||||
| 	commit      : b8, | ||||
| } | ||||
|  | ||||
| UI_StyleFlag :: enum u32 { | ||||
| 	Fixed_Position_X, | ||||
| 	Fixed_Position_Y, | ||||
| 	Fixed_Width, | ||||
| 	Fixed_Height, | ||||
|  | ||||
| 	Text_Wrap, | ||||
|  | ||||
| 	Count, | ||||
| } | ||||
| UI_StyleFlags :: bit_set[UI_StyleFlag; u32] | ||||
|  | ||||
| UI_StylePreset :: enum u32 { | ||||
| 	Default, | ||||
| 	Disabled, | ||||
| 	Hovered, | ||||
| 	Focused, | ||||
| 	Count, | ||||
| } | ||||
|  | ||||
| UI_Style :: struct { | ||||
| 	flags : UI_StyleFlags, | ||||
|  | ||||
| 	bg_color     : Color, | ||||
| 	border_color : Color, | ||||
|  | ||||
| @@ -168,6 +207,13 @@ UI_Style :: struct { | ||||
| 	transition_time : f32, | ||||
| } | ||||
|  | ||||
| UI_StyleTheme :: struct #raw_union { | ||||
| 	array : [UI_StylePreset.Count] UI_Style, | ||||
| 	using styles : struct { | ||||
| 		default, disabled, hovered, focused : UI_Style, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| UI_TextAlign :: enum u32 { | ||||
| 	Left, | ||||
| 	Center, | ||||
| @@ -175,45 +221,35 @@ UI_TextAlign :: enum u32 { | ||||
| 	Count | ||||
| } | ||||
|  | ||||
| UI_InteractState :: struct { | ||||
| 	hot_time      : f32, | ||||
| 	active_time   : f32, | ||||
| 	disabled_time : f32, | ||||
| } | ||||
|  | ||||
| UI_Box :: struct { | ||||
| 	// Cache ID | ||||
| 	key   : UI_Key, | ||||
| 	label : string, | ||||
|  | ||||
| 	// Regenerated per frame. | ||||
| 	using _      : DLL_NodeFull( UI_Box ), // first, last, prev, next | ||||
| 	using links  : DLL_NodeFull( UI_Box ), // first, last, prev, next | ||||
| 	parent       : ^ UI_Box, | ||||
| 	num_children : i32, | ||||
|  | ||||
| 	flags    : UI_BoxFlags, | ||||
| 	computed : UI_Computed, | ||||
| 	style    : UI_Style, | ||||
| 	theme    : UI_StyleTheme, | ||||
| 	style    : ^ UI_Style, | ||||
|  | ||||
| 	// Persistent Data | ||||
| 	// hash_links : DLL_Node_PN( ^ UI_Box), // This isn't necessary if not using RJF hash table. | ||||
| 	style_delta : f32, | ||||
| 	// prev_computed : UI_Computed, | ||||
| 	// prev_style    : UI_Style,v | ||||
| 	mouse         : UI_InteractState, | ||||
| 	keyboard      : UI_InteractState, | ||||
| } | ||||
|  | ||||
| // UI_BoxFlags_Stack_Size    :: 512 | ||||
| UI_Layout_Stack_Size      :: 512 | ||||
| UI_Style_Stack_Size       :: 512 | ||||
| UI_Parent_Stack_Size      :: 1024 | ||||
| UI_Built_Boxes_Array_Size :: Kilobyte * 8 | ||||
|  | ||||
| UI_FramePassKind :: enum { | ||||
| 	Generate, | ||||
| 	Compute, | ||||
| 	Logical, | ||||
| } | ||||
|  | ||||
| UI_State :: struct { | ||||
| 	// TODO(Ed) : Use these | ||||
| 	build_arenas : [2]Arena, | ||||
| @@ -225,16 +261,19 @@ UI_State :: struct { | ||||
| 	prev_cache : ^ HMapZPL( UI_Box ), | ||||
| 	curr_cache : ^ HMapZPL( UI_Box ), | ||||
|  | ||||
| 	root : ^ UI_Box, | ||||
| 	null_box : ^ UI_Box, // Ryan had this, I don't know why yet. | ||||
| 	root     : ^ UI_Box, | ||||
|  | ||||
| 	// Do we need to recompute the layout? | ||||
| 	layout_dirty  : b32, | ||||
|  | ||||
| 	// TODO(Ed) : Look into using a build arena like Ryan does for these possibly (and thus have a linked-list stack) | ||||
| 	style_stack   : Stack( UI_Style,  UI_Style_Stack_Size ), | ||||
| 	parent_stack  : Stack( ^ UI_Box,  UI_Parent_Stack_Size ), | ||||
| 	theme_stack   : Stack( UI_StyleTheme, UI_Style_Stack_Size ), | ||||
| 	parent_stack  : Stack( ^ UI_Box, UI_Parent_Stack_Size ), | ||||
| 	// flag_stack    : Stack( UI_BoxFlags, UI_BoxFlags_Stack_Size ), | ||||
|  | ||||
| 	hot            : UI_Key, | ||||
| 	active_mouse   : [MouseBtn.count] UI_Key, | ||||
| 	active         : UI_Key, | ||||
| 	clipboard_copy : UI_Key, | ||||
| 	last_clicked   : UI_Key, | ||||
| @@ -242,21 +281,9 @@ UI_State :: struct { | ||||
| 	drag_start_mouse : Vec2, | ||||
| 	// drag_state_arena : ^ Arena, | ||||
| 	// drag_state data  : string, | ||||
| } | ||||
|  | ||||
| ui_key_from_string :: proc( value : string ) -> UI_Key { | ||||
| 	key := cast(UI_Key) crc32( transmute([]byte) value ) | ||||
| 	return key | ||||
| } | ||||
|  | ||||
| ui_box_equal :: proc( a, b : ^ UI_Box ) -> b32 { | ||||
| 	BoxSize :: size_of(UI_Box) | ||||
|  | ||||
| 	result : b32 = true | ||||
| 	result &= a.key   == b.key   // We assume for now the label is the same as the key, if not something is terribly wrong. | ||||
| 	result &= a.flags == b.flags | ||||
|  | ||||
| 	return result | ||||
| 	last_pressed_key    : [MouseBtn.count] UI_Key, | ||||
| 	last_pressed_key_us : [MouseBtn.count] f32, | ||||
| } | ||||
|  | ||||
| ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator ) | ||||
| @@ -287,53 +314,13 @@ ui_reload :: proc( ui : ^ UI_State, cache_allocator : Allocator ) | ||||
| ui_shutdown :: proc() { | ||||
| } | ||||
|  | ||||
| ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) | ||||
| { | ||||
| 	get_state().ui_context = ui | ||||
| 	using get_state().ui_context | ||||
| ui_box_equal :: proc( a, b : ^ UI_Box ) -> b32 { | ||||
| 	BoxSize :: size_of(UI_Box) | ||||
|  | ||||
| 	swap( & curr_cache, & prev_cache ) | ||||
|  | ||||
| 	root = ui_box_make( {}, "root#001" ) | ||||
| 	ui_parent_push(root) | ||||
|  | ||||
| 	log("BUILD GRAPH BEGIN") | ||||
| } | ||||
|  | ||||
| // TODO(Ed) :: Is this even needed? | ||||
| ui_graph_build_end :: proc() | ||||
| { | ||||
| 	ui_parent_pop() | ||||
|  | ||||
| 	// Regenerate the computed layout if dirty | ||||
| 	// ui_compute_layout() | ||||
|  | ||||
| 	get_state().ui_context = nil | ||||
| 	log("BUILD GRAPH END") | ||||
| } | ||||
|  | ||||
| @(deferred_none = ui_graph_build_end) | ||||
| ui_graph_build :: proc( ui : ^ UI_State ) { | ||||
| 	ui_graph_build_begin( ui ) | ||||
| } | ||||
|  | ||||
| ui_parent_push :: proc( ui : ^ UI_Box ) { | ||||
| 	stack := & get_state().ui_context.parent_stack | ||||
| 	stack_push( & get_state().ui_context.parent_stack, ui ) | ||||
| } | ||||
|  | ||||
| ui_parent_pop :: proc() { | ||||
| 	// If size_to_content is set, we need to compute the layout now. | ||||
|  | ||||
| 	// Check to make sure that the parent's children are the same for this frame, | ||||
| 	// if its not we need to mark the layout as dirty. | ||||
|  | ||||
| 	stack_pop( & get_state().ui_context.parent_stack ) | ||||
| } | ||||
|  | ||||
| @(deferred_none = ui_parent_pop) | ||||
| ui_parent :: proc( ui : ^ UI_Box) { | ||||
| 	ui_parent_push( ui ) | ||||
| 	result : b32 = true | ||||
| 	result &= a.key   == b.key   // We assume for now the label is the same as the key, if not something is terribly wrong. | ||||
| 	result &= a.flags == b.flags | ||||
| 	return result | ||||
| } | ||||
|  | ||||
| ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) | ||||
| @@ -362,6 +349,7 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) | ||||
| 		curr_box = set_result | ||||
| 	} | ||||
|  | ||||
| 	// TODO(Ed) : Review this when we learn layouts more... | ||||
| 	if prev_box != nil { | ||||
| 		layout_dirty &= ! ui_box_equal( curr_box, prev_box ) | ||||
| 	} | ||||
| @@ -370,45 +358,225 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) | ||||
| 	} | ||||
|  | ||||
| 	curr_box.flags  = flags | ||||
| 	curr_box.style  = ( stack_peek( & style_stack )  ^ ) | ||||
| 	curr_box.parent = ( stack_peek( & parent_stack ) ^ ) | ||||
| 	curr_box.theme  = stack_peek( & theme_stack ) | ||||
| 	curr_box.parent = stack_peek( & parent_stack ) | ||||
|  | ||||
| 	// Clear old links | ||||
| 	curr_box.parent = nil | ||||
| 	curr_box.links  = {} | ||||
| 	curr_box.num_children = 0 | ||||
|  | ||||
| 	// If there is a parent, setup the relevant references | ||||
| 	if curr_box.parent != nil | ||||
| 	parent := stack_peek( & parent_stack ) | ||||
| 	if parent != nil | ||||
| 	{ | ||||
| 		// dbl_linked_list_push_back( box.parent, nil, box ) | ||||
| 		curr_box.parent.last = curr_box | ||||
|  | ||||
| 		if curr_box.parent.first == nil { | ||||
| 			curr_box.parent.first = curr_box | ||||
| 		} | ||||
| 		dll_full_push_back( null_box, parent, curr_box ) | ||||
| 		parent.num_children += 1 | ||||
| 		curr_box.parent      = parent | ||||
| 	} | ||||
|  | ||||
| 	return curr_box | ||||
| } | ||||
|  | ||||
| ui_set_layout :: proc ( layout : UI_Layout ) { | ||||
| 	log("LAYOUT SET") | ||||
| ui_box_tranverse_next :: proc( box : ^ UI_Box ) -> (^ UI_Box) { | ||||
| 	// If current has children, do them first | ||||
| 	if box.first != nil { | ||||
| 		return box.first | ||||
| 	} | ||||
|  | ||||
| 	if box.next == nil | ||||
| 	{ | ||||
| 		// There is no more adjacent nodes | ||||
| 		if box.parent != nil { | ||||
| 			// Lift back up to parent, and set it to its next. | ||||
| 			return box.parent.next | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return box.next | ||||
| } | ||||
|  | ||||
| ui_compute_layout :: proc() { | ||||
| 	// TODO(Ed) : This generates the bounds for each box. | ||||
| ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) | ||||
| { | ||||
| 	get_state().ui_context = ui | ||||
| 	using get_state().ui_context | ||||
|  | ||||
| 	curr_cache, prev_cache = swap( curr_cache, prev_cache ) | ||||
|  | ||||
| 	if ui.active == UI_Key(0) { | ||||
| 		ui.hot = UI_Key(0) | ||||
| 	} | ||||
|  | ||||
| 	root = ui_box_make( {}, "root#001" ) | ||||
| 	root.style = & root.theme.default | ||||
| 	ui_parent_push(root) | ||||
| } | ||||
|  | ||||
| ui_layout_set_size :: proc( size : Vec2 ) { | ||||
| // TODO(Ed) :: Is this even needed? | ||||
| ui_graph_build_end :: proc() | ||||
| { | ||||
| 	ui_parent_pop() // Should be ui_context.root | ||||
|  | ||||
| 	// Regenerate the computed layout if dirty | ||||
| 	ui_compute_layout() | ||||
|  | ||||
| 	get_state().ui_context = nil | ||||
| } | ||||
|  | ||||
| ui_style_push :: proc( preset : UI_Style ) { | ||||
| 	log("STYLE PUSH") | ||||
| 	stack_push( & get_state().ui_context.style_stack, preset ) | ||||
| @(deferred_none = ui_graph_build_end) | ||||
| ui_graph_build :: proc( ui : ^ UI_State ) { | ||||
| 	ui_graph_build_begin( ui ) | ||||
| } | ||||
|  | ||||
| ui_style_pop :: proc() { | ||||
| 	log("STYLE POP") | ||||
| 	stack_pop( & get_state().ui_context.style_stack ) | ||||
| ui_key_from_string :: proc( value : string ) -> UI_Key { | ||||
| 	key := cast(UI_Key) crc32( transmute([]byte) value ) | ||||
| 	return key | ||||
| } | ||||
|  | ||||
| @(deferred_none = ui_style_pop) | ||||
| ui_style :: proc( preset : UI_Style ) { | ||||
| 	ui_style_push( preset ) | ||||
| ui_parent_push :: proc( ui : ^ UI_Box ) { | ||||
| 	stack := & get_state().ui_context.parent_stack | ||||
| 	stack_push( & get_state().ui_context.parent_stack, ui ) | ||||
| } | ||||
|  | ||||
| ui_parent_pop :: proc() { | ||||
| 	// If size_to_content is set, we need to compute the layout now. | ||||
|  | ||||
| 	// Check to make sure that the parent's children are the same for this frame, | ||||
| 	// if its not we need to mark the layout as dirty. | ||||
|  | ||||
| 	stack_pop( & get_state().ui_context.parent_stack ) | ||||
| } | ||||
|  | ||||
| @(deferred_none = ui_parent_pop) | ||||
| ui_parent :: proc( ui : ^ UI_Box) { | ||||
| 	ui_parent_push( ui ) | ||||
| } | ||||
|  | ||||
| ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal | ||||
| { | ||||
| 	ui    := get_state().ui_context | ||||
| 	input := get_state().input | ||||
|  | ||||
| 	signal := UI_Signal { box = box } | ||||
|  | ||||
| 	if ui == & get_state().project.workspace.ui { | ||||
| 		signal.cursor_pos = screen_to_world( input.mouse.pos ) | ||||
| 	} | ||||
| 	else { | ||||
| 		signal.cursor_pos = input.mouse.pos | ||||
| 	} | ||||
| 	signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds ) | ||||
|  | ||||
| 	left_pressed  := pressed( input.mouse.left ) | ||||
| 	left_released := released( input.mouse.left ) | ||||
|  | ||||
| 	mouse_clickable    := UI_BoxFlag.Mouse_Clickable    in box.flags | ||||
| 	keyboard_clickable := UI_BoxFlag.Keyboard_Clickable in box.flags | ||||
|  | ||||
| 	if mouse_clickable && signal.cursor_over && left_pressed | ||||
| 	{ | ||||
| 		ui.hot                         = box.key | ||||
| 		ui.active                      = box.key | ||||
| 		ui.active_mouse[MouseBtn.Left] = box.key | ||||
| 		ui.drag_start_mouse            = signal.cursor_pos | ||||
| 		ui.last_pressed_key            = box.key | ||||
|  | ||||
| 		signal.pressed = true | ||||
| 		// TODO(Ed) : Support double-click detection | ||||
| 	} | ||||
|  | ||||
| 	if mouse_clickable && signal.cursor_over && left_released | ||||
| 	{ | ||||
| 		ui.active                      = UI_Key(0) | ||||
| 		ui.active_mouse[MouseBtn.Left] = UI_Key(0) | ||||
| 		signal.released     = true | ||||
| 		signal.left_clicked = true | ||||
|  | ||||
| 		ui.last_clicked = box.key | ||||
| 	} | ||||
|  | ||||
| 	if mouse_clickable && ! signal.cursor_over && left_released { | ||||
| 		ui.hot    = UI_Key(0) | ||||
| 		ui.active = UI_Key(0) | ||||
| 		ui.active_mouse[MouseBtn.Left] = UI_Key(0) | ||||
| 		signal.released     = true | ||||
| 		signal.left_clicked = false | ||||
| 	} | ||||
|  | ||||
| 	if keyboard_clickable | ||||
| 	{ | ||||
| 		// TODO(Ed) : Add keyboard interaction support | ||||
| 	} | ||||
|  | ||||
| 	// TODO(Ed) : Add scrolling support | ||||
| 	if UI_BoxFlag.Scroll_X in box.flags { | ||||
|  | ||||
| 	} | ||||
| 	if UI_BoxFlag.Scroll_Y in box.flags { | ||||
|  | ||||
| 	} | ||||
| 	// TODO(Ed) : Add panning support | ||||
| 	if UI_BoxFlag.Pan_X in box.flags { | ||||
|  | ||||
| 	} | ||||
| 	if UI_BoxFlag.Pan_Y in box.flags { | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	if signal.cursor_over && | ||||
| 		ui.hot    == UI_Key(0) || ui.hot    == box.key && | ||||
| 		ui.active == UI_Key(0) || ui.active == box.key | ||||
| 	{ | ||||
| 		ui.hot = box.key | ||||
| 	} | ||||
|  | ||||
| 	style_preset := UI_StylePreset.Default | ||||
| 	if box.key == ui.hot { | ||||
| 		style_preset = UI_StylePreset.Hovered | ||||
| 	} | ||||
| 	if box.key == ui.active { | ||||
| 		style_preset = UI_StylePreset.Focused | ||||
| 	} | ||||
| 	if UI_BoxFlag.Disabled in box.flags { | ||||
| 		style_preset = UI_StylePreset.Disabled | ||||
| 	} | ||||
| 	box.style = & box.theme.array[style_preset] | ||||
|  | ||||
| 	return signal | ||||
| } | ||||
|  | ||||
| ui_style_ref :: proc( box_state : UI_StylePreset ) -> (^ UI_Style) { | ||||
| 	return & stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] | ||||
| } | ||||
|  | ||||
| ui_style_set :: proc ( style : UI_Style, box_state : UI_StylePreset ) { | ||||
| 	stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] = style | ||||
| } | ||||
|  | ||||
| ui_style_set_layout :: proc ( layout : UI_Layout, preset : UI_StylePreset ) { | ||||
| 	stack_peek_ref( & get_state().ui_context.theme_stack ).array[preset].layout = layout | ||||
| } | ||||
|  | ||||
| ui_style_theme_push :: proc( preset : UI_StyleTheme ) { | ||||
| 	stack_push( & get_state().ui_context.theme_stack, preset ) | ||||
| } | ||||
|  | ||||
| ui_style_theme_pop :: proc() { | ||||
| 	stack_pop( & get_state().ui_context.theme_stack ) | ||||
| } | ||||
|  | ||||
| @(deferred_none = ui_style_theme_pop) | ||||
| ui_style_theme :: proc( preset : UI_StyleTheme ) { | ||||
| 	ui_style_theme_push( preset ) | ||||
| } | ||||
|  | ||||
| ui_style_theme_set_layout :: proc ( layout : UI_Layout ) { | ||||
| 	for & preset in stack_peek_ref( & get_state().ui_context.theme_stack ).array { | ||||
| 		preset.layout = layout | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ui_set_layout :: proc { | ||||
| 	ui_style_set_layout, | ||||
| 	ui_style_theme_set_layout, | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user