Editor improvements and some refactoring

This commit is contained in:
sergeypdev 2025-05-10 19:01:48 +04:00
parent f8b73786aa
commit 890ac2494a
7 changed files with 307 additions and 247 deletions

View File

@ -1,6 +1,9 @@
package game package game
import "core:fmt"
import lg "core:math/linalg" import lg "core:math/linalg"
import "game:physics/collision"
import "game:ui"
import rl "libs:raylib" import rl "libs:raylib"
update_editor :: proc(es: ^Editor_State, dt: f32) { update_editor :: proc(es: ^Editor_State, dt: f32) {
@ -40,11 +43,21 @@ update_editor :: proc(es: ^Editor_State, dt: f32) {
clear(&es.point_selection) clear(&es.point_selection)
} }
if rl.IsKeyPressed(.G) { if rl.IsKeyPressed(.G) {
track := &world_stack_current(&es.world_stack).track
selected_count := 0
es.move_initial_pos = 0
for k in es.point_selection {
es.move_initial_pos += track.points[k]
selected_count += 1
}
if selected_count > 0 {
es.move_initial_pos /= f32(selected_count)
es.track_edit_state = .Move es.track_edit_state = .Move
es.move_axis = .None es.move_axis = .None
es.total_movement_world = {} es.total_movement_world = {}
world_stack_push(&es.world_stack) world_stack_push(&es.world_stack)
// es.initial_point_pos = g_mem.track.points[es.selected_track_point] }
} }
} }
} }
@ -89,24 +102,53 @@ update_editor :: proc(es: ^Editor_State, dt: f32) {
camera := game_camera_3d() camera := game_camera_3d()
mouse_delta := rl.GetMouseDelta() * 0.05 mouse_pos := rl.GetMousePosition()
prev_mouse_pos := es.prev_mouse_pos
view_rotation := lg.transpose(rl.GetCameraMatrix(camera)) prev_ray := rl.GetScreenToWorldRay(prev_mouse_pos, camera)
view_rotation[3].xyz = 0 ray := rl.GetScreenToWorldRay(mouse_pos, camera)
view_proj := view_rotation * rl.MatrixOrtho(-1, 1, 1, -1, -1, 1)
axes_buf: [2]rl.Vector3 axes_buf: [2]rl.Vector3
colors_buf: [2]rl.Color colors_buf: [2]rl.Color
axes, _ := get_movement_axes(es.move_axis, &axes_buf, &colors_buf) axes, _ := get_movement_axes(es.move_axis, &axes_buf, &colors_buf)
movement_world: rl.Vector3 movement_world: rl.Vector3
for axis in axes { if len(axes) == 2 {
axis_screen := (rl.Vector4{axis.x, axis.y, axis.z, 1} * view_proj).xy normal := lg.normalize0(lg.cross(axes[0], axes[1]))
axis_screen = lg.normalize0(axis_screen) plane := collision.plane_from_point_normal(es.move_initial_pos, normal)
movement_screen := lg.dot(axis_screen, mouse_delta) * axis_screen _, pos1 := collision.intersect_ray_plane(
movement_world += prev_ray.position,
(rl.Vector4{movement_screen.x, movement_screen.y, 0, 1} * rl.MatrixInvert(view_proj)).xyz prev_ray.direction,
plane,
)
_, pos2 := collision.intersect_ray_plane(
ray.position,
ray.direction,
plane,
)
world_delta := pos2 - pos1
world_delta -= lg.dot(world_delta, plane.normal) * plane.normal
movement_world += world_delta
} else if len(axes) == 1 {
for axis in axes {
tangent := lg.cross(es.move_initial_pos - camera.position, axis)
normal := lg.normalize0(lg.cross(axis, tangent))
plane := collision.plane_from_point_normal(es.move_initial_pos, normal)
_, pos1 := collision.intersect_ray_plane(
prev_ray.position,
prev_ray.direction,
plane,
)
_, pos2 := collision.intersect_ray_plane(
ray.position,
ray.direction,
plane,
)
world_delta := pos2 - pos1
movement_world += lg.dot(axis, world_delta) * axis
}
} }
for k in es.point_selection { for k in es.point_selection {
@ -117,8 +159,26 @@ update_editor :: proc(es: ^Editor_State, dt: f32) {
} }
} }
} }
} }
// world := world_stack_current(&es.world_stack)
// update_world(world, dt, false) es.prev_mouse_pos = rl.GetMousePosition()
ui_ctx := &g_mem.ui_context
if ui.window(ui_ctx, "Editor", ui.Rect{x = 500, y = 0, w = 500, h = 600}) {
cnt := ui.get_current_container(ui_ctx)
cnt.rect.w = max(cnt.rect.w, 500)
cnt.rect.h = max(cnt.rect.h, 600)
if .ACTIVE in ui.header(ui_ctx, "Details", {.EXPANDED}) {
ui.layout_row(ui_ctx, {100, -1})
ui.label(ui_ctx, "mov")
ui.label(ui_ctx, fmt.tprintf("%v", es.move_initial_pos))
}
}
world := world_stack_current(&es.world_stack)
config := World_Update_Config {
commit_physics = false,
}
update_world(world, dt, config)
} }

View File

@ -222,13 +222,18 @@ Editor_State :: struct {
point_selection: map[int]bool, point_selection: map[int]bool,
track_edit_state: Track_Edit_State, track_edit_state: Track_Edit_State,
move_axis: Move_Axis, move_axis: Move_Axis,
prev_mouse_pos: rl.Vector2,
move_initial_pos: rl.Vector3,
total_movement_world: rl.Vector3, total_movement_world: rl.Vector3,
initial_point_pos: rl.Vector3,
camera: Free_Camera, camera: Free_Camera,
} }
editor_state_init :: proc(es: ^Editor_State, num_snapshots: int) { editor_state_init :: proc(es: ^Editor_State, num_snapshots: int) {
world_stack_init(&es.world_stack, num_snapshots) world_stack_init(&es.world_stack, num_snapshots)
world_stack_current(&es.world_stack).debug_state = {
show_menu = true,
draw_physics_scene = true,
}
} }
editor_state_destroy :: proc(es: ^Editor_State) { editor_state_destroy :: proc(es: ^Editor_State) {
@ -348,7 +353,12 @@ get_movement_axes :: proc(
return out_axes[0:0], out_colors[0:0] return out_axes[0:0], out_colors[0:0]
} }
update_world :: proc(world: ^World, dt: f32, single_step_physics: bool) { World_Update_Config :: struct {
single_step_physics: bool,
commit_physics: bool,
}
update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) {
if !world.pause { if !world.pause {
car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb") car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb")
car_bounds := rl.GetModelBoundingBox(car_model) car_bounds := rl.GetModelBoundingBox(car_model)
@ -608,8 +618,8 @@ update_world :: proc(world: ^World, dt: f32, single_step_physics: bool) {
&world.physics_scene, &world.physics_scene,
SOLVER_CONFIG, SOLVER_CONFIG,
dt, dt,
commit = true, commit = config.commit_physics,
step_mode = single_step_physics ? physics.Step_Mode.Single : physics.Step_Mode.Accumulated_Time, step_mode = config.single_step_physics ? physics.Step_Mode.Single : physics.Step_Mode.Accumulated_Time,
) )
} }
} }
@ -626,7 +636,11 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) {
if !runtime_world.rewind_simulation { if !runtime_world.rewind_simulation {
next_world := runtime_world_next_world(runtime_world) next_world := runtime_world_next_world(runtime_world)
copy_world(next_world, cur_world) copy_world(next_world, cur_world)
update_world(next_world, dt, should_single_step) config := World_Update_Config {
commit_physics = true,
single_step_physics = should_single_step,
}
update_world(next_world, dt, config)
if runtime_world.commit_simulation { if runtime_world.commit_simulation {
runtime_world.current_world_index = runtime_world.current_world_index =
@ -676,7 +690,7 @@ collect_camera_input :: proc() -> (delta: rl.Vector2, sense: f32) {
mouse_delta := g_mem.mouse_captured ? rl.GetMouseDelta() : 0 mouse_delta := g_mem.mouse_captured ? rl.GetMouseDelta() : 0
MOUSE_SENSE :: 0.01 MOUSE_SENSE :: 0.005
GAMEPAD_SENSE :: 1 GAMEPAD_SENSE :: 1
if linalg.length2(mouse_delta) > linalg.length2(gamepad_delta) { if linalg.length2(mouse_delta) > linalg.length2(gamepad_delta) {
@ -786,6 +800,14 @@ update :: proc() {
if rl.IsKeyPressed(.TAB) { if rl.IsKeyPressed(.TAB) {
g_mem.editor = !g_mem.editor g_mem.editor = !g_mem.editor
// When switching from editor to game, copy editor world into game world
if !g_mem.editor {
es := &g_mem.es
editor_world := world_stack_current(&es.world_stack)
copy_world(runtime_world_current_world(&g_mem.runtime_world), editor_world)
}
} }
if rl.IsKeyPressed(.F1) { if rl.IsKeyPressed(.F1) {
@ -921,8 +943,8 @@ draw_world :: proc(world: ^World) {
if world.debug_state.show_menu { if world.debug_state.show_menu {
ui_ctx := &g_mem.ui_context ui_ctx := &g_mem.ui_context
ui.push_font_size_style(ui_ctx, 20) //ui.push_font_size_style(ui_ctx, 20)
defer ui.pop_style(ui_ctx) // defer ui.pop_style(ui_ctx)
if ui.window(ui_ctx, "Debug Menu", {x = 0, y = 0, w = 200, h = 300}) { if ui.window(ui_ctx, "Debug Menu", {x = 0, y = 0, w = 200, h = 300}) {
cnt := ui.get_current_container(ui_ctx) cnt := ui.get_current_container(ui_ctx)
@ -961,7 +983,6 @@ draw_world :: proc(world: ^World) {
} }
} }
} }
} }
car_matrix := rl.QuaternionToMatrix(car_body.q) car_matrix := rl.QuaternionToMatrix(car_body.q)
@ -1050,9 +1071,9 @@ draw :: proc() {
for v in soa_zip(axis = axes, color = colors) { for v in soa_zip(axis = axes, color = colors) {
rlgl.Color4ub(v.color.r, v.color.g, v.color.b, v.color.a) rlgl.Color4ub(v.color.r, v.color.g, v.color.b, v.color.a)
start, end := start, end :=
es.initial_point_pos - es.move_initial_pos -
v.axis * 100000, v.axis * 100000,
es.initial_point_pos + es.move_initial_pos +
v.axis * 100000 v.axis * 100000
rlgl.Vertex3f(start.x, start.y, start.z) rlgl.Vertex3f(start.x, start.y, start.z)
@ -1251,9 +1272,8 @@ game_init :: proc() {
ui.init(&g_mem.ui_context) ui.init(&g_mem.ui_context)
g_mem.ui_context.default_style.font = ui.Font(&g_mem.default_font) g_mem.ui_context.default_style.font = ui.Font(&g_mem.default_font)
g_mem.ui_context.default_style.font_size = 32 g_mem.ui_context.default_style.font_size = 20
g_mem.ui_context.text_width = ui.rl_measure_text_width g_mem.ui_context.text_size = ui.rl_measure_text_2d
g_mem.ui_context.text_height = ui.rl_measure_text_height
game_hot_reloaded(g_mem) game_hot_reloaded(g_mem)
} }

View File

@ -620,6 +620,13 @@ intersect_ray_triangle :: proc(
return t, normal, barycentric, true return t, normal, barycentric, true
} }
intersect_ray_plane :: proc(origin, dir: Vec3, plane: Plane) -> (t: f32, pos: Vec3) {
t = (dot(plane.normal, (plane.normal * plane.dist - origin))) / dot(plane.normal, dir)
pos = origin + dir * t
return
}
intersect_segment_plane :: proc( intersect_segment_plane :: proc(
segment: [2]Vec3, segment: [2]Vec3,
plane: Plane, plane: Plane,

View File

@ -10,26 +10,115 @@ Body_Config_Inertia_Mode :: enum {
Explicit, Explicit,
} }
Immedate_State :: struct($T: typeid) {
handle: T,
// When was this referenced last time (frame number)
last_ref: u32,
}
Immediate_Container :: struct($T: typeid) {
items: map[u32]Immedate_State(T),
num_items: i32,
}
// When ok = true, it means we found an existing item
immediate_container_find_or_add :: proc(
c: $T/^Immediate_Container($E),
simulation_frame: u32,
id: u32,
) -> (
handle: ^E,
ok: bool,
) {
if id in c.items {
item := &c.items[id]
if item.last_ref != simulation_frame {
item.last_ref = simulation_frame
c.num_items += 1
}
handle = &item.handle
ok = true
} else {
c.num_items += 1
c.items[id] = {
handle = {},
last_ref = simulation_frame,
}
item := &c.items[id]
handle = &item.handle
ok = false
}
return
}
// Returns removed handles, allocated using allocator
immediate_container_prune :: proc(
c: $T/^Immediate_Container($E),
simulation_frame: u32,
allocator := context.temp_allocator,
) -> (
removed_handles: []E,
) {
if int(c.num_items) == len(c.items) {
return
}
num_unreferenced := len(c.items) - int(c.num_items)
assert(num_unreferenced >= 0)
removed_handles = make([]E, num_unreferenced, allocator)
items_to_remove := make([]u32, num_unreferenced, context.temp_allocator)
i := 0
for k, &v in c.items {
if v.last_ref != simulation_frame {
items_to_remove[i] = k
removed_handles[i] = v.handle
i += 1
}
}
assert(i == len(items_to_remove))
for k in items_to_remove {
delete_key(&c.items, k)
}
return
}
copy_map :: proc(dst, src: $T/^map[$K]$V) {
clear(dst)
reserve_map(dst, len(src))
for k, v in src {
dst[k] = v
}
}
immediate_container_copy :: proc(dst, src: $T/^Immediate_Container($E)) {
copy_map(&dst.items, &src.items)
dst.num_items = src.num_items
}
immediate_container_destroy :: proc(c: $T/^Immediate_Container($E)) {
delete_map(c.items)
}
immediate_body :: proc(scene: ^Scene, id: u32, config: Body_Config) -> (handle: Body_Handle) { immediate_body :: proc(scene: ^Scene, id: u32, config: Body_Config) -> (handle: Body_Handle) {
state := &scene.solver_state state := &scene.solver_state
sim_state := get_sim_state(scene) sim_state := get_sim_state(scene)
if id in state.immedate_bodies { h, ok := immediate_container_find_or_add(&state.immediate_bodies, state.simulation_frame, id)
body := &state.immedate_bodies[id]
if body.last_ref != state.simulation_frame { if ok {
body.last_ref = state.simulation_frame update_body_from_config(sim_state, get_body(sim_state, h^), config)
state.num_referenced_bodies += 1
}
handle = body.handle
update_body_from_config(sim_state, get_body(sim_state, handle), config)
} else { } else {
state.num_referenced_bodies += 1 h^ = add_body(sim_state, config)
handle = add_body(sim_state, config)
state.immedate_bodies[id] = {
handle = handle,
last_ref = state.simulation_frame,
}
} }
handle = h^
return return
} }
@ -41,22 +130,17 @@ immediate_suspension_constraint :: proc(
handle: Suspension_Constraint_Handle, handle: Suspension_Constraint_Handle,
) { ) {
state := &scene.solver_state state := &scene.solver_state
if id in state.immediate_suspension_constraints { h, ok := immediate_container_find_or_add(
constraint := &state.immediate_suspension_constraints[id] &state.immediate_suspension_constraints,
if constraint.last_ref != state.simulation_frame { state.simulation_frame,
constraint.last_ref = state.simulation_frame id,
state.num_referenced_suspension_constraints += 1 )
}
handle = constraint.handle if !ok {
} else { h^ = add_suspension_constraint(get_sim_state(scene), {})
state.num_referenced_suspension_constraints += 1
handle = add_suspension_constraint(get_sim_state(scene), {})
state.immediate_suspension_constraints[id] = {
handle = handle,
last_ref = state.simulation_frame,
}
} }
handle = h^
update_suspension_constraint_from_config( update_suspension_constraint_from_config(
get_suspension_constraint(get_sim_state(scene), handle), get_suspension_constraint(get_sim_state(scene), handle),
config, config,
@ -74,117 +158,36 @@ immediate_engine :: proc(
) { ) {
state := &scene.solver_state state := &scene.solver_state
sim_state := get_sim_state(scene) sim_state := get_sim_state(scene)
if id in state.immediate_engines { h, ok := immediate_container_find_or_add(&state.immediate_engines, state.simulation_frame, id)
engine := &state.immediate_engines[id]
if engine.last_ref != state.simulation_frame { if ok {
engine.last_ref = state.simulation_frame update_engine_from_config(sim_state, get_engine(sim_state, h^), config)
state.num_referenced_engines += 1
}
handle = engine.handle
update_engine_from_config(sim_state, get_engine(sim_state, handle), config)
} else { } else {
state.num_referenced_engines += 1 h^ = add_engine(sim_state, config)
handle = add_engine(sim_state, config)
state.immediate_engines[id] = {
handle = handle,
last_ref = state.simulation_frame,
}
} }
handle = h^
return return
} }
prune_immediate :: proc(scene: ^Scene) { prune_immediate :: proc(scene: ^Scene) {
tracy.Zone() tracy.Zone()
prune_immediate_bodies(scene)
prune_immediate_suspension_constraints(scene)
prune_immediate_engines(scene)
}
// TODO: Generic version sim_state := get_sim_state(scene)
prune_immediate_bodies :: proc(scene: ^Scene) {
state := &scene.solver_state state := &scene.solver_state
if int(state.num_referenced_bodies) == len(state.immedate_bodies) { removed_bodies := immediate_container_prune(&state.immediate_bodies, state.simulation_frame)
return removed_wheels := immediate_container_prune(
&state.immediate_suspension_constraints,
state.simulation_frame,
)
removed_engines := immediate_container_prune(&state.immediate_engines, state.simulation_frame)
for handle in removed_bodies {
remove_body(sim_state, handle)
} }
for handle in removed_wheels {
num_unreferenced_bodies := len(state.immedate_bodies) - int(state.num_referenced_bodies) remove_suspension_constraint(sim_state, handle)
assert(num_unreferenced_bodies >= 0)
bodies_to_remove := make([]u32, num_unreferenced_bodies, context.temp_allocator)
i := 0
for k, &v in state.immedate_bodies {
if v.last_ref != state.simulation_frame {
bodies_to_remove[i] = k
i += 1
} }
} for handle in removed_engines {
remove_engine(sim_state, handle)
assert(i == len(bodies_to_remove))
for k in bodies_to_remove {
handle := state.immedate_bodies[k].handle
delete_key(&state.immedate_bodies, k)
remove_body(get_sim_state(scene), handle)
}
}
prune_immediate_suspension_constraints :: proc(scene: ^Scene) {
state := &scene.solver_state
if int(state.num_referenced_suspension_constraints) ==
len(state.immediate_suspension_constraints) {
return
}
num_unreferenced_constraints :=
len(state.immediate_suspension_constraints) -
int(state.num_referenced_suspension_constraints)
assert(num_unreferenced_constraints >= 0)
constraints_to_remove := make([]u32, num_unreferenced_constraints, context.temp_allocator)
i := 0
for k, &v in state.immediate_suspension_constraints {
if v.last_ref != state.simulation_frame {
constraints_to_remove[i] = k
i += 1
}
}
assert(i == len(constraints_to_remove))
for k in constraints_to_remove {
handle := state.immediate_suspension_constraints[k].handle
delete_key(&state.immediate_suspension_constraints, k)
remove_suspension_constraint(get_sim_state(scene), handle)
}
}
prune_immediate_engines :: proc(scene: ^Scene) {
state := &scene.solver_state
if int(state.num_referenced_engines) == len(state.immediate_engines) {
return
}
num_unreferenced_engines := len(state.immediate_engines) - int(state.num_referenced_engines)
assert(num_unreferenced_engines >= 0)
engines_to_remove := make([]u32, num_unreferenced_engines, context.temp_allocator)
i := 0
for k, &v in state.immediate_engines {
if v.last_ref != state.simulation_frame {
engines_to_remove[i] = k
i += 1
}
}
assert(i == len(engines_to_remove))
for k in engines_to_remove {
handle := state.immediate_engines[k].handle
delete_key(&state.immediate_engines, k)
remove_engine(get_sim_state(scene), handle)
} }
} }

View File

@ -35,46 +35,27 @@ Solver_State :: struct {
simulation_frame: u32, simulation_frame: u32,
// Number of immediate bodies referenced this frame // Number of immediate bodies referenced this frame
num_referenced_bodies: i32, immediate_bodies: Immediate_Container(Body_Handle),
num_referenced_suspension_constraints: i32, immediate_suspension_constraints: Immediate_Container(Suspension_Constraint_Handle),
num_referenced_engines: i32, immediate_engines: Immediate_Container(Engine_Handle),
immedate_bodies: map[u32]Immedate_State(Body_Handle),
immediate_suspension_constraints: map[u32]Immedate_State(Suspension_Constraint_Handle),
immediate_engines: map[u32]Immedate_State(Engine_Handle),
}
copy_map :: proc(dst, src: $T/^map[$K]$V) {
clear(dst)
reserve_map(dst, len(src))
for k, v in src {
dst[k] = v
}
} }
copy_solver_state :: proc(dst, src: ^Solver_State) { copy_solver_state :: proc(dst, src: ^Solver_State) {
dst.accumulated_time = src.accumulated_time dst.accumulated_time = src.accumulated_time
dst.simulation_frame = src.simulation_frame dst.simulation_frame = src.simulation_frame
dst.num_referenced_bodies = src.num_referenced_bodies immediate_container_copy(&dst.immediate_bodies, &src.immediate_bodies)
dst.num_referenced_suspension_constraints = src.num_referenced_suspension_constraints immediate_container_copy(
dst.num_referenced_engines = src.num_referenced_engines &dst.immediate_suspension_constraints,
&src.immediate_suspension_constraints,
copy_map(&dst.immedate_bodies, &src.immedate_bodies) )
copy_map(&dst.immediate_suspension_constraints, &src.immediate_suspension_constraints) immediate_container_copy(&dst.immediate_engines, &src.immediate_engines)
copy_map(&dst.immediate_engines, &src.immediate_engines)
} }
destroy_solver_state :: proc(state: ^Solver_State) { destroy_solver_state :: proc(state: ^Solver_State) {
delete(state.immedate_bodies) immediate_container_destroy(&state.immediate_bodies)
delete(state.immediate_suspension_constraints) immediate_container_destroy(&state.immediate_suspension_constraints)
delete(state.immediate_engines) immediate_container_destroy(&state.immediate_engines)
}
Immedate_State :: struct($T: typeid) {
handle: T,
// When was this referenced last time (frame number)
last_ref: u32,
} }
MAX_STEPS :: 10 MAX_STEPS :: 10
@ -253,9 +234,9 @@ simulate :: proc(
} }
state.simulation_frame += 1 state.simulation_frame += 1
state.num_referenced_bodies = 0 state.immediate_bodies.num_items = 0
state.num_referenced_suspension_constraints = 0 state.immediate_suspension_constraints.num_items = 0
state.num_referenced_engines = 0 state.immediate_engines.num_items = 0
} }
GLOBAL_PLANE :: collision.Plane { GLOBAL_PLANE :: collision.Plane {

View File

@ -241,8 +241,7 @@ Style :: struct {
Context :: struct { Context :: struct {
/* callbacks */ /* callbacks */
text_width: proc(font: Font, font_size: i32, str: string) -> i32, text_size: proc(font: Font, font_size: i32, str: string) -> [2]i32,
text_height: proc(font: Font, font_size: i32) -> i32,
draw_frame: proc(ctx: ^Context, rect: Rect, colorid: Color_Type), draw_frame: proc(ctx: ^Context, rect: Rect, colorid: Color_Type),
/* core state */ /* core state */
default_style: Style, default_style: Style,
@ -304,6 +303,7 @@ unclipped_rect := Rect{0, 0, 0x1000000, 0x1000000}
default_style := Style { default_style := Style {
font = nil, font = nil,
size = {68, 10}, size = {68, 10},
font_size = 16,
padding = 5, padding = 5,
spacing = 4, spacing = 4,
indent = 24, indent = 24,
@ -402,8 +402,7 @@ init :: proc(
} }
begin :: proc(ctx: ^Context) { begin :: proc(ctx: ^Context) {
assert(ctx.text_width != nil, "ctx.text_width is not set") assert(ctx.text_size != nil, "ctx.text_size is not set")
assert(ctx.text_height != nil, "ctx.text_height is not set")
ctx.command_list.idx = 0 ctx.command_list.idx = 0
ctx.root_list.idx = 0 ctx.root_list.idx = 0
ctx.line_segments_num = 0 ctx.line_segments_num = 0
@ -792,12 +791,8 @@ draw_text :: proc(
pos: Vec2, pos: Vec2,
color: Color, color: Color,
) { ) {
rect := Rect { text_size := ctx.text_size(font, font_size, str)
pos.x, rect := Rect{pos.x, pos.y, text_size.x, text_size.y}
pos.y,
ctx.text_width(font, font_size, str),
ctx.text_height(font, font_size),
}
clipped := check_clip(ctx, rect) clipped := check_clip(ctx, rect)
switch clipped { switch clipped {
case .NONE: // okay case .NONE: // okay
@ -1049,13 +1044,13 @@ draw_control_text :: proc(
pos: Vec2 pos: Vec2
font := get_style(ctx).font font := get_style(ctx).font
font_size := get_style(ctx).font_size font_size := get_style(ctx).font_size
tw := ctx.text_width(font, font_size, str) ts := ctx.text_size(font, font_size, str)
push_clip_rect(ctx, rect) push_clip_rect(ctx, rect)
pos.y = rect.y + (rect.h - ctx.text_height(font, font_size)) / 2 pos.y = rect.y + (rect.h - ts.y) / 2
if .ALIGN_CENTER in opt { if .ALIGN_CENTER in opt {
pos.x = rect.x + (rect.w - tw) / 2 pos.x = rect.x + (rect.w - ts.x) / 2
} else if .ALIGN_RIGHT in opt { } else if .ALIGN_RIGHT in opt {
pos.x = rect.x + rect.w - tw - get_style(ctx).padding pos.x = rect.x + rect.w - ts.x - get_style(ctx).padding
} else { } else {
pos.x = rect.x + get_style(ctx).padding pos.x = rect.x + get_style(ctx).padding
} }
@ -1109,7 +1104,7 @@ text :: proc(ctx: ^Context, text: string) {
font_size := style.font_size font_size := style.font_size
color := style.colors[.TEXT] color := style.colors[.TEXT]
layout_begin_column(ctx) layout_begin_column(ctx)
layout_row(ctx, {-1}, ctx.text_height(font, font_size)) layout_row(ctx, {-1}, ctx.text_size(font, font_size, text).y)
for len(text) > 0 { for len(text) > 0 {
w: i32 w: i32
start: int start: int
@ -1118,12 +1113,12 @@ text :: proc(ctx: ^Context, text: string) {
for ch, i in text { for ch, i in text {
if ch == ' ' || ch == '\n' { if ch == ' ' || ch == '\n' {
word := text[start:i] word := text[start:i]
w += ctx.text_width(font, font_size, word) w += ctx.text_size(font, font_size, word).x
if w > r.w && start != 0 { if w > r.w && start != 0 {
end = start end = start
break break
} }
w += ctx.text_width(font, font_size, text[i:i + 1]) w += ctx.text_size(font, font_size, text[i:i + 1]).x
if ch == '\n' { if ch == '\n' {
end = i + 1 end = i + 1
break break
@ -1317,7 +1312,7 @@ textbox_raw :: proc(
if ctx.mouse_pos.x < if ctx.mouse_pos.x <
r.x + r.x +
ctx.textbox_offset + ctx.textbox_offset +
ctx.text_width(font, font_size, string(textbuf[:i])) { ctx.text_size(font, font_size, string(textbuf[:i])).x {
idx = i idx = i
break break
} }
@ -1336,10 +1331,11 @@ textbox_raw :: proc(
if ctx.focus_id == id { if ctx.focus_id == id {
text_color := style.colors[.TEXT] text_color := style.colors[.TEXT]
sel_color := style.colors[.SELECTION_BG] sel_color := style.colors[.SELECTION_BG]
textw := ctx.text_width(font, font_size, textstr) text_size := ctx.text_size(font, font_size, textstr)
texth := ctx.text_height(font, font_size) textw := text_size.x
headx := ctx.text_width(font, font_size, textstr[:ctx.textbox_state.selection[0]]) texth := text_size.y
tailx := ctx.text_width(font, font_size, textstr[:ctx.textbox_state.selection[1]]) headx := ctx.text_size(font, font_size, textstr[:ctx.textbox_state.selection[0]]).x
tailx := ctx.text_size(font, font_size, textstr[:ctx.textbox_state.selection[1]]).x
ofmin := max(get_style(ctx).padding - headx, r.w - textw - get_style(ctx).padding) ofmin := max(get_style(ctx).padding - headx, r.w - textw - get_style(ctx).padding)
ofmax := min(r.w - headx - get_style(ctx).padding, get_style(ctx).padding) ofmax := min(r.w - headx - get_style(ctx).padding, get_style(ctx).padding)
ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax) ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax)

View File

@ -3,6 +3,7 @@
package ui package ui
import "core:log" import "core:log"
import "core:math"
import "core:strings" import "core:strings"
import rl "libs:raylib" import rl "libs:raylib"
import "libs:raylib/rlgl" import "libs:raylib/rlgl"
@ -16,7 +17,7 @@ rl_init :: proc() {
rl.UnloadTexture(default_atlas_texture) rl.UnloadTexture(default_atlas_texture)
default_atlas_texture = {} default_atlas_texture = {}
image := rl.Image{ image := rl.Image {
data = &default_atlas_alpha, data = &default_atlas_alpha,
width = DEFAULT_ATLAS_WIDTH, width = DEFAULT_ATLAS_WIDTH,
height = DEFAULT_ATLAS_HEIGHT, height = DEFAULT_ATLAS_HEIGHT,
@ -40,7 +41,7 @@ to_rl_rect :: proc(r: Rect) -> rl.Rectangle {
return rl.Rectangle{x = f32(r.x), y = f32(r.y), width = f32(r.w), height = f32(r.h)} return rl.Rectangle{x = f32(r.x), y = f32(r.y), width = f32(r.w), height = f32(r.h)}
} }
rl_measure_text_2d :: #force_inline proc(font: Font, font_size: i32, text: string) -> rl.Vector2 { rl_measure_text_2d :: #force_inline proc(font: Font, font_size: i32, text: string) -> [2]i32 {
font := (cast(^rl.Font)font) font := (cast(^rl.Font)font)
size := rl.MeasureTextEx( size := rl.MeasureTextEx(
font^ if font != nil else rl.GetFontDefault(), font^ if font != nil else rl.GetFontDefault(),
@ -49,15 +50,7 @@ rl_measure_text_2d :: #force_inline proc(font: Font, font_size: i32, text: strin
f32(font_size / (font.baseSize if font != nil else 10)), f32(font_size / (font.baseSize if font != nil else 10)),
) )
return size return {i32(math.ceil(size.x)), i32(math.ceil(size.y))}
}
rl_measure_text_width :: proc(font: Font, font_size: i32, text: string) -> i32 {
return i32(rl_measure_text_2d(font, font_size, text).x)
}
rl_measure_text_height :: proc(font: Font, font_size: i32) -> i32 {
return i32(rl_measure_text_2d(font, font_size, "A").y)
} }
rl_draw :: proc(ctx: ^Context) { rl_draw :: proc(ctx: ^Context) {