Ed_
5b24e591eb
Biggest win was getting the intial fixes for overlapping boxes. Eventually I'll need to add support for sorting actives of top-most ancestors (pretty much just remove from linked list and add to last position (adjust indices of adjacent and new top most, etc)
460 lines
13 KiB
Odin
460 lines
13 KiB
Odin
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
|
|
}
|
|
|
|
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 2D Camera Manual Nav
|
|
// TODO(Ed): This should be per workspace view
|
|
{
|
|
// profile("Camera Manual Nav")
|
|
digital_move_speed : f32 = 200.0
|
|
|
|
if workspace.zoom_target == 0.0 {
|
|
workspace.zoom_target = cam.zoom
|
|
}
|
|
|
|
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, config.cam_min_zoom, config.cam_max_zoom)
|
|
|
|
// 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, config.cam_min_zoom, config.cam_max_zoom) // 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, config.cam_min_zoom, config.cam_max_zoom)
|
|
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 * vec2(1, -1) * ( 1 / cam.zoom )
|
|
cam.target -= pan_velocity
|
|
}
|
|
}
|
|
}
|
|
//endregion 2D Camera Manual Nav
|
|
|
|
// TODO(Ed): We need input buffer so that we can consume input actions based on the UI with priority
|
|
|
|
//region App UI Tick
|
|
{
|
|
profile("App Screenspace Imgui")
|
|
|
|
ui_graph_build( & state.app_ui )
|
|
ui := ui_context
|
|
|
|
/*
|
|
Prototype app menu
|
|
This is a menu bar for the app for now inside the same ui as the workspace's UI state
|
|
Eventually this will get moved out to its own UI state for the app itself.
|
|
*/
|
|
if true
|
|
{
|
|
profile("App Menu Bar")
|
|
fmt :: str_fmt_alloc
|
|
|
|
@static bar_pos := Vec2{0, 100}
|
|
bar_size := vec2( 400, 40 )
|
|
|
|
menu_bar : UI_Widget
|
|
{
|
|
theme := UI_Style {
|
|
flags = {
|
|
},
|
|
bg_color = { 0, 0, 0, 30 },
|
|
border_color = { 0, 0, 0, 200 },
|
|
|
|
font = default_font,
|
|
text_color = Color_White,
|
|
|
|
layout = UI_Layout {
|
|
anchor = {},
|
|
border_width = 1.0,
|
|
font_size = 12,
|
|
pos = bar_pos,
|
|
size = range2( bar_size, {}),
|
|
// padding = { 10, 10, 10, 10 },
|
|
},
|
|
}
|
|
|
|
ui_theme_via_style(theme)
|
|
menu_bar = ui_widget("App Menu Bar", { .Mouse_Clickable} )
|
|
menu_bar.text = to_str_runes_pair( fmt("%v", bar_pos))
|
|
|
|
if (menu_bar.first_frame) {
|
|
// bar_pos = screen_get_corners().top_right - vec2(0, app_window.extent.y * 0.5)
|
|
}
|
|
}
|
|
// Setup Children
|
|
settings_btn : UI_Widget
|
|
{
|
|
ui_parent(menu_bar)
|
|
|
|
style := UI_Style {
|
|
flags = {
|
|
// .Origin_At_Anchor_Center
|
|
.Fixed_Height
|
|
},
|
|
bg_color = Color_Frame_Disabled,
|
|
|
|
font = default_font,
|
|
text_color = Color_White,
|
|
|
|
layout = UI_Layout {
|
|
anchor = range2( {0, 0}, {0.0, 0} ),
|
|
alignment = { 0.0, 1.0 },
|
|
font_size = 18,
|
|
text_alignment = { 0.5, 0.5 },
|
|
pos = { 0, 0 },
|
|
size = range2( {25, bar_size.y}, {0, 0})
|
|
}
|
|
}
|
|
theme := to_ui_styletheme(style)
|
|
theme.disabled.bg_color = Color_Frame_Disabled
|
|
theme.hot.bg_color = Color_Red
|
|
theme.active.bg_color = Color_Frame_Select
|
|
ui_style_theme(theme)
|
|
|
|
move_box : UI_Widget
|
|
{
|
|
move_box = ui_button("Move Box")
|
|
if move_box.active {
|
|
bar_pos += input.mouse.delta
|
|
}
|
|
using move_box
|
|
hot := ui_box_from_key(ui.curr_cache, ui.hot)
|
|
if hot != nil {
|
|
text = to_str_runes_pair(str_fmt_tmp("Hot box: %v %v", hot.label.str, hot.hot_delta))
|
|
style.font = default_font
|
|
style.font_size = 12
|
|
style.text_color = Color_White
|
|
style.text_alignment = {0, 1}
|
|
}
|
|
}
|
|
|
|
move_settings_spacer := ui_widget("Move-Settings Spacer", {})
|
|
{
|
|
using move_settings_spacer
|
|
text = str_intern("spacer")
|
|
style.bg_color = Color_Transparent
|
|
style.layout.font_size = 10
|
|
}
|
|
|
|
settings_btn = ui_button("Settings Btn")
|
|
settings_btn.text = str_intern("Settings")
|
|
settings_btn.style.flags = {
|
|
.Scale_Width_By_Height_Ratio,
|
|
}
|
|
|
|
// HBox layout calculation?
|
|
{
|
|
hb_space_ratio_move_box := 0.1
|
|
hb_space_ratio_move_settings_spacer := 0.05
|
|
hb_space_ratio_settings_btn := 1.0
|
|
|
|
style := & move_box.box.style
|
|
style.anchor.max.x = 0.9
|
|
|
|
style = & move_settings_spacer.box.style
|
|
style.anchor.min.x = 0.1
|
|
style.anchor.max.x = 0.8
|
|
|
|
style = & settings_btn.box.style
|
|
style.anchor.min.x = 0.2
|
|
style.anchor.max.x = 0.55
|
|
}
|
|
}
|
|
|
|
@static settings_open := true
|
|
if settings_btn.pressed || settings_open
|
|
{
|
|
profile("Settings Menu")
|
|
settings_open = true
|
|
|
|
resize_border_width : f32 = 20
|
|
@static pos := Vec2 {0, 0}
|
|
@static size := Vec2 { 200, 200 }
|
|
if size.x < 200 {
|
|
size.x = 200
|
|
}
|
|
if size.y < 200 {
|
|
size.y = 200
|
|
}
|
|
settings_menu := ui_widget("Settings Menu", {.Mouse_Clickable})
|
|
{
|
|
using settings_menu
|
|
style.flags = {
|
|
// .Origin_At_Anchor_Center
|
|
}
|
|
style.pos = pos
|
|
style.alignment = { 1.0, 0.5 }
|
|
style.bg_color = Color_BG_Panel_Translucent
|
|
style.size = range2( size, {})
|
|
text = to_str_runes_pair(str_fmt_tmp("Hot Delta: %v", hot_delta))
|
|
style.font = default_font
|
|
style.font_size = 12
|
|
style.text_color = Color_White
|
|
}
|
|
|
|
ui_parent(settings_menu)
|
|
ui_theme_via_style({
|
|
bg_color = Color_Transparent,
|
|
font = default_font,
|
|
text_color = Color_White,
|
|
size = range2({0, 40}, {0, 40}), // TODO(Ed): Implment ratio scaling for height
|
|
layout = { font_size = 16 },
|
|
})
|
|
ui_style_theme_ref().hot.bg_color = Color_Blue
|
|
frame_bar := ui_widget("Settings Menu: Frame Bar", { .Mouse_Clickable, .Focusable, .Click_To_Focus })
|
|
{
|
|
using frame_bar
|
|
style.bg_color = Color_BG_Panel
|
|
style.flags = {}
|
|
style.alignment = { 0, 1 }
|
|
}
|
|
ui_parent(frame_bar)
|
|
|
|
if frame_bar.active {
|
|
pos += input.mouse.delta
|
|
}
|
|
|
|
title := ui_text("Settings Menu: Title", str_intern("Settings Menu"), {.Disabled})
|
|
{
|
|
using title
|
|
style.alignment = {0, 1}
|
|
style.margins = { 0, 0, 15, 0}
|
|
style.text_alignment = {0 , 0.5}
|
|
}
|
|
|
|
ui_style_theme(ui_style_theme_peek())
|
|
ui_style_theme_ref().default.bg_color = Color_GreyRed
|
|
ui_style_theme_ref().hot. bg_color = Color_Red
|
|
close_btn := ui_button("Settings Menu: Close Btn")
|
|
{
|
|
using close_btn
|
|
text = str_intern("close")
|
|
// style.bg_color = Color_GreyRed
|
|
style.size.min = {50, 0}
|
|
style.anchor = range2( {1.0, 0}, {})
|
|
style.alignment = {1, 1}
|
|
style.text_alignment = {0.5, 0.5}
|
|
if close_btn.pressed {
|
|
settings_open = false
|
|
}
|
|
}
|
|
|
|
ui_resizable_handles( & settings_menu, & pos, & size )
|
|
}
|
|
}
|
|
}
|
|
//endregion App UI Tick
|
|
|
|
//region WorkspaceImgui Tick
|
|
{
|
|
profile("Workspace Imgui")
|
|
|
|
// 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 },
|
|
font_size = 30,
|
|
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,
|
|
text_color = Color_White,
|
|
|
|
layout = default_layout,
|
|
}
|
|
|
|
frame_theme := to_ui_styletheme(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( & default_layout, & frame_style_default )
|
|
// test_whitespace_ast( & default_layout, & frame_style_default )
|
|
}
|
|
//endregion Workspace Imgui Tick
|
|
|
|
debug.last_mouse_pos = input.mouse.pos
|
|
|
|
should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose()
|
|
return should_shutdown
|
|
}
|