yaap/src/game.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
}