Ed_
a2b6325b5b
will eventually lift to its own generic widget I still need to implement the corner resize..
553 lines
16 KiB
Odin
553 lines
16 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
|
|
{
|
|
fmt :: str_fmt_alloc
|
|
|
|
@static bar_pos := Vec2{}
|
|
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,
|
|
font_size = 12,
|
|
text_color = Color_White,
|
|
|
|
layout = UI_Layout {
|
|
anchor = {},
|
|
border_width = 1.0,
|
|
pos = bar_pos,
|
|
size = range2( bar_size, {}),
|
|
// padding = { 10, 10, 10, 10 },
|
|
},
|
|
}
|
|
ui_theme_via_style(theme)
|
|
menu_bar = ui_widget("App Menu Bar", UI_BoxFlags {} )
|
|
menu_bar.text = { fmt("%v", bar_pos), {} }
|
|
menu_bar.text.runes = to_runes(menu_bar.text.str)
|
|
|
|
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,
|
|
font_size = 18,
|
|
text_color = Color_White,
|
|
|
|
layout = UI_Layout {
|
|
anchor = range2( {0, 0}, {0.0, 0} ),
|
|
alignment = { 0.0, 1.0 },
|
|
text_alignment = { 0.5, 0.5 },
|
|
pos = { 0, 0 },
|
|
size = range2( {25, bar_size.y}, {0, 0})
|
|
}
|
|
}
|
|
theme := UI_StyleTheme { styles = {
|
|
style,
|
|
style,
|
|
style,
|
|
style,
|
|
}}
|
|
theme.disabled.bg_color = Color_Frame_Disabled
|
|
theme.hot.bg_color = Color_White
|
|
theme.active.bg_color = Color_Frame_Select
|
|
ui_style_theme(theme)
|
|
|
|
move_box : UI_Widget
|
|
{
|
|
move_box = ui_button("Move Box")
|
|
if move_box.dragging {
|
|
bar_pos += input.mouse.delta
|
|
}
|
|
}
|
|
// bar_pos = {0, 400}
|
|
|
|
move_settings_spacer := ui_widget("Move-Settings Spacer", {})
|
|
move_settings_spacer.text = str_intern("spacer")
|
|
move_settings_spacer.style.font_size = 10
|
|
move_settings_spacer.style.bg_color = Color_Transparent
|
|
|
|
// settings_btn : UI_Widget
|
|
{
|
|
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.left_clicked || settings_open
|
|
{
|
|
settings_open = true
|
|
|
|
@static pos := Vec2 {0, 0}
|
|
@static size := Vec2 { 600, 800 }
|
|
resize_border_width : f32 = 10
|
|
|
|
// Prototype for a resize box
|
|
// Will surround one box with a resize borders
|
|
// All sides can have their borders toggled
|
|
resize_box := ui_widget("Settings Menu: Resize Box", {})
|
|
{
|
|
using resize_box
|
|
style.pos = pos
|
|
style.alignment = { 0.5, 0.5 }
|
|
style.bg_color = {}
|
|
style.size = range2( size, {})
|
|
}
|
|
ui_parent(resize_box)
|
|
|
|
// Resize handles and corners
|
|
{
|
|
flags := UI_BoxFlags { .Mouse_Clickable, .Focusable }
|
|
|
|
style_resize_width := UI_Style {
|
|
flags = { .Fixed_Width },
|
|
size = range2({resize_border_width, 0}, {}),
|
|
bg_color = Color_ResizeHandle,
|
|
alignment = {0, 1},
|
|
margins = { resize_border_width, resize_border_width, 0, 0 },
|
|
}
|
|
style_resize_height := style_resize_width
|
|
style_resize_height.flags = {.Fixed_Height}
|
|
style_resize_height.size.min = {0, resize_border_width}
|
|
style_resize_height.margins = { 0, 0, resize_border_width, resize_border_width }
|
|
|
|
ui_theme_via_style(style_resize_width)
|
|
left := ui_widget("Settings Menu: Resize Left Border", flags )
|
|
right := ui_widget("Settings Menu: Resize Right Border", flags)
|
|
right.style.anchor.left = 1
|
|
right.style.alignment = {1, 1}
|
|
|
|
ui_theme_via_style(style_resize_height)
|
|
top := ui_widget("Settings Menu: Resize Top Border", flags )
|
|
bottom := ui_widget("Settings Menu: Resize Bottom Border", flags)
|
|
bottom.style.anchor.top = 1
|
|
bottom.style.alignment = {0, 0}
|
|
|
|
style_corner := UI_Style {
|
|
flags = { .Fixed_Width, .Fixed_Height },
|
|
size = range2({resize_border_width, resize_border_width}, {}),
|
|
bg_color = Color_Blue,
|
|
alignment = {0, 1}
|
|
}
|
|
ui_theme_via_style(style_corner)
|
|
corner_tl := ui_widget("Settings Menu: Corner TL", flags)
|
|
corner_tr := ui_widget("Settings Menu: Corner TR", flags)
|
|
corner_tr.style.anchor = range2({1, 0}, {})
|
|
corner_tr.style.alignment = {1, 1}
|
|
|
|
corner_bl := ui_widget("Settings Menu: Corner BL", flags)
|
|
corner_bl.style.anchor = range2({}, {0, 1})
|
|
corner_bl.style.alignment = {}
|
|
corner_br := ui_widget("Settings Menu: Corner BR", flags)
|
|
corner_br.style.anchor = range2({1, 0}, {0, 1})
|
|
corner_br.style.alignment = {1, 0}
|
|
|
|
process_handle_drag :: #force_inline proc ( handle : ^UI_Widget,
|
|
side_scalar : f32,
|
|
size_axis : ^f32,
|
|
size_delta_axis : f32,
|
|
pos_axis : ^f32,
|
|
alignment_axis : ^f32,
|
|
alignment_while_dragging : f32 )
|
|
{
|
|
if get_state().ui_context.last_pressed_key != handle.key { return }
|
|
|
|
@static was_dragging := false
|
|
@static within_drag := false
|
|
|
|
using handle.signal
|
|
if dragging
|
|
{
|
|
if ! was_dragging {
|
|
was_dragging = true
|
|
(pos_axis ^) += size_axis^ * 0.5 * side_scalar
|
|
}
|
|
(size_axis ^) += size_delta_axis * -side_scalar
|
|
(alignment_axis ^) = alignment_while_dragging
|
|
}
|
|
else if was_dragging && released
|
|
{
|
|
(pos_axis ^) += size_axis^ * 0.5 * -side_scalar
|
|
was_dragging = false
|
|
}
|
|
}
|
|
|
|
process_handle_drag( & right , -1.0, & size.x, input.mouse.delta.x, & pos.x, & resize_box.style.alignment.x, 0)
|
|
process_handle_drag( & left, 1.0, & size.x, input.mouse.delta.x, & pos.x, & resize_box.style.alignment.x, 1)
|
|
process_handle_drag( & top, -1.0, & size.y, input.mouse.delta.y, & pos.y, & resize_box.style.alignment.y, 0)
|
|
process_handle_drag( & bottom, 1.0, & size.y, input.mouse.delta.y, & pos.y, & resize_box.style.alignment.y, 1)
|
|
|
|
// process_corner_drag :: #force_inline proc()
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
settings_menu := ui_widget("Settings Menu", {})
|
|
{
|
|
using settings_menu
|
|
style.alignment = { 0.0, 1.0 }
|
|
style.bg_color = Color_BG_Panel_Translucent
|
|
// style.border_width = 1.0
|
|
// style.border_color = Color_Blue
|
|
style.margins = {
|
|
resize_border_width,
|
|
resize_border_width,
|
|
resize_border_width,
|
|
resize_border_width, }
|
|
}
|
|
ui_parent(settings_menu)
|
|
|
|
ui_theme_via_style({
|
|
bg_color = Color_Transparent,
|
|
font = default_font,
|
|
font_size = 16,
|
|
text_color = Color_White,
|
|
size = range2({0, 40}, {0, 40}) // TODO(Ed): Implment ratio scaling for height
|
|
})
|
|
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 }
|
|
// style.size = {}
|
|
style.anchor = range2( {0, 0.95}, {0, 0} )
|
|
ui_parent(frame_bar)
|
|
|
|
if dragging {
|
|
pos += input.mouse.delta
|
|
}
|
|
|
|
title := ui_text("Settings Menu: Title", str_intern("Settings Menu"))
|
|
{
|
|
using title
|
|
style.alignment = {0, 1}
|
|
style.margins = { 0, 0, 15, 0}
|
|
style.text_alignment = {0.0 , 0.5}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//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 },
|
|
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( & 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
|
|
}
|