More cleanup. Hooray!

This commit is contained in:
Stefan Stefanov 2026-01-02 18:49:24 +02:00
parent faf42da522
commit a51f5c6b57
4 changed files with 180 additions and 251 deletions

View file

@ -4,10 +4,12 @@ import generator "../generator"
import rl "vendor:raylib" import rl "vendor:raylib"
PIXEL_WINDOW_HEIGHT :: 180 PIXEL_WINDOW_HEIGHT :: 180
FILE_DIALOG_SIZE :: 1000
scaling: f32 = 2 scaling: f32 = 2
@(rodata)
ATLAS_RENDER_SIZES := []i32{256, 512, 1024, 2048, 4096}
WindowInformation :: struct { WindowInformation :: struct {
w: f32, w: f32,
h: f32, h: f32,
@ -22,9 +24,7 @@ MonitorInformation :: struct {
FileDialogType :: enum { FileDialogType :: enum {
SourceFiles, SourceFiles,
SourceFolder,
OutputFolder, OutputFolder,
SaveFileAs,
} }
PackerSettings :: struct { PackerSettings :: struct {
@ -37,28 +37,16 @@ PackerSettings :: struct {
output_odin: bool, output_odin: bool,
} }
ApplicationState :: struct { window_info: WindowInformation
file_dialog_text_buffer: [FILE_DIALOG_SIZE + 1]u8, monitor_info: MonitorInformation
is_packing_whole_source_folder: bool, // Where the output files will be written (atlas.png json output, etc)
should_open_file_dialog: bool, output_folder_path: Maybe(string)
window_info: WindowInformation, // If a folder was chosen as input - the path
monitor_info: MonitorInformation, source_files_to_pack: Maybe([]string)
// Where the output files will be written (atlas.png, json output, etc) // Packer settings
output_folder_path: Maybe(string), packer_settings: PackerSettings
// If files were chosen as input - their paths atlas_render_texture_target: rl.RenderTexture2D
source_location_to_pack: Maybe(string), atlas_checked_background: rl.RenderTexture2D
// If a folder was chosen as input - the path atlas_render_has_preview: bool
source_files_to_pack: Maybe([]string), atlas_render_size: i32
// What type of file dialog to open atlas_metadata: Maybe([dynamic]generator.SpriteAtlasMetadata)
source_location_type: FileDialogType,
// Packer settings
packer_settings: PackerSettings,
atlas_render_texture_target: rl.RenderTexture2D,
atlas_checked_background: rl.RenderTexture2D,
should_render_atlas: bool,
atlas_render_has_preview: bool,
atlas_render_size: i32,
atlas_metadata: Maybe([dynamic]generator.SpriteAtlasMetadata),
}
g_mem: ApplicationState

View file

@ -1,5 +1,7 @@
package frontend package frontend
import "base:runtime"
import "core:c/libc"
import "core:fmt" import "core:fmt"
import "core:log" import "core:log"
import "core:math" import "core:math"
@ -12,59 +14,58 @@ import rl "vendor:raylib"
import diag "../../vendors/dialog" import diag "../../vendors/dialog"
import generator "../generator" import generator "../generator"
should_pack_atlas_and_render: bool
main :: proc() { main :: proc() {
default_allocator := context.allocator default_allocator := context.allocator
tracking_allocator: mem.Tracking_Allocator tracking_allocator: mem.Tracking_Allocator
mem.tracking_allocator_init(&tracking_allocator, default_allocator) mem.tracking_allocator_init(&tracking_allocator, default_allocator)
context.allocator = mem.tracking_allocator(&tracking_allocator) context.allocator = mem.tracking_allocator(&tracking_allocator)
mode: int = 0 context.logger = log.create_console_logger()
when ODIN_OS == .Linux || ODIN_OS == .Darwin { {
mode = os.S_IRUSR | os.S_IWUSR | os.S_IRGRP | os.S_IROTH init()
}
logh, logh_err := os.open("log.txt", (os.O_CREATE | os.O_TRUNC | os.O_RDWR), mode) defer cleanup()
if logh_err == os.ERROR_NONE { for !rl.WindowShouldClose() {
os.stdout = logh update()
os.stderr = logh draw()
}
logger := for b in tracking_allocator.bad_free_array {
logh_err == os.ERROR_NONE ? log.create_file_logger(logh) : log.create_console_logger() log.errorf("Bad free at: %v", b.location)
context.logger = logger }
defer if logh_err == os.ERROR_NONE {
log.destroy_file_logger(logger) clear(&tracking_allocator.bad_free_array)
free_all(context.temp_allocator)
}
} }
log.destroy_console_logger(context.logger)
for key, value in tracking_allocator.allocation_map {
log.errorf("%v: Leaked %v bytes\n", value.location, value.size)
}
mem.tracking_allocator_destroy(&tracking_allocator)
}
init :: proc() {
atlas_render_size = ATLAS_RENDER_SIZES[0]
rl.SetConfigFlags({.WINDOW_RESIZABLE}) rl.SetConfigFlags({.WINDOW_RESIZABLE})
rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer") rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer")
defer rl.CloseWindow()
rl.SetWindowMinSize(1400, 800) rl.SetWindowMinSize(1400, 800)
rl.SetTraceLogCallback(rl_log)
}
for !rl.WindowShouldClose() { cleanup :: proc() {
update() log.info("Bye")
draw() rl.CloseWindow()
for b in tracking_allocator.bad_free_array {
log.error("Bad free at: %v", b.location)
}
clear(&tracking_allocator.bad_free_array)
free_all(context.temp_allocator)
}
for key, value in tracking_allocator.allocation_map {
log.error("%v: Leaked %v bytes\n", value.location, value.size)
}
mem.tracking_allocator_destroy(&tracking_allocator)
} }
update :: proc() { update :: proc() {
// Update the width/height // Update the width/height
win_info := &g_mem.window_info win_info := &window_info
win_info.w = f32(rl.GetScreenWidth()) win_info.w = f32(rl.GetScreenWidth())
win_info.h = f32(rl.GetScreenHeight()) win_info.h = f32(rl.GetScreenHeight())
win_info.height_scaled = win_info.h / scaling win_info.height_scaled = win_info.h / scaling
@ -73,10 +74,6 @@ update :: proc() {
// Update the virtual mouse position (needed for GUI interaction to work properly for instance) // Update the virtual mouse position (needed for GUI interaction to work properly for instance)
mouse_scale := 1 / scaling mouse_scale := 1 / scaling
rl.SetMouseScale(mouse_scale, mouse_scale) rl.SetMouseScale(mouse_scale, mouse_scale)
if g_mem.should_open_file_dialog {
open_file_dialog()
}
} }
draw :: proc() { draw :: proc() {
@ -87,8 +84,8 @@ draw :: proc() {
draw_screen_ui() draw_screen_ui()
if g_mem.should_render_atlas { if should_pack_atlas_and_render {
draw_screen_target() pack_atlas_and_render()
} }
free_all(context.temp_allocator) free_all(context.temp_allocator)
@ -100,41 +97,23 @@ ui_camera :: proc() -> rl.Camera2D {
draw_screen_ui :: proc() { draw_screen_ui :: proc() {
rl.BeginMode2D(ui_camera()) rl.BeginMode2D(ui_camera())
defer rl.EndMode2D()
draw_atlas_settings_and_preview() draw_settings_and_preview()
rl.EndMode2D()
} }
pick_sources :: proc() { save_atlas_to_file :: proc() {
g_mem.should_open_file_dialog = true generator.save_output(output_folder_path, atlas_metadata, atlas_render_texture_target)
g_mem.source_location_type = .SourceFiles
} }
pick_output :: proc() { save_to_atlas_to_file :: proc() {
g_mem.should_open_file_dialog = true open_file_dialog(.OutputFolder)
g_mem.source_location_type = .OutputFolder save_atlas_to_file()
} }
pack_atlas :: proc() { pack_atlas_and_render :: proc() {
g_mem.should_render_atlas = true atlas_render_target := &atlas_render_texture_target
}
save :: proc() {
generator.save_output(
g_mem.output_folder_path,
g_mem.atlas_metadata,
g_mem.atlas_render_texture_target,
)
}
save_to :: proc() {
// if output_folder, ok := g_mem.output_folder_path.(string); ok {
// generator.save_metadata_simple(output_folder, g_mem.atlas_metadata, nil, nil, nil)
// }
}
draw_screen_target :: proc() {
atlas_render_target := &g_mem.atlas_render_texture_target
rl.BeginTextureMode(atlas_render_target^) rl.BeginTextureMode(atlas_render_target^)
defer rl.EndTextureMode() defer rl.EndTextureMode()
@ -142,28 +121,21 @@ draw_screen_target :: proc() {
atlas_entries: [dynamic]generator.AtlasEntry atlas_entries: [dynamic]generator.AtlasEntry
delete(atlas_entries) delete(atlas_entries)
if files, ok := g_mem.source_files_to_pack.([]string); ok { if files, ok := source_files_to_pack.([]string); ok {
generator.unmarshall_aseprite_files(files, &atlas_entries) generator.unmarshall_aseprite_files(files, &atlas_entries)
} else { } else {
log.error("No source folder or files set! Can't pack the void!!!") log.error("No source folder or files set! Can't pack the void!!!")
g_mem.should_render_atlas = false should_pack_atlas_and_render = false
return return
} }
atlas: rl.Image = rl.GenImageColor(g_mem.atlas_render_size, g_mem.atlas_render_size, rl.BLANK) atlas: rl.Image = rl.GenImageColor(atlas_render_size, atlas_render_size, rl.BLANK)
// defer rl.UnloadImage(atlas) // defer rl.UnloadImage(atlas)
padding_x := padding_x := packer_settings.pixel_padding_x_int if packer_settings.padding_enabled else 0
g_mem.packer_settings.pixel_padding_x_int if g_mem.packer_settings.padding_enabled else 0 padding_y := packer_settings.pixel_padding_y_int if packer_settings.padding_enabled else 0
padding_y :=
g_mem.packer_settings.pixel_padding_y_int if g_mem.packer_settings.padding_enabled else 0
g_mem.atlas_metadata = generator.pack_atlas_entries( atlas_metadata = generator.pack_atlas_entries(atlas_entries[:], &atlas, padding_x, padding_y)
atlas_entries[:],
&atlas,
padding_x,
padding_y,
)
// OpenGL's Y buffer is flipped // OpenGL's Y buffer is flipped
rl.ImageFlipVertical(&atlas) rl.ImageFlipVertical(&atlas)
@ -171,28 +143,27 @@ draw_screen_target :: proc() {
log.info("Packed everything!") log.info("Packed everything!")
atlas_render_target.texture = rl.LoadTextureFromImage(atlas) atlas_render_target.texture = rl.LoadTextureFromImage(atlas)
g_mem.should_render_atlas = false should_pack_atlas_and_render = false
g_mem.atlas_render_has_preview = true atlas_render_has_preview = true
} }
draw_atlas_settings_and_preview :: proc() { draw_settings_and_preview :: proc() {
left_half_rect := rl.Rectangle { left_half_rect := rl.Rectangle {
x = 0, x = 0,
y = 0, y = 0,
width = cast(f32)g_mem.window_info.width_scaled / 3, width = cast(f32)window_info.width_scaled / 3,
height = cast(f32)g_mem.window_info.height_scaled, height = cast(f32)window_info.height_scaled,
} }
right_half_rect := rl.Rectangle { right_half_rect := rl.Rectangle {
x = cast(f32)g_mem.window_info.width_scaled / 3, x = cast(f32)window_info.width_scaled / 3,
y = 0, y = 0,
width = cast(f32)(g_mem.window_info.width_scaled / 3) * 2, width = cast(f32)(window_info.width_scaled / 3) * 2,
height = cast(f32)g_mem.window_info.height_scaled, height = cast(f32)window_info.height_scaled,
} }
rl.DrawRectangleRec(left_half_rect, rl.WHITE) rl.DrawRectangleRec(left_half_rect, rl.WHITE)
rl.DrawRectangleRec(right_half_rect, rl.MAROON) rl.DrawRectangleRec(right_half_rect, rl.MAROON)
@(static) @(static) spinner_edit_mode: bool
spinner_edit_mode: bool
small_offset := 10 * scaling small_offset := 10 * scaling
big_offset := 30 * scaling big_offset := 30 * scaling
@ -201,10 +172,7 @@ draw_atlas_settings_and_preview :: proc() {
rl.GuiPanel(left_half_rect, "Atlas Settings") rl.GuiPanel(left_half_rect, "Atlas Settings")
elements_height += small_offset / 2 elements_height += small_offset / 2
@(static) @(static) settings_dropdown_box_active_idx: i32
SettingsDropBoxEditMode: bool
@(static)
SettingsDropdownBoxActive: i32
elements_height += small_offset + 5 * scaling elements_height += small_offset + 5 * scaling
@ -214,10 +182,8 @@ draw_atlas_settings_and_preview :: proc() {
) )
elements_height += small_offset / 2 elements_height += small_offset / 2
@(static) @(static) dropdown_resolution_edit_mode: bool
dropdown_resolution_edit_mode: bool @(static) dropdown_resolution_mode: i32
@(static)
dropdown_resolution_mode: i32
dropdown_rect := rl.Rectangle { dropdown_rect := rl.Rectangle {
x = small_offset, x = small_offset,
@ -230,21 +196,21 @@ draw_atlas_settings_and_preview :: proc() {
if dropdown_resolution_edit_mode {rl.GuiLock()} if dropdown_resolution_edit_mode {rl.GuiLock()}
if rl.GuiDropdownBox( if rl.GuiDropdownBox(
dropdown_rect, dropdown_rect,
"256x;512x;1024x;2048x;4096x", "256x;512x;1024x;2048x;4096x",
&dropdown_resolution_mode, &dropdown_resolution_mode,
dropdown_resolution_edit_mode, dropdown_resolution_edit_mode,
) { ) {
dropdown_resolution_edit_mode = !dropdown_resolution_edit_mode dropdown_resolution_edit_mode = !dropdown_resolution_edit_mode
g_mem.atlas_render_size = 256 * auto_cast math.pow(2, f32(dropdown_resolution_mode)) atlas_render_size =
ATLAS_RENDER_SIZES[max(i32(len(ATLAS_RENDER_SIZES) - 1), dropdown_resolution_mode)]
} }
rl.GuiUnlock() rl.GuiUnlock()
} }
elements_height += small_offset * 2 elements_height += small_offset * 2
// General Options // General Options
if SettingsDropdownBoxActive == 0 { if settings_dropdown_box_active_idx == 0 {
padding_settings_y := elements_height padding_settings_y := elements_height
{ {
defer { defer {
@ -267,7 +233,7 @@ draw_atlas_settings_and_preview :: proc() {
rl.GuiCheckBox( rl.GuiCheckBox(
enable_padding_rect, enable_padding_rect,
" Enable padding", " Enable padding",
&g_mem.packer_settings.padding_enabled, &packer_settings.padding_enabled,
) )
elements_height += small_offset * 2 elements_height += small_offset * 2
@ -282,7 +248,7 @@ draw_atlas_settings_and_preview :: proc() {
padding_x_spinner := rl.GuiSpinner( padding_x_spinner := rl.GuiSpinner(
padding_x_spinner_rect, padding_x_spinner_rect,
"", "",
&g_mem.packer_settings.pixel_padding_x_int, &packer_settings.pixel_padding_x_int,
0, 0,
10, 10,
spinner_edit_mode, spinner_edit_mode,
@ -291,7 +257,7 @@ draw_atlas_settings_and_preview :: proc() {
spinner_edit_mode = !spinner_edit_mode spinner_edit_mode = !spinner_edit_mode
} }
rl.GuiLabel( rl.GuiLabel(
{ {
x = (small_offset * 2) + big_offset * 2, x = (small_offset * 2) + big_offset * 2,
y = elements_height, y = elements_height,
width = big_offset, width = big_offset,
@ -313,7 +279,7 @@ draw_atlas_settings_and_preview :: proc() {
padding_y_spinner := rl.GuiSpinner( padding_y_spinner := rl.GuiSpinner(
padding_y_spinner_rect, padding_y_spinner_rect,
"", "",
&g_mem.packer_settings.pixel_padding_y_int, &packer_settings.pixel_padding_y_int,
0, 0,
10, 10,
spinner_edit_mode, spinner_edit_mode,
@ -322,7 +288,7 @@ draw_atlas_settings_and_preview :: proc() {
spinner_edit_mode = !spinner_edit_mode spinner_edit_mode = !spinner_edit_mode
} }
rl.GuiLabel( rl.GuiLabel(
{ {
x = (small_offset * 2) + big_offset * 2, x = (small_offset * 2) + big_offset * 2,
y = elements_height, y = elements_height,
width = big_offset, width = big_offset,
@ -336,9 +302,6 @@ draw_atlas_settings_and_preview :: proc() {
} }
elements_height += small_offset elements_height += small_offset
// rl.GuiLine({y = elements_height, width = left_half_rect.width}, "Actions")
// elements_height += small_offset
{ {
actions_label_y := elements_height actions_label_y := elements_height
@ -360,7 +323,7 @@ draw_atlas_settings_and_preview :: proc() {
height = small_offset, height = small_offset,
} }
if rl.GuiButton(pick_sources_rect, "Pick Source(s)") { if rl.GuiButton(pick_sources_rect, "Pick Source(s)") {
pick_sources() open_file_dialog(.SourceFiles)
} }
pick_output_rect := rl.Rectangle { pick_output_rect := rl.Rectangle {
@ -370,7 +333,7 @@ draw_atlas_settings_and_preview :: proc() {
height = small_offset, height = small_offset,
} }
if rl.GuiButton(pick_output_rect, "Pick Output") { if rl.GuiButton(pick_output_rect, "Pick Output") {
pick_output() open_file_dialog(.OutputFolder)
} }
elements_height += small_offset * 2 elements_height += small_offset * 2
@ -381,7 +344,7 @@ draw_atlas_settings_and_preview :: proc() {
height = small_offset, height = small_offset,
} }
if rl.GuiButton(pack_atlas_rect, "Pack Atlas") { if rl.GuiButton(pack_atlas_rect, "Pack Atlas") {
pack_atlas() should_pack_atlas_and_render = true
} }
clear_atlas_rect := rl.Rectangle { clear_atlas_rect := rl.Rectangle {
@ -402,7 +365,7 @@ draw_atlas_settings_and_preview :: proc() {
height = small_offset, height = small_offset,
} }
if rl.GuiButton(save_rect, "Save") { if rl.GuiButton(save_rect, "Save") {
save() save_atlas_to_file()
} }
save_to_rect := rl.Rectangle { save_to_rect := rl.Rectangle {
@ -412,7 +375,7 @@ draw_atlas_settings_and_preview :: proc() {
height = small_offset, height = small_offset,
} }
if rl.GuiButton(save_to_rect, "Save To...") { if rl.GuiButton(save_to_rect, "Save To...") {
save_to() save_to_atlas_to_file()
} }
elements_height += small_offset * 2 elements_height += small_offset * 2
} }
@ -420,10 +383,9 @@ draw_atlas_settings_and_preview :: proc() {
} }
// Packing Options // Packing Options
if SettingsDropdownBoxActive == 1 { if settings_dropdown_box_active_idx == 1 {
@(static) @(static) active_tab: i32
active_tab: i32
tabs: []cstring = {"One", "Two", "Three"} tabs: []cstring = {"One", "Two", "Three"}
rl.GuiTabBar( rl.GuiTabBar(
{x = small_offset, y = elements_height, width = 100, height = small_offset}, {x = small_offset, y = elements_height, width = 100, height = small_offset},
@ -434,9 +396,8 @@ draw_atlas_settings_and_preview :: proc() {
} }
// Save Options // Save Options
if SettingsDropdownBoxActive == 2 { // if settings_dropdown_box_active_idx == 2 {
// }
}
elements_height = 0 elements_height = 0
rl.GuiPanel(right_half_rect, "Atlas Preview") rl.GuiPanel(right_half_rect, "Atlas Preview")
@ -450,11 +411,11 @@ draw_atlas_settings_and_preview :: proc() {
width = short_edge, width = short_edge,
height = short_edge, height = short_edge,
} }
if !g_mem.atlas_render_has_preview { if !atlas_render_has_preview {
rl.GuiDummyRec(preview_rect, "PREVIEW") rl.GuiDummyRec(preview_rect, "PREVIEW")
} else { } else {
// rl.DrawRectangleRec(preview_rect, rl.WHITE) // rl.DrawRectangleRec(preview_rect, rl.WHITE)
bg_texture := g_mem.atlas_checked_background.texture bg_texture := atlas_checked_background.texture
rl.DrawTexturePro( rl.DrawTexturePro(
bg_texture, bg_texture,
{width = auto_cast bg_texture.width, height = auto_cast bg_texture.height}, {width = auto_cast bg_texture.width, height = auto_cast bg_texture.height},
@ -463,9 +424,7 @@ draw_atlas_settings_and_preview :: proc() {
0, 0,
rl.WHITE, rl.WHITE,
) )
// preview_rect.x += atlas_texture := atlas_render_texture_target.texture
// 10;preview_rect.y += 10;preview_rect.height -= 20;preview_rect.width -= 20
atlas_texture := g_mem.atlas_render_texture_target.texture
rl.DrawTexturePro( rl.DrawTexturePro(
atlas_texture, atlas_texture,
{width = auto_cast atlas_texture.width, height = auto_cast -atlas_texture.height}, {width = auto_cast atlas_texture.width, height = auto_cast -atlas_texture.height},
@ -477,96 +436,80 @@ draw_atlas_settings_and_preview :: proc() {
} }
} }
open_file_dialog :: proc() { open_file_dialog :: proc(dialog_type: FileDialogType) {
switch g_mem.source_location_type { switch dialog_type {
case .SourceFiles: case .SourceFiles:
// `open_file_dialog` returns a single cstring with one or more paths, divided by a separator ('|'), // `open_file_dialog` returns a single cstring with one or more paths divided by a separator ('|'),
// https://github.com/native-toolkit/libtinyfiledialogs/blob/master/tinyfiledialogs.c#L2706 // https://github.com/native-toolkit/libtinyfiledialogs/blob/master/tinyfiledialogs.c#L2706
file_paths_conc := cstring( file_paths_conc := cstring(
diag.open_file_dialog( diag.open_file_dialog("Select source files", nil, 0, nil, "", 1),
"Select source files",
cstring(&g_mem.file_dialog_text_buffer[0]),
0,
nil,
"",
1,
),
) )
if len(file_paths_conc) > 0 { if len(file_paths_conc) > 0 {
// todo(stefan): Currently we're not doing any checks if the filepaths are valid at all, // todo(stefan): We're assuming the filepaths returned libtinydialog are valid...
// this should be fine because it's returned by the OS' file picker but who knows... source_files := strings.clone_from_cstring(file_paths_conc, context.allocator)
source_files_to_pack := strings.clone_from_cstring(file_paths_conc, context.allocator) source_files_to_pack = strings.split(source_files, "|")
g_mem.source_files_to_pack = strings.split(source_files_to_pack, "|")
log.info(g_mem.source_files_to_pack) log.info(source_files_to_pack)
} else { } else {
log.error("No files were selected!") log.error("No files were selected!")
} }
case .SourceFolder:
file := cstring(
diag.select_folder_dialog(
"Select source folder",
cstring(&g_mem.file_dialog_text_buffer[0]),
),
)
if len(file) > 0 {
g_mem.source_location_to_pack = strings.clone_from_cstring(file)
log.info(g_mem.source_location_to_pack)
} else {
log.error("Got an empty path from the file dialog!")
}
case .OutputFolder: case .OutputFolder:
file := cstring( file := cstring(diag.select_folder_dialog("Select source folder", nil))
diag.select_folder_dialog(
"Select source folder",
cstring(&g_mem.file_dialog_text_buffer[0]),
),
)
if len(file) > 0 { if len(file) > 0 {
g_mem.output_folder_path = strings.clone_from_cstring(file) output_folder_path = strings.clone_from_cstring(file)
log.info(g_mem.output_folder_path) log.info(output_folder_path)
} else { } else {
log.error("Got an empty path from the file dialog!") log.error("Got an empty path from the file dialog!")
} }
case .SaveFileAs:
file_path: cstring
patterns: []cstring = {"*.png"}
if default_path, ok := g_mem.output_folder_path.(string); ok {
default_path_filename := strings.concatenate(
{default_path, generator.OS_FILE_SEPARATOR, "atlas.png"},
)
default_path_to_save: cstring = strings.clone_to_cstring(default_path_filename)
file_path = cstring(
diag.save_file_dialog(
"Save as...",
default_path_to_save,
1,
&patterns[0],
"Atlas",
),
)
} else {
file_path = cstring(diag.save_file_dialog("Save as...", "", 1, &patterns[0], "Atlas"))
}
if file_path != nil {
generator.save_output(
g_mem.output_folder_path,
g_mem.atlas_metadata,
g_mem.atlas_render_texture_target,
)
}
} }
g_mem.should_open_file_dialog = false
} }
clear_atlas_data :: proc() { clear_atlas_data :: proc() {
if metadata, ok := g_mem.atlas_metadata.([dynamic]generator.SpriteAtlasMetadata); ok { if metadata, ok := atlas_metadata.([dynamic]generator.SpriteAtlasMetadata); ok {
delete(metadata) delete(metadata)
} }
g_mem.atlas_render_has_preview = false atlas_render_has_preview = false
}
logger: log.Logger
rl_log_buf: []byte
rl_log :: proc "c" (logLevel: rl.TraceLogLevel, text: cstring, args: ^libc.va_list) {
context = runtime.default_context()
context.logger = logger
level: log.Level
switch logLevel {
case .TRACE, .DEBUG:
level = .Debug
case .ALL, .NONE, .INFO:
level = .Info
case .WARNING:
level = .Warning
case .ERROR:
level = .Error
case .FATAL:
level = .Fatal
}
if level < logger.lowest_level {
return
}
if rl_log_buf == nil {
rl_log_buf = make([]byte, 1024)
}
defer mem.zero_slice(rl_log_buf)
n: int
for {
va := args
n = int(libc.vsnprintf(raw_data(rl_log_buf), len(rl_log_buf), text, va))
if n < len(rl_log_buf) do break
log.infof("Resizing raylib log buffer from %m to %m", len(rl_log_buf), len(rl_log_buf) * 2)
rl_log_buf, _ = mem.resize_bytes(rl_log_buf, len(rl_log_buf) * 2)
}
formatted := string(rl_log_buf[:n])
log.log(level, formatted)
} }

View file

@ -182,28 +182,27 @@ pack_atlas_entries :: proc(
atlas: ^rl.Image, atlas: ^rl.Image,
offset_x: i32, offset_x: i32,
offset_y: i32, offset_y: i32,
allocator := context.allocator,
) -> [dynamic]SpriteAtlasMetadata { ) -> [dynamic]SpriteAtlasMetadata {
assert(atlas.width != 0, "Atlas width shouldn't be 0!") assert(atlas.width != 0, "Atlas width shouldn't be 0!")
assert(atlas.height != 0, "Atlas height shouldn't be 0!") assert(atlas.height != 0, "Atlas height shouldn't be 0!")
all_cell_images := make([dynamic]rl.Image, allocator) // it's fine to store it like this, rl.Image just stores a pointer to the data all_cell_images := make([dynamic]rl.Image) // it's fine to store it like this, rl.Image just stores a pointer to the data
for &entry in entries { for &entry in entries {
for cell in entry.cells { for cell in entry.cells {
append(&all_cell_images, cell.img) append(&all_cell_images, cell.img)
} }
entry.layer_cell_count = make([dynamic]i32, len(entry.cells), allocator) entry.layer_cell_count = make([dynamic]i32, len(entry.cells))
} }
num_entries := len(all_cell_images) num_entries := len(all_cell_images)
nodes := make([]stbrp.Node, num_entries, allocator) nodes := make([]stbrp.Node, num_entries)
rects := make([]stbrp.Rect, num_entries, allocator) rects := make([]stbrp.Rect, num_entries)
EntryAndCell :: struct { EntryAndCell :: struct {
entry: ^AtlasEntry, entry: ^AtlasEntry,
cell_of_entry: ^CellData, cell_of_entry: ^CellData,
} }
rect_idx_to_entry_and_cell := make(map[int]EntryAndCell, 100, allocator) rect_idx_to_entry_and_cell := make(map[int]EntryAndCell, 100)
// Set the custom IDs // Set the custom IDs
cellIdx: int cellIdx: int
@ -241,15 +240,15 @@ pack_atlas_entries :: proc(
src_rect := rl.Rectangle { src_rect := rl.Rectangle {
x = 0, x = 0,
y = 0, y = 0,
width = auto_cast cell.img.width, width = f32(cell.img.width),
height = auto_cast cell.img.height, height = f32(cell.img.height),
} }
dst_rect := rl.Rectangle { dst_rect := rl.Rectangle {
auto_cast rect.x + auto_cast offset_x, f32(i32(rect.x) + offset_x),
auto_cast rect.y + auto_cast offset_y, f32(i32(rect.y) + offset_y),
auto_cast cell.img.width, f32(cell.img.width),
auto_cast cell.img.height, f32(cell.img.height),
} }
// note(stefan): drawing the sprite in the atlas in the packed coordinates // note(stefan): drawing the sprite in the atlas in the packed coordinates
@ -258,7 +257,7 @@ pack_atlas_entries :: proc(
log.infof("Src rect: {0}\nDst rect:{1}", src_rect, dst_rect) log.infof("Src rect: {0}\nDst rect:{1}", src_rect, dst_rect)
} }
metadata := make([dynamic]SpriteAtlasMetadata, allocator) metadata := make([dynamic]SpriteAtlasMetadata)
for rect, rectIdx in rects { for rect, rectIdx in rects {
entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx] entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx]
entry := entry_and_cell.entry entry := entry_and_cell.entry
@ -267,10 +266,9 @@ pack_atlas_entries :: proc(
cell_name: string cell_name: string
if entry.layer_cell_count[cell.layer_index] > 1 { if entry.layer_cell_count[cell.layer_index] > 1 {
cell_name = fmt.aprintf( cell_name = fmt.aprintf(
"{0}_%d", "{0}_{1}",
entry.layer_names[cell.layer_index], entry.layer_names[cell.layer_index],
cell.frame_index, cell.frame_index,
allocator,
) )
} else { } else {
cell_name = entry.layer_names[cell.layer_index] cell_name = entry.layer_names[cell.layer_index]
@ -278,8 +276,8 @@ pack_atlas_entries :: proc(
cell_metadata := SpriteAtlasMetadata { cell_metadata := SpriteAtlasMetadata {
name = cell_name, name = cell_name,
location = { location = {
auto_cast rect.x + auto_cast offset_x, i32(rect.x) + offset_x,
auto_cast rect.y + auto_cast offset_y, i32(rect.y) + offset_y,
}, },
size = {auto_cast cell.img.width, auto_cast cell.img.height}, size = {auto_cast cell.img.width, auto_cast cell.img.height},
} }
@ -308,7 +306,7 @@ odin_source_generator_metadata := SourceCodeGeneratorMetadata {
}, },
array_data = { array_data = {
name = "ATLAS_SPRITES", name = "ATLAS_SPRITES",
type = "[]AtlasRect", type = "[AtlasSprite]AtlasRect",
begin_line = "%v := %v {{\n", begin_line = "%v := %v {{\n",
entry_line = "\t.%v = {{ x = %v, y = %v, w = %v, h = %v }},\n", entry_line = "\t.%v = {{ x = %v, y = %v, w = %v, h = %v }},\n",
end_line = "}\n\n", end_line = "}\n\n",
@ -369,7 +367,7 @@ generate_odin_enums_and_atlas_offsets_file_sb :: proc(
// start offsets array // start offsets array
// todo(stefan): the name of the array can be based on the output name? // todo(stefan): the name of the array can be based on the output name?
strings.write_string(&sb, "ATLAS_SPRITES := []AtlasRect {\n") strings.write_string(&sb, "ATLAS_SPRITES := [AtlasSprite]AtlasRect {\n")
{ {
entry: string entry: string
for cell in metadata { for cell in metadata {

View file

@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/usr/bin/env sh
set -e
gcc ./libtinyfiledialogs/tinyfiledialogs.c -c -o libtinyfiledialogs.o gcc ./libtinyfiledialogs/tinyfiledialogs.c -c -o libtinyfiledialogs.o
ar rcs libtinyfiledialogs.a libtinyfiledialogs.o ar rcs libtinyfiledialogs.a libtinyfiledialogs.o
rm libtinyfiledialogs.o rm libtinyfiledialogs.o