coordinate space math fixes, got resize handles working in settings menu prototype

will eventually lift to its own generic widget

I still need to implement the corner resize..
This commit is contained in:
Edward R. Gonzalez 2024-05-09 04:02:33 -04:00
parent b8e8e7c88a
commit a2b6325b5b
15 changed files with 857 additions and 554 deletions

View File

@ -22,6 +22,7 @@ The dependencies are:
* An ini parser
The client(sectr) module's organization is relatively flat due to the nature of odin's package management not allowing for cyclic dependencies across modules, and modules can only be in one directory.
This makes it difficult to unflatten as the depedency chain must clear with no inter-module collisions, not something organic todo in a prototype...
Even so the notatble groups are:

View File

@ -4,20 +4,24 @@ import rl "vendor:raylib"
Color :: rl.Color
Color_Blue :: rl.BLUE
Color_Green :: rl.GREEN
// 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, 180 }
Color_BG_TextBox_Green :: Color { 102, 102, 110, 255 }
Color_Frame_Disabled :: Color { 22, 22, 22, 120 }
Color_Frame_Hover :: Color { 122, 122, 125, 200 }
Color_Frame_Select :: Color { 188, 188, 188, 220 }
Color_GreyRed :: Color { 220, 100, 100, 50 }
Color_White_A125 :: Color { 255, 255, 255, 165 }
Color_Black :: Color { 0, 0, 0, 255 }
Color_Transparent :: Color { 0, 0, 0, 0 }
Color_BG :: Color { 61, 61, 64, 255 }
Color_BG_TextBox :: Color { 32, 32, 32, 180 }
Color_BG_Panel :: Color { 32, 32, 32, 255 }
Color_BG_Panel_Translucent :: Color { 32, 32, 32, 220 }
Color_BG_TextBox_Green :: Color { 102, 102, 110, 255 }
Color_Frame_Disabled :: Color { 22, 22, 22, 120 }
Color_Frame_Hover :: Color { 122, 122, 125, 200 }
Color_Frame_Select :: Color { 188, 188, 188, 220 }
Color_GreyRed :: Color { 220, 100, 100, 50 }
Color_White_A125 :: Color { 255, 255, 255, 165 }
Color_Black :: Color { 0, 0, 0, 255 }
Color_Green :: Color { 0, 180, 0, 255 }
Color_ResizeHandle :: Color { 90, 90, 100, 255 }
Color_3D_BG :: Color { 188, 182 , 170, 255 }

View File

@ -202,6 +202,9 @@ State :: struct {
// the screen-space UI and the current workspace UI.
// This is used so that the ui api doesn't need to have the user pass the context every single time.
ui_context : ^UI_State,
// The camera is considered the "context" for coodrinate space operations in rendering
cam_context : Camera,
}
get_state :: proc "contextless" () -> ^ State {

View File

@ -134,9 +134,9 @@ dot :: proc {
dot_unitv3_vs,
}
draw_text :: proc {
draw_text_string,
draw_text_string_cached,
ws_view_draw_text :: proc {
ws_view_draw_text_string,
ws_view_draw_text_StrRunesPair,
}
from_bytes :: proc {

View File

@ -1,8 +1,15 @@
/*
This was a tracking allocator made to kill off various bugs left with grime's pool & slab allocators
It doesn't perform that well on a per-frame basis and should be avoided for general memory debugging
It only makes sure that memory allocations don't collide in the allocator and deallocations don't occur for memory never allocated.
I'm keeping it around as an artifact & for future allocators I may make.
*/
package sectr
MemoryTrackerEntry :: struct {
start, end : rawptr,
// owner : string,
}
MemoryTracker :: struct {

View File

@ -269,14 +269,14 @@ MouseState :: struct {
side, forward, back, extra : DigitalBtn
}
},
pos, delta : Vec2,
raw_pos, pos, delta : Vec2,
vertical_wheel, horizontal_wheel : AnalogAxis
}
mouse_world_delta :: #force_inline proc "contextless" () -> Vec2 {
using state := get_state()
cam := & state.project.workspace.cam
return { input.mouse.delta.x, -input.mouse.delta.y } * ( 1 / cam.zoom )
return input.mouse.delta * ( 1 / cam.zoom )
}
InputState :: struct {
@ -347,8 +347,9 @@ poll_input :: proc( old, new : ^ InputState )
input_process_digital_btn( old_btn, new_btn, is_down )
}
new.mouse.pos = rl.GetMousePosition() - transmute(Vec2) get_state().app_window.extent
new.mouse.delta = rl.GetMouseDelta()
new.mouse.raw_pos = rl.GetMousePosition()
new.mouse.pos = render_to_surface_pos(new.mouse.raw_pos)
new.mouse.delta = rl.GetMouseDelta() * {1, -1}
new.mouse.vertical_wheel = rl.GetMouseWheelMove()
}
}

View File

@ -140,9 +140,9 @@ screen_size :: proc "contextless" () -> AreaSize {
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}
surface_extent := state.app_window.extent
bottom_left := Vec2 { -surface_extent.x, -surface_extent.y}
top_right := Vec2 { surface_extent.x, surface_extent.y}
return range2( bottom_left, top_right )
}
@ -156,6 +156,7 @@ screen_get_corners :: #force_inline proc "contextless"() -> BoundsCorners2 {
return { top_left, top_right, bottom_left, bottom_right }
}
// TODO(Ed): Use a cam/workspace context instead (when multiple workspaces viewproting supported)
view_get_bounds :: #force_inline proc "contextless"() -> Range2 {
state := get_state(); using state
cam := & project.workspace.cam
@ -166,6 +167,7 @@ view_get_bounds :: #force_inline proc "contextless"() -> Range2 {
return range2( bottom_left, top_right )
}
// TODO(Ed): Use a cam/workspace context instead (when multiple workspace viewproting)
view_get_corners :: #force_inline proc "contextless"() -> BoundsCorners2 {
state := get_state(); using state
cam := & project.workspace.cam
@ -178,30 +180,64 @@ view_get_corners :: #force_inline proc "contextless"() -> BoundsCorners2 {
return { top_left, top_right, bottom_left, bottom_right }
}
screen_to_world :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 {
state := get_state(); using state
cam := & project.workspace.cam
result := Vec2 { cam.target.x, -cam.target.y} + Vec2 { pos.x, -pos.y } * (1 / cam.zoom)
render_to_surface_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 {
extent := & get_state().app_window.extent
result := Vec2 {
pos.x - extent.x,
pos.y * -1 + extent.y
}
return result
}
screen_to_render :: #force_inline proc "contextless"(pos: Vec2) -> Vec2 {
screen_extent := transmute(Vec2) get_state().project.workspace.cam.offset
return pos + { screen_extent.x, -screen_extent.y }
render_to_ws_view_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 {
return {}
}
world_screen_extent :: #force_inline proc "contextless"() -> Extents2 {
surface_to_ws_view_pos :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 {
state := get_state(); using state
cam := & project.workspace.cam
result := Vec2 { cam.target.x, -cam.target.y} + Vec2 { pos.x, pos.y } * (1 / cam.zoom)
return result
}
// (Surface) Centered screen space to conventional screen space used for rendering
surface_to_render_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 {
screen_extent := transmute(Vec2) get_state().app_window.extent
return pos * {1, -1} + { screen_extent.x, screen_extent.y }
}
// TODO(Ed): These should assume a cam_context or have the ability to provide it in params
// Extent of workspace view (currently hardcoded to the app window's extent, eventually will be based on a viewport object's extent field)
// TODO(Ed): Support a position which would not be centered on the screen if in a viewport
ws_view_extent :: #force_inline proc "contextless"() -> Extents2 {
state := get_state(); using state
cam_zoom_ratio := 1.0 / project.workspace.cam.zoom
return app_window.extent * cam_zoom_ratio
}
world_to_screen_pos :: #force_inline proc "contextless"(position: Vec2) -> Vec2 {
// Workspace view to surface space position
// TODO(Ed): Support a position which would not be centered on the screen if in a viewport
ws_view_to_surface_pos :: #force_inline proc "contextless"(position: Vec2) -> Vec2 {
return position
}
ws_view_to_render_pos :: #force_inline proc "contextless"(position: Vec2) -> Vec2 {
return { position.x, position.y * -1 }
}
world_to_screen_no_zoom :: #force_inline proc "contextless"(position: Vec2) -> Vec2 {
// Workspace view to surface space position (zoom agnostic)
// TODO(Ed): Support a position which would not be centered on the screen if in a viewport
ws_view_to_surface_pos_no_zoom :: #force_inline proc "contextless"(position: Vec2) -> Vec2 {
state := get_state(); using state
cam_zoom_ratio := 1.0 / state.project.workspace.cam.zoom
return { position.x, position.y * -1 } * cam_zoom_ratio
return { position.x, position.y } * cam_zoom_ratio
}
// Workspace view to render space position (zoom agnostic)
// TODO(Ed): Support a position which would not be centered on the screen if in a viewport
ws_view_to_render_pos_no_zoom :: #force_inline proc "contextless"(position: Vec2) -> Vec2 {
state := get_state(); using state
cam_zoom_ratio := 1.0 / state.project.workspace.cam.zoom
return { position.x, position.y } * cam_zoom_ratio
}

View File

@ -21,7 +21,7 @@ debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Co
// if ( len(font) == 0 ) {
font = default_font
}
pos := screen_to_render(pos)
pos := surface_to_render_pos(pos)
px_size := size
@ -34,7 +34,34 @@ debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Co
tint = color );
}
draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
draw_text_screenspace :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
{
// profile(#procedure)
state := get_state(); using state
if len( content.str ) == 0 {
return
}
font := font
if font.key == Font_Default.key {
font = default_font
}
pos := pos
rl_font := to_rl_Font(font, size )
runes := content.runes
rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.TRILINEAR)
rl.DrawTextCodepoints( rl_font,
raw_data(runes), cast(i32) len(runes),
position = transmute(rl.Vector2) pos,
fontSize = size,
spacing = 0.0,
tint = color );
rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT)
}
ws_view_draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
{
// profile(#procedure)
state := get_state(); using state
@ -50,7 +77,7 @@ draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.C
// if len(font) == 0 {
font = default_font
}
pos := world_to_screen_pos(pos)
pos := ws_view_to_render_pos(pos)
px_size := size
zoom_adjust := px_size * project.workspace.cam.zoom
@ -66,7 +93,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 : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
ws_view_draw_text_StrRunesPair :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
{
// profile(#procedure)
state := get_state(); using state
@ -78,7 +105,7 @@ draw_text_string_cached :: proc( content : StrRunesPair, pos : Vec2, size : f32,
if font.key == Font_Default.key {
font = default_font
}
pos := world_to_screen_pos(pos)
pos := ws_view_to_render_pos(pos)
px_size := size
zoom_adjust := px_size * project.workspace.cam.zoom

View File

@ -4,6 +4,23 @@ import "core:fmt"
import rl "vendor:raylib"
draw_rectangle :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style ) {
if style.layout.corner_radii[0] > 0 {
rl.DrawRectangleRounded( rect, style.layout.corner_radii[0], 9, style.bg_color )
}
else {
rl.DrawRectangleRec( rect, style.bg_color )
}
}
draw_rectangle_lines :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style, color : Color, thickness : f32 ) {
if style.layout.corner_radii[0] > 0 {
rl.DrawRectangleRoundedLines( rect, style.layout.corner_radii[0], 9, thickness, color )
}
else {
rl.DrawRectangleLinesEx( rect, thickness, color )
}
}
render :: proc()
{
@ -15,124 +32,34 @@ render :: proc()
rl.BeginDrawing()
rl.ClearBackground( Color_BG )
render_mode_2d()
render_mode_2d_workspace()
render_mode_screenspace()
rl.EndDrawing()
}
render_mode_screenspace :: proc ()
// Experimental 3d viewport, not really the focus of this prototype
// Until we can have a native or interpreted program render to it its not very useful.
// Note(Ed): Other usecase could be 3d vis notes & math/graphical debug
render_mode_3d :: proc()
{
profile("Render Screenspace")
profile(#procedure)
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
}
rl.BeginDrawing()
rl.BeginTextureMode( debug.viewport_rt )
rl.BeginMode3D( debug.cam_vp )
rl.ClearBackground( Color_3D_BG )
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,
}
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 }
debug_draw_text( fps_msg, fps_msg_pos, 16.0, color = rl.GREEN )
debug_text :: proc( format : string, args : ..any )
{
@static draw_text_scratch : [Kilobyte * 64]u8
state := get_state(); using state
if debug.draw_debug_text_y > 800 {
debug.draw_debug_text_y = 50
}
cam := & project.workspace.cam
screen_corners := screen_get_corners()
position := screen_corners.top_right
position.x -= 800
position.y += debug.draw_debug_text_y
content := str_fmt_buffer( draw_text_scratch[:], format, ..args )
debug_draw_text( content, position, 14.0 )
debug.draw_debug_text_y += 14
}
// Debug Text
{
// debug_text( "Screen Width : %v", rl.GetScreenWidth () )
// debug_text( "Screen Height: %v", rl.GetScreenHeight() )
debug_text( "frametime_target_ms : %f ms", frametime_target_ms )
debug_text( "frametime : %f ms", frametime_delta_ms )
// debug_text( "frametime_last_elapsed_ms : %f ms", frametime_elapsed_ms )
if replay.mode == ReplayMode.Record {
debug_text( "Recording Input")
}
if replay.mode == ReplayMode.Playback {
debug_text( "Replaying Input")
}
}
debug_text("Zoom Target: %v", project.workspace.zoom_target)
if debug.mouse_vis {
debug_text( "Mouse Vertical Wheel: %v", input.mouse.vertical_wheel )
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 )
}
ui := & project.workspace.ui
// debug_text("Box Count: %v", ui.built_box_count )
hot_box := ui_box_from_key( ui.curr_cache, ui.hot )
active_box := ui_box_from_key( ui.curr_cache, ui.active )
if hot_box != nil {
debug_text("Hot Box : %v", hot_box.label.str )
// debug_text("Hot Range2: %v", hot_box.computed.bounds.pts)
}
if active_box != nil{
// debug_text("Active Box: %v", active_box.label.str )
}
// debug_text("Active Resizing: %v", ui.active_start_signal.resizing)
view := view_get_bounds()
// debug_text("View Bounds (World): %v", view.pts )
debug.draw_debug_text_y = 50
rl.EndMode3D()
rl.EndTextureMode()
rl.EndDrawing()
}
render_mode_2d :: proc()
// TODO(Ed): Eventually this needs to become a 'viewport within a UI'
// This would allow the user to have more than one workspace open at the same time
render_mode_2d_workspace :: proc()
{
profile(#procedure)
state := get_state(); using state
@ -164,8 +91,8 @@ render_mode_2d :: proc()
when false
{
render_view := Range2 { pts = {
world_to_screen_pos(view_bounds.min),
world_to_screen_pos(view_bounds.max),
world_to_screen_pos( view_bounds.min),
world_to_screen_pos( view_bounds.max),
}}
view_rect := rl.Rectangle {
render_view.min.x,
@ -205,24 +132,24 @@ render_mode_2d :: proc()
// profile_begin("Calculating Raylib rectangles")
render_anchors := range2(
world_to_screen_pos(computed.anchors.min),
world_to_screen_pos(computed.anchors.max),
ws_view_to_render_pos(computed.anchors.min),
ws_view_to_render_pos(computed.anchors.max),
)
render_margins := range2(
world_to_screen_pos(computed.margins.min),
world_to_screen_pos(computed.margins.max),
ws_view_to_render_pos(computed.margins.min),
ws_view_to_render_pos(computed.margins.max),
)
render_bounds := range2(
world_to_screen_pos(computed.bounds.min),
world_to_screen_pos(computed.bounds.max),
ws_view_to_render_pos(computed.bounds.min),
ws_view_to_render_pos(computed.bounds.max),
)
render_padding := range2(
world_to_screen_pos(computed.padding.min),
world_to_screen_pos(computed.padding.max),
ws_view_to_render_pos(computed.padding.min),
ws_view_to_render_pos(computed.padding.max),
)
render_content := range2(
world_to_screen_pos(computed.content.min),
world_to_screen_pos(computed.content.max),
ws_view_to_render_pos(computed.content.min),
ws_view_to_render_pos(computed.content.max),
)
rect_anchors := range2_to_rl_rect( render_anchors )
@ -232,24 +159,6 @@ render_mode_2d :: proc()
rect_content := range2_to_rl_rect( render_content )
// profile_end()
draw_rectangle :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style ) {
if style.layout.corner_radii[0] > 0 {
rl.DrawRectangleRounded( rect, style.layout.corner_radii[0], 9, style.bg_color )
}
else {
rl.DrawRectangleRec( rect, style.bg_color )
}
}
draw_rectangle_lines :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style, color : Color, thickness : f32 ) {
if style.layout.corner_radii[0] > 0 {
rl.DrawRectangleRoundedLines( rect, style.layout.corner_radii[0], 9, thickness, color )
}
else {
rl.DrawRectangleLinesEx( rect, thickness, color )
}
}
// profile_begin("rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color )")
if style.bg_color.a != 0
{
@ -281,8 +190,8 @@ render_mode_2d :: proc()
{ -resize_percent_width.x, resize_percent_width.x }))
render_resize := range2(
world_to_screen_pos(resize_border_non_range.min),
world_to_screen_pos(resize_border_non_range.max),
ws_view_to_render_pos(resize_border_non_range.min),
ws_view_to_render_pos(resize_border_non_range.max),
)
rect_resize := rl.Rectangle {
render_resize.min.x,
@ -307,7 +216,7 @@ render_mode_2d :: proc()
// profile_end()
if len(current.text.str) > 0 {
draw_text( current.text, world_to_screen_pos(computed.text_pos), style.font_size, style.text_color )
ws_view_draw_text( current.text, ws_view_to_render_pos(computed.text_pos * {1, -1}), style.font_size, style.text_color )
}
}
}
@ -315,8 +224,8 @@ render_mode_2d :: proc()
if debug.mouse_vis {
cursor_world_pos := screen_to_world(input.mouse.pos)
rl.DrawCircleV( world_to_screen_pos(cursor_world_pos), 5, Color_GreyRed )
cursor_world_pos := surface_to_ws_view_pos(input.mouse.pos)
rl.DrawCircleV( ws_view_to_render_pos(cursor_world_pos), 5, Color_GreyRed )
}
rl.DrawCircleV( { 0, 0 }, 1 * cam_zoom_ratio, Color_White )
@ -324,18 +233,208 @@ render_mode_2d :: proc()
rl.EndMode2D()
}
render_mode_3d :: 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
render_app_ui()
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 } - { 5, 5 }
debug_draw_text( fps_msg, fps_msg_pos, 16.0, color = rl.GREEN )
debug_text :: proc( format : string, args : ..any )
{
@static draw_text_scratch : [Kilobyte * 64]u8
state := get_state(); using state
if debug.draw_debug_text_y > 800 {
debug.draw_debug_text_y = 0
}
cam := & project.workspace.cam
screen_corners := screen_get_corners()
position := screen_corners.top_right
position.x -= app_window.extent.x
position.y -= debug.draw_debug_text_y
content := str_fmt_buffer( draw_text_scratch[:], format, ..args )
debug_draw_text( content, position, 14.0 )
debug.draw_debug_text_y += 14
}
// Debug Text
{
// debug_text( "Screen Width : %v", rl.GetScreenWidth () )
// debug_text( "Screen Height: %v", rl.GetScreenHeight() )
debug_text( "frametime_target_ms : %f ms", frametime_target_ms )
debug_text( "frametime : %f ms", frametime_delta_ms )
// debug_text( "frametime_last_elapsed_ms : %f ms", frametime_elapsed_ms )
if replay.mode == ReplayMode.Record {
debug_text( "Recording Input")
}
if replay.mode == ReplayMode.Playback {
debug_text( "Replaying Input")
}
}
debug_text("Zoom Target: %v", project.workspace.zoom_target)
if debug.mouse_vis {
debug_text("Mouse Vertical Wheel: %v", input.mouse.vertical_wheel )
debug_text("Mouse Position (Render) : %v", input.mouse.raw_pos )
debug_text("Mouse Position (Surface) : %v", input.mouse.pos )
debug_text("Mouse Position (Workspace View): %v", surface_to_ws_view_pos(input.mouse.pos) )
rl.DrawCircleV( input.mouse.raw_pos, 10, Color_White_A125 )
rl.DrawCircleV( surface_to_render_pos(input.mouse.pos), 2, Color_BG )
}
ui := & project.workspace.ui
// debug_text("Box Count: %v", ui.built_box_count )
hot_box := ui_box_from_key( ui.curr_cache, ui.hot )
active_box := ui_box_from_key( ui.curr_cache, ui.active )
if hot_box != nil {
debug_text("Hot Box : %v", hot_box.label.str )
// debug_text("Hot Range2: %v", hot_box.computed.bounds.pts)
}
if active_box != nil{
// debug_text("Active Box: %v", active_box.label.str )
}
// debug_text("Active Resizing: %v", ui.active_start_signal.resizing)
view := view_get_bounds()
// debug_text("View Bounds (World): %v", view.pts )
debug.draw_debug_text_y = 14
}
// A non-zoomable static-view for ui
// Only a scalar factor may be applied to the size of widgets & fonts
// 'Window tiled' panels reside here
render_app_ui :: proc()
{
profile(#procedure)
state := get_state(); using state
using state := get_state()
rl.BeginDrawing()
rl.BeginTextureMode( debug.viewport_rt )
rl.BeginMode3D( debug.cam_vp )
rl.ClearBackground( Color_3D_BG )
//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
}
rl.EndMode3D()
rl.EndTextureMode()
rl.EndDrawing()
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
render_anchors := range2(
surface_to_render_pos(computed.anchors.min),
surface_to_render_pos(computed.anchors.max),
)
render_margins := range2(
surface_to_render_pos(computed.margins.min),
surface_to_render_pos(computed.margins.max),
)
render_bounds := range2(
surface_to_render_pos(computed.bounds.min),
surface_to_render_pos(computed.bounds.max),
)
render_padding := range2(
surface_to_render_pos(computed.padding.min),
surface_to_render_pos(computed.padding.max),
)
render_content := range2(
surface_to_render_pos(computed.content.min),
surface_to_render_pos(computed.content.max),
)
rect_anchors := range2_to_rl_rect( render_anchors )
rect_margins := range2_to_rl_rect( render_margins )
rect_bounds := range2_to_rl_rect( render_bounds )
rect_padding := range2_to_rl_rect( render_padding )
rect_content := range2_to_rl_rect( render_content )
// profile_end()
// profile_begin("rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color )")
if style.bg_color.a != 0
{
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 : f32 = 1
// 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 )
}
else {
// draw_rectangle_lines( rect_content, style, Color_Debug_UI_Content_Bounds, line_thickness )
}
// profile_end()
if .Mouse_Resizable in current.flags
{
// profile("Resize Bounds")
resize_border_width := cast(f32) get_state().config.ui_resize_border_width
resize_percent_width := computed_size * (resize_border_width * 1.0/ 200.0)
resize_border_non_range := add(current.computed.bounds, range2(
{ resize_percent_width.x, -resize_percent_width.x },
{ -resize_percent_width.x, resize_percent_width.x }))
render_resize := range2(
resize_border_non_range.min,
resize_border_non_range.max,
)
rect_resize := rl.Rectangle {
render_resize.min.x,
render_resize.min.y,
render_resize.max.x - render_resize.min.x,
render_resize.max.y - render_resize.min.y,
}
draw_rectangle_lines( rect_padding, style, Color_Red, line_thickness )
}
point_radius : f32 = 3
// profile_begin("circles")
// center := Vec2 {
// render_bounds.p0.x + computed_size.x * 0.5,
// render_bounds.p0.y - computed_size.y * 0.5,
// }
// rl.DrawCircleV( center, point_radius, Color_White )
// rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red )
// rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue )
// profile_end()
if len(current.text.str) > 0 {
draw_text_screenspace( current.text, surface_to_render_pos(computed.text_pos), style.font_size, style.text_color )
}
}
}
//endregion App UI
}

View File

@ -149,6 +149,7 @@ update :: proc( delta_time : f64 ) -> b32
}
//region 2D Camera Manual Nav
// TODO(Ed): This should be per workspace view
{
// profile("Camera Manual Nav")
digital_move_speed : f32 = 200.0
@ -184,13 +185,314 @@ update :: proc( delta_time : f64 ) -> b32
if debug_actions.cam_mouse_pan
{
if is_within_screenspace(input.mouse.pos) {
pan_velocity := input.mouse.delta * ( 1 / cam.zoom )
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")
@ -240,180 +542,9 @@ update :: proc( delta_time : f64 ) -> b32
// test_text_box()
// test_parenting( & default_layout, & frame_style_default )
// test_whitespace_ast( & default_layout, & frame_style_default )
/*
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 * (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)
style := UI_Style {
flags = {
// .Origin_At_Anchor_Center
.Fixed_Height
},
bg_color = Color_Frame_Disabled,
font = default_font,
font_size = 18 * (1.0/cam.zoom),
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} * (1.0/cam.zoom), {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 += mouse_world_delta()
bar_pos += state.input.mouse.delta
}
}
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,
}
}
// 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 := 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 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
should_shutdown : b32 = ! cast(b32) rl.WindowShouldClose()

View File

@ -69,6 +69,8 @@ UI_BoxFlag :: enum u64 {
Pan_X,
Pan_Y,
Screenspace,
Count,
}
UI_BoxFlags :: bit_set[UI_BoxFlag; u64]
@ -86,13 +88,6 @@ UI_Computed :: struct {
text_size : Vec2, // Size of text within content
}
UI_LayoutSide :: struct {
// using _ : struct {
top, bottom : UI_Scalar,
left, right : UI_Scalar,
// }
}
UI_Cursor :: struct {
placeholder : int,
}
@ -119,32 +114,6 @@ UI_ScalarConstraint :: struct {
UI_Scalar2 :: [Axis2.Count]UI_Scalar
// Desiered constraints on the UI_Box.
UI_Layout :: struct {
anchor : Range2,
alignment : Vec2,
text_alignment : Vec2,
border_width : UI_Scalar,
margins : UI_LayoutSide,
padding : UI_LayoutSide,
// TODO(Ed): We cannot support individual corners unless we add it to raylib (or finally change the rendering backend)
corner_radii : [Corner.Count]f32,
// 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,
size : Range2,
// TODO(Ed) : Should thsi just always be WS_Pos for workspace UI?
// (We can union either varient and just know based on checking if its the screenspace UI)
// 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,
}
UI_Signal :: struct {
cursor_pos : Vec2,
drag_delta : Vec2,
@ -163,91 +132,6 @@ UI_Signal :: struct {
commit : b8,
}
UI_StyleFlag :: enum u32 {
// Will perform scissor pass on children to their parent's bounds
// (Specified in the parent)
Clip_Children_To_Bounds,
// Enforces the box will always remain in a specific position relative to the parent.
// Overriding the anchors and margins.
Fixed_Position_X,
Fixed_Position_Y,
// Enforces box will always be within the bounds of the parent box.
Clamp_Position_X,
Clamp_Position_Y,
// Enroces the widget will maintain its size reguardless of any 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,
// Will size the box to its text. (Padding & Margins will thicken )
Size_To_Text,
Text_Wrap,
Count,
}
UI_StyleFlags :: bit_set[UI_StyleFlag; u32]
UI_StylePreset :: enum u32 {
Default,
Disabled,
Hot,
Active,
Count,
}
UI_Style :: struct {
flags : UI_StyleFlags,
bg_color : Color,
border_color : Color,
// TODO(Ed) : Add support for this eventually
blur_size : f32,
font : FontID,
// TODO(Ed): Should this get moved to the layout struct? Techncially font-size is mainly
font_size : f32,
text_color : Color,
cursor : UI_Cursor,
using layout : UI_Layout,
// Used with style, prev_style, and style_delta to produce a simple interpolated animation
// Applied in the layout pass & the rendering pass for their associated fields.
transition_time : f32,
}
UI_StyleTheme :: struct #raw_union {
array : [UI_StylePreset.Count] UI_Style,
using styles : struct {
default, disabled, hot, active : UI_Style,
}
}
UI_TextAlign :: enum u32 {
Left,
Center,
Right,
Count
}
UI_Box :: struct {
// Cache ID
key : UI_Key,
@ -443,16 +327,16 @@ ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box)
ui_cursor_pos :: #force_inline proc "contextless" () -> Vec2 {
using state := get_state()
if ui_context == & state.project.workspace.ui {
return screen_to_world( input.mouse.pos )
return surface_to_ws_view_pos( input.mouse.pos )
}
else {
return input.mouse.pos
}
}
ui_drag_delta :: #force_inline proc "contextless" () -> Vec2 {
ui_ws_drag_delta :: #force_inline proc "contextless" () -> Vec2 {
using state := get_state()
return ui_cursor_pos() - state.ui_context.active_start_signal.cursor_pos
return surface_to_ws_view_pos(input.mouse.pos) - state.ui_context.active_start_signal.cursor_pos
}
ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
@ -481,19 +365,19 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
}
// TODO(Ed) :: Is this even needed?
ui_graph_build_end :: proc()
ui_graph_build_end :: proc( ui : ^UI_State )
{
profile(#procedure)
ui_parent_pop() // Should be ui_context.root
// Regenerate the computed layout if dirty
ui_compute_layout()
ui_compute_layout( ui )
get_state().ui_context = nil
}
@(deferred_none = ui_graph_build_end)
@(deferred_in = ui_graph_build_end)
ui_graph_build :: proc( ui : ^ UI_State ) {
ui_graph_build_begin( ui )
}
@ -520,10 +404,6 @@ ui_key_from_string :: #force_inline proc "contextless" ( value : string ) -> UI_
return key
}
ui_layout_padding :: proc( pixels : f32 ) -> UI_LayoutSide {
return { pixels, pixels, pixels, pixels }
}
ui_parent_push :: proc( ui : ^ UI_Box ) {
stack := & get_state().ui_context.parent_stack
stack_push( & get_state().ui_context.parent_stack, ui )
@ -542,54 +422,3 @@ ui_parent_pop :: proc() {
ui_parent :: proc( ui : ^UI_Box) {
ui_parent_push( ui )
}
ui_style_peek :: proc( box_state : UI_StylePreset ) -> UI_Style {
return stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state]
}
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 ) {
push( & get_state().ui_context.theme_stack, preset )
}
ui_style_theme_pop :: proc() {
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 )
}
@(deferred_none = ui_style_theme_pop)
ui_theme_via_style :: proc ( style : UI_Style ) {
ui_style_theme_push( UI_StyleTheme { styles = { style, style, style, style } })
}
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_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)
}

View File

@ -5,12 +5,12 @@ import "core:math/linalg"
// Note(Ed): This is naturally pretty expensive
ui_compute_layout :: proc()
ui_compute_layout :: proc( ui : ^UI_State )
{
profile(#procedure)
state := get_state()
root := state.project.workspace.ui.root
root := ui.root
{
computed := & root.computed
style := root.style
@ -172,7 +172,7 @@ ui_compute_layout :: proc()
text_pos.y += ( content_size.y - text_size.y ) * layout.text_alignment.y
computed.text_size = text_size
computed.text_pos = { text_pos.x, -text_pos.y }
computed.text_pos = { text_pos.x, text_pos.y }
}
current = ui_box_tranverse_next( current )

174
code/ui_style.odin Normal file
View File

@ -0,0 +1,174 @@
package sectr
UI_LayoutSide :: struct {
// using _ : struct {
top, bottom : UI_Scalar,
left, right : UI_Scalar,
// }
}
// Desiered constraints on the UI_Box.
UI_Layout :: struct {
anchor : Range2,
alignment : Vec2,
text_alignment : Vec2,
border_width : UI_Scalar,
margins : UI_LayoutSide,
padding : UI_LayoutSide,
// TODO(Ed): We cannot support individual corners unless we add it to raylib (or finally change the rendering backend)
corner_radii : [Corner.Count]f32,
// 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,
size : Range2,
// TODO(Ed) : Should thsi just always be WS_Pos for workspace UI?
// (We can union either varient and just know based on checking if its the screenspace UI)
// 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,
}
UI_StyleFlag :: enum u32 {
// Will perform scissor pass on children to their parent's bounds
// (Specified in the parent)
Clip_Children_To_Bounds,
// Enforces the box will always remain in a specific position relative to the parent.
// Overriding the anchors and margins.
Fixed_Position_X,
Fixed_Position_Y,
// Enforces box will always be within the bounds of the parent box.
Clamp_Position_X,
Clamp_Position_Y,
// Enroces the widget will maintain its size reguardless of any 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,
// Will size the box to its text. (Padding & Margins will thicken )
Size_To_Text,
Text_Wrap,
Count,
}
UI_StyleFlags :: bit_set[UI_StyleFlag; u32]
UI_StylePreset :: enum u32 {
Default,
Disabled,
Hot,
Active,
Count,
}
UI_Style :: struct {
flags : UI_StyleFlags,
bg_color : Color,
border_color : Color,
// TODO(Ed) : Add support for this eventually
blur_size : f32,
font : FontID,
// TODO(Ed): Should this get moved to the layout struct? Techncially font-size is mainly
font_size : f32,
text_color : Color,
cursor : UI_Cursor,
using layout : UI_Layout,
// Used with style, prev_style, and style_delta to produce a simple interpolated animation
// Applied in the layout pass & the rendering pass for their associated fields.
transition_time : f32,
}
UI_StyleTheme :: struct #raw_union {
array : [UI_StylePreset.Count] UI_Style,
using styles : struct {
default, disabled, hot, active : UI_Style,
}
}
UI_TextAlign :: enum u32 {
Left,
Center,
Right,
Count
}
ui_layout_padding :: proc( pixels : f32 ) -> UI_LayoutSide {
return { pixels, pixels, pixels, pixels }
}
ui_style_peek :: proc( box_state : UI_StylePreset ) -> UI_Style {
return stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state]
}
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 ) {
push( & get_state().ui_context.theme_stack, preset )
}
ui_style_theme_pop :: proc() {
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 )
}
@(deferred_none = ui_style_theme_pop)
ui_theme_via_style :: proc ( style : UI_Style ) {
ui_style_theme_push( UI_StyleTheme { styles = { style, style, style, style } })
}
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_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)
}

View File

@ -66,6 +66,9 @@ test_draggable :: proc()
draggable.style.layout.pos = debug.draggable_box_pos
draggable.style.layout.size.min = debug.draggable_box_size
draggable.text = { str_fmt_alloc("%v", debug.draggable_box_pos), {} }
draggable.text.runes = to_runes(draggable.text.str)
}
test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_Style )

View File

@ -1,18 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json",
"collections": [
{
"name": "core",
"path": "C:/projects/SectrPrototype/toolchain/Odin/core"
},
{
"name": "vendor",
"path": "C:/projects/SectrPrototype/toolchain/Odin/vendor"
},
{
"name": "code",
"path": "C:/projects/SectrPrototype/code"
},
{
"name": "thirdparty",
"path": "C:/projects/SectrPrototype/thirdparty"