From 75598b4fb1c137716c1818a403498621edaee482 Mon Sep 17 00:00:00 2001 From: Kyle Conel Date: Thu, 5 Sep 2024 11:26:38 -0400 Subject: [PATCH] Create a new project in the editor --- src/Main.jl | 2 +- src/editor/JulGameEditor/Editor.jl | 30 +++-- src/editor/JulGameEditor/Utils/EditorUtils.jl | 117 +++++++++++++++++- .../JulGameEditor/Utils/FileContents.jl | 112 ++++++++++++++++- src/editor/JulGameEditor/Utils/SceneUtils.jl | 22 ++++ 5 files changed, 263 insertions(+), 20 deletions(-) diff --git a/src/Main.jl b/src/Main.jl index 45f30930..2e146c0f 100644 --- a/src/Main.jl +++ b/src/Main.jl @@ -681,7 +681,7 @@ function game_loop(this::Main, startTime::Ref{UInt64} = Ref(UInt64(0)), lastPhys function start_game_in_editor(this::Main, path::String) this.isGameModeRunningInEditor = true SceneBuilderModule.add_scripts_to_entities(path) - initialize_scripts_and_components(false) + initialize_scripts_and_components() end function stop_game_in_editor(this::Main) diff --git a/src/editor/JulGameEditor/Editor.jl b/src/editor/JulGameEditor/Editor.jl index 67667a95..5a3ca216 100644 --- a/src/editor/JulGameEditor/Editor.jl +++ b/src/editor/JulGameEditor/Editor.jl @@ -22,7 +22,9 @@ module Editor include.(filter(contains(r".jl$"), readdir(joinpath(@__DIR__, "Windows"); join=true))) function run(is_test_mode::Bool=false) - info = init_sdl_and_imgui() + windowTitle = "JulGame Editor v0.1.0" + + info = init_sdl_and_imgui(windowTitle) window, renderer, ctx, io, clear_color = info[1], info[2], info[3], info[4], info[5] startingSize = ImVec2(1920, 1080) sceneTexture = SDL2.SDL_CreateTexture(renderer, SDL2.SDL_PIXELFORMAT_BGRA8888, SDL2.SDL_TEXTUREACCESS_TARGET, startingSize.x, startingSize.y)# SDL2.SDL_SetRenderTarget(renderer, sceneTexture) @@ -35,7 +37,7 @@ module Editor currentSceneMain = nothing currentSceneName = "" currentScenePath = "" - currentSelectedProjectPath = "" + currentSelectedProjectPath = Ref("") gameInfo = [] ############################## # Hierarchy variables @@ -64,11 +66,14 @@ module Editor duplicationMode = false + # Engine Timing startTime = Ref(UInt64(0)) lastPhysicsTime = Ref(UInt64(SDL2.SDL_GetTicks())) + # Dialogs currentDialog::Base.RefValue{String} = Ref("") newSceneText = Ref("") + newProjectText = Ref("") try while !quit @@ -84,7 +89,7 @@ module Editor ################################# MAIN MENU BAR events = Dict{String, Function}() if currentSceneMain !== nothing - events["Save"] = save_scene_event(currentSceneMain.scene.entities, currentSceneMain.scene.uiElements, currentSelectedProjectPath, String(currentSceneName)) + events["Save"] = save_scene_event(currentSceneMain.scene.entities, currentSceneMain.scene.uiElements, currentSelectedProjectPath[], String(currentSceneName)) end events["New-project"] = create_project_event(currentDialog) events["Select-project"] = select_project_event(currentSceneMain, scenesLoadedFromFolder, currentDialog) @@ -117,7 +122,7 @@ module Editor JulGame.IS_EDITOR = true JulGame.PIXELS_PER_UNIT = 16 currentDialog[] = "Open Scene" - currentSelectedProjectPath = SceneLoaderModule.get_project_path_from_full_scene_path(scene) + currentSelectedProjectPath[] = SceneLoaderModule.get_project_path_from_full_scene_path(scene) else currentDialog[] = "Open Scene" end @@ -128,6 +133,10 @@ module Editor CImGui.End() end + if currentSelectedProjectPath[] != "" && unsafe_string(SDL2.SDL_GetWindowTitle(window)) != "$(windowTitle) - $(currentSelectedProjectPath[])" + newWindowTitle = "$(windowTitle) - $(currentSelectedProjectPath[])" + SDL2.SDL_SetWindowTitle(window, newWindowTitle) + end if currentDialog[] == "Open Scene" #println("Opening scene: $(currentDialog[][2])") if confirmation_dialog(currentDialog) == "ok" && currentSceneName != "" @@ -142,13 +151,13 @@ module Editor newSceneName = new_scene_dialog(currentDialog, newSceneText) if newSceneName != "" currentSceneName = newSceneName - currentScenePath = joinpath(currentSelectedProjectPath, "scenes", "$(newSceneName).json") + currentScenePath = joinpath(currentSelectedProjectPath[], "scenes", "$(newSceneName).json") touch(currentScenePath) file = open(currentScenePath, "w") println(file, sceneJsonContents) close(file) JulGame.change_scene("$(String(currentSceneName)).json") - scenesLoadedFromFolder[] = get_all_scenes_from_folder(currentSelectedProjectPath) + scenesLoadedFromFolder[] = get_all_scenes_from_folder(currentSelectedProjectPath[]) end elseif currentDialog[] == "Select Project" selectedProjectPath = select_project_dialog(currentDialog, scenesLoadedFromFolder) @@ -156,12 +165,9 @@ module Editor currentSceneMain = nothing end elseif currentDialog[] == "New Project" - selectedProjectPath = create_project_dialog(currentDialog, scenesLoadedFromFolder) + selectedProjectPath = create_project_dialog(currentDialog, scenesLoadedFromFolder, currentSelectedProjectPath, newProjectText) if selectedProjectPath != "" println("Selected project path: $(selectedProjectPath)") - #currentSceneMain = nothing - # currentSelectedProjectPath = selectedProjectPath - # scenesLoadedFromFolder[] = get_all_scenes_from_folder(currentSelectedProjectPath) end end @@ -179,7 +185,7 @@ module Editor sceneWindowSize = show_scene_window(currentSceneMain, sceneTexture, scrolling, zoom_level, duplicationMode, playMode) if playMode[] != wasPlaying && currentSceneMain !== nothing if playMode[] - JulGame.MainLoop.start_game_in_editor(currentSceneMain, currentSelectedProjectPath) + JulGame.MainLoop.start_game_in_editor(currentSceneMain, currentSelectedProjectPath[]) elseif !playMode[] JulGame.MainLoop.stop_game_in_editor(currentSceneMain) JulGame.change_scene(String(currentSceneName)) @@ -443,7 +449,7 @@ module Editor catch e backup_file_name = backup_file_name = "$(replace(currentSceneName, ".json" => ""))-backup-$(replace(Dates.format(Dates.now(), "yyyy-mm-ddTHH:MM:SS"), ":" => "-")).json" @info string("Backup file name: ", backup_file_name) - SceneWriterModule.serialize_entities(currentSceneMain.scene.entities, currentSceneMain.scene.uiElements, currentSelectedProjectPath, backup_file_name) + SceneWriterModule.serialize_entities(currentSceneMain.scene.entities, currentSceneMain.scene.uiElements, currentSelectedProjectPath[], backup_file_name) Base.show_backtrace(stderr, catch_backtrace()) @warn "Error in renderloop!" exception=e finally diff --git a/src/editor/JulGameEditor/Utils/EditorUtils.jl b/src/editor/JulGameEditor/Utils/EditorUtils.jl index 7daa9202..77716e50 100644 --- a/src/editor/JulGameEditor/Utils/EditorUtils.jl +++ b/src/editor/JulGameEditor/Utils/EditorUtils.jl @@ -1,11 +1,11 @@ -function init_sdl_and_imgui() +function init_sdl_and_imgui(windowTitle::String) if SDL2.SDL_Init(SDL2.SDL_INIT_VIDEO | SDL2.SDL_INIT_TIMER | SDL2.SDL_INIT_GAMECONTROLLER) < 0 println("failed to init: ", unsafe_string(SDL2.SDL_GetError())); end SDL2.SDL_SetHint(SDL2.SDL_HINT_IME_SHOW_UI, "1") window = SDL2.SDL_CreateWindow( - "JulGame Editor v0.1.0", SDL2.SDL_WINDOWPOS_CENTERED, SDL2.SDL_WINDOWPOS_CENTERED, 1280, 720, + windowTitle, SDL2.SDL_WINDOWPOS_CENTERED, SDL2.SDL_WINDOWPOS_CENTERED, 1280, 720, SDL2.SDL_WINDOW_SHOWN | SDL2.SDL_WINDOW_RESIZABLE | SDL2.SDL_WINDOW_ALLOW_HIGHDPI ) if window == C_NULL @@ -182,20 +182,41 @@ function create_project_event(dialog) return event end -function create_project_dialog(dialog, scenesLoadedFromFolder) +function create_project_dialog(dialog, scenesLoadedFromFolder, selectedProjectPath, newProjectText) CImGui.OpenPopup(dialog[]) if CImGui.BeginPopupModal(dialog[], C_NULL, CImGui.ImGuiWindowFlags_AlwaysAutoResize) CImGui.Text("Are you sure you would like to open another project?\nIf you currently have a project open, any unsaved changes will be lost.\n\n") CImGui.NewLine() - if CImGui.Button("OK", (120, 0)) + text = text_input_single_line("Project Name", newProjectText) + newProjectText[] = strip(newProjectText[]) + newProjectText[] = replace(newProjectText[], " " => "-") + newProjectText[] = replace(newProjectText[], "." => "-") + CImGui.NewLine() + + if CImGui.Button("Open", (120, 0)) + selectedProjectPath[] = choose_folder_with_dialog() + end + CImGui.SameLine() + + newProjectPath = joinpath(selectedProjectPath[], newProjectText[]) + CImGui.Text("Full Path: $(newProjectPath)") + CImGui.NewLine() + + pathAlreadyExists = isdir(newProjectPath) + if !pathAlreadyExists && selectedProjectPath[] != "" && newProjectText[] != "" && CImGui.Button("Confirm", (120, 0)) CImGui.CloseCurrentPopup() dialog[] = "" - projectPath = choose_folder_with_dialog() #|> (dir) -> (create_new_project(dir)) - println("Project path: ", projectPath) + create_new_project(newProjectPath, newProjectText[]) + scenesLoadedFromFolder[] = get_all_scenes_from_base_folder(joinpath(newProjectPath, newProjectText[])) end + + if pathAlreadyExists + CImGui.Text("The path already exists. Please choose a different name.") + end + CImGui.SetItemDefaultFocus() CImGui.SameLine() if CImGui.Button("Cancel",(120, 0)) @@ -207,6 +228,90 @@ function create_project_dialog(dialog, scenesLoadedFromFolder) return "" end +function create_new_project(newProjectPath, newProjectName) + # create the project folder + if !isdir(newProjectPath) + mkdir(newProjectPath) + end + + # add the julia .gitignore file + gitignore = joinpath(newProjectPath, ".gitignore") + touch(gitignore) + file = open(gitignore, "w") + println(file, gitIgnoreFileContent) + close(file) + + # create a readme file + readme = joinpath(newProjectPath, "README.md") + touch(readme) + file = open(readme, "w") + println(file, readMeFileContent(newProjectName)) + close(file) + + # create the inner project folder (same name as the project) + projectFolder = joinpath(newProjectPath, newProjectName) + mkdir(projectFolder) + if !isdir(projectFolder) + mkdir(projectFolder) + end + + # create assets folder. Inside of the assets folder, we also create folders for fonts, images, and sounds + mkdir(joinpath(projectFolder, "assets")) + mkdir(joinpath(projectFolder, "assets", "fonts")) + mkdir(joinpath(projectFolder, "assets", "images")) + mkdir(joinpath(projectFolder, "assets", "sounds")) + + # Todo: insert the default font into the fonts folder + # copy(joinpath(pwd(), "..", "Fonts", "FiraCode", "ttf", "FiraCode-Regular.ttf"), joinpath(projectFolder, "assets", "fonts", "FiraCode-Regular.ttf")) + + + # create the scenes folder + scenesFolder = joinpath(projectFolder, "scenes") + mkdir(scenesFolder) + + #create default scene + defaultScene = joinpath(scenesFolder, "scene.json") + touch(defaultScene) + file = open(defaultScene, "w") + println(file, sceneJsonContents) + close(file) + + # create the scripts folder + scriptsFolder = joinpath(projectFolder, "scripts") + mkdir(scriptsFolder) + + # create the src folder + srcFolder = joinpath(projectFolder, "src") + mkdir(srcFolder) + + # create the src files, one named after the project, and one named Run.jl + srcFile = joinpath(srcFolder, "$(newProjectName).jl") + touch(srcFile) + file = open(srcFile, "w") + println(file, mainFileContent(newProjectName)) + close(file) + + runFile = joinpath(srcFolder, "Run.jl") + touch(runFile) + file = open(runFile, "w") + println(file, runFileContent(newProjectName)) + close(file) + + # create precompile_app.jl + precompileFile = joinpath(srcFolder, "precompile_app.jl") + touch(precompileFile) + file = open(precompileFile, "w") + println(file, precompileFileContent(newProjectName)) + close(file) + + # create the project.toml file + projectToml = joinpath(projectFolder, "Project.toml") + touch(projectToml) + file = open(projectToml, "w") + println(file, projectTomlContent(newProjectName)) + close(file) +end + function move_entities(entities, origin, destination) if indexin([destination], origin) != [nothing] return diff --git a/src/editor/JulGameEditor/Utils/FileContents.jl b/src/editor/JulGameEditor/Utils/FileContents.jl index 812c0fe0..925dd49f 100644 --- a/src/editor/JulGameEditor/Utils/FileContents.jl +++ b/src/editor/JulGameEditor/Utils/FileContents.jl @@ -1,4 +1,114 @@ sceneJsonContents = "{ \"Entities\": [ ], \"UIElements\": [ ] -}" \ No newline at end of file +}" + + +gitIgnoreFileContent = "# Files generated by invoking Julia with --code-coverage +*.jl.cov +*.jl.*.cov + +# Files generated by invoking Julia with --track-allocation +*.jl.mem + +# System-specific files and directories generated by the BinaryProvider and BinDeps packages +# They contain absolute paths specific to the host computer, and so should not be committed +deps/deps.jl +deps/build.log +deps/downloads/ +deps/usr/ +deps/src/ + +# Build artifacts for creating documentation generated by the Documenter package +docs/build/ +docs/site/ + +# Build artifacts for PackageCompiler builds +/Build + +# File generated by Pkg, the package manager, based on a corresponding Project.toml +# It records a fixed state of all packages used by the project. As such, it should not be +# committed for packages, but should be committed for applications that require a static +# environment. +Manifest.toml +" + +function readMeFileContent(projectName) + return "# $projectName \n Made with JulGame.jl: https://github.com/Kyjor/JulGame.jl \nHow to run from command line: cd $projectName\\src then run \"julia Run.jl\"" +end + +function mainFileContent(projectName) + return "module $projectName + using JulGame + using JulGame.Math + + function run() + JulGame.MAIN = JulGame.Main(Float64(1.0)) + JulGame.PIXELS_PER_UNIT = 16 + scene = SceneBuilderModule.Scene(\"scene.json\") + SceneBuilderModule.load_and_prepare_scene(scene, \"$projectName\", Vector2(1280, 720),Vector2(1920, 1080), false, 1.0, true, 60) + end + + julia_main() = run() + end" +end + +function runFileContent(projectName) + return "include(\"$projectName.jl\") + + using .$projectName + + $projectName.julia_main()" +end + +function precompileFileContent(projectName) + return "using $projectName + + $projectName.julia_main()" +end + +function projectTomlContent(projectName) + return "name = \"$projectName\" + uuid = \"$(JulGame.generate_uuid())\" + authors = [\"Your Name Here\"] + version = \"0.1.0\" + + [deps] + JulGame = \"4850f9bb-d191-4a1e-9f97-ee64062927c3\"" +end + +function newScriptContent(scriptName) + return "mutable struct $scriptName + parent # do not remove this line, this is a reference to the entity that this script is attached to + # This is where you define your script's fields + # Example: speed::Float64 + + function $scriptName() + this = new() # do not remove this line + + # this is where you initialize your script's fields + # Example: this.speed = 1.0 + + + return this # do not remove this line + end +end + +# This is called when a scene is loaded, or when script is added to an entity +# This is where you should register collision events or other events +# Do not remove this function +function JulGame.initialize(this::$scriptName) +end + +# This is called every frame +# Do not remove this function +function JulGame.update(this::$scriptName, deltaTime) +end + +# This is called when the script is removed from an entity (scene change, entity deletion) +# Do not remove this function +function JulGame.on_shutdown(this::$scriptName) +end + +" +end \ No newline at end of file diff --git a/src/editor/JulGameEditor/Utils/SceneUtils.jl b/src/editor/JulGameEditor/Utils/SceneUtils.jl index b9e32032..cadc4655 100644 --- a/src/editor/JulGameEditor/Utils/SceneUtils.jl +++ b/src/editor/JulGameEditor/Utils/SceneUtils.jl @@ -54,6 +54,28 @@ function get_all_scenes_from_folder(projectPath::String) return sceneFiles end +function get_all_scenes_from_base_folder(projectPath::String) + sceneFiles = [] + try + # search through projectpath and it's subdirectories for a scenes folder. If it exists, return all of the json files from it + if !isdir(joinpath(projectPath, "scenes")) + @error "No scenes folder found in project directory: $projectPath" + else + for (root, dirs, files) in walkdir(joinpath(projectPath, "scenes")) + for file in files + if occursin(r".json$", file) + push!(sceneFiles, joinpath(root, file)) + end + end + end + end + catch e + rethrow(e) + end + + return sceneFiles +end + """ choose_folder_with_dialog()