From 8d36f8bbd8618c0cbff0ee9d91c0990b072d053e Mon Sep 17 00:00:00 2001 From: Stefan Stefanov Date: Sat, 20 Apr 2024 18:53:50 +0300 Subject: [PATCH] working-ish state for the app & some gui work done on the packing screen --- .vscode/settings.json | 10 +- src/dialog/tinyfiledialog.odin | 34 ++++ src/game.odin | 318 ++++++++++++++++++++++++++++++--- src/input_box.odin | 15 -- src/symbol_exports.odin | 23 ++- styles/style_candy.rgs | Bin 0 -> 3844 bytes 6 files changed, 349 insertions(+), 51 deletions(-) create mode 100644 src/dialog/tinyfiledialog.odin delete mode 100644 src/input_box.odin create mode 100644 styles/style_candy.rgs 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 0000000000000000000000000000000000000000..75a70f5d93876eae4397681c2b5a9e2600e065d4 GIT binary patch literal 3844 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 WkS!wU7eYtOD2v7~_Rl#%(EKM0u>Ge1 literal 0 HcmV?d00001