package sectr import "base:runtime" import "core:math" import "core:math/linalg" import "core:os" import str "core:strings" import rl "vendor:raylib" DebugActions :: struct { load_project : b32, save_project : b32, pause_renderer : b32, load_auto_snapshot : b32, record_replay : b32, play_replay : b32, show_mouse_pos : b32, mouse_select : b32, cam_move_up : b32, cam_move_left : b32, cam_move_down : b32, cam_move_right : b32, cam_mouse_pan : b32, } poll_debug_actions :: proc( actions : ^ DebugActions, input : ^ InputState ) { // profile(#procedure) using actions using input modifier_active := keyboard.right_alt.ended_down || keyboard.right_control.ended_down || keyboard.right_shift.ended_down || keyboard.left_alt.ended_down || keyboard.left_control.ended_down || keyboard.left_shift.ended_down load_project = keyboard.left_control.ended_down && pressed( keyboard.O ) save_project = keyboard.left_control.ended_down && pressed( keyboard.S ) base_replay_bind := keyboard.right_alt.ended_down && pressed( keyboard.L) record_replay = base_replay_bind && keyboard.right_shift.ended_down play_replay = base_replay_bind && ! keyboard.right_shift.ended_down show_mouse_pos = keyboard.right_alt.ended_down && pressed(keyboard.M) mouse_select = pressed(mouse.left) cam_move_up = keyboard.W.ended_down && ( ! modifier_active || keyboard.left_shift.ended_down ) cam_move_left = keyboard.A.ended_down && ( ! modifier_active || keyboard.left_shift.ended_down ) cam_move_down = keyboard.S.ended_down && ( ! modifier_active || keyboard.left_shift.ended_down ) cam_move_right = keyboard.D.ended_down && ( ! modifier_active || keyboard.left_shift.ended_down ) cam_mouse_pan = mouse.right.ended_down && ! pressed(mouse.right) } frametime_delta32 :: #force_inline proc "contextless" () -> f32 { return cast(f32) get_state().frametime_delta_seconds } //@(optimization_mode="speed") update :: proc( delta_time : f64 ) -> b32 { profile(#procedure) state := get_state(); using state replay := & Memory_App.replay 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 } state.input, state.input_prev = swap( state.input, state.input_prev ) poll_input( state.input_prev, state.input ) debug_actions : DebugActions = {} poll_debug_actions( & debug_actions, state.input ) // Saving & Loading { if debug_actions.save_project { project_save( & project ) } if debug_actions.load_project { project_load( str_tmp_from_any( project.path, project.name, ".sectr_proj", sep = "" ), & project ) } } //region Input Replay // TODO(Ed) : Implment host memory mapping api when false { if debug_actions.record_replay { #partial switch replay.mode { case ReplayMode.Off : { save_snapshot( & Memory_App.snapshot ) replay_recording_begin( Path_Input_Replay ) } case ReplayMode.Record : { replay_recording_end() } }} if debug_actions.play_replay { switch replay.mode { case ReplayMode.Off : { if ! file_exists( Path_Input_Replay ) { save_snapshot( & Memory_App.snapshot ) replay_recording_begin( Path_Input_Replay ) } else { load_snapshot( & Memory_App.snapshot ) replay_playback_begin( Path_Input_Replay ) } } case ReplayMode.Playback : { replay_playback_end() load_snapshot( & Memory_App.snapshot ) } case ReplayMode.Record : { replay_recording_end() load_snapshot( & Memory_App.snapshot ) replay_playback_begin( Path_Input_Replay ) } }} if replay.mode == ReplayMode.Record { record_input( replay.active_file, input ) } else if replay.mode == ReplayMode.Playback { play_input( replay.active_file, input ) } } //endregion Input Replay if debug_actions.show_mouse_pos { debug.mouse_vis = !debug.mouse_vis } //region Camera Manual Nav { // profile("Camera Manual Nav") digital_move_speed : f32 = 200.0 if workspace.zoom_target == 0.0 { workspace.zoom_target = cam.zoom } config.cam_zoom_smooth_snappiness = 10.0 config.cam_zoom_mode = .Smooth switch config.cam_zoom_mode { case .Smooth: zoom_delta := input.mouse.vertical_wheel * config.cam_zoom_sensitivity_smooth workspace.zoom_target *= 1 + zoom_delta * f32(delta_time) workspace.zoom_target = clamp(workspace.zoom_target, 0.05, 10.0) // Linearly interpolate cam.zoom towards zoom_target lerp_factor := config.cam_zoom_smooth_snappiness // Adjust this value to control the interpolation speed cam.zoom += (workspace.zoom_target - cam.zoom) * lerp_factor * f32(delta_time) cam.zoom = clamp(cam.zoom, 0.05, 10.0) // Ensure cam.zoom stays within bounds case .Digital: zoom_delta := input.mouse.vertical_wheel * config.cam_zoom_sensitivity_digital workspace.zoom_target = clamp(workspace.zoom_target + zoom_delta, 0.05, 10.0) cam.zoom = workspace.zoom_target } move_velocity : Vec2 = { - cast(f32) i32(debug_actions.cam_move_left) + cast(f32) i32(debug_actions.cam_move_right), - 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 if debug_actions.cam_mouse_pan { if is_within_screenspace(input.mouse.pos) { pan_velocity := input.mouse.delta * ( 1 / cam.zoom ) cam.target -= pan_velocity } } } //endregion Camera Manual Nav //region Imgui Tick { profile("Imgui Tick") // Creates the root box node, set its as the first parent. ui_graph_build( & state.project.workspace.ui ) ui := ui_context frame_style_flags : UI_StyleFlags = { .Fixed_Position_X, .Fixed_Position_Y, .Fixed_Width, .Fixed_Height, } default_layout := UI_Layout { anchor = {}, alignment = { 0., 0.0 }, text_alignment = { 0.0, 0.0 }, // corner_radii = { 0.2, 0.2, 0.2, 0.2 }, pos = { 0, 0 }, size = range2( { 1000, 1000 }, {}), // padding = { 20, 20, 20, 20 } } frame_style_default := UI_Style { flags = frame_style_flags, bg_color = Color_BG_TextBox, font = default_font, font_size = 30, text_color = Color_White, layout = default_layout, } frame_theme := UI_StyleTheme { styles = { frame_style_default, frame_style_default, frame_style_default, frame_style_default, }} frame_theme.disabled.bg_color = Color_Frame_Disabled frame_theme.hot.bg_color = Color_Frame_Hover frame_theme.active.bg_color = Color_Frame_Select ui_style_theme( frame_theme ) config.ui_resize_border_width = 2.5 // test_draggable() // test_text_box() // test_parenting() if true { // frame := ui_widget( "Frame", {} ) // ui_parent(frame) parent_layout := default_layout parent_layout.size = range2( { 300, 300 }, {} ) parent_layout.alignment = { 0.5, 0.5 } parent_layout.margins = { 100, 100, 100, 100 } parent_layout.padding = {} parent_layout.pos = { 0, 0 } parent_theme := frame_style_default parent_theme.layout = parent_layout parent_theme.flags = { // .Fixed_Position_X, .Fixed_Position_Y, .Fixed_Width, .Fixed_Height, } ui_theme_via_style(parent_theme) parent := ui_widget( "Parent", { .Mouse_Clickable, .Mouse_Resizable }) ui_parent(parent) { if parent.first_frame { debug.draggable_box_pos = parent.style.layout.pos debug.draggable_box_size = parent.style.layout.size.min } if parent.dragging { debug.draggable_box_pos += mouse_world_delta() } if parent.resizing { og_layout := ui_context.active_start_style.layout center := debug.draggable_box_pos original_distance := linalg.distance(ui.active_start_signal.cursor_pos, center) cursor_distance := linalg.distance(parent.cursor_pos, center) scale_factor := cursor_distance * (1 / original_distance) debug.draggable_box_size = og_layout.size.min * scale_factor } if (ui.hot == parent.key) && (ui.hot_resizable || ui.active_start_signal.resizing) { parent.style.bg_color = Color_Blue } parent.style.layout.pos = debug.draggable_box_pos parent.style.layout.size.min = debug.draggable_box_size } child_layout := default_layout child_layout.size = range2({ 0, 0 }, { 0, 0 }) child_layout.alignment = { 0.5, 0.5 } child_layout.margins = { 20, 20, 20, 20 } child_layout.padding = {} child_layout.anchor = range2({ 0.0, 0.0 }, { 0.0, 0.0 }) child_layout.pos = { 0, 0 } child_theme := frame_style_default child_theme.bg_color = Color_GreyRed child_theme.flags = { // .Fixed_Width, .Fixed_Height, } child_theme.layout = child_layout ui_theme_via_style(child_theme) child := ui_widget( "Child", { .Mouse_Clickable }) } // Whitespace AST test if false { profile("Whitespace AST test") text_style := frame_style_default text_style.flags = { .Origin_At_Anchor_Center, .Fixed_Position_X, .Fixed_Position_Y, // .Fixed_Width, .Fixed_Height, } text_style.text_alignment = { 0.0, 0.5 } text_style.alignment = { 0.0, 1.0 } text_style.size.min = { 1600, 30 } text_theme := UI_StyleTheme { styles = { text_style, text_style, text_style, text_style, }} text_theme.default.bg_color = Color_Transparent text_theme.disabled.bg_color = Color_Frame_Disabled text_theme.hot.bg_color = Color_Frame_Hover text_theme.active.bg_color = Color_Frame_Select ui_style_theme( text_theme ) layout_text := text_style.layout alloc_error : AllocatorError; success : bool // debug.lorem_content, success = os.read_entire_file( debug.path_lorem, frame_allocator() ) // debug.lorem_parse, alloc_error = pws_parser_parse( transmute(string) debug.lorem_content, frame_slab_allocator() ) // verify( alloc_error == .None, "Faield to parse due to allocation failure" ) text_space := str_intern( " " ) text_tab := str_intern( "\t") // index := 0 widgets : Array(UI_Widget) widgets, alloc_error = array_init_reserve( UI_Widget, frame_slab_allocator(), Kilobyte * 4 ) widgets_ptr := & widgets label_id := 0 line_id := 0 for line in array_to_slice_num( debug.lorem_parse.lines ) { if line_id == 0 { line_id += 1 continue } ui_style_theme_set_layout( layout_text ) line_hbox := ui_widget(str_fmt_alloc( "line %v", line_id ), {}) if line_hbox.key == ui.hot { line_hbox.text = StringCached {} ui_parent(line_hbox) chunk_layout := layout_text chunk_layout.alignment = { 0.0, 1.0 } chunk_layout.anchor = range2({ 0.0, 0 }, { 0.0, 0 }) chunk_layout.pos = {} chunk_style := text_style chunk_style.flags = { .Fixed_Position_X, .Size_To_Text } chunk_style.layout = chunk_layout chunk_theme := UI_StyleTheme { styles = { chunk_style, chunk_style, chunk_style, chunk_style, }} ui_style_theme( chunk_theme ) head := line.first for ; head != nil; { ui_style_theme_set_layout( chunk_layout ) widget : UI_Widget #partial switch head.type { case .Visible: label := str_intern( str_fmt_alloc( "%v %v", head.content.str, label_id )) widget = ui_text( label.str, head.content ) label_id += 1 chunk_layout.pos.x += size_range2( widget.computed.bounds ).x case .Spaces: label := str_intern( str_fmt_alloc( "%v %v", "space", label_id )) widget = ui_text_spaces( label.str ) label_id += 1 for idx in 1 ..< len( head.content.runes ) { // TODO(Ed): VIRTUAL WHITESPACE // widget.style.layout.size.x += range2_size( widget.computed.bounds ) } chunk_layout.pos.x += size_range2( widget.computed.bounds ).x case .Tabs: label := str_intern( str_fmt_alloc( "%v %v", "tab", label_id )) widget = ui_text_tabs( label.str ) label_id += 1 for idx in 1 ..< len( head.content.runes ) { // widget.style.layout.size.x += range2_size( widget.computed.bounds ) } chunk_layout.pos.x += size_range2( widget.computed.bounds ).x } array_append( widgets_ptr, widget ) head = head.next } line_hbox.style.size.min.x = chunk_layout.pos.x } else { builder_backing : [16 * Kilobyte] byte builder := str.builder_from_bytes( builder_backing[:] ) line_hbox.style.flags |= { .Size_To_Text } head := line.first.next for ; head != nil; { str.write_string( & builder, head.content.str ) head = head.next } line_hbox.text = str_intern( to_string( builder ) ) } array_append( widgets_ptr, line_hbox ) layout_text.pos.x = text_style.layout.pos.x layout_text.pos.y += size_range2(line_hbox.computed.bounds).y line_id += 1 } label_id += 1 // Dummy action } } //endregion Imgui Tick debug.last_mouse_pos = input.mouse.pos should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose() return should_shutdown }