491 lines
12 KiB
Odin
491 lines
12 KiB
Odin
// This file is compiled as part of the `odin.dll` file. It contains the
|
|
// procs that `game.exe` will call, such as:
|
|
//
|
|
// game_init: Sets up the game state
|
|
// game_update: Run once per frame
|
|
// game_shutdown: Shuts down game and frees memory
|
|
// game_memory: Run just before a hot reload, so game.exe has a pointer to the
|
|
// game's memory.
|
|
// game_hot_reloaded: Run after a hot reload so that the `g_mem` global variable
|
|
// can be set to whatever pointer it was in the old DLL.
|
|
|
|
package game
|
|
|
|
import "core:fmt"
|
|
import "core:math"
|
|
import "core:strings"
|
|
import rl "vendor:raylib"
|
|
|
|
import diag "dialog"
|
|
|
|
g_mem: ^GameMemory
|
|
|
|
game_camera :: proc() -> rl.Camera2D {
|
|
w = f32(rl.GetScreenWidth())
|
|
h = f32(rl.GetScreenHeight())
|
|
|
|
return {zoom = h / PixelWindowHeight, target = {}, offset = {w / 2, h / 2}}
|
|
}
|
|
|
|
ui_camera :: proc() -> rl.Camera2D {
|
|
return {zoom = scaling}
|
|
}
|
|
|
|
update :: proc() {
|
|
// Update the width/height
|
|
win_info := &g_mem.window_info
|
|
win_info.w = f32(rl.GetScreenWidth())
|
|
win_info.h = f32(rl.GetScreenHeight())
|
|
win_info.height_scaled = win_info.h / scaling
|
|
win_info.width_scaled = win_info.w / scaling
|
|
w = win_info.w
|
|
h = win_info.h
|
|
|
|
// Update the virtual mouse position (needed for GUI interaction to work properly for instance)
|
|
rl.SetMouseScale(1 / scaling, 1 / scaling)
|
|
|
|
if g_mem.should_open_file_dialog {
|
|
open_file_dialog()
|
|
}
|
|
}
|
|
|
|
draw :: proc() {
|
|
rl.BeginDrawing()
|
|
defer rl.EndDrawing()
|
|
|
|
rl.ClearBackground(rl.BLACK)
|
|
|
|
draw_screen_ui()
|
|
|
|
if g_mem.should_render_atlas {
|
|
draw_screen_target()
|
|
}
|
|
|
|
free_all(context.temp_allocator)
|
|
}
|
|
|
|
draw_screen_ui :: proc() {
|
|
rl.BeginMode2D(ui_camera())
|
|
defer rl.EndMode2D()
|
|
|
|
draw_atlas_settings_and_preview()
|
|
}
|
|
|
|
draw_screen_target :: proc() {
|
|
atlas_render_target := &g_mem.atlas_render_texture_target
|
|
|
|
rl.BeginTextureMode(atlas_render_target^)
|
|
defer rl.EndTextureMode()
|
|
|
|
atlas_entries: [dynamic]AtlasEntry
|
|
delete(atlas_entries)
|
|
|
|
if files, ok := g_mem.source_files_to_pack.([]string); ok {
|
|
unmarshall_aseprite_files(files, &atlas_entries)
|
|
} else {
|
|
fmt.println("No source folder or files set! Can't pack the void!!!")
|
|
g_mem.should_render_atlas = false
|
|
return
|
|
}
|
|
|
|
atlas: rl.Image = rl.GenImageColor(g_mem.atlas_render_size, g_mem.atlas_render_size, rl.BLANK)
|
|
// defer rl.UnloadImage(atlas)
|
|
|
|
padding_x :=
|
|
g_mem.packer_settings.pixel_padding_x_int if g_mem.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
|
|
pack_atlas_entries(atlas_entries[:], &atlas, padding_x, padding_y)
|
|
|
|
// OpenGL's Y buffer is flipped
|
|
rl.ImageFlipVertical(&atlas)
|
|
// rl.UnloadTexture(atlas_render_target.texture)
|
|
fmt.println("Packed everything!")
|
|
atlas_render_target.texture = rl.LoadTextureFromImage(atlas)
|
|
|
|
g_mem.should_render_atlas = false
|
|
g_mem.atlas_render_has_preview = true
|
|
}
|
|
|
|
draw_atlas_settings_and_preview :: proc() {
|
|
left_half_rect := rl.Rectangle {
|
|
x = 0,
|
|
y = 0,
|
|
width = auto_cast g_mem.window_info.width_scaled / 3,
|
|
height = auto_cast g_mem.window_info.height_scaled,
|
|
}
|
|
right_half_rect := rl.Rectangle {
|
|
x = auto_cast g_mem.window_info.width_scaled / 3,
|
|
y = 0,
|
|
width = auto_cast (g_mem.window_info.width_scaled / 3) * 2,
|
|
height = auto_cast g_mem.window_info.height_scaled,
|
|
}
|
|
rl.DrawRectangleRec(left_half_rect, rl.WHITE)
|
|
rl.DrawRectangleRec(right_half_rect, rl.MAROON)
|
|
|
|
@(static)
|
|
spinner_edit_mode: bool
|
|
|
|
small_offset := 10 * scaling
|
|
big_offset := 30 * scaling
|
|
elements_height: f32 = 0
|
|
|
|
rl.GuiPanel(left_half_rect, "Atlas Settings")
|
|
elements_height += small_offset / 2
|
|
|
|
@(static)
|
|
SettingsDropBoxEditMode: bool
|
|
@(static)
|
|
SettingsDropdownBoxActive: i32
|
|
|
|
elements_height += small_offset + 5 * scaling
|
|
|
|
rl.GuiLabel(
|
|
{x = small_offset, y = elements_height, width = left_half_rect.width},
|
|
"Atlas Size",
|
|
)
|
|
elements_height += small_offset / 2
|
|
|
|
@(static)
|
|
DropdownBox000EditMode: bool
|
|
@(static)
|
|
DropdownBox000Active: i32
|
|
|
|
dropdown_rect := rl.Rectangle {
|
|
x = small_offset,
|
|
y = elements_height,
|
|
width = left_half_rect.width - small_offset * 2,
|
|
height = small_offset,
|
|
}
|
|
|
|
// Because we want to render this ontop of everything else, we can just 'defer' it at the end of the draw function
|
|
defer {
|
|
if DropdownBox000EditMode {rl.GuiLock()}
|
|
|
|
if rl.GuiDropdownBox(
|
|
dropdown_rect,
|
|
"256x;512x;1024x;2048x;4096x",
|
|
&DropdownBox000Active,
|
|
DropdownBox000EditMode,
|
|
) {
|
|
DropdownBox000EditMode = !DropdownBox000EditMode
|
|
fmt.println(DropdownBox000Active)
|
|
g_mem.atlas_render_size = 256 * auto_cast math.pow(2, f32(DropdownBox000Active))
|
|
}
|
|
rl.GuiUnlock()
|
|
}
|
|
elements_height += small_offset * 2
|
|
|
|
|
|
// General Options
|
|
if SettingsDropdownBoxActive == 0 {
|
|
padding_settings_y := elements_height
|
|
{
|
|
defer rl.GuiGroupBox(
|
|
{
|
|
x = small_offset / 2,
|
|
y = padding_settings_y,
|
|
width = left_half_rect.width - small_offset,
|
|
height = elements_height - padding_settings_y,
|
|
},
|
|
"Padding Settings",
|
|
)
|
|
elements_height += small_offset
|
|
|
|
rl.GuiCheckBox(
|
|
{
|
|
x = small_offset,
|
|
y = elements_height,
|
|
width = small_offset,
|
|
height = small_offset,
|
|
},
|
|
" Enable padding",
|
|
&g_mem.packer_settings.padding_enabled,
|
|
)
|
|
elements_height += small_offset * 2
|
|
|
|
if (rl.GuiSpinner(
|
|
{
|
|
x = small_offset,
|
|
y = elements_height,
|
|
width = big_offset * 2,
|
|
height = small_offset,
|
|
},
|
|
"",
|
|
&g_mem.packer_settings.pixel_padding_x_int,
|
|
0,
|
|
10,
|
|
spinner_edit_mode,
|
|
)) >
|
|
0 {spinner_edit_mode = !spinner_edit_mode}
|
|
rl.GuiLabel(
|
|
{
|
|
x = (small_offset * 2) + big_offset * 2,
|
|
y = elements_height,
|
|
width = big_offset,
|
|
height = small_offset,
|
|
},
|
|
"Padding X",
|
|
)
|
|
elements_height += small_offset * 2
|
|
|
|
if (rl.GuiSpinner(
|
|
{
|
|
x = small_offset,
|
|
y = elements_height,
|
|
width = big_offset * 2,
|
|
height = small_offset,
|
|
},
|
|
"",
|
|
&g_mem.packer_settings.pixel_padding_y_int,
|
|
0,
|
|
10,
|
|
spinner_edit_mode,
|
|
)) >
|
|
0 {spinner_edit_mode = !spinner_edit_mode}
|
|
rl.GuiLabel(
|
|
{
|
|
x = (small_offset * 2) + big_offset * 2,
|
|
y = elements_height,
|
|
width = big_offset,
|
|
height = small_offset,
|
|
},
|
|
"Padding Y",
|
|
)
|
|
elements_height += small_offset * 2
|
|
|
|
}
|
|
elements_height += small_offset
|
|
|
|
// rl.GuiLine({y = elements_height, width = left_half_rect.width}, "Actions")
|
|
// elements_height += small_offset
|
|
|
|
actions_label_y := elements_height
|
|
{
|
|
defer rl.GuiGroupBox(
|
|
{
|
|
x = small_offset / 2,
|
|
y = actions_label_y,
|
|
width = left_half_rect.width - small_offset,
|
|
height = elements_height - actions_label_y,
|
|
},
|
|
"Actions",
|
|
)
|
|
elements_height += small_offset
|
|
|
|
if rl.GuiButton(
|
|
{
|
|
x = small_offset,
|
|
y = elements_height,
|
|
width = left_half_rect.width / 2 - small_offset,
|
|
height = small_offset,
|
|
},
|
|
"Pick Source(s)",
|
|
) {
|
|
g_mem.should_open_file_dialog = true
|
|
g_mem.source_location_type = .SourceFiles
|
|
}
|
|
if rl.GuiButton(
|
|
{
|
|
x = left_half_rect.width / 2,
|
|
y = elements_height,
|
|
width = left_half_rect.width / 2 - small_offset,
|
|
height = small_offset,
|
|
},
|
|
"Pick Output",
|
|
) {
|
|
g_mem.should_open_file_dialog = true
|
|
g_mem.source_location_type = .OutputFolder
|
|
}
|
|
elements_height += small_offset * 2
|
|
|
|
|
|
if rl.GuiButton(
|
|
{
|
|
x = small_offset,
|
|
y = elements_height,
|
|
width = left_half_rect.width / 2 - small_offset,
|
|
height = small_offset,
|
|
},
|
|
"Pack Atlas",
|
|
) {
|
|
g_mem.should_render_atlas = true
|
|
}
|
|
if rl.GuiButton(
|
|
{
|
|
x = left_half_rect.width / 2,
|
|
y = elements_height,
|
|
width = left_half_rect.width / 2 - small_offset,
|
|
height = small_offset,
|
|
},
|
|
"Clear Atlas",
|
|
) {
|
|
g_mem.atlas_render_has_preview = false
|
|
}
|
|
elements_height += small_offset * 2
|
|
|
|
if rl.GuiButton(
|
|
{
|
|
x = small_offset,
|
|
y = elements_height,
|
|
width = left_half_rect.width / 2 - small_offset,
|
|
height = small_offset,
|
|
},
|
|
"Save",
|
|
) {
|
|
save_output()
|
|
}
|
|
if rl.GuiButton(
|
|
{
|
|
x = left_half_rect.width / 2,
|
|
y = elements_height,
|
|
width = left_half_rect.width / 2 - small_offset,
|
|
height = small_offset,
|
|
},
|
|
"Save To...",
|
|
) {
|
|
}
|
|
elements_height += small_offset * 2
|
|
}
|
|
|
|
}
|
|
|
|
// Packing Options
|
|
if SettingsDropdownBoxActive == 1 {
|
|
|
|
@(static)
|
|
active_tab: i32
|
|
tabs: []cstring = {"One", "Two", "Three"}
|
|
rl.GuiTabBar(
|
|
{x = small_offset, y = elements_height, width = 100, height = small_offset},
|
|
&tabs[0],
|
|
auto_cast len(tabs),
|
|
&active_tab,
|
|
)
|
|
}
|
|
|
|
// Save Options
|
|
if SettingsDropdownBoxActive == 2 {
|
|
|
|
}
|
|
|
|
elements_height = 0
|
|
rl.GuiPanel(right_half_rect, "Atlas Preview")
|
|
short_edge := min(
|
|
right_half_rect.height - big_offset * 1.5,
|
|
right_half_rect.width - big_offset * 1.5,
|
|
)
|
|
preview_rect := rl.Rectangle {
|
|
x = (right_half_rect.width / 2 + right_half_rect.x) - (short_edge / 2),
|
|
y = (right_half_rect.height / 2 + right_half_rect.y) - (short_edge / 2),
|
|
width = short_edge,
|
|
height = short_edge,
|
|
}
|
|
if !g_mem.atlas_render_has_preview {
|
|
rl.GuiDummyRec(preview_rect, "PREVIEW")
|
|
} else {
|
|
// rl.DrawRectangleRec(preview_rect, rl.WHITE)
|
|
bg_texture := g_mem.atlas_checked_background.texture
|
|
rl.DrawTexturePro(
|
|
bg_texture,
|
|
{width = auto_cast bg_texture.width, height = auto_cast bg_texture.height},
|
|
preview_rect,
|
|
{},
|
|
0,
|
|
rl.WHITE,
|
|
)
|
|
// preview_rect.x +=
|
|
// 10;preview_rect.y += 10;preview_rect.height -= 20;preview_rect.width -= 20
|
|
atlas_texture := g_mem.atlas_render_texture_target.texture
|
|
rl.DrawTexturePro(
|
|
atlas_texture,
|
|
{width = auto_cast atlas_texture.width, height = auto_cast -atlas_texture.height},
|
|
preview_rect,
|
|
{0, 0},
|
|
0,
|
|
rl.WHITE,
|
|
)
|
|
}
|
|
}
|
|
|
|
open_file_dialog :: proc() {
|
|
switch g_mem.source_location_type {
|
|
case .SourceFiles:
|
|
// `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
|
|
file_paths_conc := cstring(
|
|
diag.open_file_dialog(
|
|
"Select source files",
|
|
cstring(&g_mem.file_dialog_text_buffer[0]),
|
|
0,
|
|
nil,
|
|
"",
|
|
1,
|
|
),
|
|
)
|
|
if len(file_paths_conc) > 0 {
|
|
// todo(stefan): Currently we're not doing any checks if the filepaths are valid at all,
|
|
// this should be fine because it's returned by the OS' file picker but who knows...
|
|
source_files_to_pack := strings.clone_from_cstring(file_paths_conc, context.allocator)
|
|
g_mem.source_files_to_pack = strings.split(source_files_to_pack, "|")
|
|
|
|
fmt.println(g_mem.source_files_to_pack)
|
|
} else {
|
|
fmt.println("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)
|
|
fmt.println(g_mem.source_location_to_pack)
|
|
} else {
|
|
fmt.println("Got an empty path from the file dialog!")
|
|
}
|
|
|
|
|
|
case .OutputFolder:
|
|
file := cstring(
|
|
diag.select_folder_dialog(
|
|
"Select source folder",
|
|
cstring(&g_mem.file_dialog_text_buffer[0]),
|
|
),
|
|
)
|
|
if len(file) > 0 {
|
|
g_mem.output_folder_path = strings.clone_from_cstring(file)
|
|
fmt.println(g_mem.output_folder_path)
|
|
} else {
|
|
fmt.println("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, atlas_path})
|
|
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 {
|
|
save_output()
|
|
}
|
|
}
|
|
|
|
|
|
g_mem.should_open_file_dialog = false
|
|
}
|