Progress on setting up app's UI and horizontal/vertical box widgets
This commit is contained in:
parent
e282397bf0
commit
b8e8e7c88a
@ -190,6 +190,9 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
|
||||
log( "Default font loaded" )
|
||||
}
|
||||
|
||||
// Setup the app ui state
|
||||
ui_startup( & app_ui, cache_allocator = persistent_slab_allocator() )
|
||||
|
||||
// Demo project setup
|
||||
{
|
||||
using project
|
||||
@ -274,6 +277,7 @@ reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_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.
|
||||
|
@ -10,6 +10,7 @@ import rl "vendor:raylib"
|
||||
|
||||
Str_App_State := "App State"
|
||||
|
||||
|
||||
Memory_App : Memory
|
||||
|
||||
Memory_Base_Address_Persistent :: Terabyte * 1
|
||||
@ -177,6 +178,7 @@ State :: struct {
|
||||
|
||||
config : AppConfig,
|
||||
app_window : AppWindow,
|
||||
app_ui : UI_State,
|
||||
|
||||
monitor_id : i32,
|
||||
monitor_refresh_hz : i32,
|
||||
@ -203,8 +205,7 @@ State :: struct {
|
||||
}
|
||||
|
||||
get_state :: proc "contextless" () -> ^ State {
|
||||
// return cast( ^ State ) Memory_App.persistent.reserve_start
|
||||
return Memory_App.state
|
||||
return cast( ^ State ) Memory_App.persistent.reserve_start
|
||||
}
|
||||
|
||||
AppWindow :: struct {
|
||||
@ -223,8 +224,8 @@ ProjectConfig :: struct {
|
||||
}
|
||||
|
||||
Project :: struct {
|
||||
path : StringCached,
|
||||
name : StringCached,
|
||||
path : StrRunesPair,
|
||||
name : StrRunesPair,
|
||||
|
||||
config : ProjectConfig,
|
||||
codebase : CodeBase,
|
||||
@ -242,7 +243,7 @@ Frame :: struct
|
||||
}
|
||||
|
||||
Workspace :: struct {
|
||||
name : StringCached,
|
||||
name : StrRunesPair,
|
||||
|
||||
cam : Camera,
|
||||
zoom_target : f32,
|
||||
|
@ -20,14 +20,14 @@ StringKey :: distinct u64
|
||||
RunesCached :: []rune
|
||||
|
||||
// TODO(Ed): Should this just track the key instead? (by default)
|
||||
StringCached :: struct {
|
||||
StrRunesPair :: struct {
|
||||
str : string,
|
||||
runes : []rune,
|
||||
}
|
||||
|
||||
StringCache :: struct {
|
||||
slab : Slab,
|
||||
table : HMapZPL(StringCached),
|
||||
table : HMapZPL(StrRunesPair),
|
||||
}
|
||||
|
||||
str_cache_init :: proc( /*allocator : Allocator*/ ) -> ( cache : StringCache ) {
|
||||
@ -61,7 +61,7 @@ str_cache_init :: proc( /*allocator : Allocator*/ ) -> ( cache : StringCache ) {
|
||||
cache.slab, alloc_error = slab_init( & policy, allocator = persistent_allocator(), dbg_name = dbg_name )
|
||||
verify(alloc_error == .None, "Failed to initialize the string cache" )
|
||||
|
||||
cache.table, alloc_error = zpl_hmap_init_reserve( StringCached, persistent_allocator(), 1 * Megabyte, dbg_name )
|
||||
cache.table, alloc_error = zpl_hmap_init_reserve( StrRunesPair, persistent_allocator(), 1 * Megabyte, dbg_name )
|
||||
return
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ str_cache_init :: proc( /*allocator : Allocator*/ ) -> ( cache : StringCache ) {
|
||||
// cache : ^StringCache,
|
||||
str_intern :: proc(
|
||||
content : string
|
||||
) -> StringCached
|
||||
) -> StrRunesPair
|
||||
{
|
||||
// profile(#procedure)
|
||||
cache := & get_state().string_cache
|
||||
@ -97,8 +97,8 @@ str_intern :: proc(
|
||||
|
||||
slab_validate_pools( get_state().persistent_slab )
|
||||
|
||||
// result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) byte_slice(str_mem, length), runes } )
|
||||
result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) str_mem, runes } )
|
||||
// result, alloc_error = zpl_hmap_set( & cache.table, key, StrRunesPair { transmute(string) byte_slice(str_mem, length), runes } )
|
||||
result, alloc_error = zpl_hmap_set( & cache.table, key, StrRunesPair { transmute(string) str_mem, runes } )
|
||||
verify( alloc_error == .None, "String cache had a backing allocator error" )
|
||||
|
||||
slab_validate_pools( get_state().persistent_slab )
|
||||
@ -108,7 +108,7 @@ str_intern :: proc(
|
||||
return (result ^)
|
||||
}
|
||||
|
||||
// runes_intern :: proc( content : []rune ) -> StringCached
|
||||
// runes_intern :: proc( content : []rune ) -> StrRunesPair
|
||||
// {
|
||||
// cache := get_state().string_cache
|
||||
// }
|
||||
|
@ -7,7 +7,7 @@ rune16 :: distinct u16
|
||||
|
||||
// Exposing the alloc_error
|
||||
@(require_results)
|
||||
string_to_runes :: proc ( content : string, allocator := context.allocator) -> (runes : []rune, alloc_error : AllocatorError) {
|
||||
string_to_runes :: proc ( content : string, allocator := context.allocator) -> (runes : []rune, alloc_error : AllocatorError) #optional_allocator_error {
|
||||
num := str_rune_count(content)
|
||||
|
||||
runes, alloc_error = make([]rune, num, allocator)
|
||||
|
@ -77,6 +77,13 @@ Range2 :: struct #raw_union {
|
||||
x0, y0 : f32,
|
||||
x1, y1 : f32,
|
||||
},
|
||||
using side : struct {
|
||||
left, bottom : f32,
|
||||
right, top : f32,
|
||||
},
|
||||
ratio : struct {
|
||||
x, y : f32,
|
||||
},
|
||||
|
||||
// TODO(Ed) : Test these
|
||||
array : [4]f32,
|
||||
|
@ -17,6 +17,8 @@ Rotor2 :: struct {
|
||||
|
||||
rotor2_to_complex64 :: #force_inline proc( rotor : Rotor2 ) -> complex64 { return transmute(complex64) rotor; }
|
||||
|
||||
vec2 :: #force_inline proc "contextless" ( x, y : f32 ) -> Vec2 { return {x, y} }
|
||||
|
||||
dot_vec2 :: proc "contextless" ( a, b : Vec2 ) -> (s : f32) {
|
||||
x := a.x * b.x
|
||||
y := a.y + b.y
|
||||
|
@ -63,7 +63,7 @@ PWS_LexResult :: struct {
|
||||
PWS_Token :: struct {
|
||||
type : PWS_TokenType,
|
||||
line, column : u32,
|
||||
content : StringCached,
|
||||
content : StrRunesPair,
|
||||
}
|
||||
|
||||
PWS_AST_Type :: enum u32 {
|
||||
@ -80,7 +80,7 @@ PWS_AST :: struct {
|
||||
type : PWS_AST_Type,
|
||||
|
||||
line, column : u32,
|
||||
content : StringCached,
|
||||
content : StrRunesPair,
|
||||
}
|
||||
|
||||
PWS_ParseError :: struct {
|
||||
|
@ -138,6 +138,14 @@ screen_size :: proc "contextless" () -> AreaSize {
|
||||
return transmute(AreaSize) ( extent * 2.0 )
|
||||
}
|
||||
|
||||
screen_get_bounds :: #force_inline proc "contextless" () -> Range2 {
|
||||
state := get_state(); using state
|
||||
screen_extent := state.app_window.extent
|
||||
bottom_left := Vec2 { -screen_extent.x, -screen_extent.y}
|
||||
top_right := Vec2 { screen_extent.x, screen_extent.y}
|
||||
return range2( bottom_left, top_right )
|
||||
}
|
||||
|
||||
screen_get_corners :: #force_inline proc "contextless"() -> BoundsCorners2 {
|
||||
state := get_state(); using state
|
||||
screen_extent := state.app_window.extent
|
||||
|
@ -66,7 +66,7 @@ draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.C
|
||||
rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT)
|
||||
}
|
||||
|
||||
draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
|
||||
draw_text_string_cached :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
|
||||
{
|
||||
// profile(#procedure)
|
||||
state := get_state(); using state
|
||||
|
@ -23,17 +23,42 @@ render :: proc()
|
||||
|
||||
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
|
||||
|
||||
//region App UI
|
||||
Render_App_UI:
|
||||
{
|
||||
profile("App UI")
|
||||
ui := & state.app_ui
|
||||
root := ui.root
|
||||
if root.num_children == 0 {
|
||||
break Render_App_UI
|
||||
}
|
||||
|
||||
current := root.first
|
||||
for ; current != nil; current = ui_box_tranverse_next( current )
|
||||
{
|
||||
// profile("Box")
|
||||
parent := current.parent
|
||||
|
||||
style := current.style
|
||||
computed := & current.computed
|
||||
|
||||
computed_size := computed.bounds.p1 - computed.bounds.p0
|
||||
}
|
||||
}
|
||||
//endregion App UI
|
||||
|
||||
screen_top_left : Vec2 = {
|
||||
-win_extent.x + cam.target.x,
|
||||
-win_extent.y + cam.target.y,
|
||||
}
|
||||
|
||||
profile("Render Screenspace")
|
||||
fps_msg := str_fmt_tmp( "FPS: %f", fps_avg)
|
||||
fps_msg_width := measure_text_size( fps_msg, default_font, 16.0, 0.0 ).x
|
||||
fps_msg_pos := screen_get_corners().top_right - { fps_msg_width, 0 }
|
||||
@ -230,16 +255,19 @@ render_mode_2d :: proc()
|
||||
{
|
||||
draw_rectangle( rect_bounds, style )
|
||||
}
|
||||
if style.border_width > 0 {
|
||||
draw_rectangle_lines( rect_bounds, style, style.border_color, style.border_width )
|
||||
}
|
||||
// profile_end()
|
||||
|
||||
line_thickness := 1 * cam_zoom_ratio
|
||||
|
||||
// profile_begin("rl.DrawRectangleRoundedLines: padding & content")
|
||||
if equal_range2(computed.content, computed.padding) {
|
||||
draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Padding_Bounds, line_thickness )
|
||||
// draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Padding_Bounds, line_thickness )
|
||||
}
|
||||
else {
|
||||
draw_rectangle_lines( rect_content, style, Color_Debug_UI_Content_Bounds, line_thickness )
|
||||
// draw_rectangle_lines( rect_content, style, Color_Debug_UI_Content_Bounds, line_thickness )
|
||||
}
|
||||
// profile_end()
|
||||
|
||||
@ -291,7 +319,7 @@ render_mode_2d :: proc()
|
||||
rl.DrawCircleV( world_to_screen_pos(cursor_world_pos), 5, Color_GreyRed )
|
||||
}
|
||||
|
||||
// rl.DrawCircleV( { 0, 0 }, 1 * cam_zoom_ratio, Color_White )
|
||||
rl.DrawCircleV( { 0, 0 }, 1 * cam_zoom_ratio, Color_White )
|
||||
|
||||
rl.EndMode2D()
|
||||
}
|
||||
|
@ -191,9 +191,9 @@ update :: proc( delta_time : f64 ) -> b32
|
||||
}
|
||||
//endregion 2D Camera Manual Nav
|
||||
|
||||
//region Imgui Tick
|
||||
//region WorkspaceImgui Tick
|
||||
{
|
||||
profile("Imgui Tick")
|
||||
profile("Workspace Imgui")
|
||||
|
||||
// Creates the root box node, set its as the first parent.
|
||||
ui_graph_build( & state.project.workspace.ui )
|
||||
@ -238,235 +238,181 @@ update :: proc( delta_time : f64 ) -> b32
|
||||
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 )
|
||||
|
||||
// test_parenting()
|
||||
if false
|
||||
{
|
||||
// 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 = { 5, 10, 5, 5 }
|
||||
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 = { 5, 5, 5, 5 }
|
||||
child_layout.anchor = range2({ 0.2, 0.1 }, { 0.1, 0.15 })
|
||||
child_layout.pos = { 0, 0 }
|
||||
|
||||
child_theme := frame_style_default
|
||||
child_theme.bg_color = Color_GreyRed
|
||||
child_theme.flags = {
|
||||
// .Fixed_Width, .Fixed_Height,
|
||||
.Origin_At_Anchor_Center
|
||||
}
|
||||
child_theme.layout = child_layout
|
||||
ui_theme_via_style(child_theme)
|
||||
child := ui_widget( "Child", { .Mouse_Clickable })
|
||||
}
|
||||
|
||||
// Whitespace AST test
|
||||
/*
|
||||
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("Whitespace AST test")
|
||||
fmt :: str_fmt_alloc
|
||||
|
||||
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 }
|
||||
@static bar_pos := Vec2 {}
|
||||
bar_size := vec2( 400, 40 )
|
||||
|
||||
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(), 8 )
|
||||
widgets, alloc_error = array_init_reserve( UI_Widget, frame_slab_allocator(), 4 * Kilobyte )
|
||||
widgets_ptr := & widgets
|
||||
|
||||
label_id := 0
|
||||
|
||||
line_id := 0
|
||||
for line in array_to_slice_num( debug.lorem_parse.lines )
|
||||
menu_bar : UI_Widget
|
||||
{
|
||||
if line_id == 0 {
|
||||
line_id += 1
|
||||
continue
|
||||
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 * (1.0/cam.zoom),
|
||||
pos = screen_to_world(bar_pos),
|
||||
size = range2( bar_size * (1.0/cam.zoom), {}),
|
||||
// padding = { 10, 10, 10, 10 },
|
||||
},
|
||||
}
|
||||
ui_theme_via_style(theme)
|
||||
menu_bar = ui_widget("App Menu Bar", UI_BoxFlags {} )
|
||||
}
|
||||
// Setup Children
|
||||
settings_btn : UI_Widget
|
||||
{
|
||||
ui_parent(menu_bar)
|
||||
|
||||
ui_style_theme_set_layout( layout_text )
|
||||
line_hbox := ui_widget(str_fmt_alloc( "line %v", line_id ), {})
|
||||
style := UI_Style {
|
||||
flags = {
|
||||
// .Origin_At_Anchor_Center
|
||||
.Fixed_Height
|
||||
},
|
||||
bg_color = Color_Frame_Disabled,
|
||||
|
||||
if line_hbox.key == ui.hot
|
||||
{
|
||||
line_hbox.text = StringCached {}
|
||||
ui_parent(line_hbox)
|
||||
font = default_font,
|
||||
font_size = 18 * (1.0/cam.zoom),
|
||||
text_color = Color_White,
|
||||
|
||||
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
|
||||
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} * (1.0/cam.zoom), {0, 0})
|
||||
}
|
||||
|
||||
line_hbox.style.size.min.x = chunk_layout.pos.x
|
||||
}
|
||||
else
|
||||
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
|
||||
{
|
||||
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
|
||||
move_box = ui_button("Move Box")
|
||||
if move_box.dragging {
|
||||
// bar_pos += mouse_world_delta()
|
||||
bar_pos += state.input.mouse.delta
|
||||
}
|
||||
|
||||
line_hbox.text = str_intern( to_string( builder ) )
|
||||
// if len(line_hbox.text.str) == 0 {
|
||||
// line_hbox.text = str_intern( " " )
|
||||
// }
|
||||
}
|
||||
|
||||
if len(line_hbox.text.str) > 0 {
|
||||
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
|
||||
}
|
||||
else {
|
||||
layout_text.pos.y += size_range2( (& widgets.data[ widgets.num - 1 ]).computed.bounds ).y
|
||||
move_settings_spacer := ui_widget("Move-Settings Spacer", {})
|
||||
move_settings_spacer.text = str_intern("")
|
||||
move_settings_spacer.style.font_size = 10 * (1.0/cam.zoom)
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
line_id += 1
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
label_id += 1 // Dummy action
|
||||
@static settings_open := false
|
||||
if settings_btn.left_clicked || settings_open
|
||||
{
|
||||
settings_open = true
|
||||
|
||||
@static pos := Vec2 {0, 0}
|
||||
|
||||
settings_menu := ui_widget("Settings Menu", { .Mouse_Clickable, .Focusable, .Click_To_Focus })
|
||||
settings_menu.style.pos = screen_to_world(pos)
|
||||
settings_menu.style.size = range2( {600, 800} * (1/cam.zoom), {})
|
||||
settings_menu.style.text_alignment = {0, 0.0}
|
||||
settings_menu.style.alignment = { 0.5, 0.5 }
|
||||
settings_menu.style.bg_color = Color_Transparent
|
||||
settings_menu.style.border_width = 1.0 * (1/cam.zoom)
|
||||
settings_menu.style.border_color = Color_Blue
|
||||
// settings_menu.style.padding = { 10, 10, 10, 10 }
|
||||
|
||||
settings_menu.text = { fmt("%v", pos), {} }
|
||||
settings_menu.text.runes = to_runes(settings_menu.text.str)
|
||||
settings_menu.style.font_size = 16 * (1/cam.zoom)
|
||||
|
||||
// pos.x += frametime_delta32() * 100
|
||||
if settings_menu.dragging {
|
||||
pos += state.input.mouse.delta
|
||||
// pos.x += frametime_delta32() * 1
|
||||
}
|
||||
ui_parent(settings_menu)
|
||||
|
||||
frame_bar := ui_widget("Settings Menu: Frame Bar", {})
|
||||
{
|
||||
using frame_bar
|
||||
// style.bg_color = Color_Red
|
||||
style.flags = {}
|
||||
style.alignment = { 0, 1 }
|
||||
style.size = {}
|
||||
style.anchor = range2( {0, 0.95}, {0, 0} )
|
||||
|
||||
// Close button
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion Imgui Tick
|
||||
//endregion Workspace Imgui Tick
|
||||
|
||||
//region App Screenspace Imgui Tick
|
||||
{
|
||||
profile("App Screenspace Imgui")
|
||||
|
||||
ui_graph_build( & state.app_ui )
|
||||
ui := ui_context
|
||||
|
||||
/*
|
||||
Prototype app menu
|
||||
TODO(Ed): Move it to here
|
||||
*/
|
||||
}
|
||||
//endregion App Screenspace Imgui Tick
|
||||
|
||||
debug.last_mouse_pos = input.mouse.pos
|
||||
|
||||
|
25
code/ui.odin
25
code/ui.odin
@ -179,10 +179,18 @@ UI_StyleFlag :: enum u32 {
|
||||
Clamp_Position_Y,
|
||||
|
||||
// Enroces the widget will maintain its size reguardless of any constraints
|
||||
// Will override parent constraints
|
||||
// Will override parent constraints (use the size.min.xy to specify the width & height)
|
||||
Fixed_Width,
|
||||
Fixed_Height,
|
||||
|
||||
// TODO(Ed): Implement this!
|
||||
// Enforces the widget will have a width specified as a ratio of its height (use the size.min/max.x to specify the scalar)
|
||||
// If you wish for the width to stay fixed couple with the Fixed_Width flag
|
||||
Scale_Width_By_Height_Ratio,
|
||||
// Enforces the widget will have a height specified as a ratio of its width (use the size.min/max.y to specify the scalar)
|
||||
// If you wish for the height to stay fixed couple with the Fixed_Height flag
|
||||
Scale_Height_By_Width_Ratio,
|
||||
|
||||
// Sets the (0, 0) position of the child box to the parents anchor's center (post-margins bounds)
|
||||
// By Default, the origin is at the top left of the anchor's bounds
|
||||
Origin_At_Anchor_Center,
|
||||
@ -244,8 +252,8 @@ UI_Box :: struct {
|
||||
// Cache ID
|
||||
key : UI_Key,
|
||||
// label : string,
|
||||
label : StringCached,
|
||||
text : StringCached,
|
||||
label : StrRunesPair,
|
||||
text : StrRunesPair,
|
||||
|
||||
// Regenerated per frame.
|
||||
using links : DLL_NodeFull( UI_Box ), // first, last, prev, next
|
||||
@ -574,3 +582,14 @@ ui_style_theme_set_layout :: proc ( layout : UI_Layout ) {
|
||||
preset.layout = layout
|
||||
}
|
||||
}
|
||||
|
||||
ui_style_theme_layout_push :: proc ( layout : UI_Layout ) {
|
||||
ui := get_state().ui_context
|
||||
ui_style_theme_push( stack_peek( & ui.theme_stack) )
|
||||
ui_style_theme_set_layout(layout)
|
||||
}
|
||||
|
||||
@(deferred_none = ui_style_theme_pop)
|
||||
ui_style_theme_layout :: proc( layout : UI_Layout ) {
|
||||
ui_style_theme_layout_push(layout)
|
||||
}
|
||||
|
@ -153,8 +153,8 @@ ui_compute_layout :: proc()
|
||||
|
||||
// Determine Content Bounds
|
||||
content_bounds := range2(
|
||||
bounds.min + { layout.padding.left, layout.padding.bottom },
|
||||
bounds.max - { layout.padding.right, layout.padding.top },
|
||||
bounds.min + { layout.padding.left, layout.padding.bottom } + border_offset,
|
||||
bounds.max - { layout.padding.right, layout.padding.top } - border_offset,
|
||||
)
|
||||
|
||||
computed.anchors = anchored_bounds
|
||||
|
@ -1,6 +1,7 @@
|
||||
package sectr
|
||||
|
||||
import "core:math/linalg"
|
||||
import str "core:strings"
|
||||
|
||||
test_hover_n_click :: proc()
|
||||
{
|
||||
@ -67,6 +68,75 @@ test_draggable :: proc()
|
||||
draggable.style.layout.size.min = debug.draggable_box_size
|
||||
}
|
||||
|
||||
test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_Style )
|
||||
{
|
||||
state := get_state(); using state
|
||||
ui := ui_context
|
||||
|
||||
// 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 = { 5, 10, 5, 5 }
|
||||
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 = { 5, 5, 5, 5 }
|
||||
child_layout.anchor = range2({ 0.2, 0.1 }, { 0.1, 0.15 })
|
||||
child_layout.pos = { 0, 0 }
|
||||
|
||||
child_theme := frame_style_default ^
|
||||
child_theme.bg_color = Color_GreyRed
|
||||
child_theme.flags = {
|
||||
// .Fixed_Width, .Fixed_Height,
|
||||
.Origin_At_Anchor_Center
|
||||
}
|
||||
child_theme.layout = child_layout
|
||||
ui_theme_via_style(child_theme)
|
||||
child := ui_widget( "Child", { .Mouse_Clickable })
|
||||
}
|
||||
|
||||
test_text_box :: proc()
|
||||
{
|
||||
state := get_state(); using state
|
||||
@ -95,3 +165,164 @@ test_text_box :: proc()
|
||||
|
||||
text_box.style.size.min = { text_box.computed.text_size.x * 1.5, text_box.computed.text_size.y * 3 }
|
||||
}
|
||||
|
||||
test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_Style )
|
||||
{
|
||||
profile("Whitespace AST test")
|
||||
state := get_state(); using state
|
||||
ui := ui_context
|
||||
|
||||
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(), 8 )
|
||||
widgets, alloc_error = array_init_reserve( UI_Widget, frame_slab_allocator(), 4 * Kilobyte )
|
||||
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 = StrRunesPair {}
|
||||
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 ) )
|
||||
// if len(line_hbox.text.str) == 0 {
|
||||
// line_hbox.text = str_intern( " " )
|
||||
// }
|
||||
}
|
||||
|
||||
if len(line_hbox.text.str) > 0 {
|
||||
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
|
||||
}
|
||||
else {
|
||||
layout_text.pos.y += size_range2( (& widgets.data[ widgets.num - 1 ]).computed.bounds ).y
|
||||
}
|
||||
|
||||
line_id += 1
|
||||
}
|
||||
|
||||
label_id += 1 // Dummy action
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ UI_Widget :: struct {
|
||||
using signal : UI_Signal,
|
||||
}
|
||||
|
||||
|
||||
ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
|
||||
{
|
||||
// profile(#procedure)
|
||||
@ -15,7 +14,6 @@ ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget)
|
||||
{
|
||||
// profile(#procedure)
|
||||
@ -26,8 +24,76 @@ ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widge
|
||||
return
|
||||
}
|
||||
|
||||
//region Horizontal Box
|
||||
/*
|
||||
Horizontal Boxes automatically manage a collection of widgets and
|
||||
attempt to slot them adjacent to each other along the x-axis.
|
||||
|
||||
ui_text :: proc( label : string, content : StringCached, flags : UI_BoxFlags = {} ) -> UI_Widget
|
||||
The user must provide the direction that the hbox will append entries.
|
||||
How the widgets will be scaled will be based on the individual entires style flags.
|
||||
|
||||
All the usual behaviors that the style and box flags do apply when manage by the box widget.
|
||||
Whether or not the horizontal box will scale the widget's width is if:
|
||||
fixed size or "scale by ratio" flags are not used for the width.
|
||||
The hbox will use the anchor's (range2) ratio.x value to determine the "stretch ratio".
|
||||
|
||||
Keep in mind the stretch ratio is only respected if no size.min.x value is violated for each of the widgets.
|
||||
*/
|
||||
|
||||
ui_hbox_begin :: proc( label : string, flags : UI_BoxFlags = {}
|
||||
//, direction
|
||||
) -> (widget : UI_Widget) {
|
||||
// profile(#procedure)
|
||||
|
||||
widget.box = ui_box_make( flags, label )
|
||||
widget.signal = ui_signal_from_box( widget.box )
|
||||
return
|
||||
}
|
||||
ui_hbox_end :: proc( hbox : UI_Widget ) -> UI_Widget {
|
||||
hbox_width := hbox.computed.content.max.y - hbox.computed.content.min.y
|
||||
|
||||
// do layout calculations for the children
|
||||
total_stretch_ratio : f32 = 0.0
|
||||
size_req_children : f32 = 0
|
||||
for child := hbox.first; child != nil; child = child.next
|
||||
{
|
||||
using child
|
||||
using style.layout
|
||||
scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in style.flags)
|
||||
if .Fixed_Width in style.flags
|
||||
{
|
||||
if scaled_width_by_height {
|
||||
height := size.max.y != 0 ? size.max.y : hbox_width
|
||||
width := height * size.min.x
|
||||
|
||||
size_req_children += width
|
||||
continue
|
||||
}
|
||||
|
||||
size_req_children += size.min.x
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
availble_flexible_space := hbox_width - size_req_children
|
||||
return hbox
|
||||
}
|
||||
ui_hbox_auto_end :: proc( vbox : UI_Widget ) {
|
||||
ui_hbox_end(vbox)
|
||||
ui_parent_pop()
|
||||
}
|
||||
|
||||
@(deferred_out = ui_hbox_end)
|
||||
ui_hbox :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) {
|
||||
widget = ui_hbox_begin(label, flags)
|
||||
ui_parent(widget)
|
||||
return
|
||||
}
|
||||
|
||||
//endregion Horizontal Box
|
||||
|
||||
ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {} ) -> UI_Widget
|
||||
{
|
||||
// profile(#procedure)
|
||||
state := get_state(); using state
|
||||
@ -39,7 +105,6 @@ ui_text :: proc( label : string, content : StringCached, flags : UI_BoxFlags = {
|
||||
return { box, signal }
|
||||
}
|
||||
|
||||
|
||||
ui_text_spaces :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
|
||||
{
|
||||
// profile(#procedure)
|
||||
@ -55,7 +120,6 @@ ui_text_spaces :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
|
||||
return { box, signal }
|
||||
}
|
||||
|
||||
|
||||
ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
|
||||
{
|
||||
// profile(#procedure)
|
||||
@ -70,3 +134,28 @@ ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
|
||||
box.text = tab_str
|
||||
return { box, signal }
|
||||
}
|
||||
|
||||
ui_vbox_begin :: proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) {
|
||||
// profile(#procedure)
|
||||
|
||||
widget.box = ui_box_make( flags, label )
|
||||
// widget.signal = ui_signal_from_box( widget.box )
|
||||
return
|
||||
}
|
||||
ui_vbox_end :: proc( hbox : UI_Widget ) -> UI_Widget {
|
||||
// do layout calculations for the children
|
||||
return hbox
|
||||
}
|
||||
ui_vbox_auto_end :: proc( hbox : UI_Widget ) {
|
||||
ui_vbox_end(hbox)
|
||||
ui_parent_pop()
|
||||
}
|
||||
|
||||
// ui_vbox_append( widget : UI_Widget )
|
||||
|
||||
@(deferred_out = ui_vbox_auto_end)
|
||||
ui_vbox :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) {
|
||||
widget = ui_vbox_begin(label, flags)
|
||||
ui_parent_push(widget)
|
||||
return
|
||||
}
|
||||
|
8
ols.json
8
ols.json
@ -14,12 +14,8 @@
|
||||
"path": "C:/projects/SectrPrototype/code"
|
||||
},
|
||||
{
|
||||
"name": "ini",
|
||||
"path": "C:/projects/SectrPrototype/thirdparty/ini"
|
||||
},
|
||||
{
|
||||
"name": "backtrace",
|
||||
"path": "C:/projects/SectrPrototype/thirdparty/backtrace"
|
||||
"name": "thirdparty",
|
||||
"path": "C:/projects/SectrPrototype/thirdparty"
|
||||
}
|
||||
],
|
||||
"odin_command": "C:/projects/SectrPrototype/toolchain/Odin/odin.exe",
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 647d7ed9e3e07b8b248d3b56eaa8fa60b451e1c9
|
||||
Subproject commit 373733fb2b410cd51b4d674b09f5ed9e38677c99
|
Loading…
Reference in New Issue
Block a user