diff --git a/.vscode/settings.json b/.vscode/settings.json index 91653d1..9e476f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { - "workbench.colorCustomizations": { - "activityBar.background": "#322C2D", - "titleBar.activeBackground": "#463E3F", - "titleBar.activeForeground": "#FAFAFA" -} + "workbench.colorCustomizations": { + "activityBar.background": "#322C2D", + "titleBar.activeBackground": "#463E3F", + "titleBar.activeForeground": "#FAFAFA" + } } \ No newline at end of file diff --git a/src/dialog/tinyfiledialog.odin b/src/dialog/tinyfiledialog.odin new file mode 100644 index 0000000..ae6dd35 --- /dev/null +++ b/src/dialog/tinyfiledialog.odin @@ -0,0 +1,34 @@ +package tinyfiledialogs + +import "core:c" + +when ODIN_OS == .Windows { + foreign import lib {"tinyfiledialogs.lib", "system:comdlg32.lib", "system:Ole32.lib"} +} +when ODIN_OS == .Linux { + + foreign import lib {"libtinyfiledialogs.a"} +} + +foreign lib { + @(link_name = "tinyfd_notifyPopup") + notify_popup :: proc(title, message, icon_type: cstring) -> c.int --- + + @(link_name = "tinyfd_messageBox") + message_box :: proc(title, message, dialog_type, icon_type: cstring, default_button: c.int) -> c.int --- + + @(link_name = "tinyfd_inputBox") + input_box :: proc(title, message, default_input: cstring) -> [^]c.char --- + + @(link_name = "tinyfd_saveFileDialog") + save_file_dialog :: proc(title, default_path: cstring, pattern_count: c.int, patterns: [^]cstring, file_desc: cstring) -> [^]c.char --- + + @(link_name = "tinyfd_openFileDialog") + open_file_dialog :: proc(title, default_path: cstring, pattern_count: c.int, patterns: [^]cstring, file_desc: cstring, allow_multi: c.int) -> [^]c.char --- + + @(link_name = "tinyfd_selectFolderDialog") + select_folder_dialog :: proc(title, default_path: cstring) -> [^]c.char --- + + @(link_name = "tinyfd_colorChooser") + color_chooser :: proc(title, default_hex_rgb: cstring, default_rgb, result_rgb: [3]byte) -> [^]c.char --- +} diff --git a/src/game.odin b/src/game.odin index 35ca8a3..3f7a5ad 100644 --- a/src/game.odin +++ b/src/game.odin @@ -11,15 +11,68 @@ package game -// import "core:fmt" -// import "core:math/linalg" +import "core:fmt" +import "core:strings" import rl "vendor:raylib" +import diag "dialog" + PixelWindowHeight :: 180 +/* + `SourceFilesPicker` // Screen 1: Shows the file dialog box, meant for the user to choose the source files/folder + `OutputLocationPicker` // Screen 2: Shows the file dialog box, meant for the user to choose the output file name & location + `PackSettingsAndPreview` Screen 3: Shows settings about the packing operations, `save` & `save as` button + `SaveToOutputPicker` // Screen 4: After clicking the `save as` button on screen 3, ask the user for a new location & name and save the file +*/ + +AppScreen :: enum { + SourceFilesPicker, + OutputLocationPicker, + PackSettingsAndPreview, + SaveToOutputPicker, +} + +WindowInformation :: struct { + w: f32, + h: f32, + width_scaled: f32, + height_scaled: f32, +} + +MonitorInformation :: struct { + max_width: f32, + max_height: f32, +} + +FileDialogType :: enum { + SourceFiles, + SourceFolder, + OutputFolder, + Exit, +} + + FILE_DIALOG_SIZE :: 1000 GameMemory :: struct { - file_dialog_text_buffer: [FILE_DIALOG_SIZE + 1]u8, + file_dialog_text_buffer: [FILE_DIALOG_SIZE + 1]u8, + is_packing_whole_source_folder: bool, + should_open_file_dialog: bool, + window_info: WindowInformation, + monitor_info: MonitorInformation, + // atlas packer state + app_screen: AppScreen, + // Where the output files will be written (atlas.png, json output, etc) + output_path_set: bool, + output_folder_path: string, + // If files were chosen as input - their paths + input_path_set: bool, + source_location_to_pack: string, + // If a folder was chosen as input - the path + input_files_set: bool, + source_files_to_pack: []string, + // What type of file dialog to open + source_location_type: FileDialogType, } g_mem: ^GameMemory @@ -35,7 +88,6 @@ game_camera :: proc() -> rl.Camera2D { scaling: f32 = 2 ui_camera :: proc() -> rl.Camera2D { - // return {zoom = f32(rl.GetScreenHeight()) / PixelWindowHeight} return {zoom = scaling} } @@ -43,35 +95,249 @@ input_box_loc: rl.Vector2 = {} moving_input_box: bool update :: proc() { // Update the width/height - w = f32(rl.GetScreenWidth()) - h = f32(rl.GetScreenHeight()) + 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 rl.IsMouseButtonDown(.RIGHT) { - input_box_loc = rl.GetMousePosition() - } + update_screen() } draw :: proc() { rl.BeginDrawing() - defer rl.EndDrawing() + defer rl.EndDrawing() rl.ClearBackground(rl.BLACK) - rl.BeginMode2D(ui_camera()) - rl.GuiTextInputBox( - rl.Rectangle { - x = input_box_loc.x, - y = input_box_loc.y, - width = (w / scaling) / 2, - height = (h / scaling) / 2, - }, - "Files", - "File input box", - "Button", - cstring(rawptr(&g_mem.file_dialog_text_buffer)), - FILE_DIALOG_SIZE, - nil, - ) - rl.EndMode2D() + draw_screen_ui() + + draw_screen_target() +} + +update_screen :: proc() { + if (g_mem.input_files_set || g_mem.input_path_set) { + if !g_mem.output_path_set { + g_mem.app_screen = .OutputLocationPicker + } else { + g_mem.app_screen = .PackSettingsAndPreview + } + } else { + g_mem.app_screen = .SourceFilesPicker + } + + switch g_mem.app_screen { + case .SourceFilesPicker: + fallthrough + case .OutputLocationPicker: + fallthrough + case .SaveToOutputPicker: + if g_mem.should_open_file_dialog { + open_file_dialog_and_store_output_paths() + } + case .PackSettingsAndPreview: + } +} + +draw_screen_ui :: proc() { + rl.BeginMode2D(ui_camera()) + defer rl.EndMode2D() + + switch g_mem.app_screen { + case .SourceFilesPicker: + fallthrough + case .OutputLocationPicker: + fallthrough + case .SaveToOutputPicker: + draw_and_handle_source_files_logic() + case .PackSettingsAndPreview: + draw_atlas_settings_and_preview() + } +} + +draw_screen_target :: proc() { + rl.BeginMode2D(ui_camera()) + defer rl.EndMode2D() + +} +draw_atlas_settings_and_preview :: proc() { + left_half_rect := rl.Rectangle { + 0, + 0, + auto_cast g_mem.window_info.width_scaled / 2, + auto_cast g_mem.window_info.height_scaled, + } + right_half_rect := rl.Rectangle { + auto_cast g_mem.window_info.width_scaled / 2, + 0, + auto_cast g_mem.window_info.width_scaled / 2, + auto_cast g_mem.window_info.height_scaled, + } + rl.DrawRectangleRec(left_half_rect, rl.WHITE) + rl.DrawRectangleRec(right_half_rect, rl.MAROON) + + // font := rl.GuiGetFont() + // text_size := rl.MeasureTextEx(font, "Atlas Packer Settings", auto_cast font.baseSize / scaling, 1) + + small_offset := 10 * scaling + elements_height: f32 = 0 + rl.GuiLabel( + rl.Rectangle{x = small_offset, y = 0, width = 100 * scaling, height = 25 * scaling}, + "Atlas Packer Settings", + ) + elements_height += 25 * scaling + rl.GuiLine({y = elements_height, width = left_half_rect.width}, "Packer Settings") + elements_height += small_offset + rl.GuiLine({y = elements_height, width = left_half_rect.width}, "Save Settings") + elements_height += small_offset + if rl.GuiButton( + { + x = small_offset, + y = elements_height, + width = left_half_rect.width / 2 - small_offset * 2, + height = 25 * scaling, + }, + "Save", + ) { + + } + if rl.GuiButton( + { + x = left_half_rect.width / 2, + y = elements_height, + width = left_half_rect.width / 2 - small_offset, + height = 25 * scaling, + }, + "Save To...", + ) { + + } +} + +open_file_dialog_and_store_output_paths :: proc() { + if g_mem.source_location_type == .SourceFiles { + files := cstring( + diag.open_file_dialog( + "Select source files", + cstring(&g_mem.file_dialog_text_buffer[0]), + 0, + nil, + "", + 1, + ), + ) + + source_files_to_pack := strings.clone_from_cstring(files, context.allocator) + // File dialog returns an array of path(s), separated by a '|' + g_mem.source_files_to_pack = strings.split(source_files_to_pack, "|") + g_mem.input_files_set = (len(source_files_to_pack) > 0) + + fmt.println(g_mem.source_files_to_pack) + } + if g_mem.source_location_type == .SourceFolder { + file := cstring( + diag.select_folder_dialog( + "Select source folder", + cstring(&g_mem.file_dialog_text_buffer[0]), + ), + ) + g_mem.source_location_to_pack = strings.clone_from_cstring(file) + g_mem.input_path_set = (len(file) > 0) + fmt.println(g_mem.source_location_to_pack) + } + if g_mem.source_location_type == .OutputFolder { + file := cstring( + diag.select_folder_dialog( + "Select source folder", + cstring(&g_mem.file_dialog_text_buffer[0]), + ), + ) + g_mem.output_folder_path = strings.clone_from_cstring(file) + + g_mem.output_path_set = (len(file) > 0) + fmt.println(g_mem.output_folder_path) + } + + g_mem.should_open_file_dialog = false +} + +draw_and_handle_source_files_logic :: proc() { + switch g_mem.app_screen { + case .SourceFilesPicker: + result := rl.GuiTextInputBox( + rl.Rectangle{width = (w / scaling), height = (h / scaling)}, + "Files", + "File input box", + "Open Source Files;Open Source Folder", + cstring(rawptr(&g_mem.file_dialog_text_buffer)), + FILE_DIALOG_SIZE, + nil, + ) + if result != -1 { + file_dialg_type: FileDialogType + if result == 1 || result == 2 { + file_dialg_type = .SourceFiles if result == 1 else .SourceFolder + } else if result == 0 { + file_dialg_type = .Exit + } + handle_source_file_logic(file_dialg_type) + fmt.println("result: ", result) + } + case .OutputLocationPicker: + result := rl.GuiTextInputBox( + rl.Rectangle{width = (w / scaling), height = (h / scaling)}, + "Files", + "Output Folder", + "Choose Output Folder", + cstring(rawptr(&g_mem.file_dialog_text_buffer)), + FILE_DIALOG_SIZE, + nil, + ) + if result != -1 { + file_dialg_type: FileDialogType = .OutputFolder if result == 1 else .Exit + handle_source_file_logic(file_dialg_type) + fmt.println("result: ", result) + } + case .SaveToOutputPicker: + result := rl.GuiTextInputBox( + rl.Rectangle{width = (w / scaling), height = (h / scaling)}, + "Files", + "Output Folder", + "Choose Output Folder", + cstring(rawptr(&g_mem.file_dialog_text_buffer)), + FILE_DIALOG_SIZE, + nil, + ) + if result != -1 { + file_dialg_type: FileDialogType = .SourceFolder if result == 1 else .Exit + handle_source_file_logic(file_dialg_type) + fmt.println("result: ", result) + } + case .PackSettingsAndPreview: + draw_packer_and_settings() + } +} + +draw_packer_and_settings :: proc() { + +} + +handle_source_file_logic :: proc(picker_type: FileDialogType) { + switch picker_type { + case .Exit: + g_mem.should_open_file_dialog = false + rl.CloseWindow() + case .SourceFiles: + fallthrough + case .SourceFolder: + fallthrough + case .OutputFolder: + g_mem.source_location_type = picker_type + g_mem.should_open_file_dialog = true + } } diff --git a/src/input_box.odin b/src/input_box.odin deleted file mode 100644 index f7984c1..0000000 --- a/src/input_box.odin +++ /dev/null @@ -1,15 +0,0 @@ -package game - -import rl "vendor:raylib" - -// Check if any key is pressed -// NOTE: We limit keys check to keys between 32 (KEY_SPACE) and 126 -IsAnyKeyPressed :: proc() -> (keyPressed: bool) { - key := rl.GetKeyPressed() - - if (i32(key) >= 32) && (i32(key) <= 126) { - keyPressed = true - } - - return -} diff --git a/src/symbol_exports.odin b/src/symbol_exports.odin index 492956c..592f1b4 100644 --- a/src/symbol_exports.odin +++ b/src/symbol_exports.odin @@ -12,19 +12,31 @@ game_update :: proc() -> bool { @(export) game_init_window :: proc() { rl.SetConfigFlags({.WINDOW_RESIZABLE}) - rl.InitWindow(1280, 720, "Odin + Raylib + Hot Reload template!") + rl.InitWindow(1280, 720, "YAAP - Yet Another Atlas Packer, Powered by Raylib & Odin") rl.SetWindowPosition(200, 200) - rl.SetTargetFPS(500) } @(export) game_init :: proc() { g_mem = new(GameMemory) - g_mem^ = GameMemory { - } + g_mem^ = GameMemory{} game_hot_reloaded(g_mem) + + current_monitor := rl.GetCurrentMonitor() + g_mem.monitor_info = MonitorInformation { + max_width = auto_cast rl.GetMonitorWidth(current_monitor), + max_height = auto_cast rl.GetMonitorHeight(current_monitor), + } + + g_mem.window_info = WindowInformation { + w = 1280, + h = 720, + } + + rl.SetTargetFPS(rl.GetMonitorRefreshRate(current_monitor)) + rl.GuiLoadStyle("./styles/style_candy.rgs") } @(export) @@ -50,6 +62,7 @@ game_memory_size :: proc() -> int { @(export) game_hot_reloaded :: proc(mem: rawptr) { g_mem = (^GameMemory)(mem) + rl.GuiLoadStyle("./styles/style_candy.rgs") } @(export) @@ -60,4 +73,4 @@ game_force_reload :: proc() -> bool { @(export) game_force_restart :: proc() -> bool { return rl.IsKeyPressed(.F6) -} \ No newline at end of file +} diff --git a/styles/style_candy.rgs b/styles/style_candy.rgs new file mode 100644 index 0000000..75a70f5 Binary files /dev/null and b/styles/style_candy.rgs differ