Compare commits
6 Commits
pre-0.2.2-
...
0.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ce29e1436 | ||
|
|
18b7fb9abf | ||
|
|
75bf38fa71 | ||
|
|
92cc574e15 | ||
|
|
546093360f | ||
|
|
d94b7a3e81 |
20
.github/workflows/cmake.yml
vendored
20
.github/workflows/cmake.yml
vendored
@@ -2,8 +2,14 @@ name: CMake
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'test/**'
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
pull_request:
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'test/**'
|
||||||
branches: [ "master" ]
|
branches: [ "master" ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -18,13 +24,25 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install Video Libs
|
- name: Install Video Libs
|
||||||
run: sudo apt install libglew-dev
|
run: sudo apt install libglew-dev
|
||||||
|
|
||||||
|
- name: Install Audio Libs
|
||||||
|
run: sudo apt install libwavpack-dev
|
||||||
|
|
||||||
|
- name: Install Abseil-cpp
|
||||||
|
run: sudo apt install libabsl-dev
|
||||||
|
|
||||||
|
- name: Install Boost and Boost Python
|
||||||
|
run: sudo apt install libboost-all-dev libboost-python-dev
|
||||||
|
|
||||||
|
- name: Install CPython headers
|
||||||
|
run: sudo apt install python3-dev libpython3-dev
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
|
||||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
|
||||||
|
|||||||
45
.github/workflows/doxy.yml
vendored
Normal file
45
.github/workflows/doxy.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Doxygen Action
|
||||||
|
|
||||||
|
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||||
|
# events but only for the master branch
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
# This workflow contains a single job called "build"
|
||||||
|
build:
|
||||||
|
# The type of runner that the job will run on
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
|
steps:
|
||||||
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Delete the html directory if it exists
|
||||||
|
- name: Delete html directory
|
||||||
|
run: rm -rf html
|
||||||
|
|
||||||
|
# Installs graphviz for DOT graphs
|
||||||
|
- name: Install graphviz
|
||||||
|
run: sudo apt-get install graphviz
|
||||||
|
|
||||||
|
- name: Doxygen Action
|
||||||
|
uses: mattnotmitt/doxygen-action@v1.1.0
|
||||||
|
with:
|
||||||
|
# Path to Doxyfile
|
||||||
|
doxyfile-path: "./Doxyfile" # default is ./Doxyfile
|
||||||
|
# Working directory
|
||||||
|
working-directory: "." # default is .
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# Default Doxyfile build documentation to html directory.
|
||||||
|
# Change the directory if changes in Doxyfile
|
||||||
|
publish_dir: ./html
|
||||||
28
.gitignore
vendored
28
.gitignore
vendored
@@ -1,19 +1,21 @@
|
|||||||
|
.favorites.json
|
||||||
build/
|
build/
|
||||||
.cache/
|
.cache/
|
||||||
.vscode/
|
.vscode/
|
||||||
src/lib/SDL2
|
disasm/
|
||||||
src/lib/cmake
|
src/etc
|
||||||
src/lib/GL
|
src/lib/
|
||||||
src/lib/abseil-cpp
|
|
||||||
src/lib/libGLEW.2.2.0.dylib
|
|
||||||
src/lib/libGLEW.2.2.dylib
|
|
||||||
src/lib/libGLEW.a
|
|
||||||
src/lib/libGLEW.dylib
|
|
||||||
src/lib/libSDL2_test.a
|
|
||||||
src/lib/libSDL2-2.0.0.dylib
|
|
||||||
src/lib/libSDL2.a
|
|
||||||
src/lib/libSDL2.dylib
|
|
||||||
src/lib/libSDL2main.a
|
|
||||||
checks.json
|
checks.json
|
||||||
assets/lib/libasar.dll
|
assets/lib/libasar.dll
|
||||||
cmake/yaze.plist.in
|
cmake/yaze.plist.in
|
||||||
|
etc/
|
||||||
|
latex/
|
||||||
|
html/
|
||||||
|
docs/zarby_algos.md
|
||||||
|
docs/overworld-expansion.md
|
||||||
|
assets/asm/EditorCore.asm
|
||||||
|
src/app/emu/cpu/internal/old_cpu.cc
|
||||||
|
build-windows
|
||||||
|
src/lib/libpng
|
||||||
|
src/lib/zlib
|
||||||
|
assets/layouts/ow_toolset.zeml
|
||||||
18
.gitmodules
vendored
18
.gitmodules
vendored
@@ -1,15 +1,6 @@
|
|||||||
[submodule "src/lib/imgui"]
|
[submodule "src/lib/imgui"]
|
||||||
path = src/lib/imgui
|
path = src/lib/imgui
|
||||||
url = https://github.com/ocornut/imgui.git
|
url = https://github.com/ocornut/imgui.git
|
||||||
[submodule "src/lib/ImGuiFileDialog"]
|
|
||||||
path = src/lib/ImGuiFileDialog
|
|
||||||
url = https://github.com/aiekick/ImGuiFileDialog.git
|
|
||||||
[submodule "src/lib/ImGuiColorTextEdit"]
|
|
||||||
path = src/lib/ImGuiColorTextEdit
|
|
||||||
url = https://github.com/BalazsJako/ImGuiColorTextEdit.git
|
|
||||||
[submodule "src/lib/sneshacking"]
|
|
||||||
path = src/lib/sneshacking
|
|
||||||
url = https://github.com/Skarsnik/sneshacking.git
|
|
||||||
[submodule "assets/asm/alttp-hacker-workspace"]
|
[submodule "assets/asm/alttp-hacker-workspace"]
|
||||||
path = assets/asm/alttp-hacker-workspace
|
path = assets/asm/alttp-hacker-workspace
|
||||||
url = https://github.com/scawful/alttp-hacker-workspace.git
|
url = https://github.com/scawful/alttp-hacker-workspace.git
|
||||||
@@ -22,9 +13,6 @@
|
|||||||
[submodule "src/lib/asar"]
|
[submodule "src/lib/asar"]
|
||||||
path = src/lib/asar
|
path = src/lib/asar
|
||||||
url = https://github.com/RPGHacker/asar.git
|
url = https://github.com/RPGHacker/asar.git
|
||||||
[submodule "src/lib/snes_spc"]
|
[submodule "src/lib/imgui_test_engine"]
|
||||||
path = src/lib/snes_spc
|
path = src/lib/imgui_test_engine
|
||||||
url = https://github.com/blarggs-audio-libraries/snes_spc.git
|
url = https://github.com/ocornut/imgui_test_engine.git
|
||||||
[submodule "src/lib/SDL_mixer"]
|
|
||||||
path = src/lib/SDL_mixer
|
|
||||||
url = https://github.com/libsdl-org/SDL_mixer.git
|
|
||||||
|
|||||||
101
CMakeLists.txt
101
CMakeLists.txt
@@ -1,81 +1,58 @@
|
|||||||
# CMake Specifications --------------------------------------------------------
|
|
||||||
cmake_minimum_required(VERSION 3.10)
|
|
||||||
|
|
||||||
# Yet Another Zelda3 Editor
|
# Yet Another Zelda3 Editor
|
||||||
# by scawful
|
# by scawful
|
||||||
project(yaze VERSION 0.01)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(yaze VERSION 0.2.2
|
||||||
|
DESCRIPTION "Yet Another Zelda3 Editor"
|
||||||
|
LANGUAGES CXX)
|
||||||
|
configure_file(src/yaze_config.h.in yaze_config.h)
|
||||||
|
|
||||||
# C++ Standard Specifications -------------------------------------------------
|
# Build Flags
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(YAZE_BUILD_APP ON)
|
||||||
|
set(YAZE_BUILD_LIB ON)
|
||||||
|
set(YAZE_BUILD_EMU ON)
|
||||||
|
set(YAZE_BUILD_Z3ED ON)
|
||||||
|
set(YAZE_BUILD_PYTHON OFF)
|
||||||
|
set(YAZE_BUILD_TESTS ON)
|
||||||
|
set(YAZE_INSTALL_LIB OFF)
|
||||||
|
|
||||||
|
# libpng features in bitmap.cc
|
||||||
|
add_definitions("-DYAZE_LIB_PNG=1")
|
||||||
|
|
||||||
|
# C++ Standard and CMake Specifications
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS ON)
|
|
||||||
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
|
|
||||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
set(CMAKE_MODULE_LINKER_FLAGS \"-Wl,--no-undefined -Wl,--no-undefined\")
|
|
||||||
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
|
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH})
|
||||||
set(BUILD_SHARED_LIBS ON)
|
set(BUILD_SHARED_LIBS OFF)
|
||||||
|
set(CMAKE_FIND_FRAMEWORK LAST)
|
||||||
|
set(CMAKE_SHARED_MODULE_PREFIX "")
|
||||||
|
|
||||||
# Abseil Standard Specifications ----------------------------------------------
|
|
||||||
include(cmake/absl.cmake)
|
|
||||||
add_subdirectory(src/lib/abseil-cpp)
|
|
||||||
|
|
||||||
include(cmake/openssl.cmake)
|
|
||||||
set(Protobuf_PROTOC_EXECUTABLE "/Users/scawful/code/protobuf/bin/protoc")
|
|
||||||
set(Protobuf_INCLUDE_DIRS "/Users/scawful/code/protobuf/include/")
|
|
||||||
set(PROTOBUF_INCLUDE_PATH ${CMAKE_CURRENT_BINARY_DIR}
|
|
||||||
CACHE INTERNAL "Path to generated protobuf files.")
|
|
||||||
include_directories(${PROTOBUF_INCLUDE_PATH})
|
|
||||||
|
|
||||||
# Video Libraries -------------------------------------------------------------
|
|
||||||
find_package(PNG REQUIRED)
|
|
||||||
find_package(OpenGL REQUIRED)
|
|
||||||
find_package(GLEW REQUIRED)
|
|
||||||
|
|
||||||
# SDL2 ------------------------------------------------------------------------
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
add_subdirectory(src/lib/SDL)
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Dlinux -Dstricmp=strcasecmp")
|
||||||
else()
|
|
||||||
find_package(SDL2)
|
|
||||||
endif()
|
endif()
|
||||||
set(SDL2MIXER_OPUS OFF)
|
|
||||||
set(SDL2MIXER_FLAC OFF)
|
|
||||||
set(SDL2MIXER_MOD OFF)
|
|
||||||
set(SDL2MIXER_MIDI_FLUIDSYNTH OFF)
|
|
||||||
find_library(SDL_MIXER_LIBRARY
|
|
||||||
NAMES SDL_mixer
|
|
||||||
HINTS
|
|
||||||
ENV SDLMIXERDIR
|
|
||||||
ENV SDLDIR
|
|
||||||
PATH_SUFFIXES lib ${VC_LIB_PATH_SUFFIX}
|
|
||||||
)
|
|
||||||
add_subdirectory(src/lib/SDL_mixer)
|
|
||||||
|
|
||||||
# Asar ------------------------------------------------------------------------
|
if (MACOS)
|
||||||
add_subdirectory(src/lib/asar/src)
|
set(CMAKE_INSTALL_PREFIX /usr/local)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
include(cmake/vcpkg.cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Abseil Standard Specifications
|
||||||
|
include(cmake/absl.cmake)
|
||||||
|
|
||||||
|
# SDL2 and PNG
|
||||||
|
include(cmake/sdl2.cmake)
|
||||||
|
|
||||||
|
# Asar
|
||||||
include(cmake/asar.cmake)
|
include(cmake/asar.cmake)
|
||||||
|
|
||||||
# snes-spc --------------------------------------------------------------------
|
# ImGui
|
||||||
ADD_DEFINITIONS(-DSNES_SPC_EXPORTS)
|
|
||||||
set(SNES_SPC_SOURCES
|
|
||||||
"../src/lib/snes_spc/snes_spc/spc.cpp"
|
|
||||||
"../src/lib/snes_spc/snes_spc/SNES_SPC.cpp"
|
|
||||||
"../src/lib/snes_spc/snes_spc/SNES_SPC_misc.cpp"
|
|
||||||
"../src/lib/snes_spc/snes_spc/SNES_SPC_state.cpp"
|
|
||||||
"../src/lib/snes_spc/snes_spc/SPC_DSP.cpp"
|
|
||||||
"../src/lib/snes_spc/snes_spc/dsp.cpp"
|
|
||||||
"../src/lib/snes_spc/snes_spc/SPC_Filter.cpp"
|
|
||||||
"../src/lib/snes_spc/demo/wave_writer.c"
|
|
||||||
"../src/lib/snes_spc/demo/demo_util.c"
|
|
||||||
)
|
|
||||||
include_directories(src/lib/snes_spc/snes_spc)
|
|
||||||
ADD_LIBRARY(snes_spc STATIC ${SNES_SPC_SOURCES} src/app/spc700/spc700.def)
|
|
||||||
|
|
||||||
# ImGui -----------------------------------------------------------------------
|
|
||||||
include(cmake/imgui.cmake)
|
include(cmake/imgui.cmake)
|
||||||
|
|
||||||
# Project Files
|
# Project Files
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(test)
|
|
||||||
6
LICENSE
6
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (C) 2022 Justin Scofield
|
Copyright (C) 2024 Justin Scofield
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -13,3 +13,7 @@ GNU General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
- SDL2 <https://www.libsdl.org/license.php>
|
||||||
|
- ImGui <https://github.com/ocornut/imgui/blob/master/LICENSE.txt>
|
||||||
|
- Abseil <https://github.com/abseil/abseil-cpp/blob/master/LICENSE>
|
||||||
41
README.md
41
README.md
@@ -1,12 +1,14 @@
|
|||||||
# Yet Another Zelda3 Editor
|
# Yet Another Zelda3 Editor
|
||||||
|
|
||||||
- Platform: Windows, macOS, GNU/Linux
|
- Platform: Windows, macOS, iOS, GNU/Linux
|
||||||
- Dependencies: SDL2, ImGui
|
- Dependencies: SDL2, ImGui, abseil-cpp
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
General purpose editor for The Legend of Zelda: A Link to the Past for the Super Nintendo.
|
General purpose editor for The Legend of Zelda: A Link to the Past for the Super Nintendo.
|
||||||
|
|
||||||
|
Provides bindings in C and Python for building custom tools and utilities.
|
||||||
|
|
||||||
Takes heavy inspiration from ALTTP community efforts such as [Hyrule Magic](https://www.romhacking.net/utilities/200/) and [ZScream](https://github.com/Zarby89/ZScreamDungeon)
|
Takes heavy inspiration from ALTTP community efforts such as [Hyrule Magic](https://www.romhacking.net/utilities/200/) and [ZScream](https://github.com/Zarby89/ZScreamDungeon)
|
||||||
|
|
||||||
Building and installation
|
Building and installation
|
||||||
@@ -14,25 +16,44 @@ Building and installation
|
|||||||
[CMake](http://www.cmake.org "CMake") is required to build yaze
|
[CMake](http://www.cmake.org "CMake") is required to build yaze
|
||||||
|
|
||||||
1. Clone the repository
|
1. Clone the repository
|
||||||
|
|
||||||
git clone --recurse-submodules https://github.com/scawful/yaze.git
|
|
||||||
|
|
||||||
2. Create the build directory and configuration
|
2. Create the build directory and configuration
|
||||||
|
3. Build and run the application
|
||||||
|
4. (Optional) Run the tests
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone --recurse-submodules https://github.com/scawful/yaze.git
|
||||||
cmake -S . -B build
|
cmake -S . -B build
|
||||||
|
|
||||||
3. Build and run.
|
|
||||||
|
|
||||||
cmake --build build
|
cmake --build build
|
||||||
|
```
|
||||||
|
|
||||||
|
By default this will build all targets.
|
||||||
|
|
||||||
|
- **yaze**: Editor Application
|
||||||
|
- **yaze_c**: C Library
|
||||||
|
- **yaze_emu**: SNES Emulator
|
||||||
|
- **yaze_py**: Python Module
|
||||||
|
- **yaze_test**: Unit Tests
|
||||||
|
- **z3ed**: Command Line Interface
|
||||||
|
|
||||||
|
Dependencies are included as submodules and will be built automatically. For those who want to reduce compile times, consider installing the dependencies on your system. See [build-instructions.md](docs/build-instructions.md) for more information.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze.
|
||||||
|
- For developers, please refer to the [documentation](https://scawful.github.io/yaze/index.html) for information on the project's infrastructure.
|
||||||
|
|
||||||
License
|
License
|
||||||
--------
|
--------
|
||||||
YAZE is distributed under the [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.txt) license.
|
YAZE is distributed under the [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.txt) license.
|
||||||
|
|
||||||
SDL2, ImGUI and Abseil are subject to respective licenses.
|
SDL2, ImGui and Abseil are subject to respective licenses.
|
||||||
|
|
||||||
Screenshots
|
Screenshots
|
||||||
--------
|
--------
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3563
assets/asm/ZSCustomOverworld.asm
Normal file
3563
assets/asm/ZSCustomOverworld.asm
Normal file
File diff suppressed because it is too large
Load Diff
Submodule assets/asm/alttp-hacker-workspace updated: ea81eb3425...2520fb70c3
22
assets/asm/yaze.asm
Normal file
22
assets/asm/yaze.asm
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
; =========================================================
|
||||||
|
; yaze custom assembly code
|
||||||
|
; =========================================================
|
||||||
|
|
||||||
|
namespace yaze
|
||||||
|
{
|
||||||
|
|
||||||
|
!YAZE_CUSTOM_MOSAIC = 1
|
||||||
|
|
||||||
|
|
||||||
|
if !YAZE_CUSTOM_MOSAIC != 0
|
||||||
|
incsrc "mosaic_change.asm"
|
||||||
|
endif
|
||||||
|
|
||||||
|
!ZS_CUSTOM_OVERWORLD = 1
|
||||||
|
|
||||||
|
if !ZS_CUSTOM_OVERWORLD != 0
|
||||||
|
incsrc "ZSCustomOverworld.asm"
|
||||||
|
endif
|
||||||
|
|
||||||
|
}
|
||||||
|
namespace off
|
||||||
BIN
assets/font/IBMPlexSansJP-Bold.ttf
Normal file
BIN
assets/font/IBMPlexSansJP-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/font/NotoSansJP.ttf
Normal file
BIN
assets/font/NotoSansJP.ttf
Normal file
Binary file not shown.
25
assets/layouts/overworld.zeml
Normal file
25
assets/layouts/overworld.zeml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
BeginTabBar title="##OwEditorTabBar" {
|
||||||
|
BeginTabItem title="Map Editor" {
|
||||||
|
Function id="owToolset",
|
||||||
|
|
||||||
|
Table id="##owEditTable" count="2" flags="Resizable|Reorderable|Hideable|BordersOuter|BordersV" {
|
||||||
|
TableSetupColumn title="Canvas" flags="WidthStretch",
|
||||||
|
TableSetupColumn title="Tile Selector" flags="WidthFixed" width="256",
|
||||||
|
TableHeadersRow
|
||||||
|
TableNextRow,
|
||||||
|
TableNextColumn,
|
||||||
|
Function id="OverworldCanvas",
|
||||||
|
TableNextColumn,
|
||||||
|
Function id="OverworldTileSelector",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BeginTabItem title="Tile16 Editor" {
|
||||||
|
Function id="OwTile16Editor"
|
||||||
|
}
|
||||||
|
BeginTabItem title "Graphics Group Editor" {
|
||||||
|
Function id="OwGfxGroupEditor"
|
||||||
|
}
|
||||||
|
BeginTabItem title="Usage Statistics" {
|
||||||
|
Function id="OwUsageStats"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
assets/yaze.png
Normal file
BIN
assets/yaze.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
@@ -1,3 +1,8 @@
|
|||||||
|
if (MINGW)
|
||||||
|
add_subdirectory(src/lib/abseil-cpp)
|
||||||
|
else()
|
||||||
|
find_package(absl)
|
||||||
|
endif()
|
||||||
set(ABSL_PROPAGATE_CXX_STD ON)
|
set(ABSL_PROPAGATE_CXX_STD ON)
|
||||||
set(ABSL_CXX_STANDARD 17)
|
set(ABSL_CXX_STANDARD 17)
|
||||||
set(ABSL_USE_GOOGLETEST_HEAD ON)
|
set(ABSL_USE_GOOGLETEST_HEAD ON)
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
get_target_property(ASAR_INCLUDE_DIR asar-static INCLUDE_DIRECTORIES)
|
# Asar Assembler for 65816 SNES Assembly
|
||||||
target_include_directories(asar-static PRIVATE ${ASAR_INCLUDE_DIR})
|
add_subdirectory(src/lib/asar/src)
|
||||||
|
|
||||||
set(ASAR_GEN_EXE OFF)
|
set(ASAR_GEN_EXE OFF)
|
||||||
set(ASAR_GEN_DLL ON)
|
set(ASAR_GEN_DLL ON)
|
||||||
set(ASAR_GEN_LIB ON)
|
set(ASAR_GEN_LIB ON)
|
||||||
set(ASAR_GEN_EXE_TEST OFF)
|
set(ASAR_GEN_EXE_TEST OFF)
|
||||||
set(ASAR_GEN_DLL_TEST OFF)
|
set(ASAR_GEN_DLL_TEST OFF)
|
||||||
|
set(ASAR_STATIC_SRC_DIR "${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar")
|
||||||
|
|
||||||
|
get_target_property(ASAR_INCLUDE_DIR asar-static INCLUDE_DIRECTORIES)
|
||||||
|
list(APPEND ASAR_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/src/lib/asar/src")
|
||||||
|
target_include_directories(asar-static PRIVATE ${ASAR_INCLUDE_DIR})
|
||||||
|
|
||||||
set(ASAR_STATIC_SRC
|
set(ASAR_STATIC_SRC
|
||||||
"../src/lib/asar/src/asar/interface-lib.cpp"
|
"${ASAR_STATIC_SRC_DIR}/interface-lib.cpp"
|
||||||
"../src/lib/asar/src/asar/addr2line.cpp"
|
"${ASAR_STATIC_SRC_DIR}/addr2line.cpp"
|
||||||
"../src/lib/asar/src/asar/arch-65816.cpp"
|
"${ASAR_STATIC_SRC_DIR}/arch-65816.cpp"
|
||||||
"../src/lib/asar/src/asar/arch-spc700.cpp"
|
"${ASAR_STATIC_SRC_DIR}/arch-spc700.cpp"
|
||||||
"../src/lib/asar/src/asar/arch-superfx.cpp"
|
"${ASAR_STATIC_SRC_DIR}/arch-superfx.cpp"
|
||||||
"../src/lib/asar/src/asar/assembleblock.cpp"
|
"${ASAR_STATIC_SRC_DIR}/assembleblock.cpp"
|
||||||
"../src/lib/asar/src/asar/crc32.cpp"
|
"${ASAR_STATIC_SRC_DIR}/crc32.cpp"
|
||||||
"../src/lib/asar/src/asar/libcon.cpp"
|
"${ASAR_STATIC_SRC_DIR}/libcon.cpp"
|
||||||
"../src/lib/asar/src/asar/libsmw.cpp"
|
"${ASAR_STATIC_SRC_DIR}/libsmw.cpp"
|
||||||
"../src/lib/asar/src/asar/libstr.cpp"
|
"${ASAR_STATIC_SRC_DIR}/libstr.cpp"
|
||||||
"../src/lib/asar/src/asar/macro.cpp"
|
"${ASAR_STATIC_SRC_DIR}/macro.cpp"
|
||||||
"../src/lib/asar/src/asar/main.cpp"
|
"${ASAR_STATIC_SRC_DIR}/main.cpp"
|
||||||
"../src/lib/asar/src/asar/asar_math.cpp"
|
"${ASAR_STATIC_SRC_DIR}/asar_math.cpp"
|
||||||
"../src/lib/asar/src/asar/virtualfile.cpp"
|
"${ASAR_STATIC_SRC_DIR}/virtualfile.cpp"
|
||||||
"../src/lib/asar/src/asar/warnings.cpp"
|
"${ASAR_STATIC_SRC_DIR}/warnings.cpp"
|
||||||
"../src/lib/asar/src/asar/errors.cpp"
|
"${ASAR_STATIC_SRC_DIR}/errors.cpp"
|
||||||
"../src/lib/asar/src/asar/platform/file-helpers.cpp"
|
"${ASAR_STATIC_SRC_DIR}/platform/file-helpers.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32 OR MINGW)
|
if(WIN32 OR MINGW)
|
||||||
list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/windows/file-helpers-win32.cpp")
|
list(APPEND ASAR_STATIC_SRC "${ASAR_STATIC_SRC_DIR}/platform/windows/file-helpers-win32.cpp")
|
||||||
else()
|
else()
|
||||||
list(APPEND ASAR_STATIC_SRC "../src/lib/asar/src/asar/platform/linux/file-helpers-linux.cpp")
|
list(APPEND ASAR_STATIC_SRC "${ASAR_STATIC_SRC_DIR}/platform/linux/file-helpers-linux.cpp")
|
||||||
endif()
|
endif()
|
||||||
11
cmake/gtest.cmake
Normal file
11
cmake/gtest.cmake
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# GoogleTest ------------------------------------------------------------------
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
googletest
|
||||||
|
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
|
||||||
|
)
|
||||||
|
|
||||||
|
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
||||||
|
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(googletest)
|
||||||
|
enable_testing()
|
||||||
@@ -7,19 +7,11 @@ target_include_directories(ImGui PUBLIC ${SDL2_INCLUDE_DIR})
|
|||||||
target_compile_definitions(ImGui PUBLIC
|
target_compile_definitions(ImGui PUBLIC
|
||||||
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
||||||
|
|
||||||
set(IMGUI_FILE_DLG_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiFileDialog)
|
set(IMGUI_TEST_ENGINE_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine/imgui_test_engine)
|
||||||
file(GLOB IMGUI_FILE_DLG_SOURCES ${IMGUI_FILE_DLG_PATH}/*.cpp)
|
file(GLOB IMGUI_TEST_ENGINE_SOURCES ${IMGUI_TEST_ENGINE_PATH}/*.cpp)
|
||||||
add_library("ImGuiFileDialog" STATIC ${IMGUI_FILE_DLG_SOURCES})
|
add_library("ImGuiTestEngine" STATIC ${IMGUI_TEST_ENGINE_SOURCES})
|
||||||
target_include_directories(ImGuiFileDialog PUBLIC ${IMGUI_PATH})
|
target_include_directories(ImGuiTestEngine PUBLIC ${IMGUI_PATH} ${CMAKE_SOURCE_DIR}/src/lib)
|
||||||
target_compile_definitions(ImGuiFileDialog PUBLIC
|
target_link_libraries(ImGuiTestEngine PUBLIC ImGui)
|
||||||
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
|
||||||
|
|
||||||
set(IMGUI_COLOR_TEXT_EDIT_PATH ${CMAKE_SOURCE_DIR}/src/lib/ImGuiColorTextEdit)
|
|
||||||
file(GLOB IMGUI_COLOR_TEXT_EDIT_SOURCES ${IMGUI_COLOR_TEXT_EDIT_PATH}/*.cpp)
|
|
||||||
add_library("ImGuiColorTextEdit" STATIC ${IMGUI_COLOR_TEXT_EDIT_SOURCES})
|
|
||||||
target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH})
|
|
||||||
target_compile_definitions(ImGuiColorTextEdit PUBLIC
|
|
||||||
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
|
||||||
|
|
||||||
set(
|
set(
|
||||||
IMGUI_SRC
|
IMGUI_SRC
|
||||||
@@ -27,9 +19,10 @@ set(
|
|||||||
${IMGUI_PATH}/imgui_demo.cpp
|
${IMGUI_PATH}/imgui_demo.cpp
|
||||||
${IMGUI_PATH}/imgui_draw.cpp
|
${IMGUI_PATH}/imgui_draw.cpp
|
||||||
${IMGUI_PATH}/imgui_widgets.cpp
|
${IMGUI_PATH}/imgui_widgets.cpp
|
||||||
${IMGUI_PATH}/backends/imgui_impl_sdl.cpp
|
${IMGUI_PATH}/backends/imgui_impl_sdl2.cpp
|
||||||
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer.cpp
|
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer2.cpp
|
||||||
${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp
|
${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp
|
||||||
${IMGUI_FILE_DLG_PATH}/ImGuiFileDialog.cpp
|
|
||||||
${IMGUI_COLOR_TEXT_EDIT_PATH}/TextEditor.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# For integration test
|
||||||
|
add_definitions("-DIMGUI_ENABLE_TEST_ENGINE -DIMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL=1")
|
||||||
|
|||||||
21
cmake/mingw64.cmake
Normal file
21
cmake/mingw64.cmake
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# cmake -DCMAKE_TOOLCHAIN_FILE=./cmake/mingw64.cmake -B build/build-windows && cmake --build ./build/build-windows
|
||||||
|
|
||||||
|
set(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
set(CMAKE_SYSTEM_VERSION 1)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
|
||||||
|
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
|
||||||
|
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||||
|
|
||||||
|
set(CMAKE_FIND_ROOT_PATH /usr/local/opt/mingw-w64)
|
||||||
|
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||||
|
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -pipe -g -feliminate-unused-debug-types")
|
||||||
|
|
||||||
|
# Static link the C++ standard library
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
if (UNIX)
|
|
||||||
set(OPENSSL_INCLUDE_DIR "/usr/local/Cellar/openssl@1.1/1.1.1q/include")
|
|
||||||
set(OPENSSL_CRYPTO_LIBRARY "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/libcrypto.dylib")
|
|
||||||
set(OPENSSL_SSL_LIBRARY "/usr/local/Cellar/openssl@1.1/1.1.1q/lib/libssl.dylib")
|
|
||||||
endif()
|
|
||||||
16
cmake/sdl2.cmake
Normal file
16
cmake/sdl2.cmake
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# SDL2
|
||||||
|
if (UNIX OR MINGW)
|
||||||
|
add_subdirectory(src/lib/SDL)
|
||||||
|
else()
|
||||||
|
find_package(SDL2)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SDL_TARGETS SDL2::SDL2)
|
||||||
|
|
||||||
|
if(WIN32 OR MINGW)
|
||||||
|
list(PREPEND SDL_TARGETS SDL2::SDL2main ws2_32)
|
||||||
|
add_definitions("-DSDL_MAIN_HANDLED")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# libpng
|
||||||
|
find_package(PNG REQUIRED)
|
||||||
6
cmake/vcpkg.cmake
Normal file
6
cmake/vcpkg.cmake
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
add_definitions("-DMICROSOFT_WINDOWS_WINBASE_H_DEFINE_INTERLOCKED_CPLUSPLUS_OVERLOADS=0")
|
||||||
|
|
||||||
|
set(VCPKG_TARGET_ARCHITECTURE x64)
|
||||||
|
set(VCPKG_CRT_LINKAGE dynamic)
|
||||||
|
set(VCPKG_LIBRARY_LINKAGE dynamic)
|
||||||
223
docs/asm-style-guide.md
Normal file
223
docs/asm-style-guide.md
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# Asm Style Guide
|
||||||
|
|
||||||
|
65816 Assembly is the assembly language used by the Super Nintendo Entertainment System (SNES) and its Ricoh 5A22 processor. This style guide provides conventions and best practices for writing 65816 assembly code in the context of the yaze project. Following these guidelines will help maintain consistency and readability across the codebase.
|
||||||
|
|
||||||
|
This guide is based primarily on the [Oracle of Secrets](https://github.com/scawful/Oracle-of-Secrets) codebase and is meant for the [Asar](https://github.com/RPGHacker/asar) assembler and derives influence from the [Asar 1.9 Manual](https://rpghacker.github.io/asar/asar_19/manual/).
|
||||||
|
|
||||||
|
Custom assembly code applied to the game should be included through the `yaze.asm` file found in `assets/asm`. This file can be applied to the ROM by the editor using the Asar library or included into a projects codebase for use with the Asar assembler.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [File Structure](#file-structure)
|
||||||
|
- [Labels and Symbols](#labels-and-symbols)
|
||||||
|
- [Comments](#comments)
|
||||||
|
- [Directives](#directives)
|
||||||
|
- [Instructions](#instructions)
|
||||||
|
- [Macros](#macros)
|
||||||
|
- [Loops and Branching](#loops-and-branching)
|
||||||
|
- [Data Structures](#data-structures)
|
||||||
|
- [Code Organization](#code-organization)
|
||||||
|
- [Custom Code](#custom-code)
|
||||||
|
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
- **File Extension**: Use `.asm` as the file extension for 65816 assembly files.
|
||||||
|
- **Header Comments**: Include a header comment at the beginning of each file describing its purpose and the author.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
; =========================================================
|
||||||
|
; Purpose: [Brief description of the file’s functionality]
|
||||||
|
; Author: [Your Name]
|
||||||
|
; =========================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Section Headers**: Use clear and consistent section headers to divide code into logical blocks. Each major section (e.g., sprite properties, main logic, subroutines) should start with a delineated header.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
; =========================================================
|
||||||
|
; Minecart Sprite Properties
|
||||||
|
; =========================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Macro Definitions and Includes**: Place macros and include directives at the beginning of the file to keep them organized and easily accessible.
|
||||||
|
|
||||||
|
## Labels and Symbols
|
||||||
|
|
||||||
|
- **Naming Conventions**:
|
||||||
|
- **Global Labels**: Use descriptive names in `PascalCase` for global labels (e.g., `Sprite_Minecart_Main`).
|
||||||
|
- **Local Labels**: Prefix local labels with a dot (`.`) to indicate their limited scope (e.g., `.check_direction`).
|
||||||
|
- **Constants and Flags**: Use `ALL_CAPS_WITH_UNDERSCORES` for constants and flags (e.g., `!MINECART_SPEED`, `!HARVESTING_FLAG`).
|
||||||
|
- **Variables**: Use `CamelCase` for variable names to maintain readability (e.g., `LinkInCart`, `SpriteDirection`).
|
||||||
|
|
||||||
|
- **Alignment**: Align labels to the left margin for better readability. Indent instructions and comments to separate them from labels.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
Sprite_Minecart_Main:
|
||||||
|
{
|
||||||
|
JSR HandleTileDirections
|
||||||
|
JSR HandleDynamicSwitchTileDirections
|
||||||
|
RTS
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
- **Purpose**: Comments should explain why the code exists and what it is intended to do, especially for complex logic.
|
||||||
|
- **Placement**:
|
||||||
|
- Comments can be placed above the code block they describe for longer explanations.
|
||||||
|
- Inline comments can be used for single lines of code where the purpose might not be immediately clear.
|
||||||
|
- **Clarity**: Avoid stating the obvious. Focus on explaining the logic rather than restating the code.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
LDA $22 : SEC : SBC $3F : STA $31 ; Adjust X position for camera movement
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
- **Single Line Instructions**: Combine multiple instructions on a single line using colons (`:`) where appropriate for related operations.
|
||||||
|
- **Separation**: Use line breaks to separate distinct sections of code logically, improving readability.
|
||||||
|
- **Optimization**: Always consider the most efficient instruction for the task at hand, especially in performance-critical sections.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
LDA #$01 : STA !LinkInCart ; Set Link in cart flag
|
||||||
|
```
|
||||||
|
|
||||||
|
## Macros
|
||||||
|
|
||||||
|
- **Naming**: Use `PascalCase` for macro names, with the first letter of each word capitalized (e.g., `InitMovement`, `MoveCart`).
|
||||||
|
- **Parameters**: Clearly define and document parameters within macros to ensure they are used correctly.
|
||||||
|
- **Reuse**: Encourage the reuse of macros to avoid code duplication and simplify maintenance.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
%macro HandlePlayerCamera
|
||||||
|
LDA $22 : SEC : SBC $3F : STA $31
|
||||||
|
LDA $20 : SEC : SBC $3E : STA $30
|
||||||
|
JSL Link_HandleMovingAnimation_FullLongEntry
|
||||||
|
JSL HandleIndoorCameraAndDoors
|
||||||
|
RTS
|
||||||
|
endmacro
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loops and Branching
|
||||||
|
|
||||||
|
- **Branch Labels**: Use meaningful names for branch labels, prefixed with a dot (`.`) for local branches.
|
||||||
|
- **Optimization**: Minimize the number of instructions within loops and branches to improve performance.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
.loop_start
|
||||||
|
LDA $00 : CMP #$10 : BEQ .end_loop
|
||||||
|
INC $00
|
||||||
|
BRA .loop_start
|
||||||
|
.end_loop
|
||||||
|
RTS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Structures
|
||||||
|
|
||||||
|
- **Alignment**: Align data tables and structures clearly, and use comments to describe the purpose and layout of each.
|
||||||
|
- **Access**: Ensure that data structures are accessed consistently, with clear boundaries between read and write operations.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
.DirectionTileLookup
|
||||||
|
{
|
||||||
|
db $02, $00, $04, $00 ; North
|
||||||
|
db $00, $00, $03, $01 ; East
|
||||||
|
db $00, $02, $00, $04 ; South
|
||||||
|
db $03, $01, $00, $00 ; West
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Structs**: Use structs to group related data together, improving readability and maintainability.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
struct AncillaAdd_HookshotData $099AF8
|
||||||
|
.speed_y: skip 4
|
||||||
|
.speed_x: skip 4
|
||||||
|
.offset_y: skip 8
|
||||||
|
.offset_x: skip 8
|
||||||
|
endstruct
|
||||||
|
|
||||||
|
AncillaAdd_Hookshot:
|
||||||
|
; $099AF0
|
||||||
|
.speed_y
|
||||||
|
db -64 ; up
|
||||||
|
db 64 ; down
|
||||||
|
db 0 ; left
|
||||||
|
db 0 ; right
|
||||||
|
; $099AFC
|
||||||
|
.speed_x
|
||||||
|
db 0 ; up
|
||||||
|
db 0 ; down
|
||||||
|
db -64 ; left
|
||||||
|
db 64 ; right
|
||||||
|
; $099B00
|
||||||
|
.offset_y
|
||||||
|
dw 4 ; up
|
||||||
|
dw 20 ; down
|
||||||
|
dw 8 ; left
|
||||||
|
dw 8 ; right
|
||||||
|
; $099B08
|
||||||
|
.offset_x
|
||||||
|
dw 0 ; up
|
||||||
|
dw 0 ; down
|
||||||
|
dw -4 ; left
|
||||||
|
dw 11 ; right
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Organization
|
||||||
|
|
||||||
|
- **Logical Grouping**: Organize code into logical sections, with related routines and macros grouped together.
|
||||||
|
- **Separation of Concerns**: Ensure that each section of code is responsible for a specific task or set of related tasks, avoiding tightly coupled code.
|
||||||
|
- **Modularity**: Write code in a modular way, making it easier to reuse and maintain.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
; =========================================================
|
||||||
|
; Minecart Sprite Logic
|
||||||
|
; =========================================================
|
||||||
|
Sprite_Minecart_Main:
|
||||||
|
{
|
||||||
|
PHX
|
||||||
|
JSR HandleMinecartMovement
|
||||||
|
PLX
|
||||||
|
|
||||||
|
REP #$20
|
||||||
|
LDA !SpriteDirection : STA $00
|
||||||
|
SEP #$20
|
||||||
|
RTS
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Code
|
||||||
|
|
||||||
|
- **Integration**: Include custom assembly code in the `yaze.asm` file to ensure it is applied correctly to the ROM. The module should include a define and conditional statement to allow users to disable the module if needed.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
!YAZE_CUSTOM_MOSAIC = 1
|
||||||
|
|
||||||
|
if !YAZE_CUSTOM_MOSAIC != 0
|
||||||
|
incsrc "mosaic_change.asm"
|
||||||
|
endif
|
||||||
|
```
|
||||||
94
docs/build-instructions.md
Normal file
94
docs/build-instructions.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Build Instructions
|
||||||
|
|
||||||
|
For VSCode users, use the following CMake extensions
|
||||||
|
|
||||||
|
- https://marketplace.visualstudio.com/items?itemName=twxs.cmake
|
||||||
|
- https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
|
||||||
|
|
||||||
|
Yaze uses CMake to build the project. If you are unexperienced with CMake, please refer to the [CMake documentation](https://cmake.org/documentation/).
|
||||||
|
|
||||||
|
The gui editor is built using SDL2 and ImGui. For reference on how to use ImGui, see the [Getting Started](https://github.com/ocornut/imgui/wiki/Getting-Started) guide. For SDL2, see the [SDL2 documentation](https://wiki.libsdl.org/).
|
||||||
|
|
||||||
|
For those who want to reduce compile times, consider installing the dependencies on your system.
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
### vcpkg
|
||||||
|
|
||||||
|
For Visual Studio users, follow the [Install and use packages with CMake](https://learn.microsoft.com/en-us/vcpkg/get_started/get-started) tutorial from Microsoft.
|
||||||
|
|
||||||
|
Define the following dependencies in `vcpkg.json`
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"dependencies": [
|
||||||
|
"abseil",
|
||||||
|
"sdl2",
|
||||||
|
"libpng"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Target the architecture in `CMakePresets.json`
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"name": "vcpkg",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"binaryDir": "${sourceDir}/build",
|
||||||
|
"architecture": {
|
||||||
|
"value": "arm64/x64",
|
||||||
|
"strategy": "external"
|
||||||
|
},
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||||
|
"CMAKE_SYSTEM_PROCESSOR": "arm64/x64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### msys2
|
||||||
|
|
||||||
|
[msys2](https://www.msys2.org/) is an alternative you may use for a Unix-like environment on Windows. Beware that this is for more experienced developers who know how to manage their system PATH.
|
||||||
|
|
||||||
|
Add to environment variables `C:\msys64\mingw64\bin`
|
||||||
|
|
||||||
|
Install the following packages using `pacman -S <package-name>`
|
||||||
|
|
||||||
|
- `mingw-w64-x86_64-gcc`
|
||||||
|
- `mingw-w64-x86_64-gcc-libs`
|
||||||
|
- `mingw-w64-x86_64-cmake`
|
||||||
|
- `mingw-w64-x86_64-sdl2`
|
||||||
|
- `mingw-w64-x86_64-libpng`
|
||||||
|
- `mingw-w64-x86_64-abseil-cpp`
|
||||||
|
|
||||||
|
For `yaze_py` you will need Boost Python
|
||||||
|
|
||||||
|
- `mingw-w64-x86_64-boost`
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
|
||||||
|
Prefer to use clang provided with XCode command line tools over gcc.
|
||||||
|
|
||||||
|
Install the following packages using `brew install <package-name>`
|
||||||
|
|
||||||
|
- `cmake`
|
||||||
|
- `sdl2`
|
||||||
|
- `zlib`
|
||||||
|
- `libpng`
|
||||||
|
- `abseil`
|
||||||
|
- `boost-python3`
|
||||||
|
|
||||||
|
# iOS
|
||||||
|
|
||||||
|
Xcode is required to build for iOS. Currently testing with iOS 18 on iPad Pro.
|
||||||
|
|
||||||
|
The xcodeproject file is located in the `ios` directory.
|
||||||
|
|
||||||
|
You will need to link `SDL2.framework` and `libpng.a` to the project.
|
||||||
|
|
||||||
|
# GNU/Linux
|
||||||
|
|
||||||
|
You can use your package manager to install the same dependencies as macOS.
|
||||||
|
|
||||||
|
I trust you know how to use your package manager.
|
||||||
@@ -1,26 +1,102 @@
|
|||||||
## September 2022
|
# Changelog
|
||||||
|
|
||||||
- Drawing Overworld maps to the screen
|
## 0.2.2 (12-31-2024)
|
||||||
- Drawing entrance data on the overworld
|
|
||||||
- Drawing 2bpp inventory graphics data
|
|
||||||
- Started the YazeDelta project for version control.
|
|
||||||
|
|
||||||
## August 2022
|
- DungeonMap editing improvements
|
||||||
|
- ZSCustomOverworld support
|
||||||
|
- Cross platform file handling
|
||||||
|
|
||||||
- Added ValidateCompressionResults to ROM::Compress
|
## 0.2.1 (08-20-2024)
|
||||||
- Improved Overworld systems in preparation for drawing maps.
|
|
||||||
|
|
||||||
## July 2022
|
- Improved MessageEditor parsing
|
||||||
|
- Added integration test window
|
||||||
|
- Bitmap bug fixes
|
||||||
|
|
||||||
- Display current overworld map graphics tile sheets.
|
## 0.2.0 (07-20-2024)
|
||||||
- Added CreateAllGraphicsData to the ROM class
|
|
||||||
- Added Google Abseil C++ library for error handling, string manipulation
|
|
||||||
- Refactor ROM class to use smart pointers and STL containers
|
|
||||||
|
|
||||||
## June 2022
|
- iOS app support
|
||||||
|
- Graphics Sheet Browser
|
||||||
|
- Project Files
|
||||||
|
|
||||||
- Implemented LC_LZ2 Decompression
|
## 0.1.0 (05-11-2024)
|
||||||
- Created Bitmap class for displaying SNES Graphics
|
|
||||||
- Added Overworld and OverworldMap class definitions
|
- Bitmap bug fixes
|
||||||
- Built user interface using ImGui and SDL2
|
- Error handling improvements
|
||||||
- Started YAZE
|
|
||||||
|
## 0.0.9 (04-14-2024)
|
||||||
|
|
||||||
|
- Documentation updates
|
||||||
|
- Entrance tile types
|
||||||
|
- Emulator subsystem overhaul
|
||||||
|
|
||||||
|
## 0.0.8 (02-08-2024)
|
||||||
|
|
||||||
|
- Hyrule Magic Compression
|
||||||
|
- Dungeon Room Entrances
|
||||||
|
- Png Export
|
||||||
|
|
||||||
|
## 0.0.7 (01-27-2024)
|
||||||
|
|
||||||
|
- OverworldEntities
|
||||||
|
- Entrances
|
||||||
|
- Exits
|
||||||
|
- Items
|
||||||
|
- Sprites
|
||||||
|
|
||||||
|
## 0.0.6 (11-22-2023)
|
||||||
|
|
||||||
|
- ScreenEditor DungeonMap
|
||||||
|
- Tile16 Editor
|
||||||
|
- Canvas updates
|
||||||
|
|
||||||
|
## 0.0.5 (11-21-2023)
|
||||||
|
|
||||||
|
- DungeonEditor
|
||||||
|
- DungeonObjectRenderer
|
||||||
|
|
||||||
|
## 0.0.4 (11-11-2023)
|
||||||
|
|
||||||
|
- Tile16Editor
|
||||||
|
- GfxGroupEditor
|
||||||
|
- Add GfxGroups fns to Rom
|
||||||
|
- Add Tile16Editor and GfxGroupEditor to OverworldEditor
|
||||||
|
|
||||||
|
## 0.0.3 (10-26-2023)
|
||||||
|
|
||||||
|
- Emulator subsystem
|
||||||
|
- Snes Ppu and PpuRegisters
|
||||||
|
- Direct Memory Access
|
||||||
|
- Cpu Tests
|
||||||
|
- Read/Write Tile16 functions
|
||||||
|
- CompressionV3
|
||||||
|
- Rom::LoadLinkGraphics
|
||||||
|
|
||||||
|
|
||||||
|
## 0.0.2 (08-26-2023)
|
||||||
|
|
||||||
|
- Emulator subsystem
|
||||||
|
- Spc700
|
||||||
|
- Emulator loop
|
||||||
|
- Clock and MockClock
|
||||||
|
- Ppu and Apu cycling
|
||||||
|
- Setup Snes initialization
|
||||||
|
- 65816 Cpu opcodes
|
||||||
|
- JP Font support
|
||||||
|
- SCAD Format support for CGX, COL, OBJ files
|
||||||
|
- Overworld Save
|
||||||
|
- Overworld Map Tile Editing
|
||||||
|
|
||||||
|
## 0.0.1 (07-22-2023)
|
||||||
|
|
||||||
|
- GraphicsEditor
|
||||||
|
- Palette management
|
||||||
|
- lc_lz2 Compression
|
||||||
|
- SnesTo8bppSheet
|
||||||
|
- Bitmap Canvas
|
||||||
|
|
||||||
|
## 0.0.0 (06-08-2022)
|
||||||
|
|
||||||
|
- Started project
|
||||||
|
- Added ImGui
|
||||||
|
- Added SDL2
|
||||||
|
- Added yaze_test target with gtest
|
||||||
|
|||||||
108
docs/contributing.md
Normal file
108
docs/contributing.md
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
This project is looking for contributors to help improve the software and enhance the user experience. If you are interested in contributing, please read the following guidelines and suggestions for areas where you can make a difference.
|
||||||
|
|
||||||
|
Discussion on the editor and its development can be found on the [Oracle of Secrets Discord](https://discord.gg/MBFkMTPEmk) server.
|
||||||
|
|
||||||
|
## Style Guide
|
||||||
|
|
||||||
|
When contributing to the project, please follow these guidelines to ensure consistency and readability across the codebase:
|
||||||
|
|
||||||
|
C++ Code should follow the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) with the following exceptions:
|
||||||
|
|
||||||
|
- Boost libraries are allowed, but require cross platform compatibility.
|
||||||
|
|
||||||
|
Objective-C Code should follow the [Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.html).
|
||||||
|
|
||||||
|
Python Code should follow the [PEP 8 Style Guide](https://pep8.org/).
|
||||||
|
|
||||||
|
Assembly code should follow the [65816 Style Guide](docs/asm-style-guide.md).
|
||||||
|
|
||||||
|
## Testing Facilities
|
||||||
|
|
||||||
|
The project includes the `yaze_test` target which defines unit tests and an integration test window. The unit tests make use of GoogleTest and GoogleMock. The integration test window is an ImGui window build out of the yaze::core::Controller and yaze::test::integration::TestEditor. The integration test window can be accessed by passing the argument `integration` to the target.
|
||||||
|
|
||||||
|
New modules should define unit tests in the `src/test` directory and integration tests in the `src/test/integration` directory. The `yaze_test` target will automatically include all tests in these directories.
|
||||||
|
|
||||||
|
## Key Areas of Contribution
|
||||||
|
|
||||||
|
### 1. Extensions System
|
||||||
|
|
||||||
|
Yaze *(stylized as yaze)* emphasizes extensibility. The `yaze_ext` library allows developers to build and integrate extensions using C, C++, or Python. This system is central to yaze's modular design, enabling new features, custom editors, or tools to be added without modifying the core codebase.
|
||||||
|
|
||||||
|
- C/C++ Extensions: Utilize the `yaze_extension` interface to integrate custom functionality into the editor. You can add new tabs, manipulate ROM data, or extend the editor’s capabilities with custom tools.
|
||||||
|
- Python Extensions: Currently unimplemented, Python extensions will allow developers to write scripts that interact with the editor, modify ROM data, or automate repetitive tasks.
|
||||||
|
|
||||||
|
Examples of Extensions:
|
||||||
|
|
||||||
|
- UI enhancements like additional menus, panels, or status displays.
|
||||||
|
- Rom manipulation tools for editing data structures, such as the overworld maps or dungeon objects.
|
||||||
|
- Custom editors for specific tasks, like file format conversion, data visualization, or event scripting.
|
||||||
|
|
||||||
|
### 2. Sprite Builder System
|
||||||
|
|
||||||
|
The sprite builder system in yaze is based on the [ZSpriteMaker](https://github.com/Zarby89/ZSpriteMaker/) project and allows users to create custom sprites for use in ROM hacks. The goal is to support ZSM files and provide an intuitive interface for editing sprites without the need for writing assembly code. Contributions to the sprite builder system might include:
|
||||||
|
|
||||||
|
- Implementing new features for sprite editing, such as palette management, animation preview, or tileset manipulation.
|
||||||
|
- Extending the sprite builder interface by writing assembly code for sprite behavior.
|
||||||
|
|
||||||
|
### 3. Emulator Subsystem
|
||||||
|
|
||||||
|
yaze includes an emulator subsystem that allows developers to test their modifications directly within the editor. The emulator can currently run certain test ROMs but lacks the ability to play any complex games with audio because of timing issues with the APU and Spc700. Contributions to the emulator subsystem might include:
|
||||||
|
|
||||||
|
- Improving the accuracy and performance of the emulator to support more games and features.
|
||||||
|
- Implementing new debugging tools, such as memory viewers, breakpoints, or trace logs.
|
||||||
|
- Extending the emulator to support additional features, such as save states, cheat codes, or multiplayer modes.
|
||||||
|
|
||||||
|
## Building the Project
|
||||||
|
|
||||||
|
For detailed instructions on building YAZE, including its dependencies and supported platforms, refer to [build-instructions.md](docs/build-instructions.md).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Clone the Repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/yourusername/yaze.git
|
||||||
|
cd yaze
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Initialize the Submodules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule update --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build the Project:
|
||||||
|
|
||||||
|
Follow the instructions in the [build-instructions.md](docs/build-instructions.md). file to configure and build the project on your target platform.
|
||||||
|
|
||||||
|
4. Run the Application:
|
||||||
|
|
||||||
|
After building, you can run the application on your chosen platform and start exploring the existing features.
|
||||||
|
|
||||||
|
## Contributing your Changes
|
||||||
|
|
||||||
|
1. Fork the Repository:
|
||||||
|
|
||||||
|
Create a fork of the project on GitHub and clone your fork to your local machine.
|
||||||
|
|
||||||
|
2. Create a Branch:
|
||||||
|
|
||||||
|
Create a new branch for your feature or bugfix.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -b feature/my-new-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Implement Your Changes:
|
||||||
|
|
||||||
|
Follow the guidelines above to implement new features, extensions, or improvements.
|
||||||
|
|
||||||
|
4. Test Your Changes:
|
||||||
|
|
||||||
|
Ensure your changes don’t introduce new bugs or regressions. Write unit tests where applicable.
|
||||||
|
|
||||||
|
5. Submit a Pull Request:
|
||||||
|
|
||||||
|
Push your changes to your fork and submit a pull request to the main repository. Provide a clear description of your changes and why they are beneficial.
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
For VSCode users, use the following CMake extensions with MinGW-w64
|
|
||||||
|
|
||||||
https://marketplace.visualstudio.com/items?itemName=twxs.cmake
|
|
||||||
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
|
|
||||||
|
|
||||||
https://www.msys2.org/
|
|
||||||
|
|
||||||
Add to environment variables `C:\msys64\mingw64\bin`
|
|
||||||
|
|
||||||
Install the following packages using `pacman -S <package-name>`
|
|
||||||
|
|
||||||
`mingw-w64-x86_64-gcc`
|
|
||||||
`mingw-w64-x86_64-gcc-libs`
|
|
||||||
`mingw-w64-x86_64-cmake`
|
|
||||||
`mingw-w64-x86_64-glew`
|
|
||||||
`mingw-w64-x86_64-lib-png`
|
|
||||||
60
docs/getting-started.md
Normal file
60
docs/getting-started.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Getting Started
|
||||||
|
|
||||||
|
This software allows you to modify "The Legend of Zelda: A Link to the Past" (US or JP) ROMs.
|
||||||
|
|
||||||
|
This editor is built to be compatible with ZScream projects and is designed to be cross platform.
|
||||||
|
|
||||||
|
Please note that this project is currently a work in progress, and some features may not be fully implemented or may be subject to change.
|
||||||
|
|
||||||
|
## General Tips
|
||||||
|
|
||||||
|
- Experiment flags determine whether certain features are enabled or not. To change your flags, go to `File` > `Options` > `Experiment Flags` or in the Settings tab.
|
||||||
|
- Backup files are enabled by default. Each save will produce a timestamped copy of your ROM before you last saved. You can disable this feature in the settings.
|
||||||
|
|
||||||
|
## Extending Functionality
|
||||||
|
|
||||||
|
In addition to the built-in features, this software provides a pure C library interface and a Python module that can be used for building extensions and custom sprites without assembly. In the editor these can be loaded under the `Extensions` menu.
|
||||||
|
|
||||||
|
This feature is still in development and is not yet fully documented.
|
||||||
|
|
||||||
|
## Supported Features
|
||||||
|
|
||||||
|
| Feature | Status | Details |
|
||||||
|
|---------|--------|-------------|
|
||||||
|
| Overworld Maps | Done | Edit and save tile32 data. |
|
||||||
|
| Overworld Map Properties | Done | Edit and save map properties. |
|
||||||
|
| Overworld Entrances | Done | Edit and save entrance data. |
|
||||||
|
| Overworld Exits | Done | Edit and save exit data. |
|
||||||
|
| Overworld Sprites | In Progress | Edit sprite positions, add and remove sprites. |
|
||||||
|
| Tile16 Editing | Todo | Edit and save tile16 data. |
|
||||||
|
| Dungeon | In Progress | View dungeon room metadata and edit room data. |
|
||||||
|
| Palette | In Progress | Edit and save palettes, palette groups. |
|
||||||
|
| Graphics Sheets | In Progress | Edit and save graphics sheets. |
|
||||||
|
| Graphics Groups | Done | Edit and save graphics groups. |
|
||||||
|
| Sprite | Todo | View-only sprite data. |
|
||||||
|
| Custom Sprites | Todo | Edit and create custom sprite data. |
|
||||||
|
| Music | Todo | Edit music data. |
|
||||||
|
| Dungeon Maps | Todo | Edit dungeon maps. |
|
||||||
|
| Scad Format | Done-ish | Open and view scad files (SCR, CGX, COL) |
|
||||||
|
| Hex Editing | Done | View and edit ROM data in hex. |
|
||||||
|
| Asar Patching | In Progress | Apply Asar patches to your ROM or Project. |
|
||||||
|
|
||||||
|
## Command Line Interface
|
||||||
|
|
||||||
|
Included with the editor is a command line interface (CLI) that allows you to perform various operations on your ROMs from the command line. This aims to reduce the need for multiple tools in zelda3 hacking like Zcompress, LunarExpand, LunarAddress, Asar, and others.
|
||||||
|
|
||||||
|
| Command | Arg | Params | Status |
|
||||||
|
|---------|-----|--------|--------|
|
||||||
|
| Apply BPS Patch | -a | rom_file bps_file | In progress |
|
||||||
|
| Create BPS Patch | -c | bps_file src_file modified_file | Not started |
|
||||||
|
| Asar Patch | -asar | asm_file rom_file | In progress |
|
||||||
|
| Open ROM | -o | rom_file | Complete |
|
||||||
|
| Backup ROM | -b | rom_file [new_file] | In progress |
|
||||||
|
| Expand ROM | -x | rom_file file_size | Not started |
|
||||||
|
| Transfer Tile16 | -t | src_rom dest_rom tile32_id_list(csv) | Complete |
|
||||||
|
| Export Graphics | -e | rom_file bin_file | In progress |
|
||||||
|
| Import Graphics | -i | bin_file rom_file | Not started |
|
||||||
|
| SNES to PC Address | -s | address | Complete |
|
||||||
|
| PC to SNES Address | -p | address | Complete |
|
||||||
|
|
||||||
|
|
||||||
85
docs/infrastructure.md
Normal file
85
docs/infrastructure.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Infrastructure Overview
|
||||||
|
|
||||||
|
For developers to reference.
|
||||||
|
|
||||||
|
The goal of yaze is to build a cross platform editor for the Legend of Zelda: A Link to the Past. The project is built using C++20, SDL2, and ImGui. The project is built using CMake and is designed to be modular and extensible. The project is designed to be built on Windows, macOS, iOS, and Linux.
|
||||||
|
|
||||||
|
## Targets
|
||||||
|
|
||||||
|
- **yaze**: Desktop application for Windows/macOS/Linux
|
||||||
|
- **z3ed**: Command Line Interface
|
||||||
|
- **yaze_c**: C Library
|
||||||
|
- **yaze_py**: Python Module
|
||||||
|
- **yaze_test**: Unit test executable
|
||||||
|
- **yaze_ios**: iOS application
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
- **assets**: Hosts assets like fonts, icons, assembly source, etc.
|
||||||
|
- **cmake**: Contains CMake configurations.
|
||||||
|
- **docs**: Contains documentation for users and developers.
|
||||||
|
- **incl**: Contains the public headers for `yaze_c`
|
||||||
|
- **src**: Contains source files.
|
||||||
|
- **app**: Contains the GUI editor `yaze`
|
||||||
|
- **app/emu**: Contains a standalone Snes emulator application `yaze_emu`
|
||||||
|
- **cli**: Contains the command line interface `z3ed`
|
||||||
|
- **cli/python**: Contains the Python module `yaze_py`
|
||||||
|
- **ios**: Contains the iOS application `yaze_ios`
|
||||||
|
- **lib**: Contains the dependencies as git submodules
|
||||||
|
- **test**: Contains testing interface `yaze_test`
|
||||||
|
- **win32**: Contains Windows resource file and icon
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
See [build-instructions.md](docs/build-instructions.md) for more information.
|
||||||
|
|
||||||
|
- **SDL2**: Graphics library
|
||||||
|
- **ImGui**: GUI library
|
||||||
|
- **Abseil**: C++ library
|
||||||
|
- **libpng**: Image library
|
||||||
|
- **Boost**: Python library
|
||||||
|
|
||||||
|
## Flow of Control
|
||||||
|
|
||||||
|
- app/yaze.cc
|
||||||
|
- Initializes `absl::FailureSignalHandler` for stack tracing.
|
||||||
|
- Runs the `core::Controller` loop.
|
||||||
|
- app/core/controller.cc
|
||||||
|
- Initializes SDLRenderer and SDLWindow
|
||||||
|
- Initializes ImGui, fonts, themes, and clipboard.
|
||||||
|
- Handles user input from keyboard and mouse.
|
||||||
|
- Renders the output to the screen.
|
||||||
|
- Handles the teardown of SDL and ImGui resources.
|
||||||
|
- app/editor/editor_manager.cc
|
||||||
|
- Handles the main menu bar
|
||||||
|
- Handles `absl::Status` errors as popups delivered to the user.
|
||||||
|
- Dispatches messages to the various editors.
|
||||||
|
- Update all the editors in a tab view.
|
||||||
|
- app/editor/code/assembly_editor.cc
|
||||||
|
- app/editor/dungeon/dungeon_editor.cc
|
||||||
|
- app/editor/graphics/graphics_editor.cc
|
||||||
|
- app/editor/graphics/gfx_group_editor.cc
|
||||||
|
- app/editor/graphics/palette_editor.cc
|
||||||
|
- app/editor/graphics/tile16_editor.cc
|
||||||
|
- app/editor/message/message_editor.cc
|
||||||
|
- app/editor/music/music_editor.cc
|
||||||
|
- app/editor/overworld/overworld_editor.cc
|
||||||
|
- app/editor/graphics/screen_editor.cc
|
||||||
|
- app/editor/sprite/sprite_editor.cc
|
||||||
|
- app/editor/system/settings_editor.cc
|
||||||
|
|
||||||
|
## Rom
|
||||||
|
|
||||||
|
- app/rom.cc
|
||||||
|
- app/rom.h
|
||||||
|
|
||||||
|
The Rom class provides methods to manipulate and access data from a ROM.
|
||||||
|
|
||||||
|
Currently implemented as a singleton with SharedRom which is not great but has helped with development velocity. Potential room for improvement is to refactor the editors to take the ROM as a parameter.
|
||||||
|
|
||||||
|
## Bitmap
|
||||||
|
|
||||||
|
- app/gfx/bitmap.cc
|
||||||
|
- app/gfx/bitmap.h
|
||||||
|
|
||||||
|
This class is responsible for creating, managing, and manipulating bitmap data, which can be displayed on the screen using SDL2 Textures and the ImGui draw list. It also provides functions for exporting these bitmaps to the clipboard in PNG format using libpng.
|
||||||
Binary file not shown.
@@ -1,40 +0,0 @@
|
|||||||
\documentclass[12pt, oneside]{report}
|
|
||||||
\title{Yet Another Zelda3 Editor}
|
|
||||||
\author{Justin Scofield\thanks{Special thanks to JaredBrian, Zarby89}}
|
|
||||||
\date{June 2022 - October 2022}
|
|
||||||
\pagestyle{headings}
|
|
||||||
|
|
||||||
\begin{document}
|
|
||||||
\maketitle
|
|
||||||
|
|
||||||
\tableofcontents
|
|
||||||
|
|
||||||
\chapter{Introduction}
|
|
||||||
|
|
||||||
{\bf Yet Another Zelda3 Editor} is a multi-purpose editor for the retro video game title {\it {"The Legend of Zelda: A Link to the Past"}} for the Super Nintendo Entertainment System. The editor only supports the US version.
|
|
||||||
|
|
||||||
\section{Getting Started}
|
|
||||||
\section{Loading from ROM}
|
|
||||||
\section{Saving to ROM}
|
|
||||||
|
|
||||||
\chapter{Overworld}
|
|
||||||
|
|
||||||
The editor provides an interface for the user to make various changes to the overworld maps. These changes include the manpulation of the maps tiles, palettes, entrances, exits, sprites, area music, and other properties. Here we will explain the basics of the tile system.
|
|
||||||
|
|
||||||
\section{Tile System}
|
|
||||||
\section{Map Toolset}
|
|
||||||
\section{Map Canvas}
|
|
||||||
|
|
||||||
\chapter{Dungeons}
|
|
||||||
|
|
||||||
\chapter{Palettes}
|
|
||||||
|
|
||||||
\chapter{Sprites}
|
|
||||||
|
|
||||||
\chapter{Screens}
|
|
||||||
\section{Inventory}
|
|
||||||
\section{Heads-up Display}
|
|
||||||
|
|
||||||
\chapter{Modules}
|
|
||||||
|
|
||||||
\end{document}
|
|
||||||
63
docs/yaze.org
Normal file
63
docs/yaze.org
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#+TITLE: yaze todo
|
||||||
|
#+SUBTITLE: yet another zelda3 editor todo list
|
||||||
|
#+AUTHOR: @scawful
|
||||||
|
#+TODO: TODO ACTIVE FEEDBACK VERIFY | DONE
|
||||||
|
|
||||||
|
* Daily Log
|
||||||
|
|
||||||
|
<2024-11-14 Thu>
|
||||||
|
Been making lots of adjustments and cleaning up old code. Primarily improving the dungeon map editor and supporting bin graphics for my Oracle of Secrets dungeon maps. Additionally, working to support saving for resources like graphics sheets and expanded the project file system.
|
||||||
|
|
||||||
|
<2024-09-07 Sat>
|
||||||
|
Various header cleanup using the LSP in emacs to detect unused includes.
|
||||||
|
Making adjustments to font loading so the editor can be opened from terminal/emacs.
|
||||||
|
Currently the font files and the zeml files require the binary to be relative to `assets/layouts` and `assets/fonts`
|
||||||
|
I've set it up so that the macOS app bundles the resources into the `yaze.app` so that the binary can be run from anywhere. This will need to be adjusted for other platforms.
|
||||||
|
|
||||||
|
<2024-09-02 Mon>
|
||||||
|
Extracted the DisplayPalette function out of the PaletteEditor and into its own standalone function.
|
||||||
|
|
||||||
|
<2024-09-01 Sun>
|
||||||
|
Started learning spacemacs and org-mode.
|
||||||
|
|
||||||
|
* Editors
|
||||||
|
** Overworld
|
||||||
|
*** TODO ZSCustomOverworld implementation.
|
||||||
|
**** DONE Custom Overworld Map Settings Inputs
|
||||||
|
**** DONE Load ZSCOW data from ROM in OverworldMap
|
||||||
|
**** TODO Add Main Palette support
|
||||||
|
**** TODO Add Custom Area BG Color support
|
||||||
|
|
||||||
|
*** TODO Fix sprite icon draw positions
|
||||||
|
*** TODO Fix exit icon draw positions
|
||||||
|
|
||||||
|
** Dungeon
|
||||||
|
*** TODO Draw dungeon objects
|
||||||
|
|
||||||
|
** Graphics
|
||||||
|
*** TODO Tile16 Editor
|
||||||
|
- [ ] Draw tile8 to tile16 quadrant.
|
||||||
|
|
||||||
|
*** TODO Fix graphics sheet pencil drawing
|
||||||
|
|
||||||
|
** Message
|
||||||
|
*** TODO Fix Message Parsing
|
||||||
|
|
||||||
|
** Palette
|
||||||
|
*** TODO Persist color changes for saving to ROM.
|
||||||
|
|
||||||
|
** Screens
|
||||||
|
*** ACTIVE Dungeon Maps
|
||||||
|
*** ACTIVE Inventory Menu
|
||||||
|
*** TODO Overworld Map
|
||||||
|
*** TODO Title Screen
|
||||||
|
*** TODO Naming Screen
|
||||||
|
|
||||||
|
* Infrastructure
|
||||||
|
** File Handling
|
||||||
|
*** TODO Update recent files manager to bundle the recent files list with the application
|
||||||
|
*** DONE Create a util for handling file operations from the bundled resources.
|
||||||
|
** Font Loading
|
||||||
|
*** TODO Make font sizes variables so they can be reloaded by the user.
|
||||||
|
** ZEML
|
||||||
|
*** DONE Package layout files with the executable to avoid relative file lookup
|
||||||
67
incl/dungeon.h
Normal file
67
incl/dungeon.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#ifndef YAZE_BASE_DUNGEON_H_
|
||||||
|
#define YAZE_BASE_DUNGEON_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct z3_object_door {
|
||||||
|
short id;
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t size;
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t layer;
|
||||||
|
} z3_object_door;
|
||||||
|
|
||||||
|
typedef struct z3_dungeon_destination {
|
||||||
|
uint8_t index;
|
||||||
|
uint8_t target;
|
||||||
|
uint8_t target_layer;
|
||||||
|
} z3_dungeon_destination;
|
||||||
|
|
||||||
|
typedef struct z3_staircase {
|
||||||
|
uint8_t id;
|
||||||
|
uint8_t room;
|
||||||
|
const char *label;
|
||||||
|
} z3_staircase;
|
||||||
|
|
||||||
|
typedef struct z3_chest {
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t item;
|
||||||
|
bool picker;
|
||||||
|
bool big_chest;
|
||||||
|
} z3_chest;
|
||||||
|
|
||||||
|
typedef struct z3_chest_data {
|
||||||
|
uint8_t id;
|
||||||
|
bool size;
|
||||||
|
} z3_chest_data;
|
||||||
|
|
||||||
|
typedef enum z3_dungeon_background2 {
|
||||||
|
Off,
|
||||||
|
Parallax,
|
||||||
|
Dark,
|
||||||
|
OnTop,
|
||||||
|
Translucent,
|
||||||
|
Addition,
|
||||||
|
Normal,
|
||||||
|
Transparent,
|
||||||
|
DarkRoom
|
||||||
|
} z3_dungeon_background2;
|
||||||
|
|
||||||
|
typedef struct z3_dungeon_room {
|
||||||
|
z3_dungeon_background2 bg2;
|
||||||
|
z3_dungeon_destination pits;
|
||||||
|
z3_dungeon_destination stairs[4];
|
||||||
|
} z3_dungeon_room;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // YAZE_BASE_DUNGEON_H_
|
||||||
47
incl/overworld.h
Normal file
47
incl/overworld.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef YAZE_OVERWORLD_H
|
||||||
|
#define YAZE_OVERWORLD_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "sprite.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Primitive of an overworld map.
|
||||||
|
*/
|
||||||
|
typedef struct z3_overworld_map {
|
||||||
|
uint8_t id; /**< ID of the overworld map. */
|
||||||
|
uint8_t parent_id;
|
||||||
|
uint8_t quadrant_id;
|
||||||
|
uint8_t world_id;
|
||||||
|
uint8_t game_state;
|
||||||
|
uint8_t area_graphics;
|
||||||
|
uint8_t area_palette;
|
||||||
|
|
||||||
|
uint8_t sprite_graphics[3];
|
||||||
|
uint8_t sprite_palette[3];
|
||||||
|
uint8_t area_music[4];
|
||||||
|
uint8_t static_graphics[16];
|
||||||
|
} z3_overworld_map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Primitive of the overworld.
|
||||||
|
*/
|
||||||
|
typedef struct z3_overworld {
|
||||||
|
void *impl; // yaze::Overworld*
|
||||||
|
|
||||||
|
uint8_t *tile32_data; /**< Pointer to the 32x32 tile data. */
|
||||||
|
uint8_t *tile16_data; /**< Pointer to the 16x16 tile data. */
|
||||||
|
|
||||||
|
z3_sprite **sprites; /**< Pointer to the sprites per map. */
|
||||||
|
z3_overworld_map **maps; /**< Pointer to the overworld maps. */
|
||||||
|
} z3_overworld;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // YAZE_OVERWORLD_H
|
||||||
32
incl/snes_color.h
Normal file
32
incl/snes_color.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef YAZE_BASE_SNES_COLOR_H_
|
||||||
|
#define YAZE_BASE_SNES_COLOR_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Primitive of 16-bit RGB SNES color.
|
||||||
|
*/
|
||||||
|
typedef struct snes_color {
|
||||||
|
uint16_t red; /**< Red component of the color. */
|
||||||
|
uint16_t blue; /**< Blue component of the color. */
|
||||||
|
uint16_t green; /**< Green component of the color. */
|
||||||
|
} snes_color;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Primitive of a SNES color palette.
|
||||||
|
*/
|
||||||
|
typedef struct snes_palette {
|
||||||
|
unsigned int id; /**< ID of the palette. */
|
||||||
|
unsigned int size; /**< Size of the palette. */
|
||||||
|
snes_color* colors; /**< Pointer to the colors in the palette. */
|
||||||
|
} snes_palette;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // YAZE_BASE_SNES_COLOR_H_
|
||||||
40
incl/snes_tile.h
Normal file
40
incl/snes_tile.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef YAZE_INCL_SNES_TILE_H
|
||||||
|
#define YAZE_INCL_SNES_TILE_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct snes_tile8 {
|
||||||
|
uint32_t id;
|
||||||
|
uint32_t palette_id;
|
||||||
|
uint8_t data[64];
|
||||||
|
} snes_tile8;
|
||||||
|
|
||||||
|
typedef struct snes_tile_info {
|
||||||
|
uint16_t id;
|
||||||
|
uint8_t palette;
|
||||||
|
bool priority;
|
||||||
|
bool vertical_mirror;
|
||||||
|
bool horizontal_mirror;
|
||||||
|
} snes_tile_info;
|
||||||
|
|
||||||
|
typedef struct snes_tile16 {
|
||||||
|
snes_tile_info tiles[4];
|
||||||
|
} snes_tile16;
|
||||||
|
|
||||||
|
typedef struct snes_tile32 {
|
||||||
|
uint16_t t0;
|
||||||
|
uint16_t t1;
|
||||||
|
uint16_t t2;
|
||||||
|
uint16_t t3;
|
||||||
|
} snes_tile32;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
23
incl/sprite.h
Normal file
23
incl/sprite.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef YAZE_BASE_SPRITE_H_
|
||||||
|
#define YAZE_BASE_SPRITE_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Primitive of a sprite.
|
||||||
|
*/
|
||||||
|
typedef struct z3_sprite {
|
||||||
|
const char* name; /**< Name of the sprite. */
|
||||||
|
uint8_t id; /**< ID of the sprite. */
|
||||||
|
uint8_t subtype; /**< Subtype of the sprite. */
|
||||||
|
} z3_sprite;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // YAZE_BASE_SPRITE_H_
|
||||||
159
incl/yaze.h
Normal file
159
incl/yaze.h
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
#ifndef YAZE_H
|
||||||
|
#define YAZE_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "dungeon.h"
|
||||||
|
#include "overworld.h"
|
||||||
|
#include "snes_color.h"
|
||||||
|
#include "sprite.h"
|
||||||
|
|
||||||
|
typedef struct z3_rom z3_rom;
|
||||||
|
|
||||||
|
typedef struct yaze_project yaze_project;
|
||||||
|
typedef struct yaze_command_registry yaze_command_registry;
|
||||||
|
typedef struct yaze_event_dispatcher yaze_event_dispatcher;
|
||||||
|
|
||||||
|
typedef struct yaze_editor_context {
|
||||||
|
z3_rom* rom;
|
||||||
|
yaze_project* project;
|
||||||
|
|
||||||
|
yaze_command_registry* command_registry;
|
||||||
|
yaze_event_dispatcher* event_dispatcher;
|
||||||
|
} yaze_editor_context;
|
||||||
|
|
||||||
|
void yaze_check_version(const char* version);
|
||||||
|
int yaze_init(yaze_editor_context*);
|
||||||
|
void yaze_cleanup(yaze_editor_context*);
|
||||||
|
|
||||||
|
struct yaze_project {
|
||||||
|
const char* name;
|
||||||
|
const char* filepath;
|
||||||
|
const char* rom_filename;
|
||||||
|
const char* code_folder;
|
||||||
|
const char* labels_filename;
|
||||||
|
};
|
||||||
|
|
||||||
|
yaze_project yaze_load_project(const char* filename);
|
||||||
|
|
||||||
|
struct z3_rom {
|
||||||
|
const char* filename;
|
||||||
|
const uint8_t* data;
|
||||||
|
size_t size;
|
||||||
|
void* impl; // yaze::Rom*
|
||||||
|
};
|
||||||
|
|
||||||
|
z3_rom* yaze_load_rom(const char* filename);
|
||||||
|
void yaze_unload_rom(z3_rom* rom);
|
||||||
|
|
||||||
|
typedef struct yaze_bitmap {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
uint8_t bpp;
|
||||||
|
uint8_t* data;
|
||||||
|
} yaze_bitmap;
|
||||||
|
|
||||||
|
yaze_bitmap yaze_load_bitmap(const char* filename);
|
||||||
|
|
||||||
|
snes_color yaze_get_color_from_paletteset(const z3_rom* rom, int palette_set,
|
||||||
|
int palette, int color);
|
||||||
|
|
||||||
|
z3_overworld* yaze_load_overworld(const z3_rom* rom);
|
||||||
|
|
||||||
|
z3_dungeon_room* yaze_load_all_rooms(const z3_rom* rom);
|
||||||
|
|
||||||
|
struct yaze_command_registry {
|
||||||
|
void (*register_command)(const char* name, void (*command)(void));
|
||||||
|
};
|
||||||
|
|
||||||
|
struct yaze_event_dispatcher {
|
||||||
|
void (*register_event_hook)(void (*event_hook)(void));
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (*yaze_initialize_func)(yaze_editor_context* context);
|
||||||
|
typedef void (*yaze_cleanup_func)(void);
|
||||||
|
typedef void (*yaze_extend_ui_func)(yaze_editor_context* context);
|
||||||
|
typedef void (*yaze_manipulate_rom_func)(z3_rom* rom);
|
||||||
|
typedef void (*yaze_command_func)(void);
|
||||||
|
typedef void (*yaze_event_hook_func)(void);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
YAZE_EVENT_ROM_LOADED,
|
||||||
|
YAZE_EVENT_ROM_SAVED,
|
||||||
|
YAZE_EVENT_SPRITE_MODIFIED,
|
||||||
|
YAZE_EVENT_PALETTE_CHANGED,
|
||||||
|
} yaze_event_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extension interface for Yaze.
|
||||||
|
*
|
||||||
|
* @details Yaze extensions can be written in C or Python.
|
||||||
|
*/
|
||||||
|
typedef struct yaze_extension {
|
||||||
|
const char* name;
|
||||||
|
const char* version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function to initialize the extension.
|
||||||
|
*
|
||||||
|
* @details This function is called when the extension is loaded. It can be
|
||||||
|
* used to set up any resources or state needed by the extension.
|
||||||
|
*/
|
||||||
|
yaze_initialize_func initialize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function to clean up the extension.
|
||||||
|
*
|
||||||
|
* @details This function is called when the extension is unloaded. It can be
|
||||||
|
* used to clean up any resources or state used by the extension.
|
||||||
|
*/
|
||||||
|
yaze_cleanup_func cleanup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function to manipulate the ROM.
|
||||||
|
*
|
||||||
|
* @param rom The ROM to manipulate.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
yaze_manipulate_rom_func manipulate_rom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function to extend the UI.
|
||||||
|
*
|
||||||
|
* @param context The editor context.
|
||||||
|
*
|
||||||
|
* @details This function is called when the extension is loaded. It can be
|
||||||
|
* used to add custom UI elements to the editor. The context parameter
|
||||||
|
* provides access to the project, command registry, event dispatcher, and
|
||||||
|
* ImGui context.
|
||||||
|
*/
|
||||||
|
yaze_extend_ui_func extend_ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register commands in the yaze_command_registry.
|
||||||
|
*/
|
||||||
|
yaze_command_func register_commands;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register custom tools in the yaze_command_registry.
|
||||||
|
*/
|
||||||
|
yaze_command_func register_custom_tools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register event hooks in the yaze_event_dispatcher.
|
||||||
|
*/
|
||||||
|
void (*register_event_hooks)(yaze_event_type event,
|
||||||
|
yaze_event_hook_func hook);
|
||||||
|
|
||||||
|
} yaze_extension;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // YAZE_H
|
||||||
@@ -1,115 +1,65 @@
|
|||||||
# yaze source files -----------------------------------------------------------
|
|
||||||
set(
|
set(
|
||||||
YAZE_APP_CORE_SRC
|
YAZE_APP_EMU_SRC
|
||||||
app/core/common.cc
|
app/emu/audio/apu.cc
|
||||||
app/core/controller.cc
|
app/emu/audio/spc700.cc
|
||||||
|
app/emu/audio/dsp.cc
|
||||||
|
app/emu/audio/internal/addressing.cc
|
||||||
|
app/emu/audio/internal/instructions.cc
|
||||||
|
app/emu/cpu/internal/addressing.cc
|
||||||
|
app/emu/cpu/internal/instructions.cc
|
||||||
|
app/emu/cpu/cpu.cc
|
||||||
|
app/emu/video/ppu.cc
|
||||||
|
app/emu/memory/dma.cc
|
||||||
|
app/emu/memory/memory.cc
|
||||||
|
app/emu/snes.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
set(
|
set(YAZE_RESOURCE_FILES
|
||||||
YAZE_APP_EDITOR_SRC
|
${CMAKE_SOURCE_DIR}/assets/layouts/overworld.zeml
|
||||||
app/editor/assembly_editor.cc
|
${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf
|
||||||
app/editor/dungeon_editor.cc
|
${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf
|
||||||
app/editor/master_editor.cc
|
${CMAKE_SOURCE_DIR}/assets/font/Cousine-Regular.ttf
|
||||||
app/editor/music_editor.cc
|
${CMAKE_SOURCE_DIR}/assets/font/DroidSans.ttf
|
||||||
app/editor/overworld_editor.cc
|
${CMAKE_SOURCE_DIR}/assets/font/NotoSansJP.ttf
|
||||||
app/editor/palette_editor.cc
|
${CMAKE_SOURCE_DIR}/assets/font/IBMPlexSansJP-Bold.ttf
|
||||||
app/editor/screen_editor.cc
|
${CMAKE_SOURCE_DIR}/assets/font/MaterialIcons-Regular.ttf
|
||||||
)
|
)
|
||||||
|
|
||||||
set(
|
foreach (FILE ${YAZE_RESOURCE_FILES})
|
||||||
YAZE_APP_GFX_SRC
|
file(RELATIVE_PATH NEW_FILE "${CMAKE_SOURCE_DIR}/assets" ${FILE})
|
||||||
app/gfx/bitmap.cc
|
get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY)
|
||||||
app/gfx/snes_palette.cc
|
set_source_files_properties(${FILE}
|
||||||
app/gfx/snes_tile.cc
|
PROPERTIES
|
||||||
|
MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}"
|
||||||
)
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
set(
|
if (YAZE_BUILD_APP)
|
||||||
YAZE_APP_ZELDA3_SRC
|
include(app/app.cmake)
|
||||||
app/zelda3/inventory.cc
|
|
||||||
app/zelda3/overworld_map.cc
|
|
||||||
app/zelda3/overworld.cc
|
|
||||||
app/zelda3/title_screen.cc
|
|
||||||
app/zelda3/sprite.cc
|
|
||||||
)
|
|
||||||
|
|
||||||
set(
|
|
||||||
YAZE_GUI_SRC
|
|
||||||
gui/canvas.cc
|
|
||||||
gui/input.cc
|
|
||||||
gui/style.cc
|
|
||||||
gui/widgets.cc
|
|
||||||
gui/color.cc
|
|
||||||
)
|
|
||||||
|
|
||||||
# executable creation ---------------------------------------------------------
|
|
||||||
|
|
||||||
add_executable(
|
|
||||||
yaze
|
|
||||||
app/yaze.cc
|
|
||||||
app/rom.cc
|
|
||||||
${YAZE_APP_CORE_SRC}
|
|
||||||
${YAZE_APP_EDITOR_SRC}
|
|
||||||
${YAZE_APP_GFX_SRC}
|
|
||||||
${YAZE_APP_ZELDA3_SRC}
|
|
||||||
${YAZE_GUI_SRC}
|
|
||||||
${ASAR_STATIC_SRC}
|
|
||||||
${SNES_SPC_SOURCES}
|
|
||||||
${IMGUI_SRC}
|
|
||||||
)
|
|
||||||
|
|
||||||
# including libraries ---------------------------------------------------------
|
|
||||||
|
|
||||||
target_include_directories(
|
|
||||||
yaze PUBLIC
|
|
||||||
lib/
|
|
||||||
app/
|
|
||||||
${CMAKE_SOURCE_DIR}/src/
|
|
||||||
${PNG_INCLUDE_DIRS}
|
|
||||||
${SDL2_INCLUDE_DIR}
|
|
||||||
lib/SDL_mixer/include/
|
|
||||||
${GLEW_INCLUDE_DIRS}
|
|
||||||
lib/asar/src/asar/
|
|
||||||
lib/snes_spc/snes_spc/
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SDL_TARGETS SDL2::SDL2)
|
|
||||||
|
|
||||||
if(WIN32 OR MINGW)
|
|
||||||
list(PREPEND SDL_TARGETS SDL2::SDL2main)
|
|
||||||
add_definitions(-DSDL_MAIN_HANDLED)
|
|
||||||
endif()
|
endif()
|
||||||
|
if (YAZE_BUILD_EMU)
|
||||||
# linking libraries -----------------------------------------------------------
|
include(app/emu/emu.cmake)
|
||||||
|
endif()
|
||||||
target_link_libraries(
|
if (YAZE_BUILD_Z3ED)
|
||||||
yaze PUBLIC
|
include(cli/z3ed.cmake)
|
||||||
${ABSL_TARGETS}
|
endif()
|
||||||
${SDL_TARGETS}
|
if (YAZE_BUILD_PYTHON)
|
||||||
${SDLMIXER_LIBRARY}
|
include(cli/python/yaze_py.cmake)
|
||||||
SDL2_mixer
|
endif()
|
||||||
${PNG_LIBRARIES}
|
if (YAZE_BUILD_TESTS)
|
||||||
${GLEW_LIBRARIES}
|
include(test/CMakeLists.txt)
|
||||||
${OPENGL_LIBRARIES}
|
|
||||||
${CMAKE_DL_LIBS}
|
|
||||||
asar-static
|
|
||||||
snes_spc
|
|
||||||
ImGui
|
|
||||||
)
|
|
||||||
|
|
||||||
if (UNIX)
|
|
||||||
target_compile_definitions(yaze PRIVATE "linux")
|
|
||||||
target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MACOS)
|
if(MACOS)
|
||||||
set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/yaze.ico)
|
set(MACOSX_BUNDLE_ICON_FILE ${CMAKE_SOURCE_DIR}/win32/yaze.ico)
|
||||||
set_target_properties(yaze
|
set_target_properties(yaze
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
BUNDLE True
|
BUNDLE True
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||||
# MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in
|
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in
|
||||||
|
RESOURCE ${YAZE_RESOURCE_FILES}
|
||||||
)
|
)
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
set_target_properties(yaze
|
set_target_properties(yaze
|
||||||
@@ -119,65 +69,71 @@ elseif(UNIX)
|
|||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||||
)
|
)
|
||||||
|
target_compile_definitions(yaze PRIVATE "linux")
|
||||||
|
target_compile_definitions(yaze PRIVATE "stricmp=strcasecmp")
|
||||||
else()
|
else()
|
||||||
set_target_properties(yaze
|
set_target_properties(yaze
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||||
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
|
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/win32/yaze.res"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# add_subdirectory(app/delta)
|
# Yaze C API
|
||||||
|
if (YAZE_BUILD_LIB)
|
||||||
|
add_library(
|
||||||
|
yaze_c SHARED
|
||||||
|
./yaze.cc
|
||||||
|
app/rom.cc
|
||||||
|
${YAZE_APP_EMU_SRC}
|
||||||
|
${YAZE_APP_CORE_SRC}
|
||||||
|
${YAZE_APP_EDITOR_SRC}
|
||||||
|
${YAZE_APP_GFX_SRC}
|
||||||
|
${YAZE_APP_ZELDA3_SRC}
|
||||||
|
${YAZE_GUI_SRC}
|
||||||
|
${IMGUI_SRC}
|
||||||
|
${IMGUI_TEST_ENGINE_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
# add_executable(
|
target_include_directories(
|
||||||
# yaze_delta
|
yaze_c PUBLIC
|
||||||
# app/delta/delta.cc
|
lib/
|
||||||
# app/delta/viewer.cc
|
app/
|
||||||
# app/delta/service.cc
|
${CMAKE_SOURCE_DIR}/incl/
|
||||||
# app/delta/client.cc
|
${CMAKE_SOURCE_DIR}/src/
|
||||||
# app/rom.cc
|
${PNG_INCLUDE_DIRS}
|
||||||
# ${YAZE_APP_ASM_SRC}
|
${SDL2_INCLUDE_DIR}
|
||||||
# ${YAZE_APP_CORE_SRC}
|
${PROJECT_BINARY_DIR}
|
||||||
# ${YAZE_APP_EDITOR_SRC}
|
)
|
||||||
# ${YAZE_APP_GFX_SRC}
|
|
||||||
# ${YAZE_APP_ZELDA3_SRC}
|
|
||||||
# ${YAZE_GUI_SRC}
|
|
||||||
# ${IMGUI_SRC}
|
|
||||||
# ${ASAR_STATIC_SRC}
|
|
||||||
# )
|
|
||||||
|
|
||||||
# target_include_directories(
|
target_link_libraries(
|
||||||
# yaze_delta PUBLIC
|
yaze_c PRIVATE
|
||||||
# lib/
|
${ABSL_TARGETS}
|
||||||
# app/
|
${SDL_TARGETS}
|
||||||
# lib/asar/src/
|
${PNG_LIBRARIES}
|
||||||
# ${ASAR_INCLUDE_DIR}
|
${CMAKE_DL_LIBS}
|
||||||
# ${CMAKE_SOURCE_DIR}/src/
|
ImGuiTestEngine
|
||||||
# ${PNG_INCLUDE_DIRS}
|
ImGui
|
||||||
# ${SDL2_INCLUDE_DIR}
|
)
|
||||||
# ${GLEW_INCLUDE_DIRS}
|
|
||||||
# )
|
|
||||||
|
|
||||||
# target_link_libraries(
|
if (YAZE_INSTALL_LIB)
|
||||||
# yaze_delta PUBLIC
|
install(TARGETS yaze_c
|
||||||
# ${ABSL_TARGETS}
|
RUNTIME DESTINATION bin
|
||||||
# ${SDL_TARGETS}
|
LIBRARY DESTINATION lib
|
||||||
# ${PNG_LIBRARIES}
|
ARCHIVE DESTINATION lib/static)
|
||||||
# ${GLEW_LIBRARIES}
|
|
||||||
# ${OPENGL_LIBRARIES}
|
|
||||||
# ${CMAKE_DL_LIBS}
|
|
||||||
# delta-service
|
|
||||||
# asar-static
|
|
||||||
# ImGui
|
|
||||||
# )
|
|
||||||
# target_compile_definitions(yaze_delta PRIVATE "linux")
|
|
||||||
# target_compile_definitions(yaze_delta PRIVATE "stricmp=strcasecmp")
|
|
||||||
|
|
||||||
# set (source "${CMAKE_SOURCE_DIR}/assets")
|
install(
|
||||||
# set (destination "${CMAKE_CURRENT_BINARY_DIR}/assets")
|
FILES
|
||||||
# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
yaze.h
|
||||||
# COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination}
|
incl/sprite.h
|
||||||
# DEPENDS ${destination}
|
incl/snes_tile.h
|
||||||
# COMMENT "symbolic link resources folder from ${source} => ${destination}")
|
incl/snes_color.h
|
||||||
|
incl/overworld.h
|
||||||
|
incl/dungeon.h
|
||||||
|
DESTINATION
|
||||||
|
include
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|||||||
65
src/app/app.cmake
Normal file
65
src/app/app.cmake
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
include(app/core/core.cmake)
|
||||||
|
include(app/editor/editor.cmake)
|
||||||
|
include(app/gfx/gfx.cmake)
|
||||||
|
include(app/gui/gui.cmake)
|
||||||
|
include(app/zelda3/zelda3.cmake)
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
add_executable(
|
||||||
|
yaze
|
||||||
|
MACOSX_BUNDLE
|
||||||
|
app/main.cc
|
||||||
|
app/rom.cc
|
||||||
|
${YAZE_APP_EMU_SRC}
|
||||||
|
${YAZE_APP_CORE_SRC}
|
||||||
|
${YAZE_APP_EDITOR_SRC}
|
||||||
|
${YAZE_APP_GFX_SRC}
|
||||||
|
${YAZE_APP_ZELDA3_SRC}
|
||||||
|
${YAZE_GUI_SRC}
|
||||||
|
${IMGUI_SRC}
|
||||||
|
# Bundled Resources
|
||||||
|
${YAZE_RESOURCE_FILES}
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
add_executable(
|
||||||
|
yaze
|
||||||
|
app/main.cc
|
||||||
|
app/rom.cc
|
||||||
|
${YAZE_APP_EMU_SRC}
|
||||||
|
${YAZE_APP_CORE_SRC}
|
||||||
|
${YAZE_APP_EDITOR_SRC}
|
||||||
|
${YAZE_APP_GFX_SRC}
|
||||||
|
${YAZE_APP_ZELDA3_SRC}
|
||||||
|
${YAZE_GUI_SRC}
|
||||||
|
${IMGUI_SRC}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
yaze PUBLIC
|
||||||
|
lib/
|
||||||
|
app/
|
||||||
|
${ASAR_INCLUDE_DIR}
|
||||||
|
${CMAKE_SOURCE_DIR}/incl/
|
||||||
|
${CMAKE_SOURCE_DIR}/src/
|
||||||
|
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
|
||||||
|
${PNG_INCLUDE_DIRS}
|
||||||
|
${SDL2_INCLUDE_DIR}
|
||||||
|
${PROJECT_BINARY_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
yaze PUBLIC
|
||||||
|
asar-static
|
||||||
|
${ABSL_TARGETS}
|
||||||
|
${SDL_TARGETS}
|
||||||
|
${PNG_LIBRARIES}
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
|
ImGui
|
||||||
|
ImGuiTestEngine
|
||||||
|
)
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
|
||||||
|
endif()
|
||||||
|
|
||||||
@@ -1,51 +1,164 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
unsigned int SnesToPc(unsigned int addr) {
|
namespace {
|
||||||
if (addr >= 0x808000) {
|
|
||||||
addr -= 0x808000;
|
void encode(uint64_t data, std::vector<uint8_t> &output) {
|
||||||
|
while (true) {
|
||||||
|
uint8_t x = data & 0x7f;
|
||||||
|
data >>= 7;
|
||||||
|
if (data == 0) {
|
||||||
|
output.push_back(0x80 | x);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
output.push_back(x);
|
||||||
|
data--;
|
||||||
}
|
}
|
||||||
unsigned int temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
|
|
||||||
return (temp + 0x0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3) {
|
uint64_t decode(const std::vector<uint8_t> &input, size_t &offset) {
|
||||||
return (addr1 << 16) | (addr2 << 8) | addr3;
|
uint64_t data = 0;
|
||||||
|
uint64_t shift = 1;
|
||||||
|
while (true) {
|
||||||
|
uint8_t x = input[offset++];
|
||||||
|
data += (x & 0x7f) * shift;
|
||||||
|
if (x & 0x80) break;
|
||||||
|
shift <<= 7;
|
||||||
|
data += shift;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hextodec has been imported from SNESDisasm to parse hex numbers
|
uint32_t crc32(const std::vector<uint8_t> &data) {
|
||||||
int HexToDec(char *input, int length) {
|
uint32_t crc = ::crc32(0L, Z_NULL, 0);
|
||||||
int result = 0;
|
return ::crc32(crc, data.data(), data.size());
|
||||||
int value;
|
}
|
||||||
int ceiling = length - 1;
|
|
||||||
int power16 = 16;
|
|
||||||
|
|
||||||
int j = ceiling;
|
// "load little endian value at the given byte offset and shift to get its
|
||||||
|
// value relative to the base offset (powers of 256, essentially)"
|
||||||
|
unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) {
|
||||||
|
uint32_t v = p_arr[p_index];
|
||||||
|
v <<= (8 * p_index);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
for (; j >= 0; j--) {
|
void stle(uint8_t *const p_arr, size_t const p_index, unsigned const p_val) {
|
||||||
if (input[j] >= 'A' && input[j] <= 'F') {
|
uint8_t v = (p_val >> (8 * p_index)) & 0xff;
|
||||||
value = input[j] - 'F';
|
p_arr[p_index] = v;
|
||||||
value += 15;
|
}
|
||||||
|
|
||||||
|
void stle0(uint8_t *const p_arr, unsigned const p_val) {
|
||||||
|
stle(p_arr, 0, p_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stle1(uint8_t *const p_arr, unsigned const p_val) {
|
||||||
|
stle(p_arr, 1, p_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stle2(uint8_t *const p_arr, unsigned const p_val) {
|
||||||
|
stle(p_arr, 2, p_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stle3(uint8_t *const p_arr, unsigned const p_val) {
|
||||||
|
stle(p_arr, 3, p_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get the first byte in a little endian number
|
||||||
|
uint32_t ldle0(uint8_t const *const p_arr) { return ldle(p_arr, 0); }
|
||||||
|
|
||||||
|
// Helper function to get the second byte in a little endian number
|
||||||
|
uint32_t ldle1(uint8_t const *const p_arr) { return ldle(p_arr, 1); }
|
||||||
|
|
||||||
|
// Helper function to get the third byte in a little endian number
|
||||||
|
uint32_t ldle2(uint8_t const *const p_arr) { return ldle(p_arr, 2); }
|
||||||
|
|
||||||
|
// Helper function to get the third byte in a little endian number
|
||||||
|
uint32_t ldle3(uint8_t const *const p_arr) { return ldle(p_arr, 3); }
|
||||||
|
|
||||||
|
void HandleHexStringParams(const std::string &hex,
|
||||||
|
const HexStringParams ¶ms) {
|
||||||
|
std::string result = hex;
|
||||||
|
switch (params.prefix) {
|
||||||
|
case HexStringParams::Prefix::kDollar:
|
||||||
|
result = absl::StrCat("$", result);
|
||||||
|
break;
|
||||||
|
case HexStringParams::Prefix::kHash:
|
||||||
|
result = absl::StrCat("#", result);
|
||||||
|
break;
|
||||||
|
case HexStringParams::Prefix::k0x:
|
||||||
|
result = absl::StrCat("0x", result);
|
||||||
|
case HexStringParams::Prefix::kNone:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::string HexByte(uint8_t byte, HexStringParams params) {
|
||||||
|
std::string result;
|
||||||
|
const static std::string kLowerFormat = "%02x";
|
||||||
|
const static std::string kUpperFormat = "%02X";
|
||||||
|
if (params.uppercase) {
|
||||||
|
result = absl::StrFormat(kUpperFormat.c_str(), byte);
|
||||||
} else {
|
} else {
|
||||||
value = input[j] - '9';
|
result = absl::StrFormat(kLowerFormat.c_str(), byte);
|
||||||
value += 9;
|
}
|
||||||
|
HandleHexStringParams(result, params);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (j == ceiling) {
|
std::string HexWord(uint16_t word, HexStringParams params) {
|
||||||
result += value;
|
std::string result;
|
||||||
continue;
|
const static std::string kLowerFormat = "%04x";
|
||||||
|
const static std::string kUpperFormat = "%04X";
|
||||||
|
if (params.uppercase) {
|
||||||
|
result = absl::StrFormat(kUpperFormat.c_str(), word);
|
||||||
|
} else {
|
||||||
|
result = absl::StrFormat(kLowerFormat.c_str(), word);
|
||||||
|
}
|
||||||
|
HandleHexStringParams(result, params);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result += (value * power16);
|
std::string HexLong(uint32_t dword, HexStringParams params) {
|
||||||
power16 *= 16;
|
std::string result;
|
||||||
|
const static std::string kLowerFormat = "%06x";
|
||||||
|
const static std::string kUpperFormat = "%06X";
|
||||||
|
if (params.uppercase) {
|
||||||
|
result = absl::StrFormat(kUpperFormat.c_str(), dword);
|
||||||
|
} else {
|
||||||
|
result = absl::StrFormat(kLowerFormat.c_str(), dword);
|
||||||
|
}
|
||||||
|
HandleHexStringParams(result, params);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string HexLongLong(uint64_t qword, HexStringParams params) {
|
||||||
|
std::string result;
|
||||||
|
const static std::string kLowerFormat = "%08x";
|
||||||
|
const static std::string kUpperFormat = "%08X";
|
||||||
|
if (params.uppercase) {
|
||||||
|
result = absl::StrFormat(kUpperFormat.c_str(), qword);
|
||||||
|
} else {
|
||||||
|
result = absl::StrFormat(kLowerFormat.c_str(), qword);
|
||||||
|
}
|
||||||
|
HandleHexStringParams(result, params);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +171,193 @@ bool StringReplace(std::string &str, const std::string &from,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc) {
|
||||||
|
uint32_t ret =
|
||||||
|
(PcToSnes(addr) & 0xFF0000) | (data[addr + 1] << 8) | data[addr];
|
||||||
|
if (pc) {
|
||||||
|
return SnesToPc(ret);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
|
||||||
|
uint16_t const p_val) {
|
||||||
|
stle16b(p_arr + (p_index * 2), p_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stle16b(uint8_t *const p_arr, uint16_t const p_val) {
|
||||||
|
stle0(p_arr, p_val);
|
||||||
|
stle1(p_arr, p_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ldle16b(uint8_t const *const p_arr) {
|
||||||
|
uint16_t v = 0;
|
||||||
|
v |= (ldle0(p_arr) | ldle1(p_arr));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
|
||||||
|
return ldle16b(p_arr + (2 * p_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateBpsPatch(const std::vector<uint8_t> &source,
|
||||||
|
const std::vector<uint8_t> &target,
|
||||||
|
std::vector<uint8_t> &patch) {
|
||||||
|
patch.clear();
|
||||||
|
patch.insert(patch.end(), {'B', 'P', 'S', '1'});
|
||||||
|
|
||||||
|
encode(source.size(), patch);
|
||||||
|
encode(target.size(), patch);
|
||||||
|
encode(0, patch); // No metadata
|
||||||
|
|
||||||
|
size_t source_offset = 0;
|
||||||
|
size_t target_offset = 0;
|
||||||
|
int64_t source_rel_offset = 0;
|
||||||
|
int64_t target_rel_offset = 0;
|
||||||
|
|
||||||
|
while (target_offset < target.size()) {
|
||||||
|
if (source_offset < source.size() &&
|
||||||
|
source[source_offset] == target[target_offset]) {
|
||||||
|
size_t length = 0;
|
||||||
|
while (source_offset + length < source.size() &&
|
||||||
|
target_offset + length < target.size() &&
|
||||||
|
source[source_offset + length] == target[target_offset + length]) {
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
encode((length - 1) << 2 | 0, patch); // SourceRead
|
||||||
|
source_offset += length;
|
||||||
|
target_offset += length;
|
||||||
|
} else {
|
||||||
|
size_t length = 0;
|
||||||
|
while (
|
||||||
|
target_offset + length < target.size() &&
|
||||||
|
(source_offset + length >= source.size() ||
|
||||||
|
source[source_offset + length] != target[target_offset + length])) {
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
if (length > 0) {
|
||||||
|
encode((length - 1) << 2 | 1, patch); // TargetRead
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
patch.push_back(target[target_offset + i]);
|
||||||
|
}
|
||||||
|
target_offset += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SourceCopy
|
||||||
|
if (source_offset < source.size()) {
|
||||||
|
size_t length = 0;
|
||||||
|
int64_t offset = source_offset - source_rel_offset;
|
||||||
|
while (source_offset + length < source.size() &&
|
||||||
|
target_offset + length < target.size() &&
|
||||||
|
source[source_offset + length] == target[target_offset + length]) {
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
if (length > 0) {
|
||||||
|
encode((length - 1) << 2 | 2, patch);
|
||||||
|
encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch);
|
||||||
|
source_offset += length;
|
||||||
|
target_offset += length;
|
||||||
|
source_rel_offset = source_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetCopy
|
||||||
|
if (target_offset > 0) {
|
||||||
|
size_t length = 0;
|
||||||
|
int64_t offset = target_offset - target_rel_offset;
|
||||||
|
while (target_offset + length < target.size() &&
|
||||||
|
target[target_offset - 1] == target[target_offset + length]) {
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
if (length > 0) {
|
||||||
|
encode((length - 1) << 2 | 3, patch);
|
||||||
|
encode((offset < 0 ? 1 : 0) | (abs(offset) << 1), patch);
|
||||||
|
target_offset += length;
|
||||||
|
target_rel_offset = target_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
patch.resize(patch.size() + 12); // Make space for the checksums
|
||||||
|
uint32_t source_checksum = crc32(source);
|
||||||
|
uint32_t target_checksum = crc32(target);
|
||||||
|
uint32_t patch_checksum = crc32(patch);
|
||||||
|
|
||||||
|
memcpy(patch.data() + patch.size() - 12, &source_checksum, sizeof(uint32_t));
|
||||||
|
memcpy(patch.data() + patch.size() - 8, &target_checksum, sizeof(uint32_t));
|
||||||
|
memcpy(patch.data() + patch.size() - 4, &patch_checksum, sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyBpsPatch(const std::vector<uint8_t> &source,
|
||||||
|
const std::vector<uint8_t> &patch,
|
||||||
|
std::vector<uint8_t> &target) {
|
||||||
|
if (patch.size() < 4 || patch[0] != 'B' || patch[1] != 'P' ||
|
||||||
|
patch[2] != 'S' || patch[3] != '1') {
|
||||||
|
throw std::runtime_error("Invalid patch format");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t patch_offset = 4;
|
||||||
|
uint64_t target_size = decode(patch, patch_offset);
|
||||||
|
uint64_t metadata_size = decode(patch, patch_offset);
|
||||||
|
patch_offset += metadata_size;
|
||||||
|
|
||||||
|
target.resize(target_size);
|
||||||
|
size_t source_offset = 0;
|
||||||
|
size_t target_offset = 0;
|
||||||
|
int64_t source_rel_offset = 0;
|
||||||
|
int64_t target_rel_offset = 0;
|
||||||
|
|
||||||
|
while (patch_offset < patch.size() - 12) {
|
||||||
|
uint64_t data = decode(patch, patch_offset);
|
||||||
|
uint64_t command = data & 3;
|
||||||
|
uint64_t length = (data >> 2) + 1;
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 0: // SourceRead
|
||||||
|
while (length--) {
|
||||||
|
target[target_offset++] = source[source_offset++];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1: // TargetRead
|
||||||
|
while (length--) {
|
||||||
|
target[target_offset++] = patch[patch_offset++];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // SourceCopy
|
||||||
|
{
|
||||||
|
int64_t offsetData = decode(patch, patch_offset);
|
||||||
|
source_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1);
|
||||||
|
while (length--) {
|
||||||
|
target[target_offset++] = source[source_rel_offset++];
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case 3: // TargetCopy
|
||||||
|
{
|
||||||
|
uint64_t offsetData = decode(patch, patch_offset);
|
||||||
|
target_rel_offset += (offsetData & 1 ? -1 : +1) * (offsetData >> 1);
|
||||||
|
while (length--) {
|
||||||
|
target[target_offset++] = target[target_rel_offset++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Invalid patch command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t source_checksum;
|
||||||
|
uint32_t target_checksum;
|
||||||
|
uint32_t patch_checksum;
|
||||||
|
memcpy(&source_checksum, patch.data() + patch.size() - 12, sizeof(uint32_t));
|
||||||
|
memcpy(&target_checksum, patch.data() + patch.size() - 8, sizeof(uint32_t));
|
||||||
|
memcpy(&patch_checksum, patch.data() + patch.size() - 4, sizeof(uint32_t));
|
||||||
|
|
||||||
|
if (source_checksum != crc32(source) || target_checksum != crc32(target) ||
|
||||||
|
patch_checksum !=
|
||||||
|
crc32(std::vector<uint8_t>(patch.begin(), patch.end() - 4))) {
|
||||||
|
throw std::runtime_error("Checksum mismatch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
@@ -2,20 +2,270 @@
|
|||||||
#define YAZE_CORE_COMMON_H
|
#define YAZE_CORE_COMMON_H
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "absl/container/flat_hash_map.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
|
||||||
|
/**
|
||||||
|
* @namespace yaze::core
|
||||||
|
* @brief Core application logic and utilities.
|
||||||
|
*/
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
unsigned int SnesToPc(unsigned int addr);
|
struct HexStringParams {
|
||||||
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3);
|
enum class Prefix { kNone, kDollar, kHash, k0x } prefix = Prefix::kDollar;
|
||||||
int HexToDec(char *input, int length);
|
bool uppercase = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string HexByte(uint8_t byte, HexStringParams params = {});
|
||||||
|
std::string HexWord(uint16_t word, HexStringParams params = {});
|
||||||
|
std::string HexLong(uint32_t dword, HexStringParams params = {});
|
||||||
|
std::string HexLongLong(uint64_t qword, HexStringParams params = {});
|
||||||
|
|
||||||
bool StringReplace(std::string &str, const std::string &from,
|
bool StringReplace(std::string &str, const std::string &from,
|
||||||
const std::string &to);
|
const std::string &to);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ExperimentFlags
|
||||||
|
* @brief A class to manage experimental feature flags.
|
||||||
|
*/
|
||||||
|
class ExperimentFlags {
|
||||||
|
public:
|
||||||
|
struct Flags {
|
||||||
|
// Log instructions to the GUI debugger.
|
||||||
|
bool kLogInstructions = true;
|
||||||
|
|
||||||
|
// Flag to enable the saving of all palettes to the Rom.
|
||||||
|
bool kSaveAllPalettes = false;
|
||||||
|
|
||||||
|
// Flag to enable the saving of gfx groups to the rom.
|
||||||
|
bool kSaveGfxGroups = false;
|
||||||
|
|
||||||
|
// Flag to enable the change queue, which could have any anonymous
|
||||||
|
// save routine for the Rom. In practice, just the overworld tilemap
|
||||||
|
// and tile32 save.
|
||||||
|
bool kSaveWithChangeQueue = false;
|
||||||
|
|
||||||
|
// Attempt to run the dungeon room draw routine when opening a room.
|
||||||
|
bool kDrawDungeonRoomGraphics = true;
|
||||||
|
|
||||||
|
// Use the new platform specific file dialog wrappers.
|
||||||
|
bool kNewFileDialogWrapper = true;
|
||||||
|
|
||||||
|
// Uses texture streaming from SDL for my dynamic updates.
|
||||||
|
bool kLoadTexturesAsStreaming = true;
|
||||||
|
|
||||||
|
// Save dungeon map edits to the Rom.
|
||||||
|
bool kSaveDungeonMaps = false;
|
||||||
|
|
||||||
|
// Save graphics sheet to the Rom.
|
||||||
|
bool kSaveGraphicsSheet = false;
|
||||||
|
|
||||||
|
// Log to the console.
|
||||||
|
bool kLogToConsole = false;
|
||||||
|
|
||||||
|
// Overworld flags
|
||||||
|
struct Overworld {
|
||||||
|
// Load and render overworld sprites to the screen. Unstable.
|
||||||
|
bool kDrawOverworldSprites = false;
|
||||||
|
|
||||||
|
// Save overworld map edits to the Rom.
|
||||||
|
bool kSaveOverworldMaps = true;
|
||||||
|
|
||||||
|
// Save overworld entrances to the Rom.
|
||||||
|
bool kSaveOverworldEntrances = true;
|
||||||
|
|
||||||
|
// Save overworld exits to the Rom.
|
||||||
|
bool kSaveOverworldExits = true;
|
||||||
|
|
||||||
|
// Save overworld items to the Rom.
|
||||||
|
bool kSaveOverworldItems = true;
|
||||||
|
|
||||||
|
// Save overworld properties to the Rom.
|
||||||
|
bool kSaveOverworldProperties = true;
|
||||||
|
|
||||||
|
// Load custom overworld data from the ROM and enable UI.
|
||||||
|
bool kLoadCustomOverworld = false;
|
||||||
|
} overworld;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Flags &get() {
|
||||||
|
static Flags instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Serialize() const {
|
||||||
|
std::string result;
|
||||||
|
result +=
|
||||||
|
"kLogInstructions: " + std::to_string(get().kLogInstructions) + "\n";
|
||||||
|
result +=
|
||||||
|
"kSaveAllPalettes: " + std::to_string(get().kSaveAllPalettes) + "\n";
|
||||||
|
result += "kSaveGfxGroups: " + std::to_string(get().kSaveGfxGroups) + "\n";
|
||||||
|
result +=
|
||||||
|
"kSaveWithChangeQueue: " + std::to_string(get().kSaveWithChangeQueue) +
|
||||||
|
"\n";
|
||||||
|
result += "kDrawDungeonRoomGraphics: " +
|
||||||
|
std::to_string(get().kDrawDungeonRoomGraphics) + "\n";
|
||||||
|
result += "kNewFileDialogWrapper: " +
|
||||||
|
std::to_string(get().kNewFileDialogWrapper) + "\n";
|
||||||
|
result += "kLoadTexturesAsStreaming: " +
|
||||||
|
std::to_string(get().kLoadTexturesAsStreaming) + "\n";
|
||||||
|
result +=
|
||||||
|
"kSaveDungeonMaps: " + std::to_string(get().kSaveDungeonMaps) + "\n";
|
||||||
|
result += "kLogToConsole: " + std::to_string(get().kLogToConsole) + "\n";
|
||||||
|
result += "kDrawOverworldSprites: " +
|
||||||
|
std::to_string(get().overworld.kDrawOverworldSprites) + "\n";
|
||||||
|
result += "kSaveOverworldMaps: " +
|
||||||
|
std::to_string(get().overworld.kSaveOverworldMaps) + "\n";
|
||||||
|
result += "kSaveOverworldEntrances: " +
|
||||||
|
std::to_string(get().overworld.kSaveOverworldEntrances) + "\n";
|
||||||
|
result += "kSaveOverworldExits: " +
|
||||||
|
std::to_string(get().overworld.kSaveOverworldExits) + "\n";
|
||||||
|
result += "kSaveOverworldItems: " +
|
||||||
|
std::to_string(get().overworld.kSaveOverworldItems) + "\n";
|
||||||
|
result += "kSaveOverworldProperties: " +
|
||||||
|
std::to_string(get().overworld.kSaveOverworldProperties) + "\n";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class NotifyValue
|
||||||
|
* @brief A class to manage a value that can be modified and notify when it
|
||||||
|
* changes.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class NotifyValue {
|
||||||
|
public:
|
||||||
|
NotifyValue() : value_(), modified_(false), temp_value_() {}
|
||||||
|
NotifyValue(const T &value)
|
||||||
|
: value_(value), modified_(false), temp_value_() {}
|
||||||
|
|
||||||
|
void set(const T &value) {
|
||||||
|
value_ = value;
|
||||||
|
modified_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const T &get() {
|
||||||
|
modified_ = false;
|
||||||
|
return value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T &mutable_get() {
|
||||||
|
modified_ = false;
|
||||||
|
temp_value_ = value_;
|
||||||
|
return temp_value_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_changes() {
|
||||||
|
if (temp_value_ != value_) {
|
||||||
|
value_ = temp_value_;
|
||||||
|
modified_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(const T &value) { set(value); }
|
||||||
|
operator T() { return get(); }
|
||||||
|
|
||||||
|
bool modified() const { return modified_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T value_;
|
||||||
|
bool modified_;
|
||||||
|
T temp_value_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool log_to_console = false;
|
||||||
|
static const std::string kLogFileOut = "yaze_log.txt";
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
static void logf(const absl::FormatSpec<Args...> &format, const Args &...args) {
|
||||||
|
std::string message = absl::StrFormat(format, args...);
|
||||||
|
if (log_to_console) {
|
||||||
|
std::cout << message << std::endl;
|
||||||
|
}
|
||||||
|
static std::ofstream fout(kLogFileOut, std::ios::out | std::ios::app);
|
||||||
|
fout << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint32_t kFastRomRegion = 0x808000;
|
||||||
|
|
||||||
|
inline uint32_t SnesToPc(uint32_t addr) noexcept {
|
||||||
|
if (addr >= kFastRomRegion) {
|
||||||
|
addr -= kFastRomRegion;
|
||||||
|
}
|
||||||
|
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
|
||||||
|
return (temp + 0x0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t PcToSnes(uint32_t addr) {
|
||||||
|
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
|
||||||
|
b[2] = static_cast<uint8_t>(b[2] * 2);
|
||||||
|
|
||||||
|
if (b[1] >= 0x80) {
|
||||||
|
b[2] += 1;
|
||||||
|
} else {
|
||||||
|
b[1] += 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int AddressFromBytes(uint8_t bank, uint8_t high, uint8_t low) noexcept {
|
||||||
|
return (bank << 16) | (high << 8) | low;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) noexcept {
|
||||||
|
uint32_t result = 0;
|
||||||
|
result = (bank << 16) | addr;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Store little endian 16-bit value using a byte pointer, offset by an
|
||||||
|
* index before dereferencing
|
||||||
|
*/
|
||||||
|
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
|
||||||
|
uint16_t const p_val);
|
||||||
|
|
||||||
|
void stle16b(uint8_t *const p_arr, uint16_t const p_val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load little endian halfword (16-bit) dereferenced from an arrays of
|
||||||
|
* bytes. This version provides an index that will be multiplied by 2 and added
|
||||||
|
* to the base address.
|
||||||
|
*/
|
||||||
|
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
|
||||||
|
|
||||||
|
// Load little endian halfword (16-bit) dereferenced from
|
||||||
|
uint16_t ldle16b(uint8_t const *const p_arr);
|
||||||
|
|
||||||
|
struct FolderItem {
|
||||||
|
std::string name;
|
||||||
|
std::vector<FolderItem> subfolders;
|
||||||
|
std::vector<std::string> files;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct FolderItem FolderItem;
|
||||||
|
|
||||||
|
void CreateBpsPatch(const std::vector<uint8_t> &source,
|
||||||
|
const std::vector<uint8_t> &target,
|
||||||
|
std::vector<uint8_t> &patch);
|
||||||
|
|
||||||
|
void ApplyBpsPatch(const std::vector<uint8_t> &source,
|
||||||
|
const std::vector<uint8_t> &patch,
|
||||||
|
std::vector<uint8_t> &target);
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,73 +1,81 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <SDL_mixer.h>
|
|
||||||
#include <imgui/backends/imgui_impl_sdl.h>
|
|
||||||
#include <imgui/backends/imgui_impl_sdlrenderer.h>
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
#include <imgui/imgui_internal.h>
|
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
#include "app/editor/master_editor.h"
|
#include "app/core/platform/font_loader.h"
|
||||||
#include "gui/icons.h"
|
#include "app/editor/editor_manager.h"
|
||||||
#include "gui/style.h"
|
#include "app/gui/style.h"
|
||||||
|
#include "core/utils/file_util.h"
|
||||||
|
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||||
|
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
namespace {
|
absl::Status Controller::OnEntry(std::string filename) {
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
void InitializeKeymap() {
|
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
platform_ = Platform::kiOS;
|
||||||
io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
|
#elif TARGET_OS_MAC == 1
|
||||||
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
|
platform_ = Platform::kMacOS;
|
||||||
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
|
#endif
|
||||||
io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
|
#elif defined(_WIN32)
|
||||||
io.KeyMap[ImGuiKey_DownArrow] = SDL_GetScancodeFromKey(SDLK_DOWN);
|
platform_ = Platform::kWindows;
|
||||||
io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
|
#elif defined(__linux__)
|
||||||
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
|
platform_ = Platform::kLinux;
|
||||||
|
#else
|
||||||
|
platform_ = Platform::kUnknown;
|
||||||
|
#endif
|
||||||
|
RETURN_IF_ERROR(CreateWindow())
|
||||||
|
RETURN_IF_ERROR(CreateRenderer())
|
||||||
|
RETURN_IF_ERROR(CreateGuiContext())
|
||||||
|
RETURN_IF_ERROR(LoadAudioDevice())
|
||||||
|
editor_manager_.Initialize(filename);
|
||||||
|
active_ = true;
|
||||||
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleKeyDown(SDL_Event &event) {
|
void Controller::OnInput() {
|
||||||
|
int wheel = 0;
|
||||||
|
SDL_Event event;
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
switch (event.key.keysym.sym) {
|
|
||||||
case SDLK_UP:
|
while (SDL_PollEvent(&event)) {
|
||||||
case SDLK_DOWN:
|
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||||
case SDLK_RETURN:
|
switch (event.type) {
|
||||||
case SDLK_BACKSPACE:
|
case SDL_KEYDOWN:
|
||||||
case SDLK_LSHIFT:
|
case SDL_KEYUP: {
|
||||||
case SDLK_LCTRL:
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
case SDLK_TAB:
|
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
|
||||||
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
|
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
|
||||||
|
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
|
||||||
|
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDL_WINDOWEVENT:
|
||||||
|
switch (event.window.event) {
|
||||||
|
case SDL_WINDOWEVENT_CLOSE:
|
||||||
|
active_ = false;
|
||||||
|
break;
|
||||||
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||||
|
io.DisplaySize.x = static_cast<float>(event.window.data1);
|
||||||
|
io.DisplaySize.y = static_cast<float>(event.window.data2);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleKeyUp(SDL_Event &event) {
|
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
|
||||||
int key = event.key.keysym.scancode;
|
|
||||||
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
|
|
||||||
io.KeysDown[key] = (event.type == SDL_KEYDOWN);
|
|
||||||
io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0);
|
|
||||||
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
|
|
||||||
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
|
|
||||||
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChangeWindowSizeEvent(SDL_Event &event) {
|
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
|
||||||
io.DisplaySize.x = static_cast<float>(event.window.data1);
|
|
||||||
io.DisplaySize.y = static_cast<float>(event.window.data2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleMouseMovement(int &wheel) {
|
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
|
||||||
int mouseX;
|
int mouseX;
|
||||||
int mouseY;
|
int mouseY;
|
||||||
const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
|
const int buttons = SDL_GetMouseState(&mouseX, &mouseY);
|
||||||
@@ -76,154 +84,171 @@ void HandleMouseMovement(int &wheel) {
|
|||||||
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
|
||||||
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
|
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
|
||||||
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
|
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
|
||||||
|
io.MouseDown[2] = buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE);
|
||||||
io.MouseWheel = static_cast<float>(wheel);
|
io.MouseWheel = static_cast<float>(wheel);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
absl::Status Controller::OnLoad() {
|
||||||
|
if (editor_manager_.quit()) {
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
#if TARGET_OS_IPHONE != 1
|
||||||
|
constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
|
||||||
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
|
||||||
|
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
||||||
|
|
||||||
bool Controller::isActive() const { return active_; }
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
|
ImGui_ImplSDLRenderer2_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
ImGui::SetNextWindowPos(gui::kZeroPos);
|
||||||
|
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||||
|
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||||
|
|
||||||
absl::Status Controller::onEntry() {
|
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
||||||
RETURN_IF_ERROR(CreateWindow())
|
ImGui::End();
|
||||||
RETURN_IF_ERROR(CreateRenderer())
|
}
|
||||||
RETURN_IF_ERROR(CreateGuiContext())
|
#endif
|
||||||
InitializeKeymap();
|
RETURN_IF_ERROR(editor_manager_.Update());
|
||||||
master_editor_.SetupScreen(renderer_);
|
#if TARGET_OS_IPHONE != 1
|
||||||
active_ = true;
|
ImGui::End();
|
||||||
|
#endif
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::onInput() {
|
absl::Status Controller::OnTestLoad() {
|
||||||
int wheel = 0;
|
RETURN_IF_ERROR(test_editor_->Update());
|
||||||
SDL_Event event;
|
return absl::OkStatus();
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
|
||||||
|
|
||||||
while (SDL_PollEvent(&event)) {
|
|
||||||
switch (event.type) {
|
|
||||||
case SDL_KEYDOWN:
|
|
||||||
HandleKeyDown(event);
|
|
||||||
break;
|
|
||||||
case SDL_KEYUP:
|
|
||||||
HandleKeyUp(event);
|
|
||||||
break;
|
|
||||||
case SDL_TEXTINPUT:
|
|
||||||
io.AddInputCharactersUTF8(event.text.text);
|
|
||||||
break;
|
|
||||||
case SDL_MOUSEWHEEL:
|
|
||||||
wheel = event.wheel.y;
|
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT:
|
|
||||||
switch (event.window.event) {
|
|
||||||
case SDL_WINDOWEVENT_CLOSE:
|
|
||||||
CloseWindow();
|
|
||||||
break;
|
|
||||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
||||||
ChangeWindowSizeEvent(event);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleMouseMovement(wheel);
|
void Controller::DoRender() const {
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::onLoad() { master_editor_.UpdateScreen(); }
|
|
||||||
|
|
||||||
void Controller::doRender() const {
|
|
||||||
SDL_RenderClear(renderer_.get());
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
|
SDL_RenderClear(Renderer::GetInstance().renderer());
|
||||||
SDL_RenderPresent(renderer_.get());
|
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(),
|
||||||
|
Renderer::GetInstance().renderer());
|
||||||
|
SDL_RenderPresent(Renderer::GetInstance().renderer());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::onExit() const {
|
void Controller::OnExit() {
|
||||||
ImGui_ImplSDLRenderer_Shutdown();
|
SDL_PauseAudioDevice(audio_device_, 1);
|
||||||
|
SDL_CloseAudioDevice(audio_device_);
|
||||||
|
ImGui_ImplSDLRenderer2_Shutdown();
|
||||||
ImGui_ImplSDL2_Shutdown();
|
ImGui_ImplSDL2_Shutdown();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::Status Controller::CreateWindow() {
|
absl::Status Controller::CreateWindow() {
|
||||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
auto sdl_flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
|
||||||
|
|
||||||
|
if (SDL_Init(sdl_flags) != 0) {
|
||||||
return absl::InternalError(
|
return absl::InternalError(
|
||||||
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
|
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
|
||||||
} else {
|
}
|
||||||
window_ = std::unique_ptr<SDL_Window, sdl_deleter>(
|
|
||||||
|
SDL_DisplayMode display_mode;
|
||||||
|
SDL_GetCurrentDisplayMode(0, &display_mode);
|
||||||
|
int screen_width = display_mode.w * 0.8;
|
||||||
|
int screen_height = display_mode.h * 0.8;
|
||||||
|
|
||||||
|
window_ = std::unique_ptr<SDL_Window, core::SDL_Deleter>(
|
||||||
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
|
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
|
||||||
SDL_WINDOWPOS_UNDEFINED, // initial x position
|
SDL_WINDOWPOS_UNDEFINED, // initial x position
|
||||||
SDL_WINDOWPOS_UNDEFINED, // initial y position
|
SDL_WINDOWPOS_UNDEFINED, // initial y position
|
||||||
kScreenWidth, // width, in pixels
|
screen_width, // width, in pixels
|
||||||
kScreenHeight, // height, in pixels
|
screen_height, // height, in pixels
|
||||||
SDL_WINDOW_RESIZABLE),
|
SDL_WINDOW_RESIZABLE),
|
||||||
sdl_deleter());
|
core::SDL_Deleter());
|
||||||
if (window_ == nullptr) {
|
if (window_ == nullptr) {
|
||||||
return absl::InternalError(
|
return absl::InternalError(
|
||||||
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
|
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
|
||||||
}
|
}
|
||||||
// Initialize SDL_mixer
|
|
||||||
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
|
|
||||||
printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
|
|
||||||
Mix_GetError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::Status Controller::CreateRenderer() {
|
absl::Status Controller::CreateRenderer() {
|
||||||
renderer_ = std::unique_ptr<SDL_Renderer, sdl_deleter>(
|
return Renderer::GetInstance().CreateRenderer(window_.get());
|
||||||
SDL_CreateRenderer(window_.get(), -1,
|
|
||||||
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC),
|
|
||||||
sdl_deleter());
|
|
||||||
if (renderer_ == nullptr) {
|
|
||||||
return absl::InternalError(
|
|
||||||
absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
|
|
||||||
} else {
|
|
||||||
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
|
|
||||||
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
|
|
||||||
}
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::Status Controller::CreateGuiContext() {
|
absl::Status Controller::CreateGuiContext() {
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
|
|
||||||
// Initialize ImGui for SDL
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), renderer_.get());
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
ImGui_ImplSDLRenderer_Init(renderer_.get());
|
|
||||||
|
|
||||||
// Load available fonts
|
// Initialize ImGui based on the backend
|
||||||
const ImGuiIO &io = ImGui::GetIO();
|
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(),
|
||||||
io.Fonts->AddFontFromFileTTF("assets/font/Karla-Regular.ttf", 14.0f);
|
Renderer::GetInstance().renderer());
|
||||||
|
ImGui_ImplSDLRenderer2_Init(Renderer::GetInstance().renderer());
|
||||||
|
|
||||||
// merge in icons from Google Material Design
|
RETURN_IF_ERROR(LoadFontFamilies());
|
||||||
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
|
||||||
ImFontConfig icons_config;
|
|
||||||
icons_config.MergeMode = true;
|
|
||||||
icons_config.GlyphOffset.y = 5.0f;
|
|
||||||
icons_config.GlyphMinAdvanceX = 13.0f;
|
|
||||||
icons_config.PixelSnapH = true;
|
|
||||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, 18.0f, &icons_config,
|
|
||||||
icons_ranges);
|
|
||||||
io.Fonts->AddFontFromFileTTF("assets/font/Roboto-Medium.ttf", 14.0f);
|
|
||||||
io.Fonts->AddFontFromFileTTF("assets/font/Cousine-Regular.ttf", 14.0f);
|
|
||||||
io.Fonts->AddFontFromFileTTF("assets/font/DroidSans.ttf", 16.0f);
|
|
||||||
|
|
||||||
// Set the default style
|
// Set the default style
|
||||||
gui::ColorsYaze();
|
gui::ColorsYaze();
|
||||||
|
|
||||||
// Build a new ImGui frame
|
// Build a new ImGui frame
|
||||||
ImGui_ImplSDLRenderer_NewFrame();
|
ImGui_ImplSDLRenderer2_NewFrame();
|
||||||
ImGui_ImplSDL2_NewFrame(window_.get());
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Controller::LoadFontFamilies() const {
|
||||||
|
// LoadSystemFonts();
|
||||||
|
return LoadPackageFonts();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Controller::LoadAudioDevice() {
|
||||||
|
SDL_AudioSpec want, have;
|
||||||
|
SDL_memset(&want, 0, sizeof(want));
|
||||||
|
want.freq = audio_frequency_;
|
||||||
|
want.format = AUDIO_S16;
|
||||||
|
want.channels = 2;
|
||||||
|
want.samples = 2048;
|
||||||
|
want.callback = NULL; // Uses the queue
|
||||||
|
audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
|
||||||
|
if (audio_device_ == 0) {
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Failed to open audio: %s\n", SDL_GetError()));
|
||||||
|
}
|
||||||
|
// audio_buffer_ = new int16_t[audio_frequency_ / 50 * 4];
|
||||||
|
audio_buffer_ = std::make_shared<int16_t>(audio_frequency_ / 50 * 4);
|
||||||
|
SDL_PauseAudioDevice(audio_device_, 0);
|
||||||
|
editor_manager_.emulator().set_audio_buffer(audio_buffer_.get());
|
||||||
|
editor_manager_.emulator().set_audio_device_id(audio_device_);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Controller::LoadConfigFiles() {
|
||||||
|
// Create and load a dotfile for the application
|
||||||
|
// This will store the user's preferences and settings
|
||||||
|
std::string config_directory = GetConfigDirectory(platform_);
|
||||||
|
|
||||||
|
// Create the directory if it doesn't exist
|
||||||
|
if (!std::filesystem::exists(config_directory)) {
|
||||||
|
if (!std::filesystem::create_directory(config_directory)) {
|
||||||
|
return absl::InternalError(absl::StrFormat(
|
||||||
|
"Failed to create config directory %s", config_directory));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the config file exists
|
||||||
|
std::string config_file = config_directory + "yaze.cfg";
|
||||||
|
if (!std::filesystem::exists(config_file)) {
|
||||||
|
// Create the file if it doesn't exist
|
||||||
|
std::ofstream file(config_file);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Failed to create config file %s", config_file));
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
@@ -2,56 +2,74 @@
|
|||||||
#define YAZE_APP_CORE_CONTROLLER_H
|
#define YAZE_APP_CORE_CONTROLLER_H
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <imgui/backends/imgui_impl_sdl.h>
|
|
||||||
#include <imgui/backends/imgui_impl_sdlrenderer.h>
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
#include <imgui/imgui_internal.h>
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "app/editor/master_editor.h"
|
#include "app/core/platform/renderer.h"
|
||||||
#include "gui/icons.h"
|
#include "app/core/utils/file_util.h"
|
||||||
#include "gui/style.h"
|
#include "app/editor/editor.h"
|
||||||
|
#include "app/editor/editor_manager.h"
|
||||||
|
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||||
|
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||||
|
#include "imgui/imconfig.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
int main(int argc, char **argv);
|
int main(int argc, char **argv);
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
class Controller {
|
/**
|
||||||
|
* @brief Main controller for the application.
|
||||||
|
*
|
||||||
|
* This class is responsible for managing the main window and the
|
||||||
|
* main editor. It is the main entry point for the application.
|
||||||
|
*/
|
||||||
|
class Controller : public ExperimentFlags {
|
||||||
public:
|
public:
|
||||||
bool isActive() const;
|
bool IsActive() const { return active_; }
|
||||||
absl::Status onEntry();
|
absl::Status OnEntry(std::string filename = "");
|
||||||
void onInput();
|
void OnInput();
|
||||||
void onLoad();
|
absl::Status OnLoad();
|
||||||
void onLoadDelta();
|
absl::Status OnTestLoad();
|
||||||
void doRender() const;
|
void DoRender() const;
|
||||||
void onExit() const;
|
void OnExit();
|
||||||
|
|
||||||
private:
|
|
||||||
struct sdl_deleter {
|
|
||||||
void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); }
|
|
||||||
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
|
|
||||||
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
|
|
||||||
};
|
|
||||||
|
|
||||||
absl::Status CreateWindow();
|
absl::Status CreateWindow();
|
||||||
absl::Status CreateRenderer();
|
absl::Status CreateRenderer();
|
||||||
absl::Status CreateGuiContext();
|
absl::Status CreateGuiContext();
|
||||||
void CloseWindow() { active_ = false; }
|
absl::Status LoadFontFamilies() const;
|
||||||
|
absl::Status LoadAudioDevice();
|
||||||
|
absl::Status LoadConfigFiles();
|
||||||
|
|
||||||
|
void SetupScreen(std::string filename = "") {
|
||||||
|
editor_manager_.Initialize(filename);
|
||||||
|
}
|
||||||
|
auto editor_manager() -> editor::EditorManager & { return editor_manager_; }
|
||||||
|
auto renderer() -> SDL_Renderer * {
|
||||||
|
return Renderer::GetInstance().renderer();
|
||||||
|
}
|
||||||
|
auto window() -> SDL_Window * { return window_.get(); }
|
||||||
|
void init_test_editor(editor::Editor *editor) { test_editor_ = editor; }
|
||||||
|
void set_active(bool active) { active_ = active; }
|
||||||
|
auto active() { return active_; }
|
||||||
|
|
||||||
|
private:
|
||||||
friend int ::main(int argc, char **argv);
|
friend int ::main(int argc, char **argv);
|
||||||
|
|
||||||
bool active_;
|
bool active_ = false;
|
||||||
editor::MasterEditor master_editor_;
|
Platform platform_;
|
||||||
|
editor::Editor *test_editor_ = nullptr;
|
||||||
|
editor::EditorManager editor_manager_;
|
||||||
|
|
||||||
|
int audio_frequency_ = 48000;
|
||||||
|
SDL_AudioDeviceID audio_device_;
|
||||||
|
std::shared_ptr<int16_t> audio_buffer_;
|
||||||
std::shared_ptr<SDL_Window> window_;
|
std::shared_ptr<SDL_Window> window_;
|
||||||
std::shared_ptr<SDL_Renderer> renderer_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_APP_CORE_CONTROLLER_H
|
#endif // YAZE_APP_CORE_CONTROLLER_H
|
||||||
33
src/app/core/core.cmake
Normal file
33
src/app/core/core.cmake
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
set(
|
||||||
|
YAZE_APP_CORE_SRC
|
||||||
|
app/core/common.cc
|
||||||
|
app/core/controller.cc
|
||||||
|
app/emu/emulator.cc
|
||||||
|
app/core/project.cc
|
||||||
|
app/core/utils/file_util.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
if (WIN32 OR MINGW OR UNIX AND NOT APPLE)
|
||||||
|
list(APPEND YAZE_APP_CORE_SRC
|
||||||
|
app/core/platform/font_loader.cc
|
||||||
|
app/core/platform/clipboard.cc
|
||||||
|
app/core/platform/file_dialog.cc
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
list(APPEND YAZE_APP_CORE_SRC
|
||||||
|
app/core/platform/file_dialog.mm
|
||||||
|
app/core/platform/app_delegate.mm
|
||||||
|
app/core/platform/font_loader.cc
|
||||||
|
app/core/platform/font_loader.mm
|
||||||
|
app/core/platform/clipboard.mm
|
||||||
|
app/core/platform/file_path.mm
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(COCOA_LIBRARY Cocoa)
|
||||||
|
if(NOT COCOA_LIBRARY)
|
||||||
|
message(FATAL_ERROR "Cocoa not found")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "-framework ServiceManagement -framework Foundation -framework Cocoa")
|
||||||
|
endif()
|
||||||
64
src/app/core/platform/app_delegate.h
Normal file
64
src/app/core/platform/app_delegate.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
/* Apple OSX and iOS (Darwin). */
|
||||||
|
#import <CoreText/CoreText.h>
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
|
||||||
|
/* iOS in Xcode simulator */
|
||||||
|
#import <PencilKit/PencilKit.h>
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : UIResponder <UIApplicationDelegate,
|
||||||
|
UIDocumentPickerDelegate,
|
||||||
|
UITabBarControllerDelegate,
|
||||||
|
PKCanvasViewDelegate>
|
||||||
|
@property(strong, nonatomic) UIWindow *window;
|
||||||
|
|
||||||
|
@property UIDocumentPickerViewController *documentPicker;
|
||||||
|
@property(nonatomic, copy) void (^completionHandler)(NSString *selectedFile);
|
||||||
|
- (void)PresentDocumentPickerWithCompletionHandler:
|
||||||
|
(void (^)(NSString *selectedFile))completionHandler;
|
||||||
|
|
||||||
|
// TODO: Setup a tab bar controller for multiple yaze instances
|
||||||
|
@property(nonatomic) UITabBarController *tabBarController;
|
||||||
|
|
||||||
|
// TODO: Setup a font picker for the text editor and display settings
|
||||||
|
@property(nonatomic) UIFontPickerViewController *fontPicker;
|
||||||
|
|
||||||
|
// TODO: Setup the pencil kit for drawing
|
||||||
|
@property PKToolPicker *toolPicker;
|
||||||
|
@property PKCanvasView *canvasView;
|
||||||
|
|
||||||
|
// TODO: Setup the file manager for file operations
|
||||||
|
@property NSFileManager *fileManager;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the Cocoa application.
|
||||||
|
*/
|
||||||
|
void yaze_initialize_cocoa();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Run the Cocoa application delegate.
|
||||||
|
*/
|
||||||
|
void yaze_run_cocoa_app_delegate(const char *filename);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // TARGET_OS_MAC
|
||||||
|
|
||||||
|
#endif // defined(__APPLE__) && defined(__MACH__)
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||||
257
src/app/core/platform/app_delegate.mm
Normal file
257
src/app/core/platform/app_delegate.mm
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
// AppDelegate.mm
|
||||||
|
#import "app/core/platform/app_delegate.h"
|
||||||
|
#import "app/core/controller.h"
|
||||||
|
#import "app/core/platform/file_dialog.h"
|
||||||
|
#import "app/editor/editor.h"
|
||||||
|
#import "app/rom.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
/* Apple OSX and iOS (Darwin). */
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#import <CoreText/CoreText.h>
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
|
||||||
|
/* iOS in Xcode simulator */
|
||||||
|
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
/* macOS */
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||||
|
- (void)setupMenus;
|
||||||
|
// - (void)changeApplicationIcon;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
|
||||||
|
// - (void)changeApplicationIcon {
|
||||||
|
// NSImage *newIcon = [NSImage imageNamed:@"newIcon"];
|
||||||
|
// [NSApp setApplicationIconImage:newIcon];
|
||||||
|
// }
|
||||||
|
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||||
|
[self setupMenus];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setupMenus {
|
||||||
|
NSMenu *mainMenu = [NSApp mainMenu];
|
||||||
|
|
||||||
|
NSMenuItem *fileMenuItem = [mainMenu itemWithTitle:@"File"];
|
||||||
|
if (!fileMenuItem) {
|
||||||
|
NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"];
|
||||||
|
fileMenuItem = [[NSMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""];
|
||||||
|
[fileMenuItem setSubmenu:fileMenu];
|
||||||
|
|
||||||
|
NSMenuItem *openItem = [[NSMenuItem alloc] initWithTitle:@"Open"
|
||||||
|
action:@selector(openFileAction:)
|
||||||
|
keyEquivalent:@"o"];
|
||||||
|
[fileMenu addItem:openItem];
|
||||||
|
|
||||||
|
// Open Recent
|
||||||
|
NSMenu *openRecentMenu = [[NSMenu alloc] initWithTitle:@"Open Recent"];
|
||||||
|
NSMenuItem *openRecentMenuItem = [[NSMenuItem alloc] initWithTitle:@"Open Recent"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@""];
|
||||||
|
[openRecentMenuItem setSubmenu:openRecentMenu];
|
||||||
|
[fileMenu addItem:openRecentMenuItem];
|
||||||
|
|
||||||
|
// Add a separator
|
||||||
|
[fileMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
|
||||||
|
// Save
|
||||||
|
NSMenuItem *saveItem = [[NSMenuItem alloc] initWithTitle:@"Save" action:nil keyEquivalent:@"s"];
|
||||||
|
[fileMenu addItem:saveItem];
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
[fileMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
|
||||||
|
// Options submenu
|
||||||
|
NSMenu *optionsMenu = [[NSMenu alloc] initWithTitle:@"Options"];
|
||||||
|
NSMenuItem *optionsMenuItem = [[NSMenuItem alloc] initWithTitle:@"Options"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@""];
|
||||||
|
[optionsMenuItem setSubmenu:optionsMenu];
|
||||||
|
|
||||||
|
// Flag checkmark field
|
||||||
|
NSMenuItem *flagItem = [[NSMenuItem alloc] initWithTitle:@"Flag"
|
||||||
|
action:@selector(toggleFlagAction:)
|
||||||
|
keyEquivalent:@""];
|
||||||
|
[flagItem setTarget:self];
|
||||||
|
[flagItem setState:NSControlStateValueOff];
|
||||||
|
[optionsMenu addItem:flagItem];
|
||||||
|
[fileMenu addItem:optionsMenuItem];
|
||||||
|
|
||||||
|
[mainMenu insertItem:fileMenuItem atIndex:1];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMenuItem *editMenuItem = [mainMenu itemWithTitle:@"Edit"];
|
||||||
|
if (!editMenuItem) {
|
||||||
|
NSMenu *editMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
|
||||||
|
editMenuItem = [[NSMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""];
|
||||||
|
[editMenuItem setSubmenu:editMenu];
|
||||||
|
|
||||||
|
NSMenuItem *undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" action:nil keyEquivalent:@"z"];
|
||||||
|
|
||||||
|
[editMenu addItem:undoItem];
|
||||||
|
|
||||||
|
NSMenuItem *redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" action:nil keyEquivalent:@"Z"];
|
||||||
|
|
||||||
|
[editMenu addItem:redoItem];
|
||||||
|
|
||||||
|
// Add a separator
|
||||||
|
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
|
||||||
|
NSMenuItem *cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
|
||||||
|
action:@selector(cutAction:)
|
||||||
|
keyEquivalent:@"x"];
|
||||||
|
[editMenu addItem:cutItem];
|
||||||
|
|
||||||
|
NSMenuItem *copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:nil keyEquivalent:@"c"];
|
||||||
|
[editMenu addItem:copyItem];
|
||||||
|
|
||||||
|
NSMenuItem *pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@"v"];
|
||||||
|
|
||||||
|
[editMenu addItem:pasteItem];
|
||||||
|
|
||||||
|
// Add a separator
|
||||||
|
[editMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
|
||||||
|
NSMenuItem *selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@"a"];
|
||||||
|
|
||||||
|
[editMenu addItem:selectAllItem];
|
||||||
|
|
||||||
|
[mainMenu insertItem:editMenuItem atIndex:2];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMenuItem *viewMenuItem = [mainMenu itemWithTitle:@"View"];
|
||||||
|
if (!viewMenuItem) {
|
||||||
|
NSMenu *viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
|
||||||
|
viewMenuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
|
||||||
|
[viewMenuItem setSubmenu:viewMenu];
|
||||||
|
|
||||||
|
// Emulator view button
|
||||||
|
NSMenuItem *emulatorViewItem = [[NSMenuItem alloc] initWithTitle:@"Emulator View"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@"1"];
|
||||||
|
|
||||||
|
[viewMenu addItem:emulatorViewItem];
|
||||||
|
|
||||||
|
// Hex Editor View
|
||||||
|
NSMenuItem *hexEditorViewItem = [[NSMenuItem alloc] initWithTitle:@"Hex Editor View"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@"2"];
|
||||||
|
|
||||||
|
[viewMenu addItem:hexEditorViewItem];
|
||||||
|
|
||||||
|
// Disassembly view button
|
||||||
|
NSMenuItem *disassemblyViewItem = [[NSMenuItem alloc] initWithTitle:@"Disassembly View"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@"3"];
|
||||||
|
|
||||||
|
[viewMenu addItem:disassemblyViewItem];
|
||||||
|
|
||||||
|
// Memory view button
|
||||||
|
NSMenuItem *memoryViewItem = [[NSMenuItem alloc] initWithTitle:@"Memory View"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@"4"];
|
||||||
|
|
||||||
|
[viewMenu addItem:memoryViewItem];
|
||||||
|
|
||||||
|
// Add a separator
|
||||||
|
[viewMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
|
||||||
|
// Toggle fullscreen button
|
||||||
|
NSMenuItem *toggleFullscreenItem = [[NSMenuItem alloc] initWithTitle:@"Toggle Fullscreen"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@"f"];
|
||||||
|
|
||||||
|
[viewMenu addItem:toggleFullscreenItem];
|
||||||
|
|
||||||
|
[mainMenu insertItem:viewMenuItem atIndex:3];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSMenuItem *helpMenuItem = [mainMenu itemWithTitle:@"Help"];
|
||||||
|
if (!helpMenuItem) {
|
||||||
|
NSMenu *helpMenu = [[NSMenu alloc] initWithTitle:@"Help"];
|
||||||
|
helpMenuItem = [[NSMenuItem alloc] initWithTitle:@"Help" action:nil keyEquivalent:@""];
|
||||||
|
[helpMenuItem setSubmenu:helpMenu];
|
||||||
|
|
||||||
|
// URL to online documentation
|
||||||
|
NSMenuItem *documentationItem = [[NSMenuItem alloc] initWithTitle:@"Documentation"
|
||||||
|
action:nil
|
||||||
|
keyEquivalent:@"?"];
|
||||||
|
[helpMenu addItem:documentationItem];
|
||||||
|
|
||||||
|
[mainMenu insertItem:helpMenuItem atIndex:4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action method for the New menu item
|
||||||
|
- (void)newFileAction:(id)sender {
|
||||||
|
NSLog(@"New File action triggered");
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleFlagAction:(id)sender {
|
||||||
|
NSMenuItem *flagItem = (NSMenuItem *)sender;
|
||||||
|
if ([flagItem state] == NSControlStateValueOff) {
|
||||||
|
[flagItem setState:NSControlStateValueOn];
|
||||||
|
} else {
|
||||||
|
[flagItem setState:NSControlStateValueOff];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openFileAction:(id)sender {
|
||||||
|
if (!yaze::SharedRom::shared_rom_
|
||||||
|
->LoadFromFile(yaze::core::FileDialogWrapper::ShowOpenFileDialog())
|
||||||
|
.ok()) {
|
||||||
|
NSAlert *alert = [[NSAlert alloc] init];
|
||||||
|
[alert setMessageText:@"Error"];
|
||||||
|
[alert setInformativeText:@"Failed to load file."];
|
||||||
|
[alert addButtonWithTitle:@"OK"];
|
||||||
|
[alert runModal];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)cutAction:(id)sender {
|
||||||
|
// TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openRecentFileAction:(id)sender {
|
||||||
|
NSLog(@"Open Recent File action triggered");
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
extern "C" void yaze_initialize_cococa() {
|
||||||
|
@autoreleasepool {
|
||||||
|
AppDelegate *delegate = [[AppDelegate alloc] init];
|
||||||
|
[NSApplication sharedApplication];
|
||||||
|
[NSApp setDelegate:delegate];
|
||||||
|
[NSApp finishLaunching];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void yaze_run_cocoa_app_delegate(const char *filename) {
|
||||||
|
yaze_initialize_cococa();
|
||||||
|
yaze::core::Controller controller;
|
||||||
|
RETURN_VOID_IF_ERROR(controller.OnEntry(filename));
|
||||||
|
while (controller.IsActive()) {
|
||||||
|
@autoreleasepool {
|
||||||
|
controller.OnInput();
|
||||||
|
if (auto status = controller.OnLoad(); !status.ok()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
controller.DoRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.OnExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
14
src/app/core/platform/clipboard.cc
Normal file
14
src/app/core/platform/clipboard.cc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include "app/core/platform/clipboard.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
void CopyImageToClipboard(const std::vector<uint8_t>& data) {}
|
||||||
|
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
|
||||||
|
int& height) {}
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
16
src/app/core/platform/clipboard.h
Normal file
16
src/app/core/platform/clipboard.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
void CopyImageToClipboard(const std::vector<uint8_t> &data);
|
||||||
|
void GetImageFromClipboard(std::vector<uint8_t> &data, int &width, int &height);
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
|
||||||
45
src/app/core/platform/clipboard.mm
Normal file
45
src/app/core/platform/clipboard.mm
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "clipboard.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef TARGET_OS_MAC
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
void yaze::core::CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
|
||||||
|
NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()];
|
||||||
|
NSImage* image = [[NSImage alloc] initWithData:data];
|
||||||
|
|
||||||
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
[pasteboard clearContents];
|
||||||
|
[pasteboard writeObjects:@[ image ]];
|
||||||
|
}
|
||||||
|
|
||||||
|
void yaze::core::GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) {
|
||||||
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
NSArray* classArray = [NSArray arrayWithObject:[NSImage class]];
|
||||||
|
NSDictionary* options = [NSDictionary dictionary];
|
||||||
|
|
||||||
|
NSImage* image = [pasteboard readObjectsForClasses:classArray options:options].firstObject;
|
||||||
|
if (!image) {
|
||||||
|
width = height = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assuming the image is in an RGBA format
|
||||||
|
CGImageRef cgImage = [image CGImageForProposedRect:nil context:nil hints:nil];
|
||||||
|
width = (int)CGImageGetWidth(cgImage);
|
||||||
|
height = (int)CGImageGetHeight(cgImage);
|
||||||
|
|
||||||
|
size_t bytesPerRow = 4 * width;
|
||||||
|
size_t totalBytes = bytesPerRow * height;
|
||||||
|
pixel_data.resize(totalBytes);
|
||||||
|
|
||||||
|
CGContextRef context = CGBitmapContextCreate(
|
||||||
|
pixel_data.data(), width, height, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(),
|
||||||
|
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||||
|
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
||||||
|
CGContextRelease(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
143
src/app/core/platform/file_dialog.cc
Normal file
143
src/app/core/platform/file_dialog.cc
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#include "file_dialog.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// Include Windows-specific headers
|
||||||
|
#include <shobjidl.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||||
|
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||||
|
|
||||||
|
IFileDialog *pfd = NULL;
|
||||||
|
HRESULT hr =
|
||||||
|
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileDialog,
|
||||||
|
reinterpret_cast<void **>(&pfd));
|
||||||
|
std::string file_path_windows;
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
// Show the dialog
|
||||||
|
hr = pfd->Show(NULL);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
IShellItem *psiResult;
|
||||||
|
hr = pfd->GetResult(&psiResult);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
// Get the file path
|
||||||
|
PWSTR pszFilePath;
|
||||||
|
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
|
||||||
|
char str[128];
|
||||||
|
wcstombs(str, pszFilePath, 128);
|
||||||
|
file_path_windows = str;
|
||||||
|
psiResult->Release();
|
||||||
|
CoTaskMemFree(pszFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pfd->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
CoUninitialize();
|
||||||
|
return file_path_windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
||||||
|
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||||
|
|
||||||
|
IFileDialog *pfd = NULL;
|
||||||
|
HRESULT hr =
|
||||||
|
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileDialog,
|
||||||
|
reinterpret_cast<void **>(&pfd));
|
||||||
|
std::string folder_path_windows;
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
// Show the dialog
|
||||||
|
DWORD dwOptions;
|
||||||
|
hr = pfd->GetOptions(&dwOptions);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
hr = pfd->SetOptions(dwOptions | FOS_PICKFOLDERS);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
hr = pfd->Show(NULL);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
IShellItem *psiResult;
|
||||||
|
hr = pfd->GetResult(&psiResult);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
// Get the folder path
|
||||||
|
PWSTR pszFolderPath;
|
||||||
|
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFolderPath);
|
||||||
|
char str[128];
|
||||||
|
wcstombs(str, pszFolderPath, 128);
|
||||||
|
folder_path_windows = str;
|
||||||
|
psiResult->Release();
|
||||||
|
CoTaskMemFree(pszFolderPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pfd->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
CoUninitialize();
|
||||||
|
return folder_path_windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
|
||||||
|
const std::string &folder_path) {
|
||||||
|
std::vector<std::string> subdirectories;
|
||||||
|
WIN32_FIND_DATA findFileData;
|
||||||
|
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
|
||||||
|
if (hFind != INVALID_HANDLE_VALUE) {
|
||||||
|
do {
|
||||||
|
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||||
|
if (strcmp(findFileData.cFileName, ".") != 0 &&
|
||||||
|
strcmp(findFileData.cFileName, "..") != 0) {
|
||||||
|
subdirectories.push_back(findFileData.cFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (FindNextFile(hFind, &findFileData) != 0);
|
||||||
|
FindClose(hFind);
|
||||||
|
}
|
||||||
|
return subdirectories;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
|
||||||
|
const std::string &folder_path) {
|
||||||
|
std::vector<std::string> files;
|
||||||
|
WIN32_FIND_DATA findFileData;
|
||||||
|
HANDLE hFind = FindFirstFile((folder_path + "\\*").c_str(), &findFileData);
|
||||||
|
if (hFind != INVALID_HANDLE_VALUE) {
|
||||||
|
do {
|
||||||
|
if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||||
|
files.push_back(findFileData.cFileName);
|
||||||
|
}
|
||||||
|
} while (FindNextFile(hFind, &findFileData) != 0);
|
||||||
|
FindClose(hFind);
|
||||||
|
}
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||||
|
return "Linux: Open file dialog";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowOpenFolderDialog() {
|
||||||
|
return "Linux: Open folder dialog";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(
|
||||||
|
const std::string& folder_path) {
|
||||||
|
return {"Linux: Subdirectories in folder"};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(
|
||||||
|
const std::string& folder_path) {
|
||||||
|
return {"Linux: Files in folder"};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
32
src/app/core/platform/file_dialog.h
Normal file
32
src/app/core/platform/file_dialog.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
class FileDialogWrapper {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief ShowOpenFileDialog opens a file dialog and returns the selected
|
||||||
|
* filepath.
|
||||||
|
*/
|
||||||
|
static std::string ShowOpenFileDialog();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ShowOpenFolderDialog opens a file dialog and returns the selected
|
||||||
|
* folder path.
|
||||||
|
*/
|
||||||
|
static std::string ShowOpenFolderDialog();
|
||||||
|
static std::vector<std::string> GetSubdirectoriesInFolder(
|
||||||
|
const std::string& folder_path);
|
||||||
|
static std::vector<std::string> GetFilesInFolder(
|
||||||
|
const std::string& folder_path);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
|
||||||
132
src/app/core/platform/file_dialog.mm
Normal file
132
src/app/core/platform/file_dialog.mm
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
/* Apple OSX and iOS (Darwin). */
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#import <CoreText/CoreText.h>
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
|
||||||
|
/* iOS in Xcode simulator */
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||||
|
|
||||||
|
#include "app/core/platform/app_delegate.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static std::string selectedFile;
|
||||||
|
|
||||||
|
void ShowOpenFileDialogImpl(void (^completionHandler)(std::string)) {
|
||||||
|
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
|
||||||
|
[appDelegate PresentDocumentPickerWithCompletionHandler:^(NSString *filePath) {
|
||||||
|
selectedFile = std::string([filePath UTF8String]);
|
||||||
|
completionHandler(selectedFile);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ShowOpenFileDialogSync() {
|
||||||
|
__block std::string result;
|
||||||
|
|
||||||
|
ShowOpenFileDialogImpl(^(std::string filePath) {
|
||||||
|
result = filePath;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() { return ShowOpenFileDialogSync(); }
|
||||||
|
|
||||||
|
std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
|
||||||
|
|
||||||
|
std::vector<std::string> yaze::core::FileDialogWrapper::GetFilesInFolder(
|
||||||
|
const std::string &folder) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> yaze::core::FileDialogWrapper::GetSubdirectoriesInFolder(
|
||||||
|
const std::string &folder) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
/* macOS */
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
std::string yaze::core::FileDialogWrapper::ShowOpenFileDialog() {
|
||||||
|
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
||||||
|
[openPanel setCanChooseFiles:YES];
|
||||||
|
[openPanel setCanChooseDirectories:NO];
|
||||||
|
[openPanel setAllowsMultipleSelection:NO];
|
||||||
|
|
||||||
|
if ([openPanel runModal] == NSModalResponseOK) {
|
||||||
|
NSURL* url = [[openPanel URLs] objectAtIndex:0];
|
||||||
|
NSString* path = [url path];
|
||||||
|
return std::string([path UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string yaze::core::FileDialogWrapper::ShowOpenFolderDialog() {
|
||||||
|
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
||||||
|
[openPanel setCanChooseFiles:NO];
|
||||||
|
[openPanel setCanChooseDirectories:YES];
|
||||||
|
[openPanel setAllowsMultipleSelection:NO];
|
||||||
|
|
||||||
|
if ([openPanel runModal] == NSModalResponseOK) {
|
||||||
|
NSURL* url = [[openPanel URLs] objectAtIndex:0];
|
||||||
|
NSString* path = [url path];
|
||||||
|
return std::string([path UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> yaze::core::FileDialogWrapper::GetFilesInFolder(
|
||||||
|
const std::string& folder) {
|
||||||
|
std::vector<std::string> filenames;
|
||||||
|
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||||
|
NSDirectoryEnumerator* enumerator =
|
||||||
|
[fileManager enumeratorAtPath:[NSString stringWithUTF8String:folder.c_str()]];
|
||||||
|
NSString* file;
|
||||||
|
while (file = [enumerator nextObject]) {
|
||||||
|
if ([file hasPrefix:@"."]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
filenames.push_back(std::string([file UTF8String]));
|
||||||
|
}
|
||||||
|
return filenames;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> yaze::core::FileDialogWrapper::GetSubdirectoriesInFolder(
|
||||||
|
const std::string& folder) {
|
||||||
|
std::vector<std::string> subdirectories;
|
||||||
|
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||||
|
NSDirectoryEnumerator* enumerator =
|
||||||
|
[fileManager enumeratorAtPath:[NSString stringWithUTF8String:folder.c_str()]];
|
||||||
|
NSString* file;
|
||||||
|
while (file = [enumerator nextObject]) {
|
||||||
|
if ([file hasPrefix:@"."]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
BOOL isDirectory;
|
||||||
|
NSString* path =
|
||||||
|
[NSString stringWithFormat:@"%@/%@", [NSString stringWithUTF8String:folder.c_str()], file];
|
||||||
|
[fileManager fileExistsAtPath:path isDirectory:&isDirectory];
|
||||||
|
if (isDirectory) {
|
||||||
|
subdirectories.push_back(std::string([file UTF8String]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return subdirectories;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Unsupported platform
|
||||||
|
#endif // TARGET_OS_MAC
|
||||||
|
|
||||||
|
#endif // __APPLE__ && __MACH__
|
||||||
18
src/app/core/platform/file_path.h
Normal file
18
src/app/core/platform/file_path.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_FILE_PATH_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_FILE_PATH_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GetBundleResourcePath returns the path to the bundle resource
|
||||||
|
* directory. Specific to MacOS.
|
||||||
|
*/
|
||||||
|
std::string GetBundleResourcePath();
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PLATFORM_FILE_PATH_H
|
||||||
26
src/app/core/platform/file_path.mm
Normal file
26
src/app/core/platform/file_path.mm
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "file_path.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#include <Foundation/Foundation.h>
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1 || TARGET_OS_IPHONE == 1
|
||||||
|
std::string yaze::core::GetBundleResourcePath() {
|
||||||
|
NSBundle* bundle = [NSBundle mainBundle];
|
||||||
|
NSString* resourceDirectoryPath = [bundle bundlePath];
|
||||||
|
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
|
||||||
|
return [path UTF8String];
|
||||||
|
}
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
std::string yaze::core::GetBundleResourcePath() {
|
||||||
|
NSBundle* bundle = [NSBundle mainBundle];
|
||||||
|
NSString* resourceDirectoryPath = [bundle bundlePath];
|
||||||
|
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
|
||||||
|
return [path UTF8String];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
244
src/app/core/platform/font_loader.cc
Normal file
244
src/app/core/platform/font_loader.cc
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
#include "app/core/platform/font_loader.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "app/core/platform/file_path.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
absl::Status LoadPackageFonts() {
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
|
||||||
|
static const char *KARLA_REGULAR = "Karla-Regular.ttf";
|
||||||
|
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
|
||||||
|
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
|
||||||
|
static const char *DROID_SANS = "DroidSans.ttf";
|
||||||
|
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
|
||||||
|
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
|
||||||
|
static const float FONT_SIZE_DEFAULT = 16.0f;
|
||||||
|
static const float FONT_SIZE_DROID_SANS = 18.0f;
|
||||||
|
static const float ICON_FONT_SIZE = 18.0f;
|
||||||
|
|
||||||
|
// Icon configuration
|
||||||
|
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
||||||
|
ImFontConfig icons_config;
|
||||||
|
icons_config.MergeMode = true;
|
||||||
|
icons_config.GlyphOffset.y = 5.0f;
|
||||||
|
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||||
|
icons_config.PixelSnapH = true;
|
||||||
|
|
||||||
|
// Japanese font configuration
|
||||||
|
ImFontConfig japanese_font_config;
|
||||||
|
japanese_font_config.MergeMode = true;
|
||||||
|
icons_config.GlyphOffset.y = 5.0f;
|
||||||
|
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||||
|
icons_config.PixelSnapH = true;
|
||||||
|
|
||||||
|
// List of fonts to be loaded
|
||||||
|
std::vector<const char *> font_paths = {
|
||||||
|
KARLA_REGULAR, ROBOTO_MEDIUM, COUSINE_REGULAR, IBM_PLEX_JP, DROID_SANS};
|
||||||
|
|
||||||
|
// Load fonts with associated icon and Japanese merges
|
||||||
|
for (const auto &font_path : font_paths) {
|
||||||
|
float font_size =
|
||||||
|
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
|
||||||
|
|
||||||
|
std::string actual_font_path;
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#if TARGET_OS_IOS == 1
|
||||||
|
const std::string kBundlePath = GetBundleResourcePath();
|
||||||
|
actual_font_path = kBundlePath + font_path;
|
||||||
|
#else
|
||||||
|
actual_font_path = absl::StrCat(GetBundleResourcePath(),
|
||||||
|
"Contents/Resources/font/", font_path);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
actual_font_path = absl::StrCat("assets/font/", font_path);
|
||||||
|
actual_font_path = std::filesystem::absolute(actual_font_path).string();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(), font_size)) {
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("Failed to load font from %s", actual_font_path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge icon set
|
||||||
|
std::string actual_icon_font_path = "";
|
||||||
|
const char *icon_font_path = FONT_ICON_FILE_NAME_MD;
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#if TARGET_OS_IOS == 1
|
||||||
|
const std::string kIconBundlePath = GetBundleResourcePath();
|
||||||
|
actual_icon_font_path = kIconBundlePath + "MaterialIcons-Regular.ttf";
|
||||||
|
#else
|
||||||
|
actual_icon_font_path =
|
||||||
|
absl::StrCat(GetBundleResourcePath(),
|
||||||
|
"Contents/Resources/font/MaterialIcons-Regular.ttf");
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
actual_icon_font_path = std::filesystem::absolute(icon_font_path).string();
|
||||||
|
#endif
|
||||||
|
if (!io.Fonts->AddFontFromFileTTF(actual_icon_font_path.data(),
|
||||||
|
ICON_FONT_SIZE, &icons_config,
|
||||||
|
icons_ranges)) {
|
||||||
|
return absl::InternalError("Failed to load icon fonts");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge Japanese font
|
||||||
|
std::string actual_japanese_font_path = "";
|
||||||
|
const char *japanese_font_path = NOTO_SANS_JP;
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#if TARGET_OS_IOS == 1
|
||||||
|
const std::string kJapaneseBundlePath = GetBundleResourcePath();
|
||||||
|
actual_japanese_font_path = kJapaneseBundlePath + japanese_font_path;
|
||||||
|
#else
|
||||||
|
actual_japanese_font_path =
|
||||||
|
absl::StrCat(GetBundleResourcePath(), "Contents/Resources/font/",
|
||||||
|
japanese_font_path);
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
actual_japanese_font_path = absl::StrCat("assets/font/", japanese_font_path);
|
||||||
|
actual_japanese_font_path =
|
||||||
|
std::filesystem::absolute(actual_japanese_font_path).string();
|
||||||
|
#endif
|
||||||
|
io.Fonts->AddFontFromFileTTF(actual_japanese_font_path.data(), 18.0f,
|
||||||
|
&japanese_font_config,
|
||||||
|
io.Fonts->GetGlyphRangesJapanese());
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
int CALLBACK EnumFontFamExProc(const LOGFONT *lpelfe, const TEXTMETRIC *lpntme,
|
||||||
|
DWORD FontType, LPARAM lParam) {
|
||||||
|
// Step 3: Load the font into ImGui
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadSystemFonts() {
|
||||||
|
HKEY hKey;
|
||||||
|
std::vector<std::string> fontPaths;
|
||||||
|
|
||||||
|
// Open the registry key where fonts are listed
|
||||||
|
if (RegOpenKeyEx(
|
||||||
|
HKEY_LOCAL_MACHINE,
|
||||||
|
TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"), 0,
|
||||||
|
KEY_READ, &hKey) == ERROR_SUCCESS) {
|
||||||
|
DWORD valueCount;
|
||||||
|
DWORD maxValueNameSize;
|
||||||
|
DWORD maxValueDataSize;
|
||||||
|
|
||||||
|
// Query the number of entries and the maximum size of the names and values
|
||||||
|
RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount,
|
||||||
|
&maxValueNameSize, &maxValueDataSize, NULL, NULL);
|
||||||
|
|
||||||
|
char *valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
|
||||||
|
BYTE *valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
|
||||||
|
|
||||||
|
// Enumerate all font entries
|
||||||
|
for (DWORD i = 0; i < valueCount; i++) {
|
||||||
|
DWORD valueNameSize = maxValueNameSize + 1; // +1 for null terminator
|
||||||
|
DWORD valueDataSize = maxValueDataSize + 1; // +1 for null terminator
|
||||||
|
DWORD valueType;
|
||||||
|
|
||||||
|
// Clear buffers
|
||||||
|
memset(valueName, 0, valueNameSize);
|
||||||
|
memset(valueData, 0, valueDataSize);
|
||||||
|
|
||||||
|
// Get the font name and file path
|
||||||
|
if (RegEnumValue(hKey, i, valueName, &valueNameSize, NULL, &valueType,
|
||||||
|
valueData, &valueDataSize) == ERROR_SUCCESS) {
|
||||||
|
if (valueType == REG_SZ) {
|
||||||
|
// Add the font file path to the vector
|
||||||
|
std::string fontPath(reinterpret_cast<char *>(valueData),
|
||||||
|
valueDataSize);
|
||||||
|
|
||||||
|
fontPaths.push_back(fontPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] valueName;
|
||||||
|
delete[] valueData;
|
||||||
|
|
||||||
|
RegCloseKey(hKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
|
||||||
|
// List of common font face names
|
||||||
|
static const std::unordered_set<std::string> commonFontFaceNames = {
|
||||||
|
"arial",
|
||||||
|
"times",
|
||||||
|
"cour",
|
||||||
|
"verdana",
|
||||||
|
"tahoma",
|
||||||
|
"comic",
|
||||||
|
"Impact",
|
||||||
|
"ariblk",
|
||||||
|
"Trebuchet MS",
|
||||||
|
"Georgia",
|
||||||
|
"Palatino Linotype",
|
||||||
|
"Lucida Sans Unicode",
|
||||||
|
"Tahoma",
|
||||||
|
"Lucida Console"};
|
||||||
|
|
||||||
|
for (auto &fontPath : fontPaths) {
|
||||||
|
// Check if the font path has a "C:\" prefix
|
||||||
|
if (fontPath.substr(0, 2) != "C:") {
|
||||||
|
// Add "C:\Windows\Fonts\" prefix to the font path
|
||||||
|
fontPath = absl::StrFormat("C:\\Windows\\Fonts\\%s", fontPath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the font file has a .ttf or .TTF extension
|
||||||
|
std::string extension = fontPath.substr(fontPath.find_last_of(".") + 1);
|
||||||
|
if (extension == "ttf" || extension == "TTF") {
|
||||||
|
// Get the font face name from the font path
|
||||||
|
std::string fontFaceName =
|
||||||
|
fontPath.substr(fontPath.find_last_of("\\/") + 1);
|
||||||
|
fontFaceName = fontFaceName.substr(0, fontFaceName.find_last_of("."));
|
||||||
|
|
||||||
|
// Check if the font face name is in the common font face names list
|
||||||
|
if (commonFontFaceNames.find(fontFaceName) != commonFontFaceNames.end()) {
|
||||||
|
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f);
|
||||||
|
|
||||||
|
// Merge icon set
|
||||||
|
// Icon configuration
|
||||||
|
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
||||||
|
ImFontConfig icons_config;
|
||||||
|
static const float ICON_FONT_SIZE = 18.0f;
|
||||||
|
icons_config.MergeMode = true;
|
||||||
|
icons_config.GlyphOffset.y = 5.0f;
|
||||||
|
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||||
|
icons_config.PixelSnapH = true;
|
||||||
|
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE,
|
||||||
|
&icons_config, icons_ranges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
|
||||||
|
void LoadSystemFonts() {
|
||||||
|
// Load Linux System Fonts into ImGui
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
15
src/app/core/platform/font_loader.h
Normal file
15
src/app/core/platform/font_loader.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_FONTLOADER_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_FONTLOADER_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
void LoadSystemFonts();
|
||||||
|
absl::Status LoadPackageFonts();
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PLATFORM_FONTLOADER_H
|
||||||
59
src/app/core/platform/font_loader.mm
Normal file
59
src/app/core/platform/font_loader.mm
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include "app/core/platform/font_loader.h"
|
||||||
|
|
||||||
|
#import <CoreText/CoreText.h>
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#if TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1
|
||||||
|
/* iOS */
|
||||||
|
void yaze::core::LoadSystemFonts() {}
|
||||||
|
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
/* macOS */
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
void yaze::core::LoadSystemFonts() {
|
||||||
|
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
|
||||||
|
|
||||||
|
for (NSString *fontName in fontNames) {
|
||||||
|
NSFont *font = [NSFont fontWithName:fontName size:14.0];
|
||||||
|
if (!font) {
|
||||||
|
NSLog(@"Font not found: %@", fontName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTFontDescriptorRef fontDescriptor =
|
||||||
|
CTFontDescriptorCreateWithNameAndSize((CFStringRef)font.fontName, font.pointSize);
|
||||||
|
CFURLRef fontURL = (CFURLRef)CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontURLAttribute);
|
||||||
|
NSString *fontPath = [(NSURL *)fontURL path];
|
||||||
|
CFRelease(fontDescriptor);
|
||||||
|
|
||||||
|
if (fontPath != nil && [[NSFileManager defaultManager] isReadableFileAtPath:fontPath]) {
|
||||||
|
// Load the font into ImGui
|
||||||
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
ImFontConfig icons_config;
|
||||||
|
icons_config.MergeMode = true;
|
||||||
|
icons_config.GlyphOffset.y = 5.0f;
|
||||||
|
icons_config.GlyphMinAdvanceX = 13.0f;
|
||||||
|
icons_config.PixelSnapH = true;
|
||||||
|
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
|
||||||
|
static const float ICON_FONT_SIZE = 18.0f;
|
||||||
|
ImFont *imFont = io.Fonts->AddFontFromFileTTF([fontPath UTF8String], 14.0f);
|
||||||
|
if (!imFont) {
|
||||||
|
NSLog(@"Failed to load font: %@", fontPath);
|
||||||
|
}
|
||||||
|
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE, &icons_config,
|
||||||
|
icons_ranges);
|
||||||
|
} else {
|
||||||
|
NSLog(@"Font file not accessible: %@", fontPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontURL) {
|
||||||
|
CFRelease(fontURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
81
src/app/core/platform/renderer.h
Normal file
81
src/app/core/platform/renderer.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_RENDERER_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_RENDERER_H
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "app/core/utils/sdl_deleter.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Renderer
|
||||||
|
* @brief The Renderer class represents the renderer for the Yaze application.
|
||||||
|
*
|
||||||
|
* This class is a singleton that provides functionality for creating and
|
||||||
|
* rendering bitmaps to the screen. It also includes methods for updating
|
||||||
|
* bitmaps on the screen.
|
||||||
|
*/
|
||||||
|
class Renderer {
|
||||||
|
public:
|
||||||
|
static Renderer &GetInstance() {
|
||||||
|
static Renderer instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status CreateRenderer(SDL_Window *window) {
|
||||||
|
renderer_ = std::unique_ptr<SDL_Renderer, SDL_Deleter>(SDL_CreateRenderer(
|
||||||
|
window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED));
|
||||||
|
if (renderer_ == nullptr) {
|
||||||
|
return absl::InternalError(
|
||||||
|
absl::StrFormat("SDL_CreateRenderer: %s\n", SDL_GetError()));
|
||||||
|
}
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer_.get(), SDL_BLENDMODE_BLEND);
|
||||||
|
SDL_SetRenderDrawColor(renderer_.get(), 0x00, 0x00, 0x00, 0x00);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto renderer() -> SDL_Renderer * { return renderer_.get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Used to render a bitmap to the screen.
|
||||||
|
*/
|
||||||
|
void RenderBitmap(gfx::Bitmap *bitmap) {
|
||||||
|
bitmap->CreateTexture(renderer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Used to update a bitmap on the screen.
|
||||||
|
*/
|
||||||
|
void UpdateBitmap(gfx::Bitmap *bitmap) {
|
||||||
|
bitmap->UpdateTexture(renderer_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status CreateAndRenderBitmap(int width, int height, int depth,
|
||||||
|
const std::vector<uint8_t> &data,
|
||||||
|
gfx::Bitmap &bitmap,
|
||||||
|
gfx::SnesPalette &palette) {
|
||||||
|
bitmap.Create(width, height, depth, data);
|
||||||
|
RETURN_IF_ERROR(bitmap.ApplyPalette(palette));
|
||||||
|
RenderBitmap(&bitmap);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Renderer() = default;
|
||||||
|
|
||||||
|
std::unique_ptr<SDL_Renderer, SDL_Deleter> renderer_;
|
||||||
|
|
||||||
|
Renderer(const Renderer &) = delete;
|
||||||
|
Renderer &operator=(const Renderer &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
29
src/app/core/platform/view_controller.h
Normal file
29
src/app/core/platform/view_controller.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_VIEW_CONTROLLER_H
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#if TARGET_OS_OSX
|
||||||
|
#include "imgui_impl_osx.h"
|
||||||
|
@interface AppViewController : NSViewController <NSWindowDelegate>
|
||||||
|
@end
|
||||||
|
#else
|
||||||
|
@interface AppViewController : UIViewController <MTKViewDelegate>
|
||||||
|
@property(nonatomic) yaze::core::Controller *controller;
|
||||||
|
@property(nonatomic) UIHoverGestureRecognizer *hoverGestureRecognizer;
|
||||||
|
@property(nonatomic) UIPinchGestureRecognizer *pinchRecognizer;
|
||||||
|
@property(nonatomic) UISwipeGestureRecognizer *swipeRecognizer;
|
||||||
|
@property(nonatomic) UILongPressGestureRecognizer *longPressRecognizer;
|
||||||
|
@end
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@interface AppViewController () <MTKViewDelegate>
|
||||||
|
@property(nonatomic, readonly) MTKView *mtkView;
|
||||||
|
@property(nonatomic, strong) id<MTLDevice> device;
|
||||||
|
@property(nonatomic, strong) id<MTLCommandQueue> commandQueue;
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PLATFORM_APP_VIEW_CONTROLLER_H
|
||||||
188
src/app/core/project.cc
Normal file
188
src/app/core/project.cc
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#include "project.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
|
||||||
|
absl::Status Project::Open(const std::string& project_path) {
|
||||||
|
filepath = project_path;
|
||||||
|
name = project_path.substr(project_path.find_last_of("/") + 1);
|
||||||
|
|
||||||
|
std::ifstream in(project_path);
|
||||||
|
|
||||||
|
if (!in.good()) {
|
||||||
|
return absl::InternalError("Could not open project file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
std::getline(in, name);
|
||||||
|
std::getline(in, filepath);
|
||||||
|
std::getline(in, rom_filename_);
|
||||||
|
std::getline(in, code_folder_);
|
||||||
|
std::getline(in, labels_filename_);
|
||||||
|
std::getline(in, keybindings_file);
|
||||||
|
|
||||||
|
while (std::getline(in, line)) {
|
||||||
|
if (line == kEndOfProjectFile) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Project::Save() {
|
||||||
|
RETURN_IF_ERROR(CheckForEmptyFields());
|
||||||
|
|
||||||
|
std::ofstream out(filepath + "/" + name + ".yaze");
|
||||||
|
if (!out.good()) {
|
||||||
|
return absl::InternalError("Could not open project file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
out << name << std::endl;
|
||||||
|
out << filepath << std::endl;
|
||||||
|
out << rom_filename_ << std::endl;
|
||||||
|
out << code_folder_ << std::endl;
|
||||||
|
out << labels_filename_ << std::endl;
|
||||||
|
out << keybindings_file << std::endl;
|
||||||
|
|
||||||
|
out << kEndOfProjectFile << std::endl;
|
||||||
|
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceLabelManager::LoadLabels(const std::string& filename) {
|
||||||
|
std::ifstream file(filename);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
// Create the file if it does not exist
|
||||||
|
std::ofstream create_file(filename);
|
||||||
|
if (!create_file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
create_file.close();
|
||||||
|
file.open(filename);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filename_ = filename;
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
std::istringstream iss(line);
|
||||||
|
std::string type, key, value;
|
||||||
|
if (std::getline(iss, type, ',') && std::getline(iss, key, ',') &&
|
||||||
|
std::getline(iss, value)) {
|
||||||
|
labels_[type][key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
labels_loaded_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceLabelManager::SaveLabels() {
|
||||||
|
if (!labels_loaded_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::ofstream file(filename_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& type_pair : labels_) {
|
||||||
|
for (const auto& label_pair : type_pair.second) {
|
||||||
|
file << type_pair.first << "," << label_pair.first << ","
|
||||||
|
<< label_pair.second << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceLabelManager::DisplayLabels(bool* p_open) {
|
||||||
|
if (!labels_loaded_) {
|
||||||
|
ImGui::Text("No labels loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Begin("Resource Labels", p_open)) {
|
||||||
|
for (const auto& type_pair : labels_) {
|
||||||
|
if (ImGui::TreeNode(type_pair.first.c_str())) {
|
||||||
|
for (const auto& label_pair : type_pair.second) {
|
||||||
|
std::string label_id = type_pair.first + "_" + label_pair.first;
|
||||||
|
ImGui::Text("%s: %s", label_pair.first.c_str(),
|
||||||
|
label_pair.second.c_str());
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Update Labels")) {
|
||||||
|
if (SaveLabels()) {
|
||||||
|
ImGui::Text("Labels updated successfully!");
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Failed to update labels.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceLabelManager::EditLabel(const std::string& type,
|
||||||
|
const std::string& key,
|
||||||
|
const std::string& newValue) {
|
||||||
|
labels_[type][key] = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceLabelManager::SelectableLabelWithNameEdit(
|
||||||
|
bool selected, const std::string& type, const std::string& key,
|
||||||
|
const std::string& defaultValue) {
|
||||||
|
std::string label = CreateOrGetLabel(type, key, defaultValue);
|
||||||
|
ImGui::Selectable(label.c_str(), selected,
|
||||||
|
ImGuiSelectableFlags_AllowDoubleClick);
|
||||||
|
std::string label_id = type + "_" + key;
|
||||||
|
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
ImGui::OpenPopup(label_id.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginPopupContextItem(label_id.c_str())) {
|
||||||
|
std::string* new_label = &labels_[type][key];
|
||||||
|
if (ImGui::InputText("##Label", new_label,
|
||||||
|
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||||
|
labels_[type][key] = *new_label;
|
||||||
|
}
|
||||||
|
if (ImGui::Button(ICON_MD_CLOSE)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ResourceLabelManager::GetLabel(const std::string& type,
|
||||||
|
const std::string& key) {
|
||||||
|
return labels_[type][key];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ResourceLabelManager::CreateOrGetLabel(
|
||||||
|
const std::string& type, const std::string& key,
|
||||||
|
const std::string& defaultValue) {
|
||||||
|
if (labels_.find(type) == labels_.end()) {
|
||||||
|
labels_[type] = std::unordered_map<std::string, std::string>();
|
||||||
|
}
|
||||||
|
if (labels_[type].find(key) == labels_[type].end()) {
|
||||||
|
labels_[type][key] = defaultValue;
|
||||||
|
}
|
||||||
|
return labels_[type][key];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace yaze
|
||||||
135
src/app/core/project.h
Normal file
135
src/app/core/project.h
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PROJECT_H
|
||||||
|
#define YAZE_APP_CORE_PROJECT_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/utils/file_util.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
|
||||||
|
const std::string kRecentFilesFilename = "recent_files.txt";
|
||||||
|
constexpr char kEndOfProjectFile[] = "EndOfProjectFile";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct Project
|
||||||
|
* @brief Represents a project in the application.
|
||||||
|
*
|
||||||
|
* A project is a collection of files and resources that are used in the
|
||||||
|
* creation of a Zelda3 hack that can be saved and loaded. This makes it so the
|
||||||
|
* user can have different rom file names for a single project and keep track of
|
||||||
|
* backups.
|
||||||
|
*/
|
||||||
|
struct Project {
|
||||||
|
absl::Status Create(const std::string& project_name) {
|
||||||
|
name = project_name;
|
||||||
|
project_opened_ = true;
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
absl::Status CheckForEmptyFields() {
|
||||||
|
if (name.empty() || filepath.empty() || rom_filename_.empty() ||
|
||||||
|
code_folder_.empty() || labels_filename_.empty()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Project fields cannot be empty. Please load a rom file, set your "
|
||||||
|
"code folder, and set your labels file. See HELP for more details.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
absl::Status Open(const std::string &project_path);
|
||||||
|
absl::Status Save();
|
||||||
|
|
||||||
|
bool project_opened_ = false;
|
||||||
|
std::string name;
|
||||||
|
std::string flags = "";
|
||||||
|
std::string filepath;
|
||||||
|
std::string rom_filename_ = "";
|
||||||
|
std::string code_folder_ = "";
|
||||||
|
std::string labels_filename_ = "";
|
||||||
|
std::string keybindings_file = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default types
|
||||||
|
static constexpr absl::string_view kDefaultTypes[] = {
|
||||||
|
"Dungeon Names", "Dungeon Room Names", "Overworld Map Names"};
|
||||||
|
|
||||||
|
struct ResourceLabelManager {
|
||||||
|
bool LoadLabels(const std::string& filename);
|
||||||
|
bool SaveLabels();
|
||||||
|
void DisplayLabels(bool* p_open);
|
||||||
|
void EditLabel(const std::string& type, const std::string& key,
|
||||||
|
const std::string& newValue);
|
||||||
|
void SelectableLabelWithNameEdit(bool selected, const std::string& type,
|
||||||
|
const std::string& key,
|
||||||
|
const std::string& defaultValue);
|
||||||
|
std::string GetLabel(const std::string& type, const std::string& key);
|
||||||
|
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
|
||||||
|
const std::string& defaultValue);
|
||||||
|
|
||||||
|
bool labels_loaded_ = false;
|
||||||
|
std::string filename_;
|
||||||
|
struct ResourceType {
|
||||||
|
std::string key_name;
|
||||||
|
std::string display_description;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
|
||||||
|
labels_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RecentFilesManager {
|
||||||
|
public:
|
||||||
|
RecentFilesManager() : RecentFilesManager(kRecentFilesFilename) {}
|
||||||
|
RecentFilesManager(const std::string& filename) : filename_(filename) {}
|
||||||
|
|
||||||
|
void AddFile(const std::string& file_path) {
|
||||||
|
// Add a file to the list, avoiding duplicates
|
||||||
|
auto it = std::find(recent_files_.begin(), recent_files_.end(), file_path);
|
||||||
|
if (it == recent_files_.end()) {
|
||||||
|
recent_files_.push_back(file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Save() {
|
||||||
|
std::ofstream file(filename_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return; // Handle the error appropriately
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& file_path : recent_files_) {
|
||||||
|
file << file_path << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load() {
|
||||||
|
std::ifstream file(filename_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recent_files_.clear();
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
if (!line.empty()) {
|
||||||
|
recent_files_.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string>& GetRecentFiles() const {
|
||||||
|
return recent_files_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string filename_;
|
||||||
|
std::vector<std::string> recent_files_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PROJECT_H
|
||||||
95
src/app/core/utils/file_util.cc
Normal file
95
src/app/core/utils/file_util.cc
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#include "file_util.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
std::string GetFileExtension(const std::string &filename) {
|
||||||
|
size_t dot = filename.find_last_of(".");
|
||||||
|
if (dot == std::string::npos) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return filename.substr(dot + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetFileName(const std::string &filename) {
|
||||||
|
size_t slash = filename.find_last_of("/");
|
||||||
|
if (slash == std::string::npos) {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
return filename.substr(slash + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string LoadFile(const std::string &filename) {
|
||||||
|
std::string contents;
|
||||||
|
std::ifstream file(filename);
|
||||||
|
if (file.is_open()) {
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
contents = buffer.str();
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
// Throw an exception
|
||||||
|
throw std::runtime_error("Could not open file: " + filename);
|
||||||
|
}
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string LoadConfigFile(const std::string &filename) {
|
||||||
|
std::string contents;
|
||||||
|
Platform platform;
|
||||||
|
#if defined(_WIN32)
|
||||||
|
platform = Platform::kWindows;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
platform = Platform::kMacOS;
|
||||||
|
#else
|
||||||
|
platform = Platform::kLinux;
|
||||||
|
#endif
|
||||||
|
std::string filepath = GetConfigDirectory(platform) + "/" + filename;
|
||||||
|
std::ifstream file(filepath);
|
||||||
|
if (file.is_open()) {
|
||||||
|
std::stringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
contents = buffer.str();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveFile(const std::string &filename, const std::string &contents,
|
||||||
|
Platform platform) {
|
||||||
|
std::string filepath = GetConfigDirectory(platform) + "/" + filename;
|
||||||
|
std::ofstream file(filepath);
|
||||||
|
if (file.is_open()) {
|
||||||
|
file << contents;
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetConfigDirectory(Platform platform) {
|
||||||
|
std::string config_directory = ".yaze";
|
||||||
|
switch (platform) {
|
||||||
|
case Platform::kWindows:
|
||||||
|
config_directory = "~/AppData/Roaming/yaze";
|
||||||
|
break;
|
||||||
|
case Platform::kMacOS:
|
||||||
|
case Platform::kLinux:
|
||||||
|
config_directory = "~/.config/yaze";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return config_directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
23
src/app/core/utils/file_util.h
Normal file
23
src/app/core/utils/file_util.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_UTILS_FILE_UTIL_H
|
||||||
|
#define YAZE_APP_CORE_UTILS_FILE_UTIL_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
|
||||||
|
|
||||||
|
std::string GetFileExtension(const std::string &filename);
|
||||||
|
std::string GetFileName(const std::string &filename);
|
||||||
|
std::string LoadFile(const std::string &filename);
|
||||||
|
std::string LoadConfigFile(const std::string &filename);
|
||||||
|
std::string GetConfigDirectory(Platform platform);
|
||||||
|
|
||||||
|
void SaveFile(const std::string &filename, const std::string &data,
|
||||||
|
Platform platform);
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_UTILS_FILE_UTIL_H
|
||||||
34
src/app/core/utils/sdl_deleter.h
Normal file
34
src/app/core/utils/sdl_deleter.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_UTILS_SDL_DELETER_H_
|
||||||
|
#define YAZE_APP_CORE_UTILS_SDL_DELETER_H_
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deleter for SDL_Window and SDL_Renderer.
|
||||||
|
*/
|
||||||
|
struct SDL_Deleter {
|
||||||
|
void operator()(SDL_Window *p) const { SDL_DestroyWindow(p); }
|
||||||
|
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deleter for SDL_Texture.
|
||||||
|
*/
|
||||||
|
struct SDL_Texture_Deleter {
|
||||||
|
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deleter for SDL_Surface.
|
||||||
|
*/
|
||||||
|
struct SDL_Surface_Deleter {
|
||||||
|
void operator()(SDL_Surface *p) const { SDL_FreeSurface(p); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_UTILS_SDL_DELETER_H_
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
|
|
||||||
add_library(delta-service delta.proto)
|
|
||||||
target_link_libraries(delta-service
|
|
||||||
PUBLIC
|
|
||||||
protobuf::libprotobuf
|
|
||||||
gRPC::grpc
|
|
||||||
gRPC::grpc++
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(delta-service
|
|
||||||
PUBLIC
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
|
||||||
${PROTOBUF_INCLUDE_PATH}
|
|
||||||
)
|
|
||||||
|
|
||||||
get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
|
|
||||||
|
|
||||||
# compile the message types
|
|
||||||
protobuf_generate(TARGET delta-service LANGUAGE cpp)
|
|
||||||
|
|
||||||
# compile the GRPC services
|
|
||||||
protobuf_generate(
|
|
||||||
TARGET
|
|
||||||
delta-service
|
|
||||||
LANGUAGE
|
|
||||||
grpc
|
|
||||||
GENERATE_EXTENSIONS
|
|
||||||
.grpc.pb.h
|
|
||||||
.grpc.pb.cc
|
|
||||||
PLUGIN
|
|
||||||
"protoc-gen-grpc=${grpc_cpp_plugin_location}"
|
|
||||||
)
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#include "client.h"
|
|
||||||
|
|
||||||
#include <google/protobuf/message.h>
|
|
||||||
#include <grpc/support/log.h>
|
|
||||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
|
||||||
#include <grpcpp/grpcpp.h>
|
|
||||||
#include <grpcpp/health_check_service_interface.h>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "src/app/delta/delta.grpc.pb.h"
|
|
||||||
#include "src/app/delta/delta.pb.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace delta {
|
|
||||||
|
|
||||||
using grpc::Channel;
|
|
||||||
using grpc::ClientAsyncResponseReader;
|
|
||||||
using grpc::ClientContext;
|
|
||||||
using grpc::CompletionQueue;
|
|
||||||
using grpc::Server;
|
|
||||||
using grpc::ServerBuilder;
|
|
||||||
using grpc::Status;
|
|
||||||
|
|
||||||
void Client::CreateChannel() {
|
|
||||||
auto channel = grpc::CreateChannel("localhost:50051",
|
|
||||||
grpc::InsecureChannelCredentials());
|
|
||||||
stub_ = ::YazeDelta::NewStub(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status Client::InitRepo(std::string author_name,
|
|
||||||
std::string project_name) {
|
|
||||||
Repository new_repo;
|
|
||||||
new_repo.set_author_name(author_name);
|
|
||||||
new_repo.set_project_name(project_name);
|
|
||||||
new_repo.mutable_tree()->Add();
|
|
||||||
|
|
||||||
InitRequest request;
|
|
||||||
request.set_allocated_repo(&new_repo);
|
|
||||||
|
|
||||||
InitResponse response;
|
|
||||||
Status status = stub_->Init(&rpc_context, request, &response);
|
|
||||||
|
|
||||||
if (!status.ok()) {
|
|
||||||
std::cerr << status.error_code() << ": " << status.error_message()
|
|
||||||
<< std::endl;
|
|
||||||
return absl::InternalError(status.error_message());
|
|
||||||
}
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace delta
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#ifndef YAZE_APP_DELTA_CLIENT_H
|
|
||||||
#define YAZE_APP_DELTA_CLIENT_H
|
|
||||||
|
|
||||||
#include <google/protobuf/message.h>
|
|
||||||
#include <grpc/support/log.h>
|
|
||||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
|
||||||
#include <grpcpp/grpcpp.h>
|
|
||||||
#include <grpcpp/health_check_service_interface.h>
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "src/app/delta/delta.grpc.pb.h"
|
|
||||||
#include "src/app/delta/delta.pb.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace delta {
|
|
||||||
|
|
||||||
using grpc::Channel;
|
|
||||||
using grpc::ClientAsyncResponseReader;
|
|
||||||
using grpc::ClientContext;
|
|
||||||
using grpc::CompletionQueue;
|
|
||||||
using grpc::Server;
|
|
||||||
using grpc::ServerBuilder;
|
|
||||||
using grpc::Status;
|
|
||||||
|
|
||||||
class Client {
|
|
||||||
public:
|
|
||||||
Client() = default;
|
|
||||||
void CreateChannel();
|
|
||||||
absl::Status InitRepo(std::string author_name, std::string project_name);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ClientContext rpc_context;
|
|
||||||
std::vector<Repository> repos_;
|
|
||||||
std::unique_ptr<YazeDelta::Stub> stub_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace delta
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#if defined(_WIN32)
|
|
||||||
#define main SDL_main
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "absl/debugging/failure_signal_handler.h"
|
|
||||||
#include "absl/debugging/symbolize.h"
|
|
||||||
#include "app/core/controller.h"
|
|
||||||
#include "app/delta/viewer.h"
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
|
||||||
absl::InitializeSymbolizer(argv[0]);
|
|
||||||
|
|
||||||
absl::FailureSignalHandlerOptions options;
|
|
||||||
absl::InstallFailureSignalHandler(options);
|
|
||||||
|
|
||||||
yaze::app::core::Controller controller;
|
|
||||||
yaze::app::delta::Viewer viewer;
|
|
||||||
|
|
||||||
auto entry_status = controller.onEntry();
|
|
||||||
if (!entry_status.ok()) {
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (controller.isActive()) {
|
|
||||||
controller.onInput();
|
|
||||||
viewer.Update();
|
|
||||||
controller.doRender();
|
|
||||||
}
|
|
||||||
controller.onExit();
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
option cc_enable_arenas = true;
|
|
||||||
|
|
||||||
service YazeDelta {
|
|
||||||
rpc Init(InitRequest) returns (InitResponse) {}
|
|
||||||
|
|
||||||
rpc Clone(CloneRequest) returns (CloneResponse) {}
|
|
||||||
|
|
||||||
rpc Push(PushRequest) returns (PushResponse) {}
|
|
||||||
rpc Pull(PullRequest) returns (PullResponse) {}
|
|
||||||
|
|
||||||
rpc Sync(stream SyncRequest) returns (stream SyncResponse) {}
|
|
||||||
|
|
||||||
rpc CreateBranch(CreateBranchRequest) returns (CreateBranchResponse) {}
|
|
||||||
rpc DeleteBranch(DeleteBranchRequest) returns (DeleteBranchResponse) {}
|
|
||||||
|
|
||||||
rpc Merge(MergeRequest) returns (MergeResponse) {}
|
|
||||||
rpc UndoMerge(UndoMergeRequest) returns (UndoMergeResponse) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ChangeType {
|
|
||||||
OVERWORLD_MAP = 0;
|
|
||||||
DUNGEON_MAP = 1;
|
|
||||||
MONOLOGUE = 2;
|
|
||||||
PALETTE = 3;
|
|
||||||
OBJECT = 4;
|
|
||||||
ASSEMBLY = 5;
|
|
||||||
MISC = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Delta {
|
|
||||||
int64 offset = 1;
|
|
||||||
int64 length = 2;
|
|
||||||
bytes data = 3;
|
|
||||||
ChangeType type = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Commit {
|
|
||||||
int64 commit_id = 1;
|
|
||||||
int64 parent_commit_id = 2;
|
|
||||||
string author_name = 3;
|
|
||||||
string message_header = 4;
|
|
||||||
optional string message_body = 5;
|
|
||||||
repeated Delta delta = 6;
|
|
||||||
int64 signature = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Branch {
|
|
||||||
string branch_name = 1;
|
|
||||||
optional string parent_name = 2;
|
|
||||||
repeated Commit commits = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Repository {
|
|
||||||
string project_name = 1;
|
|
||||||
string author_name = 2;
|
|
||||||
int64 signature = 3;
|
|
||||||
optional bool locked = 4;
|
|
||||||
optional string password = 5;
|
|
||||||
repeated Branch tree = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InitRequest {
|
|
||||||
Repository repo = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InitResponse {
|
|
||||||
int32 response = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CloneRequest {}
|
|
||||||
|
|
||||||
message CloneResponse {}
|
|
||||||
|
|
||||||
message PushRequest {
|
|
||||||
string author_name = 1;
|
|
||||||
string repository_name= 2;
|
|
||||||
string branch_name = 3;
|
|
||||||
repeated Commit commits = 4;
|
|
||||||
}
|
|
||||||
message PushResponse {}
|
|
||||||
|
|
||||||
message PullRequest {
|
|
||||||
string repository_name = 1;
|
|
||||||
string branch_name = 2;
|
|
||||||
repeated Commit commits = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PullResponse {}
|
|
||||||
|
|
||||||
message SyncRequest {}
|
|
||||||
|
|
||||||
message SyncResponse {}
|
|
||||||
|
|
||||||
message CreateBranchRequest {}
|
|
||||||
message CreateBranchResponse {}
|
|
||||||
|
|
||||||
message DeleteBranchRequest {}
|
|
||||||
message DeleteBranchResponse {}
|
|
||||||
|
|
||||||
message MergeRequest {}
|
|
||||||
message MergeResponse {}
|
|
||||||
|
|
||||||
message UndoMergeRequest {}
|
|
||||||
message UndoMergeResponse {}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
#include "service.h"
|
|
||||||
|
|
||||||
#include <google/protobuf/message.h>
|
|
||||||
#include <grpc/support/log.h>
|
|
||||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
|
||||||
#include <grpcpp/grpcpp.h>
|
|
||||||
#include <grpcpp/health_check_service_interface.h>
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "absl/status/statusor.h"
|
|
||||||
#include "src/app/delta/delta.grpc.pb.h"
|
|
||||||
#include "src/app/delta/delta.pb.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace delta {
|
|
||||||
|
|
||||||
using grpc::Channel;
|
|
||||||
using grpc::ClientAsyncResponseReader;
|
|
||||||
using grpc::ClientContext;
|
|
||||||
using grpc::CompletionQueue;
|
|
||||||
using grpc::Server;
|
|
||||||
using grpc::ServerBuilder;
|
|
||||||
using grpc::Status;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
auto FindRepository(std::vector<Repository>& repos, const std::string& name) {
|
|
||||||
for (auto& repo : repos) {
|
|
||||||
if (repo.project_name() == name) {
|
|
||||||
return repo.mutable_tree();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto FindBranch(google::protobuf::RepeatedPtrField<Branch>* repo,
|
|
||||||
const std::string& branch_name) {
|
|
||||||
for (auto it = repo->begin(); it != repo->end(); ++it) {
|
|
||||||
if (it->branch_name() == branch_name) {
|
|
||||||
return it->mutable_commits();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
Status DeltaService::Init(grpc::ServerContext* context,
|
|
||||||
const InitRequest* request, InitResponse* reply) {
|
|
||||||
std::filesystem::create_directories("./.yaze");
|
|
||||||
repos_.push_back(request->repo());
|
|
||||||
// std::ofstream commit_stream("./.yaze/commits");
|
|
||||||
// for (const auto& repo : repos_) {
|
|
||||||
// for (const auto& branch : repo.tree()) {
|
|
||||||
// for (const auto& commit : branch.commits()) {
|
|
||||||
// commit_stream << commit.DebugString();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status DeltaService::Push(grpc::ServerContext* context,
|
|
||||||
const PushRequest* request, PushResponse* reply) {
|
|
||||||
const auto& repository_name = request->repository_name();
|
|
||||||
const auto& branch_name = request->branch_name();
|
|
||||||
auto repo = FindRepository(repos_, repository_name);
|
|
||||||
auto mutable_commits = FindBranch(repo, branch_name);
|
|
||||||
auto size = request->commits().size();
|
|
||||||
for (int i = 1; i < size; ++i) {
|
|
||||||
*mutable_commits->Add() = request->commits().at(i);
|
|
||||||
}
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status DeltaService::Pull(grpc::ServerContext* context,
|
|
||||||
const PullRequest* request, PullResponse* reply) {
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
Status DeltaService::Clone(grpc::ServerContext* context,
|
|
||||||
const CloneRequest* request, CloneResponse* reply) {
|
|
||||||
return Status::OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace delta
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#ifndef YAZE_APP_DELTA_SERVICE_H
|
|
||||||
#define YAZE_APP_DELTA_SERVICE_H
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include <google/protobuf/message.h>
|
|
||||||
#include <grpc/support/log.h>
|
|
||||||
#include <grpcpp/ext/proto_server_reflection_plugin.h>
|
|
||||||
#include <grpcpp/grpcpp.h>
|
|
||||||
#include <grpcpp/health_check_service_interface.h>
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "src/app/delta/delta.grpc.pb.h"
|
|
||||||
#include "src/app/delta/delta.pb.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace delta {
|
|
||||||
|
|
||||||
using grpc::Status;
|
|
||||||
|
|
||||||
class DeltaService final : public ::YazeDelta::Service {
|
|
||||||
public:
|
|
||||||
Status Init(grpc::ServerContext* context, const InitRequest* request,
|
|
||||||
InitResponse* reply) override;
|
|
||||||
|
|
||||||
Status Push(grpc::ServerContext* context, const PushRequest* request,
|
|
||||||
PushResponse* reply) override;
|
|
||||||
|
|
||||||
Status Pull(grpc::ServerContext* context, const PullRequest* request,
|
|
||||||
PullResponse* reply) override;
|
|
||||||
|
|
||||||
Status Clone(grpc::ServerContext* context, const CloneRequest* request,
|
|
||||||
CloneResponse* reply) override;
|
|
||||||
|
|
||||||
auto Repos() const { return repos_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<Repository> repos_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace delta
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
#include "viewer.h"
|
|
||||||
|
|
||||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
|
||||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
|
||||||
#include <imgui_memory_editor.h>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "app/core/constants.h"
|
|
||||||
#include "app/gfx/snes_palette.h"
|
|
||||||
#include "app/gfx/snes_tile.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "gui/canvas.h"
|
|
||||||
#include "gui/icons.h"
|
|
||||||
#include "gui/input.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace delta {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
constexpr ImGuiWindowFlags kMainEditorFlags =
|
|
||||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
|
|
||||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
|
|
||||||
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
|
||||||
|
|
||||||
void NewMasterFrame() {
|
|
||||||
const ImGuiIO& io = ImGui::GetIO();
|
|
||||||
ImGui::NewFrame();
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
|
||||||
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
|
||||||
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
|
|
||||||
|
|
||||||
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
|
||||||
ImGui::End();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void Viewer::Update() {
|
|
||||||
NewMasterFrame();
|
|
||||||
DrawYazeMenu();
|
|
||||||
DrawFileDialog();
|
|
||||||
|
|
||||||
ImGui::Text(ICON_MD_CHANGE_HISTORY);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Text("%s", rom_.GetTitle());
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
ImGui::Button(ICON_MD_SYNC);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Button(ICON_MD_ARROW_UPWARD);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Button(ICON_MD_ARROW_DOWNWARD);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Button(ICON_MD_MERGE);
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
ImGui::Button(ICON_MD_MANAGE_HISTORY);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Button(ICON_MD_LAN);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Button(ICON_MD_COMMIT);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Button(ICON_MD_DIFFERENCE);
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(75.f);
|
|
||||||
ImGui::Button(ICON_MD_SEND);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::InputText("Server Address", &client_address_);
|
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(75.f);
|
|
||||||
ImGui::Button(ICON_MD_DOWNLOAD);
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::InputText("Repository Source", &client_address_);
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
DrawBranchTree();
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Viewer::DrawFileDialog() {
|
|
||||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
|
|
||||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
|
||||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
|
||||||
rom_.LoadFromFile(filePathName);
|
|
||||||
}
|
|
||||||
ImGuiFileDialog::Instance()->Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Viewer::DrawYazeMenu() {
|
|
||||||
MENU_BAR()
|
|
||||||
DrawFileMenu();
|
|
||||||
DrawViewMenu();
|
|
||||||
END_MENU_BAR()
|
|
||||||
}
|
|
||||||
|
|
||||||
void Viewer::DrawFileMenu() const {
|
|
||||||
if (ImGui::BeginMenu("File")) {
|
|
||||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
|
||||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
|
|
||||||
".sfc,.smc", ".");
|
|
||||||
}
|
|
||||||
|
|
||||||
MENU_ITEM2("Save", "Ctrl+S") {}
|
|
||||||
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Viewer::DrawViewMenu() {
|
|
||||||
static bool show_imgui_metrics = false;
|
|
||||||
static bool show_imgui_style_editor = false;
|
|
||||||
static bool show_memory_editor = false;
|
|
||||||
static bool show_imgui_demo = false;
|
|
||||||
|
|
||||||
if (show_imgui_metrics) {
|
|
||||||
ImGui::ShowMetricsWindow(&show_imgui_metrics);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show_memory_editor) {
|
|
||||||
static MemoryEditor mem_edit;
|
|
||||||
mem_edit.DrawWindow("Memory Editor", (void*)&rom_, rom_.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show_imgui_demo) {
|
|
||||||
ImGui::ShowDemoWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show_imgui_style_editor) {
|
|
||||||
ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
|
|
||||||
ImGui::ShowStyleEditor();
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginMenu("View")) {
|
|
||||||
ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
|
|
||||||
ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::BeginMenu("GUI Tools")) {
|
|
||||||
ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
|
|
||||||
ImGui::MenuItem("Style Editor (ImGui)", nullptr,
|
|
||||||
&show_imgui_style_editor);
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Viewer::DrawBranchTree() {
|
|
||||||
static ImGuiTableFlags flags =
|
|
||||||
ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH |
|
|
||||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg |
|
|
||||||
ImGuiTableFlags_NoBordersInBody;
|
|
||||||
|
|
||||||
if (ImGui::BeginTable("3ways", 3, flags)) {
|
|
||||||
// The first column will use the default _WidthStretch when ScrollX is Off
|
|
||||||
// and _WidthFixed when ScrollX is On
|
|
||||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
|
|
||||||
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed,
|
|
||||||
10 * 12.0f);
|
|
||||||
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
|
|
||||||
10 * 18.0f);
|
|
||||||
ImGui::TableHeadersRow();
|
|
||||||
|
|
||||||
// Simple storage to output a dummy file-system.
|
|
||||||
struct MyTreeNode {
|
|
||||||
const char* Name;
|
|
||||||
const char* Type;
|
|
||||||
int Size;
|
|
||||||
int ChildIdx;
|
|
||||||
int ChildCount;
|
|
||||||
static void DisplayNode(const MyTreeNode* node,
|
|
||||||
const MyTreeNode* all_nodes) {
|
|
||||||
ImGui::TableNextRow();
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
const bool is_folder = (node->ChildCount > 0);
|
|
||||||
if (is_folder) {
|
|
||||||
bool open =
|
|
||||||
ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth);
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::TextDisabled("--");
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::TextUnformatted(node->Type);
|
|
||||||
if (open) {
|
|
||||||
for (int child_n = 0; child_n < node->ChildCount; child_n++)
|
|
||||||
DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes);
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ImGui::TreeNodeEx(
|
|
||||||
node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet |
|
|
||||||
ImGuiTreeNodeFlags_NoTreePushOnOpen |
|
|
||||||
ImGuiTreeNodeFlags_SpanFullWidth);
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("%d", node->Size);
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::TextUnformatted(node->Type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
static const MyTreeNode nodes[] = {
|
|
||||||
{"lttp-redux", "Repository", -1, 1, 3},
|
|
||||||
{"main", "Branch", -1, 4, 2},
|
|
||||||
{"hyrule-castle", "Branch", -1, 4, 2},
|
|
||||||
{"lost-woods", "Branch", -1, 6, 3},
|
|
||||||
{"Added some bushes", "Commit", 1024, -1, -1},
|
|
||||||
{"Constructed a new house", "Commit", 123000, -1, -1},
|
|
||||||
{"File1_b.wav", "Commit", 456000, -1, -1},
|
|
||||||
{"Image001.png", "Commit", 203128, -1, -1},
|
|
||||||
{"Copy of Image001.png", "Commit", 203256, -1, -1},
|
|
||||||
{"Copy of Image001 (Final2).png", "Commit", 203512, -1, -1},
|
|
||||||
};
|
|
||||||
|
|
||||||
MyTreeNode::DisplayNode(&nodes[0], nodes);
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace delta
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#ifndef YAZE_APP_DELTA_VIEWER_H
|
|
||||||
#define YAZE_APP_DELTA_VIEWER_H
|
|
||||||
|
|
||||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
|
||||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
|
||||||
#include <imgui_memory_editor.h>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "app/core/constants.h"
|
|
||||||
#include "app/delta/client.h"
|
|
||||||
#include "app/gfx/snes_palette.h"
|
|
||||||
#include "app/gfx/snes_tile.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "gui/canvas.h"
|
|
||||||
#include "gui/icons.h"
|
|
||||||
#include "gui/input.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace delta {
|
|
||||||
class Viewer {
|
|
||||||
public:
|
|
||||||
void Update();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void DrawFileDialog();
|
|
||||||
|
|
||||||
void DrawYazeMenu();
|
|
||||||
void DrawFileMenu() const;
|
|
||||||
void DrawViewMenu();
|
|
||||||
|
|
||||||
void DrawBranchTree();
|
|
||||||
|
|
||||||
std::string client_address_;
|
|
||||||
|
|
||||||
ROM rom_;
|
|
||||||
Client client_;
|
|
||||||
};
|
|
||||||
} // namespace delta
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
#include "assembly_editor.h"
|
|
||||||
|
|
||||||
#include "core/constants.h"
|
|
||||||
#include "gui/widgets.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
AssemblyEditor::AssemblyEditor() {
|
|
||||||
text_editor_.SetLanguageDefinition(gui::widgets::GetAssemblyLanguageDef());
|
|
||||||
text_editor_.SetPalette(TextEditor::GetDarkPalette());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssemblyEditor::Update() {
|
|
||||||
ImGui::Begin("Assembly Editor", &file_is_loaded_);
|
|
||||||
MENU_BAR()
|
|
||||||
DrawFileMenu();
|
|
||||||
DrawEditMenu();
|
|
||||||
END_MENU_BAR()
|
|
||||||
|
|
||||||
auto cpos = text_editor_.GetCursorPosition();
|
|
||||||
SetEditorText();
|
|
||||||
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
|
||||||
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
|
||||||
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
|
||||||
text_editor_.CanUndo() ? "*" : " ",
|
|
||||||
text_editor_.GetLanguageDefinition().mName.c_str(),
|
|
||||||
current_file_.c_str());
|
|
||||||
|
|
||||||
text_editor_.Render("##asm_editor");
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void AssemblyEditor::InlineUpdate() {
|
|
||||||
ChangeActiveFile("assets/asm/template_song.asm");
|
|
||||||
auto cpos = text_editor_.GetCursorPosition();
|
|
||||||
SetEditorText();
|
|
||||||
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
|
||||||
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
|
||||||
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
|
||||||
text_editor_.CanUndo() ? "*" : " ",
|
|
||||||
text_editor_.GetLanguageDefinition().mName.c_str(),
|
|
||||||
current_file_.c_str());
|
|
||||||
|
|
||||||
text_editor_.Render("##asm_editor", ImVec2(0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssemblyEditor::ChangeActiveFile(const std::string& filename) {
|
|
||||||
current_file_ = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssemblyEditor::DrawFileMenu() {
|
|
||||||
if (ImGui::BeginMenu("File")) {
|
|
||||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
|
||||||
ImGuiFileDialog::Instance()->OpenDialog(
|
|
||||||
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", ".");
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Save", "Ctrl+S")) {
|
|
||||||
// TODO: Implement this
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
|
|
||||||
if (ImGuiFileDialog::Instance()->IsOk()) {
|
|
||||||
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
|
|
||||||
}
|
|
||||||
ImGuiFileDialog::Instance()->Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssemblyEditor::DrawEditMenu() {
|
|
||||||
if (ImGui::BeginMenu("Edit")) {
|
|
||||||
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
|
|
||||||
text_editor_.Undo();
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
|
|
||||||
text_editor_.Redo();
|
|
||||||
}
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
|
|
||||||
text_editor_.Cut();
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
|
|
||||||
text_editor_.Copy();
|
|
||||||
}
|
|
||||||
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
|
|
||||||
text_editor_.Paste();
|
|
||||||
}
|
|
||||||
ImGui::Separator();
|
|
||||||
if (ImGui::MenuItem("Find", "Ctrl+F")) {
|
|
||||||
// TODO: Implement this.
|
|
||||||
}
|
|
||||||
ImGui::EndMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssemblyEditor::SetEditorText() {
|
|
||||||
if (!file_is_loaded_) {
|
|
||||||
std::ifstream t(current_file_);
|
|
||||||
if (t.good()) {
|
|
||||||
std::string str((std::istreambuf_iterator<char>(t)),
|
|
||||||
std::istreambuf_iterator<char>());
|
|
||||||
text_editor_.SetText(str);
|
|
||||||
file_is_loaded_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
|
|
||||||
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
|
|
||||||
|
|
||||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
|
||||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <istream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
class AssemblyEditor {
|
|
||||||
public:
|
|
||||||
AssemblyEditor();
|
|
||||||
|
|
||||||
void Update();
|
|
||||||
void InlineUpdate();
|
|
||||||
void ChangeActiveFile(const std::string &);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void DrawFileMenu();
|
|
||||||
void DrawEditMenu();
|
|
||||||
void SetEditorText();
|
|
||||||
|
|
||||||
bool file_is_loaded_ = false;
|
|
||||||
|
|
||||||
std::string current_file_;
|
|
||||||
TextEditor text_editor_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
358
src/app/editor/code/assembly_editor.cc
Normal file
358
src/app/editor/code/assembly_editor.cc
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
#include "assembly_editor.h"
|
||||||
|
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/modules/text_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using core::FileDialogWrapper;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::vector<std::string> RemoveIgnoredFiles(
|
||||||
|
const std::vector<std::string>& files,
|
||||||
|
const std::vector<std::string>& ignored_files) {
|
||||||
|
std::vector<std::string> filtered_files;
|
||||||
|
for (const auto& file : files) {
|
||||||
|
// Remove subdirectory files
|
||||||
|
if (file.find('/') != std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Make sure the file has an extension
|
||||||
|
if (file.find('.') == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::find(ignored_files.begin(), ignored_files.end(), file) ==
|
||||||
|
ignored_files.end()) {
|
||||||
|
filtered_files.push_back(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered_files;
|
||||||
|
}
|
||||||
|
|
||||||
|
core::FolderItem LoadFolder(const std::string& folder) {
|
||||||
|
// Check if .gitignore exists in the folder
|
||||||
|
std::ifstream gitignore(folder + "/.gitignore");
|
||||||
|
std::vector<std::string> ignored_files;
|
||||||
|
if (gitignore.good()) {
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(gitignore, line)) {
|
||||||
|
if (line[0] == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line[0] == '!') {
|
||||||
|
// Ignore the file
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ignored_files.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core::FolderItem current_folder;
|
||||||
|
current_folder.name = folder;
|
||||||
|
auto root_files = FileDialogWrapper::GetFilesInFolder(current_folder.name);
|
||||||
|
current_folder.files = RemoveIgnoredFiles(root_files, ignored_files);
|
||||||
|
|
||||||
|
for (const auto& subfolder :
|
||||||
|
FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) {
|
||||||
|
core::FolderItem folder_item;
|
||||||
|
folder_item.name = subfolder;
|
||||||
|
std::string full_folder = current_folder.name + "/" + subfolder;
|
||||||
|
auto folder_files = FileDialogWrapper::GetFilesInFolder(full_folder);
|
||||||
|
for (const auto& files : folder_files) {
|
||||||
|
// Remove subdirectory files
|
||||||
|
if (files.find('/') != std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Make sure the file has an extension
|
||||||
|
if (files.find('.') == std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (std::find(ignored_files.begin(), ignored_files.end(), files) !=
|
||||||
|
ignored_files.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
folder_item.files.push_back(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& subdir :
|
||||||
|
FileDialogWrapper::GetSubdirectoriesInFolder(full_folder)) {
|
||||||
|
core::FolderItem subfolder_item;
|
||||||
|
subfolder_item.name = subdir;
|
||||||
|
subfolder_item.files = FileDialogWrapper::GetFilesInFolder(subdir);
|
||||||
|
folder_item.subfolders.push_back(subfolder_item);
|
||||||
|
}
|
||||||
|
current_folder.subfolders.push_back(folder_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return current_folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void AssemblyEditor::OpenFolder(const std::string& folder_path) {
|
||||||
|
current_folder_ = LoadFolder(folder_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::Update(bool& is_loaded) {
|
||||||
|
ImGui::Begin("Assembly Editor", &is_loaded);
|
||||||
|
if (ImGui::BeginMenuBar()) {
|
||||||
|
DrawFileMenu();
|
||||||
|
DrawEditMenu();
|
||||||
|
ImGui::EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor");
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::InlineUpdate() {
|
||||||
|
ChangeActiveFile("assets/asm/template_song.asm");
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor", ImVec2(0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::UpdateCodeView() {
|
||||||
|
ImGui::BeginTable("##table_view", 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_Resizable);
|
||||||
|
|
||||||
|
// Table headers
|
||||||
|
ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 256.0f);
|
||||||
|
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Table data
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (current_folder_.name != "") {
|
||||||
|
DrawCurrentFolder();
|
||||||
|
} else {
|
||||||
|
if (ImGui::Button("Open Folder")) {
|
||||||
|
current_folder_ = LoadFolder(FileDialogWrapper::ShowOpenFolderDialog());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
SetEditorText();
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
text_editor_.Render("##asm_editor");
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawCurrentFolder() {
|
||||||
|
if (ImGui::BeginChild("##current_folder", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
if (ImGui::BeginTable("##file_table", 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
|
||||||
|
ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_Sortable)) {
|
||||||
|
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 256.0f);
|
||||||
|
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto& file : current_folder_.files) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Selectable(file.c_str())) {
|
||||||
|
ChangeActiveFile(absl::StrCat(current_folder_.name, "/", file));
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("File");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& subfolder : current_folder_.subfolders) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::TreeNode(subfolder.name.c_str())) {
|
||||||
|
for (const auto& file : subfolder.files) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Selectable(file.c_str())) {
|
||||||
|
ChangeActiveFile(absl::StrCat(current_folder_.name, "/",
|
||||||
|
subfolder.name, "/", file));
|
||||||
|
}
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("File");
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
} else {
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawFileTabView() {
|
||||||
|
static int next_tab_id = 0;
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("AssemblyFileTabBar", ImGuiTabBarFlags_None)) {
|
||||||
|
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_None)) {
|
||||||
|
if (std::find(active_files_.begin(), active_files_.end(),
|
||||||
|
current_file_id_) != active_files_.end()) {
|
||||||
|
// Room is already open
|
||||||
|
next_tab_id++;
|
||||||
|
}
|
||||||
|
active_files_.push_back(next_tab_id++); // Add new tab
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit our regular tabs
|
||||||
|
for (int n = 0; n < active_files_.Size;) {
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem(files_[active_files_[n]].data(), &open,
|
||||||
|
ImGuiTabItemFlags_None)) {
|
||||||
|
auto cpos = text_editor_.GetCursorPosition();
|
||||||
|
{
|
||||||
|
std::ifstream t(current_file_);
|
||||||
|
if (t.good()) {
|
||||||
|
std::string str((std::istreambuf_iterator<char>(t)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
text_editor_.SetText(str);
|
||||||
|
} else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
|
"Error opening file: %s\n", current_file_.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
|
||||||
|
cpos.mColumn + 1, text_editor_.GetTotalLines(),
|
||||||
|
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
|
||||||
|
text_editor_.CanUndo() ? "*" : " ",
|
||||||
|
text_editor_.GetLanguageDefinition().mName.c_str(),
|
||||||
|
current_file_.c_str());
|
||||||
|
|
||||||
|
open_files_[active_files_[n]].Render("##asm_editor");
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open)
|
||||||
|
active_files_.erase(active_files_.Data + n);
|
||||||
|
else
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawFileMenu() {
|
||||||
|
if (ImGui::BeginMenu("File")) {
|
||||||
|
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||||
|
auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
ChangeActiveFile(filename);
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Save", "Ctrl+S")) {
|
||||||
|
// TODO: Implement this
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::DrawEditMenu() {
|
||||||
|
if (ImGui::BeginMenu("Edit")) {
|
||||||
|
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
|
||||||
|
text_editor_.Undo();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
|
||||||
|
text_editor_.Redo();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
|
||||||
|
text_editor_.Cut();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
|
||||||
|
text_editor_.Copy();
|
||||||
|
}
|
||||||
|
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
|
||||||
|
text_editor_.Paste();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Find", "Ctrl+F")) {
|
||||||
|
// TODO: Implement this.
|
||||||
|
}
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssemblyEditor::SetEditorText() {
|
||||||
|
if (!file_is_loaded_) {
|
||||||
|
std::ifstream t(current_file_);
|
||||||
|
if (t.good()) {
|
||||||
|
std::string str((std::istreambuf_iterator<char>(t)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
text_editor_.SetText(str);
|
||||||
|
} else {
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error opening file: %s\n",
|
||||||
|
current_file_.c_str());
|
||||||
|
}
|
||||||
|
file_is_loaded_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Cut() {
|
||||||
|
text_editor_.Cut();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Copy() {
|
||||||
|
text_editor_.Copy();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Paste() {
|
||||||
|
text_editor_.Paste();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Undo() {
|
||||||
|
text_editor_.Undo();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Redo() {
|
||||||
|
text_editor_.Redo();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status AssemblyEditor::Update() { return absl::OkStatus(); }
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
73
src/app/editor/code/assembly_editor.h
Normal file
73
src/app/editor/code/assembly_editor.h
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/editor/editor.h"
|
||||||
|
#include "app/gui/modules/text_editor.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class AssemblyEditor
|
||||||
|
* @brief Text editor for modifying assembly code.
|
||||||
|
*/
|
||||||
|
class AssemblyEditor : public Editor {
|
||||||
|
public:
|
||||||
|
AssemblyEditor() {
|
||||||
|
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
|
||||||
|
text_editor_.SetPalette(TextEditor::GetDarkPalette());
|
||||||
|
text_editor_.SetShowWhitespaces(false);
|
||||||
|
type_ = EditorType::kAssembly;
|
||||||
|
}
|
||||||
|
void ChangeActiveFile(const std::string_view &filename) {
|
||||||
|
current_file_ = filename;
|
||||||
|
file_is_loaded_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update(bool &is_loaded);
|
||||||
|
void InlineUpdate();
|
||||||
|
|
||||||
|
void UpdateCodeView();
|
||||||
|
|
||||||
|
absl::Status Cut() override;
|
||||||
|
absl::Status Copy() override;
|
||||||
|
absl::Status Paste() override;
|
||||||
|
|
||||||
|
absl::Status Undo() override;
|
||||||
|
absl::Status Redo() override;
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
void OpenFolder(const std::string &folder_path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawFileMenu();
|
||||||
|
void DrawEditMenu();
|
||||||
|
|
||||||
|
void SetEditorText();
|
||||||
|
|
||||||
|
void DrawCurrentFolder();
|
||||||
|
|
||||||
|
void DrawFileTabView();
|
||||||
|
|
||||||
|
bool file_is_loaded_ = false;
|
||||||
|
|
||||||
|
std::vector<std::string> files_;
|
||||||
|
std::vector<TextEditor> open_files_;
|
||||||
|
ImVector<int> active_files_;
|
||||||
|
int current_file_id_ = 0;
|
||||||
|
|
||||||
|
std::string current_file_;
|
||||||
|
core::FolderItem current_folder_;
|
||||||
|
TextEditor text_editor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
82
src/app/editor/code/memory_editor.h
Normal file
82
src/app/editor/code/memory_editor.h
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/core/project.h"
|
||||||
|
#include "app/editor/code/assembly_editor.h"
|
||||||
|
#include "app/editor/code/memory_editor.h"
|
||||||
|
#include "app/editor/dungeon/dungeon_editor.h"
|
||||||
|
#include "app/editor/editor.h"
|
||||||
|
#include "app/editor/graphics/graphics_editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/graphics/screen_editor.h"
|
||||||
|
#include "app/editor/music/music_editor.h"
|
||||||
|
#include "app/editor/overworld/overworld_editor.h"
|
||||||
|
#include "app/editor/sprite/sprite_editor.h"
|
||||||
|
#include "app/emu/emulator.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
#include "imgui_memory_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
struct MemoryEditorWithDiffChecker : public SharedRom {
|
||||||
|
void Update(bool &show_memory_editor) {
|
||||||
|
static MemoryEditor mem_edit;
|
||||||
|
static MemoryEditor comp_edit;
|
||||||
|
static bool show_compare_rom = false;
|
||||||
|
static Rom comparison_rom;
|
||||||
|
ImGui::Begin("Hex Editor", &show_memory_editor);
|
||||||
|
if (ImGui::Button("Compare Rom")) {
|
||||||
|
auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
PRINT_IF_ERROR(comparison_rom.LoadFromFile(file_name));
|
||||||
|
show_compare_rom = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t convert_address = 0;
|
||||||
|
gui::InputHex("SNES to PC", (int *)&convert_address, 6, 200.f);
|
||||||
|
SameLine();
|
||||||
|
Text("%x", core::SnesToPc(convert_address));
|
||||||
|
|
||||||
|
// mem_edit.DrawWindow("Memory Editor", (void*)&(*rom()), rom()->size());
|
||||||
|
BEGIN_TABLE("Memory Comparison", 2, ImGuiTableFlags_Resizable);
|
||||||
|
SETUP_COLUMN("Source")
|
||||||
|
SETUP_COLUMN("Dest")
|
||||||
|
|
||||||
|
NEXT_COLUMN()
|
||||||
|
Text("%s", rom()->filename().data());
|
||||||
|
mem_edit.DrawContents((void *)&(*rom()), rom()->size());
|
||||||
|
|
||||||
|
NEXT_COLUMN()
|
||||||
|
if (show_compare_rom) {
|
||||||
|
comp_edit.SetComparisonData((void *)&(*rom()));
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
ImGui::BeginChild("Comparison ROM");
|
||||||
|
Text("%s", comparison_rom.filename().data());
|
||||||
|
comp_edit.DrawContents((void *)&(comparison_rom), comparison_rom.size());
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::EndGroup();
|
||||||
|
}
|
||||||
|
END_TABLE()
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
|
||||||
837
src/app/editor/dungeon/dungeon_editor.cc
Normal file
837
src/app/editor/dungeon/dungeon_editor.cc
Normal file
@@ -0,0 +1,837 @@
|
|||||||
|
#include "dungeon_editor.h"
|
||||||
|
|
||||||
|
#include "absl/container/flat_hash_map.h"
|
||||||
|
#include "app/core/platform/renderer.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/dungeon/object_names.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui_memory_editor.h"
|
||||||
|
#include "zelda3/dungeon/room.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using core::Renderer;
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::RadioButton;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kDungeonObjectTableFlags =
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||||
|
ImGuiTableFlags_BordersV;
|
||||||
|
|
||||||
|
absl::Status DungeonEditor::Update() {
|
||||||
|
if (!is_loaded_ && rom()->is_loaded()) {
|
||||||
|
RETURN_IF_ERROR(Initialize());
|
||||||
|
is_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refresh_graphics_) {
|
||||||
|
RETURN_IF_ERROR(RefreshGraphics());
|
||||||
|
refresh_graphics_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("##DungeonEditorTabBar")) {
|
||||||
|
TAB_ITEM("Room Editor")
|
||||||
|
status_ = UpdateDungeonRoomView();
|
||||||
|
END_TAB_ITEM()
|
||||||
|
TAB_ITEM("Usage Statistics")
|
||||||
|
if (is_loaded_) {
|
||||||
|
static bool calc_stats = false;
|
||||||
|
if (!calc_stats) {
|
||||||
|
CalculateUsageStats();
|
||||||
|
calc_stats = true;
|
||||||
|
}
|
||||||
|
DrawUsageStats();
|
||||||
|
}
|
||||||
|
END_TAB_ITEM()
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status DungeonEditor::Initialize() {
|
||||||
|
auto dungeon_man_pal_group = rom()->palette_group().dungeon_main;
|
||||||
|
|
||||||
|
for (int i = 0; i < 0x100 + 40; i++) {
|
||||||
|
rooms_.emplace_back(zelda3::Room(/*room_id=*/i));
|
||||||
|
rooms_[i].LoadHeader();
|
||||||
|
rooms_[i].LoadRoomFromROM();
|
||||||
|
if (core::ExperimentFlags::get().kDrawDungeonRoomGraphics) {
|
||||||
|
rooms_[i].LoadRoomGraphics();
|
||||||
|
}
|
||||||
|
|
||||||
|
room_size_pointers_.push_back(rooms_[i].room_size_ptr());
|
||||||
|
if (rooms_[i].room_size_ptr() != 0x0A8000) {
|
||||||
|
room_size_addresses_[i] = rooms_[i].room_size_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dungeon_palette_ptr = rom()->paletteset_ids[rooms_[i].palette][0];
|
||||||
|
ASSIGN_OR_RETURN(auto palette_id,
|
||||||
|
rom()->ReadWord(0xDEC4B + dungeon_palette_ptr));
|
||||||
|
int p_id = palette_id / 180;
|
||||||
|
auto color = dungeon_man_pal_group[p_id][3];
|
||||||
|
room_palette_[rooms_[i].palette] = color.rgb();
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadDungeonRoomSize();
|
||||||
|
// LoadRoomEntrances
|
||||||
|
for (int i = 0; i < 0x07; ++i) {
|
||||||
|
entrances_.emplace_back(zelda3::RoomEntrance(*rom(), i, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 0x85; ++i) {
|
||||||
|
entrances_.emplace_back(zelda3::RoomEntrance(*rom(), i, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the palette group and palette for the dungeon
|
||||||
|
full_palette_ = dungeon_man_pal_group[current_palette_group_id_];
|
||||||
|
ASSIGN_OR_RETURN(current_palette_group_,
|
||||||
|
gfx::CreatePaletteGroupFromLargePalette(full_palette_));
|
||||||
|
|
||||||
|
graphics_bin_ = rom()->gfx_sheets();
|
||||||
|
// Create a vector of pointers to the current block bitmaps
|
||||||
|
for (int block : rooms_[current_room_id_].blocks()) {
|
||||||
|
room_gfx_sheets_.emplace_back(&graphics_bin_[block]);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status DungeonEditor::RefreshGraphics() {
|
||||||
|
std::for_each_n(
|
||||||
|
rooms_[current_room_id_].blocks().begin(), 8,
|
||||||
|
[this](int block) -> absl::Status {
|
||||||
|
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
|
||||||
|
current_palette_group_[current_palette_id_], 0));
|
||||||
|
Renderer::GetInstance().UpdateBitmap(&graphics_bin_[block]);
|
||||||
|
return absl::OkStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1;
|
||||||
|
std::for_each_n(
|
||||||
|
rooms_[current_room_id_].blocks().begin() + 8, 8,
|
||||||
|
[this, &sprites_aux1_pal_group](int block) -> absl::Status {
|
||||||
|
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
|
||||||
|
sprites_aux1_pal_group[current_palette_id_], 0));
|
||||||
|
Renderer::GetInstance().UpdateBitmap(&graphics_bin_[block]);
|
||||||
|
return absl::OkStatus();
|
||||||
|
});
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::LoadDungeonRoomSize() {
|
||||||
|
std::map<int, std::vector<int>> rooms_by_bank;
|
||||||
|
for (const auto &room : room_size_addresses_) {
|
||||||
|
int bank = room.second >> 16;
|
||||||
|
rooms_by_bank[bank].push_back(room.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process and calculate room sizes within each bank
|
||||||
|
for (auto &bank_rooms : rooms_by_bank) {
|
||||||
|
// Sort the rooms within this bank
|
||||||
|
std::sort(bank_rooms.second.begin(), bank_rooms.second.end());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < bank_rooms.second.size(); ++i) {
|
||||||
|
int room_ptr = bank_rooms.second[i];
|
||||||
|
|
||||||
|
// Identify the room ID for the current room pointer
|
||||||
|
int room_id =
|
||||||
|
std::find_if(room_size_addresses_.begin(), room_size_addresses_.end(),
|
||||||
|
[room_ptr](const auto &entry) {
|
||||||
|
return entry.second == room_ptr;
|
||||||
|
})
|
||||||
|
->first;
|
||||||
|
|
||||||
|
if (room_ptr != 0x0A8000) {
|
||||||
|
if (i < bank_rooms.second.size() - 1) {
|
||||||
|
// Calculate size as difference between current room and next room
|
||||||
|
// in the same bank
|
||||||
|
rooms_[room_id].set_room_size(bank_rooms.second[i + 1] - room_ptr);
|
||||||
|
} else {
|
||||||
|
// Calculate size for the last room in this bank
|
||||||
|
int bank_end_address = (bank_rooms.first << 16) | 0xFFFF;
|
||||||
|
rooms_[room_id].set_room_size(bank_end_address - room_ptr + 1);
|
||||||
|
}
|
||||||
|
total_room_size_ += rooms_[room_id].room_size();
|
||||||
|
} else {
|
||||||
|
// Room with address 0x0A8000
|
||||||
|
rooms_[room_id].set_room_size(0x00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status DungeonEditor::UpdateDungeonRoomView() {
|
||||||
|
DrawToolset();
|
||||||
|
|
||||||
|
if (palette_showing_) {
|
||||||
|
ImGui::Begin("Palette Editor", &palette_showing_, 0);
|
||||||
|
auto dungeon_main_pal_group = rom()->palette_group().dungeon_main;
|
||||||
|
current_palette_ = dungeon_main_pal_group[current_palette_group_id_];
|
||||||
|
gui::SelectablePalettePipeline(current_palette_id_, refresh_graphics_,
|
||||||
|
current_palette_);
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTable("#DungeonEditTable", 3, kDungeonTableFlags, ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Room Selector");
|
||||||
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
ImGui::GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Object Selector");
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (ImGui::BeginTabBar("##DungeonRoomTabBar")) {
|
||||||
|
TAB_ITEM("Rooms");
|
||||||
|
DrawRoomSelector();
|
||||||
|
END_TAB_ITEM();
|
||||||
|
TAB_ITEM("Entrances");
|
||||||
|
DrawEntranceSelector();
|
||||||
|
END_TAB_ITEM();
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawDungeonTabView();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawTileSelector();
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawToolset() {
|
||||||
|
if (BeginTable("DWToolset", 13, ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("#undoTool");
|
||||||
|
TableSetupColumn("#redoTool");
|
||||||
|
TableSetupColumn("#separator");
|
||||||
|
TableSetupColumn("#anyTool");
|
||||||
|
|
||||||
|
TableSetupColumn("#bg1Tool");
|
||||||
|
TableSetupColumn("#bg2Tool");
|
||||||
|
TableSetupColumn("#bg3Tool");
|
||||||
|
TableSetupColumn("#separator");
|
||||||
|
TableSetupColumn("#spriteTool");
|
||||||
|
TableSetupColumn("#itemTool");
|
||||||
|
TableSetupColumn("#doorTool");
|
||||||
|
TableSetupColumn("#blockTool");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_UNDO)) {
|
||||||
|
PRINT_IF_ERROR(Undo());
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_REDO)) {
|
||||||
|
PRINT_IF_ERROR(Redo());
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
Text(ICON_MD_MORE_VERT);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_FILTER_NONE, background_type_ == kBackgroundAny)) {
|
||||||
|
background_type_ = kBackgroundAny;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_FILTER_1, background_type_ == kBackground1)) {
|
||||||
|
background_type_ = kBackground1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_FILTER_2, background_type_ == kBackground2)) {
|
||||||
|
background_type_ = kBackground2;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_FILTER_3, background_type_ == kBackground3)) {
|
||||||
|
background_type_ = kBackground3;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
Text(ICON_MD_MORE_VERT);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) {
|
||||||
|
placement_type_ = kSprite;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Sprites");
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) {
|
||||||
|
placement_type_ = kItem;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Items");
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) {
|
||||||
|
placement_type_ = kDoor;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Doors");
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) {
|
||||||
|
placement_type_ = kBlock;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Blocks");
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_PALETTE)) {
|
||||||
|
palette_showing_ = !palette_showing_;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawRoomSelector() {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
gui::InputHexWord("Room ID", ¤t_room_id_);
|
||||||
|
gui::InputHex("Palette ID", ¤t_palette_id_);
|
||||||
|
|
||||||
|
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)9);
|
||||||
|
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
int i = 0;
|
||||||
|
for (const auto each_room_name : zelda3::kRoomNames) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_room_id_ == i, "Dungeon Room Names",
|
||||||
|
core::HexByte(i), each_room_name.data());
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
// TODO: Jump to tab if room is already open
|
||||||
|
current_room_id_ = i;
|
||||||
|
if (!active_rooms_.contains(i)) {
|
||||||
|
active_rooms_.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using ImGui::Separator;
|
||||||
|
|
||||||
|
void DungeonEditor::DrawEntranceSelector() {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
auto current_entrance = entrances_[current_entrance_id_];
|
||||||
|
gui::InputHexWord("Entrance ID", ¤t_entrance.entrance_id_);
|
||||||
|
gui::InputHexWord("Room ID", ¤t_entrance.room_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon ID", ¤t_entrance.dungeon_id_, 50.f, true);
|
||||||
|
gui::InputHexByte("Blockset", ¤t_entrance.blockset_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Music", ¤t_entrance.music_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("Floor", ¤t_entrance.floor_);
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexWord("Player X ", ¤t_entrance.x_position_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Player Y ", ¤t_entrance.y_position_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Camera X", ¤t_entrance.camera_trigger_x_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Camera Y", ¤t_entrance.camera_trigger_y_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Scroll X ", ¤t_entrance.camera_x_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Scroll Y ", ¤t_entrance.camera_y_);
|
||||||
|
|
||||||
|
gui::InputHexWord("Exit", ¤t_entrance.exit_, 50.f, true);
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
Text("Camera Boundaries");
|
||||||
|
Separator();
|
||||||
|
Text("\t\t\t\t\tNorth East South West");
|
||||||
|
gui::InputHexByte("Quadrant", ¤t_entrance.camera_boundary_qn_, 50.f,
|
||||||
|
true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qe_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qs_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_qw_, 50.f, true);
|
||||||
|
|
||||||
|
gui::InputHexByte("Full room", ¤t_entrance.camera_boundary_fn_, 50.f,
|
||||||
|
true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fe_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fs_, 50.f, true);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("", ¤t_entrance.camera_boundary_fw_, 50.f, true);
|
||||||
|
|
||||||
|
if (BeginChild("EntranceSelector", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
for (int i = 0; i < 0x85 + 7; i++) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_entrance_id_ == i, "Dungeon Entrance Names",
|
||||||
|
core::HexByte(i),
|
||||||
|
zelda3::kEntranceNames[i].data());
|
||||||
|
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
current_entrance_id_ = i;
|
||||||
|
if (!active_rooms_.contains(i)) {
|
||||||
|
active_rooms_.push_back(entrances_[i].room_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawDungeonTabView() {
|
||||||
|
static int next_tab_id = 0;
|
||||||
|
|
||||||
|
if (BeginTabBar("MyTabBar", kDungeonTabBarFlags)) {
|
||||||
|
if (ImGui::TabItemButton(ICON_MD_ADD, kDungeonTabFlags)) {
|
||||||
|
if (std::find(active_rooms_.begin(), active_rooms_.end(),
|
||||||
|
current_room_id_) != active_rooms_.end()) {
|
||||||
|
// Room is already open
|
||||||
|
next_tab_id++;
|
||||||
|
}
|
||||||
|
active_rooms_.push_back(next_tab_id++); // Add new tab
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit our regular tabs
|
||||||
|
for (int n = 0; n < active_rooms_.Size;) {
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
if (active_rooms_[n] > sizeof(zelda3::kRoomNames) / 4) {
|
||||||
|
active_rooms_.erase(active_rooms_.Data + n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem(zelda3::kRoomNames[active_rooms_[n]].data(),
|
||||||
|
&open, ImGuiTabItemFlags_None)) {
|
||||||
|
DrawDungeonCanvas(active_rooms_[n]);
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open)
|
||||||
|
active_rooms_.erase(active_rooms_.Data + n);
|
||||||
|
else
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
|
||||||
|
gui::InputHexByte("Layout", &rooms_[room_id].layout);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Blockset", &rooms_[room_id].blockset);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Spriteset", &rooms_[room_id].spriteset);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Palette", &rooms_[room_id].palette);
|
||||||
|
|
||||||
|
gui::InputHexByte("Floor1", &rooms_[room_id].floor1);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexByte("Floor2", &rooms_[room_id].floor2);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
gui::InputHexWord("Message ID", &rooms_[room_id].message_id_);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
canvas_.DrawBackground(ImVec2(0x200, 0x200));
|
||||||
|
canvas_.DrawContextMenu();
|
||||||
|
if (is_loaded_) {
|
||||||
|
canvas_.DrawBitmap(rooms_[room_id].layer1(), 0, 0);
|
||||||
|
}
|
||||||
|
canvas_.DrawGrid();
|
||||||
|
canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawRoomGraphics() {
|
||||||
|
const auto height = 0x40;
|
||||||
|
const int num_sheets = 0x10;
|
||||||
|
room_gfx_canvas_.DrawBackground(ImVec2(0x100 + 1, num_sheets * height + 1));
|
||||||
|
room_gfx_canvas_.DrawContextMenu();
|
||||||
|
room_gfx_canvas_.DrawTileSelector(32);
|
||||||
|
if (is_loaded_) {
|
||||||
|
auto blocks = rooms_[current_room_id_].blocks();
|
||||||
|
int current_block = 0;
|
||||||
|
for (int block : blocks) {
|
||||||
|
int offset = height * (current_block + 1);
|
||||||
|
int top_left_y = room_gfx_canvas_.zero_point().y + 2;
|
||||||
|
if (current_block >= 1) {
|
||||||
|
top_left_y = room_gfx_canvas_.zero_point().y + height * current_block;
|
||||||
|
}
|
||||||
|
room_gfx_canvas_.draw_list()->AddImage(
|
||||||
|
(ImTextureID)(intptr_t)graphics_bin_[block].texture(),
|
||||||
|
ImVec2(room_gfx_canvas_.zero_point().x + 2, top_left_y),
|
||||||
|
ImVec2(room_gfx_canvas_.zero_point().x + 0x100,
|
||||||
|
room_gfx_canvas_.zero_point().y + offset));
|
||||||
|
current_block += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
room_gfx_canvas_.DrawGrid(32.0f);
|
||||||
|
room_gfx_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawTileSelector() {
|
||||||
|
if (BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
||||||
|
if (BeginTabItem("Room Graphics")) {
|
||||||
|
if (ImGuiID child_id = ImGui::GetID((void *)(intptr_t)3);
|
||||||
|
BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
DrawRoomGraphics();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem("Object Renderer")) {
|
||||||
|
DrawObjectRenderer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawObjectRenderer() {
|
||||||
|
if (BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
ImGui::GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Canvas");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
|
||||||
|
|
||||||
|
int selected_object = 0;
|
||||||
|
int i = 0;
|
||||||
|
for (const auto object_name : zelda3::Type1RoomObjectNames) {
|
||||||
|
if (ImGui::Selectable(object_name.data(), selected_object == i)) {
|
||||||
|
selected_object = i;
|
||||||
|
current_object_ = i;
|
||||||
|
object_renderer_.LoadObject(i,
|
||||||
|
rooms_[current_room_id_].mutable_blocks());
|
||||||
|
Renderer::GetInstance().RenderBitmap(object_renderer_.bitmap());
|
||||||
|
object_loaded_ = true;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
// Right side of the table - Canvas
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("DungeonObjectCanvas", ImVec2(276, 0x10 * 0x40 + 1), true);
|
||||||
|
|
||||||
|
object_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 1));
|
||||||
|
object_canvas_.DrawContextMenu();
|
||||||
|
object_canvas_.DrawTileSelector(32);
|
||||||
|
if (object_loaded_) {
|
||||||
|
object_canvas_.DrawBitmap(*object_renderer_.bitmap(), 0, 0);
|
||||||
|
}
|
||||||
|
object_canvas_.DrawGrid(32.0f);
|
||||||
|
object_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
EndChild();
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object_loaded_) {
|
||||||
|
ImGui::Begin("Memory Viewer", &object_loaded_, 0);
|
||||||
|
static MemoryEditor mem_edit;
|
||||||
|
mem_edit.DrawContents((void *)object_renderer_.mutable_memory(),
|
||||||
|
object_renderer_.mutable_memory()->size());
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void DungeonEditor::CalculateUsageStats() {
|
||||||
|
for (const auto &room : rooms_) {
|
||||||
|
if (blockset_usage_.find(room.blockset) == blockset_usage_.end()) {
|
||||||
|
blockset_usage_[room.blockset] = 1;
|
||||||
|
} else {
|
||||||
|
blockset_usage_[room.blockset] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spriteset_usage_.find(room.spriteset) == spriteset_usage_.end()) {
|
||||||
|
spriteset_usage_[room.spriteset] = 1;
|
||||||
|
} else {
|
||||||
|
spriteset_usage_[room.spriteset] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (palette_usage_.find(room.palette) == palette_usage_.end()) {
|
||||||
|
palette_usage_[room.palette] = 1;
|
||||||
|
} else {
|
||||||
|
palette_usage_[room.palette] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::RenderSetUsage(
|
||||||
|
const absl::flat_hash_map<uint16_t, int> &usage_map, uint16_t &selected_set,
|
||||||
|
int spriteset_offset) {
|
||||||
|
// Sort the usage map by set number
|
||||||
|
std::vector<std::pair<uint16_t, int>> sorted_usage(usage_map.begin(),
|
||||||
|
usage_map.end());
|
||||||
|
std::sort(sorted_usage.begin(), sorted_usage.end(),
|
||||||
|
[](const auto &a, const auto &b) { return a.first < b.first; });
|
||||||
|
|
||||||
|
for (const auto &[set, count] : sorted_usage) {
|
||||||
|
std::string display_str;
|
||||||
|
if (spriteset_offset != 0x00) {
|
||||||
|
display_str = absl::StrFormat("%#02x, %#02x: %d", set,
|
||||||
|
(set + spriteset_offset), count);
|
||||||
|
} else {
|
||||||
|
display_str =
|
||||||
|
absl::StrFormat("%#02x: %d", (set + spriteset_offset), count);
|
||||||
|
}
|
||||||
|
if (ImGui::Selectable(display_str.c_str(), selected_set == set)) {
|
||||||
|
selected_set = set; // Update the selected set when clicked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Calculate the unused sets in a usage map
|
||||||
|
// Range for blocksets 0-0x24
|
||||||
|
// Range for spritesets 0-0x8F
|
||||||
|
// Range for palettes 0-0x47
|
||||||
|
template <typename T>
|
||||||
|
void RenderUnusedSets(const absl::flat_hash_map<T, int> &usage_map, int max_set,
|
||||||
|
int spriteset_offset = 0x00) {
|
||||||
|
std::vector<int> unused_sets;
|
||||||
|
for (int i = 0; i < max_set; i++) {
|
||||||
|
if (usage_map.find(i) == usage_map.end()) {
|
||||||
|
unused_sets.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &set : unused_sets) {
|
||||||
|
if (spriteset_offset != 0x00) {
|
||||||
|
Text("%#02x, %#02x", set, (set + spriteset_offset));
|
||||||
|
} else {
|
||||||
|
Text("%#02x", set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void DungeonEditor::DrawUsageStats() {
|
||||||
|
if (Button("Refresh")) {
|
||||||
|
selected_blockset_ = 0xFFFF;
|
||||||
|
selected_spriteset_ = 0xFFFF;
|
||||||
|
selected_palette_ = 0xFFFF;
|
||||||
|
spriteset_usage_.clear();
|
||||||
|
blockset_usage_.clear();
|
||||||
|
palette_usage_.clear();
|
||||||
|
CalculateUsageStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
if (BeginTable("DungeonUsageStatsTable", 8,
|
||||||
|
kDungeonTableFlags | ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImGui::GetContentRegionAvail())) {
|
||||||
|
TableSetupColumn("Blockset Usage");
|
||||||
|
TableSetupColumn("Unused Blockset");
|
||||||
|
TableSetupColumn("Palette Usage");
|
||||||
|
TableSetupColumn("Unused Palette");
|
||||||
|
TableSetupColumn("Spriteset Usage");
|
||||||
|
TableSetupColumn("Unused Spriteset");
|
||||||
|
TableSetupColumn("Usage Grid");
|
||||||
|
TableSetupColumn("Group Preview");
|
||||||
|
TableHeadersRow();
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("BlocksetUsageScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderSetUsage(blockset_usage_, selected_blockset_);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("UnusedBlocksetScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderUnusedSets(blockset_usage_, 0x25);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("PaletteUsageScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderSetUsage(palette_usage_, selected_palette_);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("UnusedPaletteScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderUnusedSets(palette_usage_, 0x48);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
|
||||||
|
BeginChild("SpritesetUsageScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderSetUsage(spriteset_usage_, selected_spriteset_, 0x40);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
RenderUnusedSets(spriteset_usage_, 0x90, 0x40);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
BeginChild("UsageGrid", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
|
Text("%s", absl::StrFormat("Total size of all rooms: %d hex format: %#06x",
|
||||||
|
total_room_size_, total_room_size_)
|
||||||
|
.c_str());
|
||||||
|
DrawUsageGrid();
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (selected_blockset_ < 0x25) {
|
||||||
|
gfx_group_editor_.SetSelectedBlockset(selected_blockset_);
|
||||||
|
gfx_group_editor_.DrawBlocksetViewer(true);
|
||||||
|
} else if (selected_spriteset_ < 0x90) {
|
||||||
|
gfx_group_editor_.SetSelectedSpriteset(selected_spriteset_ + 0x40);
|
||||||
|
gfx_group_editor_.DrawSpritesetViewer(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DungeonEditor::DrawUsageGrid() {
|
||||||
|
int totalSquares = 296;
|
||||||
|
int squaresWide = 16;
|
||||||
|
int squaresTall = (totalSquares + squaresWide - 1) /
|
||||||
|
squaresWide; // Ceiling of totalSquares/squaresWide
|
||||||
|
|
||||||
|
for (int row = 0; row < squaresTall; ++row) {
|
||||||
|
ImGui::NewLine();
|
||||||
|
|
||||||
|
for (int col = 0; col < squaresWide; ++col) {
|
||||||
|
// Check if we have reached 295 squares
|
||||||
|
if (row * squaresWide + col >= totalSquares) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Determine if this square should be highlighted
|
||||||
|
const auto &room = rooms_[row * squaresWide + col];
|
||||||
|
|
||||||
|
// Create a button or selectable for each square
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
ImVec4 color = room_palette_[room.palette];
|
||||||
|
color.x = color.x / 255;
|
||||||
|
color.y = color.y / 255;
|
||||||
|
color.z = color.z / 255;
|
||||||
|
color.w = 1.0f;
|
||||||
|
if (rooms_[row * squaresWide + col].room_size() > 0xFFFF) {
|
||||||
|
color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Or any highlight color
|
||||||
|
}
|
||||||
|
if (rooms_[row * squaresWide + col].room_size() == 0) {
|
||||||
|
color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); // Or any highlight color
|
||||||
|
}
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, color);
|
||||||
|
// Make the button text darker
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
|
||||||
|
bool highlight = room.blockset == selected_blockset_ ||
|
||||||
|
room.spriteset == selected_spriteset_ ||
|
||||||
|
room.palette == selected_palette_;
|
||||||
|
|
||||||
|
// Set highlight color if needed
|
||||||
|
if (highlight) {
|
||||||
|
ImGui::PushStyleColor(
|
||||||
|
ImGuiCol_Button,
|
||||||
|
ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Or any highlight color
|
||||||
|
}
|
||||||
|
if (Button(absl::StrFormat("%#x",
|
||||||
|
rooms_[row * squaresWide + col].room_size())
|
||||||
|
.c_str(),
|
||||||
|
ImVec2(55, 30))) {
|
||||||
|
// Switch over to the room editor tab
|
||||||
|
// and add a room tab by the ID of the square
|
||||||
|
// that was clicked
|
||||||
|
}
|
||||||
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
ImGui::OpenPopup(
|
||||||
|
absl::StrFormat("RoomContextMenu%d", row * squaresWide + col)
|
||||||
|
.c_str());
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor(2);
|
||||||
|
ImGui::EndGroup();
|
||||||
|
|
||||||
|
// Reset style if it was highlighted
|
||||||
|
if (highlight) {
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the square is hovered
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
// Display a tooltip with all the room properties
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
Text("Room ID: %d", row * squaresWide + col);
|
||||||
|
Text("Blockset: %#02x", room.blockset);
|
||||||
|
Text("Spriteset: %#02x", room.spriteset);
|
||||||
|
Text("Palette: %#02x", room.palette);
|
||||||
|
Text("Floor1: %#02x", room.floor1);
|
||||||
|
Text("Floor2: %#02x", room.floor2);
|
||||||
|
Text("Message ID: %#04x", room.message_id_);
|
||||||
|
Text("Size: %#016llx", room.room_size());
|
||||||
|
Text("Size Pointer: %#016llx", room.room_size_ptr());
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep squares in the same line
|
||||||
|
SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
145
src/app/editor/dungeon/dungeon_editor.h
Normal file
145
src/app/editor/dungeon/dungeon_editor.h
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||||
|
|
||||||
|
#include "absl/container/flat_hash_map.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/editor/editor.h"
|
||||||
|
#include "app/editor/graphics/gfx_group_editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "zelda3/dungeon/room.h"
|
||||||
|
#include "zelda3/dungeon/room_entrance.h"
|
||||||
|
#include "zelda3/dungeon/room_object.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
constexpr ImGuiTabItemFlags kDungeonTabFlags =
|
||||||
|
ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip;
|
||||||
|
|
||||||
|
constexpr ImGuiTabBarFlags kDungeonTabBarFlags =
|
||||||
|
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||||
|
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||||
|
ImGuiTabBarFlags_TabListPopupButton;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kDungeonTableFlags =
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||||
|
ImGuiTableFlags_BordersV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief DungeonEditor class for editing dungeons.
|
||||||
|
*
|
||||||
|
* This class is currently a work in progress and is used for editing dungeons.
|
||||||
|
* It provides various functions for updating, cutting, copying, pasting,
|
||||||
|
* undoing, and redoing. It also includes methods for drawing the toolset, room
|
||||||
|
* selector, entrance selector, dungeon tab view, dungeon canvas, room graphics,
|
||||||
|
* tile selector, and object renderer. Additionally, it handles loading room
|
||||||
|
* entrances, calculating usage statistics, and rendering set usage.
|
||||||
|
*/
|
||||||
|
class DungeonEditor : public Editor, public SharedRom {
|
||||||
|
public:
|
||||||
|
DungeonEditor() { type_ = EditorType::kDungeon; }
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
|
||||||
|
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
|
||||||
|
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
|
||||||
|
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
|
||||||
|
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
void add_room(int i) { active_rooms_.push_back(i); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
absl::Status Initialize();
|
||||||
|
absl::Status RefreshGraphics();
|
||||||
|
|
||||||
|
void LoadDungeonRoomSize();
|
||||||
|
|
||||||
|
absl::Status UpdateDungeonRoomView();
|
||||||
|
|
||||||
|
void DrawToolset();
|
||||||
|
void DrawRoomSelector();
|
||||||
|
void DrawEntranceSelector();
|
||||||
|
|
||||||
|
void DrawDungeonTabView();
|
||||||
|
void DrawDungeonCanvas(int room_id);
|
||||||
|
|
||||||
|
void DrawRoomGraphics();
|
||||||
|
void DrawTileSelector();
|
||||||
|
void DrawObjectRenderer();
|
||||||
|
|
||||||
|
void CalculateUsageStats();
|
||||||
|
void DrawUsageStats();
|
||||||
|
void DrawUsageGrid();
|
||||||
|
void RenderSetUsage(const absl::flat_hash_map<uint16_t, int>& usage_map,
|
||||||
|
uint16_t& selected_set, int spriteset_offset = 0x00);
|
||||||
|
|
||||||
|
enum BackgroundType {
|
||||||
|
kNoBackground,
|
||||||
|
kBackground1,
|
||||||
|
kBackground2,
|
||||||
|
kBackground3,
|
||||||
|
kBackgroundAny,
|
||||||
|
};
|
||||||
|
enum PlacementType { kNoType, kSprite, kItem, kDoor, kBlock };
|
||||||
|
|
||||||
|
int background_type_ = kNoBackground;
|
||||||
|
int placement_type_ = kNoType;
|
||||||
|
int current_object_ = 0;
|
||||||
|
|
||||||
|
bool is_loaded_ = false;
|
||||||
|
bool object_loaded_ = false;
|
||||||
|
bool palette_showing_ = false;
|
||||||
|
bool refresh_graphics_ = false;
|
||||||
|
|
||||||
|
uint16_t current_entrance_id_ = 0;
|
||||||
|
uint16_t current_room_id_ = 0;
|
||||||
|
uint64_t current_palette_id_ = 0;
|
||||||
|
uint64_t current_palette_group_id_ = 0;
|
||||||
|
|
||||||
|
ImVector<int> active_rooms_;
|
||||||
|
|
||||||
|
GfxGroupEditor gfx_group_editor_;
|
||||||
|
PaletteEditor palette_editor_;
|
||||||
|
gfx::SnesPalette current_palette_;
|
||||||
|
gfx::SnesPalette full_palette_;
|
||||||
|
gfx::PaletteGroup current_palette_group_;
|
||||||
|
|
||||||
|
gui::Canvas canvas_;
|
||||||
|
gui::Canvas room_gfx_canvas_;
|
||||||
|
gui::Canvas object_canvas_;
|
||||||
|
|
||||||
|
gfx::Bitmap room_gfx_bmp_;
|
||||||
|
std::array<gfx::Bitmap, kNumGfxSheets> graphics_bin_;
|
||||||
|
|
||||||
|
std::vector<gfx::Bitmap*> room_gfx_sheets_;
|
||||||
|
std::vector<zelda3::Room> rooms_;
|
||||||
|
std::vector<zelda3::RoomEntrance> entrances_;
|
||||||
|
zelda3::DungeonObjectRenderer object_renderer_;
|
||||||
|
|
||||||
|
absl::flat_hash_map<uint16_t, int> spriteset_usage_;
|
||||||
|
absl::flat_hash_map<uint16_t, int> blockset_usage_;
|
||||||
|
absl::flat_hash_map<uint16_t, int> palette_usage_;
|
||||||
|
|
||||||
|
std::vector<int64_t> room_size_pointers_;
|
||||||
|
|
||||||
|
uint16_t selected_blockset_ = 0xFFFF; // 0xFFFF indicates no selection
|
||||||
|
uint16_t selected_spriteset_ = 0xFFFF;
|
||||||
|
uint16_t selected_palette_ = 0xFFFF;
|
||||||
|
|
||||||
|
uint64_t total_room_size_ = 0;
|
||||||
|
|
||||||
|
std::unordered_map<int, int> room_size_addresses_;
|
||||||
|
std::unordered_map<int, ImVec4> room_palette_;
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#include "dungeon_editor.h"
|
|
||||||
|
|
||||||
#include "gui/icons.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
void DungeonEditor::Update() {
|
|
||||||
DrawToolset();
|
|
||||||
canvas_.DrawBackground();
|
|
||||||
canvas_.DrawContextMenu();
|
|
||||||
canvas_.DrawGrid();
|
|
||||||
canvas_.DrawOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DungeonEditor::DrawToolset() {
|
|
||||||
if (ImGui::BeginTable("DWToolset", 9, toolset_table_flags_, ImVec2(0, 0))) {
|
|
||||||
ImGui::TableSetupColumn("#undoTool");
|
|
||||||
ImGui::TableSetupColumn("#redoTool");
|
|
||||||
ImGui::TableSetupColumn("#history");
|
|
||||||
ImGui::TableSetupColumn("#separator");
|
|
||||||
ImGui::TableSetupColumn("#bg1Tool");
|
|
||||||
ImGui::TableSetupColumn("#bg2Tool");
|
|
||||||
ImGui::TableSetupColumn("#bg3Tool");
|
|
||||||
ImGui::TableSetupColumn("#itemTool");
|
|
||||||
ImGui::TableSetupColumn("#spriteTool");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Button(ICON_MD_UNDO);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Button(ICON_MD_REDO);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Button(ICON_MD_MANAGE_HISTORY);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ICON_MD_MORE_VERT);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Button(ICON_MD_FILTER_1);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Button(ICON_MD_FILTER_2);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Button(ICON_MD_FILTER_3);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Button(ICON_MD_GRASS);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Button(ICON_MD_PEST_CONTROL_RODENT);
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
|
||||||
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include "gui/canvas.h"
|
|
||||||
#include "gui/icons.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
class DungeonEditor {
|
|
||||||
public:
|
|
||||||
void Update();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void DrawToolset();
|
|
||||||
|
|
||||||
gui::Canvas canvas_;
|
|
||||||
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
|
|
||||||
};
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
7
src/app/editor/editor.cc
Normal file
7
src/app/editor/editor.cc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
21
src/app/editor/editor.cmake
Normal file
21
src/app/editor/editor.cmake
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
set(
|
||||||
|
YAZE_APP_EDITOR_SRC
|
||||||
|
app/editor/editor.cc
|
||||||
|
app/editor/editor_manager.cc
|
||||||
|
app/editor/dungeon/dungeon_editor.cc
|
||||||
|
app/editor/overworld/overworld_editor.cc
|
||||||
|
app/editor/sprite/sprite_editor.cc
|
||||||
|
app/editor/music/music_editor.cc
|
||||||
|
app/editor/message/message_editor.cc
|
||||||
|
app/editor/message/message_data.cc
|
||||||
|
app/editor/code/assembly_editor.cc
|
||||||
|
app/editor/graphics/screen_editor.cc
|
||||||
|
app/editor/graphics/graphics_editor.cc
|
||||||
|
app/editor/graphics/palette_editor.cc
|
||||||
|
app/editor/graphics/tile16_editor.cc
|
||||||
|
app/editor/graphics/gfx_group_editor.cc
|
||||||
|
app/editor/overworld/entity.cc
|
||||||
|
app/editor/system/settings_editor.cc
|
||||||
|
app/editor/system/command_manager.cc
|
||||||
|
app/editor/system/extension_manager.cc
|
||||||
|
)
|
||||||
79
src/app/editor/editor.h
Normal file
79
src/app/editor/editor.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_EDITOR_H
|
||||||
|
#define YAZE_APP_CORE_EDITOR_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/system/command_manager.h"
|
||||||
|
#include "app/editor/system/constant_manager.h"
|
||||||
|
#include "app/editor/system/extension_manager.h"
|
||||||
|
#include "app/editor/system/history_manager.h"
|
||||||
|
#include "app/editor/system/resource_manager.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace yaze::editor
|
||||||
|
* @brief Editors are the view controllers for the application.
|
||||||
|
*/
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
struct EditorContext {
|
||||||
|
ConstantManager constant_manager;
|
||||||
|
CommandManager command_manager;
|
||||||
|
ExtensionManager extension_manager;
|
||||||
|
HistoryManager history_manager;
|
||||||
|
ResourceManager resource_manager;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class EditorType {
|
||||||
|
kAssembly,
|
||||||
|
kDungeon,
|
||||||
|
kGraphics,
|
||||||
|
kMusic,
|
||||||
|
kOverworld,
|
||||||
|
kPalette,
|
||||||
|
kScreen,
|
||||||
|
kSprite,
|
||||||
|
kMessage,
|
||||||
|
kSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array<const char *, 10> kEditorNames = {
|
||||||
|
"Assembly", "Dungeon", "Graphics", "Music", "Overworld",
|
||||||
|
"Palette", "Screen", "Sprite", "Message", "Settings",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Editor
|
||||||
|
* @brief Interface for editor classes.
|
||||||
|
*
|
||||||
|
* Provides basic editing operations that each editor should implement.
|
||||||
|
*/
|
||||||
|
class Editor {
|
||||||
|
public:
|
||||||
|
Editor() = default;
|
||||||
|
virtual ~Editor() = default;
|
||||||
|
|
||||||
|
virtual absl::Status Cut() = 0;
|
||||||
|
virtual absl::Status Copy() = 0;
|
||||||
|
virtual absl::Status Paste() = 0;
|
||||||
|
|
||||||
|
virtual absl::Status Undo() = 0;
|
||||||
|
virtual absl::Status Redo() = 0;
|
||||||
|
|
||||||
|
virtual absl::Status Update() = 0;
|
||||||
|
|
||||||
|
virtual absl::Status Find() = 0;
|
||||||
|
|
||||||
|
EditorType type() const { return type_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
EditorType type_;
|
||||||
|
EditorContext context_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_EDITOR_H
|
||||||
730
src/app/editor/editor_manager.cc
Normal file
730
src/app/editor/editor_manager.cc
Normal file
@@ -0,0 +1,730 @@
|
|||||||
|
#include "editor_manager.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/match.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/core/project.h"
|
||||||
|
#include "app/editor/code/assembly_editor.h"
|
||||||
|
#include "app/editor/dungeon/dungeon_editor.h"
|
||||||
|
#include "app/editor/graphics/graphics_editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/graphics/screen_editor.h"
|
||||||
|
#include "app/editor/music/music_editor.h"
|
||||||
|
#include "app/editor/overworld/overworld_editor.h"
|
||||||
|
#include "app/editor/sprite/sprite_editor.h"
|
||||||
|
#include "app/editor/system/flags.h"
|
||||||
|
#include "app/emu/emulator.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "editor/editor.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using namespace ImGui;
|
||||||
|
using core::FileDialogWrapper;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool BeginCentered(const char *name) {
|
||||||
|
ImGuiIO const &io = GetIO();
|
||||||
|
ImVec2 pos(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f);
|
||||||
|
SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||||
|
ImGuiWindowFlags flags =
|
||||||
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
|
||||||
|
return Begin(name, nullptr, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsEditorActive(Editor *editor, std::vector<Editor *> &active_editors) {
|
||||||
|
return std::find(active_editors.begin(), active_editors.end(), editor) !=
|
||||||
|
active_editors.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void EditorManager::Initialize(std::string filename) {
|
||||||
|
if (!filename.empty()) {
|
||||||
|
PRINT_IF_ERROR(rom()->LoadFromFile(filename));
|
||||||
|
}
|
||||||
|
overworld_editor_.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status EditorManager::Update() {
|
||||||
|
ManageKeyboardShortcuts();
|
||||||
|
|
||||||
|
DrawYazeMenu();
|
||||||
|
DrawStatusPopup();
|
||||||
|
DrawAboutPopup();
|
||||||
|
DrawInfoPopup();
|
||||||
|
|
||||||
|
if (rom()->is_loaded() && !rom_assets_loaded_) {
|
||||||
|
RETURN_IF_ERROR(rom()->LoadAllGraphicsData())
|
||||||
|
RETURN_IF_ERROR(overworld_editor_.LoadGraphics());
|
||||||
|
rom_assets_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ManageActiveEditors();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::ManageActiveEditors() {
|
||||||
|
// Show popup pane to select an editor to add
|
||||||
|
static bool show_add_editor = false;
|
||||||
|
if (show_add_editor) OpenPopup("AddEditor");
|
||||||
|
|
||||||
|
if (BeginPopup("AddEditor", ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
if (MenuItem("Overworld", nullptr, false,
|
||||||
|
!IsEditorActive(&overworld_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&overworld_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Dungeon", nullptr, false,
|
||||||
|
!IsEditorActive(&dungeon_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&dungeon_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Graphics", nullptr, false,
|
||||||
|
!IsEditorActive(&graphics_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&graphics_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Music", nullptr, false,
|
||||||
|
!IsEditorActive(&music_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&music_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Palette", nullptr, false,
|
||||||
|
!IsEditorActive(&palette_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&palette_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Screen", nullptr, false,
|
||||||
|
!IsEditorActive(&screen_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&screen_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Sprite", nullptr, false,
|
||||||
|
!IsEditorActive(&sprite_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&sprite_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Code", nullptr, false,
|
||||||
|
!IsEditorActive(&assembly_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&assembly_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Message", nullptr, false,
|
||||||
|
!IsEditorActive(&message_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&message_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
if (MenuItem("Settings", nullptr, false,
|
||||||
|
!IsEditorActive(&settings_editor_, active_editors_))) {
|
||||||
|
active_editors_.push_back(&settings_editor_);
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsPopupOpen("AddEditor")) {
|
||||||
|
show_add_editor = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabBar("##TabBar", ImGuiTabBarFlags_Reorderable |
|
||||||
|
ImGuiTabBarFlags_AutoSelectNewTabs)) {
|
||||||
|
for (auto editor : active_editors_) {
|
||||||
|
bool open = true;
|
||||||
|
switch (editor->type()) {
|
||||||
|
case EditorType::kOverworld:
|
||||||
|
if (overworld_editor_.jump_to_tab() == -1) {
|
||||||
|
if (BeginTabItem("Overworld", &open)) {
|
||||||
|
current_editor_ = &overworld_editor_;
|
||||||
|
status_ = overworld_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kDungeon:
|
||||||
|
if (BeginTabItem("Dungeon", &open)) {
|
||||||
|
current_editor_ = &dungeon_editor_;
|
||||||
|
status_ = dungeon_editor_.Update();
|
||||||
|
if (overworld_editor_.jump_to_tab() != -1) {
|
||||||
|
dungeon_editor_.add_room(overworld_editor_.jump_to_tab());
|
||||||
|
overworld_editor_.jump_to_tab_ = -1;
|
||||||
|
}
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kGraphics:
|
||||||
|
if (BeginTabItem("Graphics", &open)) {
|
||||||
|
current_editor_ = &graphics_editor_;
|
||||||
|
status_ = graphics_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kMusic:
|
||||||
|
if (BeginTabItem("Music", &open)) {
|
||||||
|
current_editor_ = &music_editor_;
|
||||||
|
|
||||||
|
status_ = music_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kPalette:
|
||||||
|
if (BeginTabItem("Palette", &open)) {
|
||||||
|
current_editor_ = &palette_editor_;
|
||||||
|
status_ = palette_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kScreen:
|
||||||
|
if (BeginTabItem("Screen", &open)) {
|
||||||
|
current_editor_ = &screen_editor_;
|
||||||
|
status_ = screen_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kSprite:
|
||||||
|
if (BeginTabItem("Sprite", &open)) {
|
||||||
|
current_editor_ = &sprite_editor_;
|
||||||
|
status_ = sprite_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kAssembly:
|
||||||
|
if (BeginTabItem("Code", &open)) {
|
||||||
|
current_editor_ = &assembly_editor_;
|
||||||
|
assembly_editor_.UpdateCodeView();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kSettings:
|
||||||
|
if (BeginTabItem("Settings", &open)) {
|
||||||
|
current_editor_ = &settings_editor_;
|
||||||
|
status_ = settings_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EditorType::kMessage:
|
||||||
|
if (BeginTabItem("Message", &open)) {
|
||||||
|
current_editor_ = &message_editor_;
|
||||||
|
status_ = message_editor_.Update();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!open) {
|
||||||
|
active_editors_.erase(
|
||||||
|
std::remove(active_editors_.begin(), active_editors_.end(), editor),
|
||||||
|
active_editors_.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing)) {
|
||||||
|
show_add_editor = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::ManageKeyboardShortcuts() {
|
||||||
|
bool ctrl_or_super = (GetIO().KeyCtrl || GetIO().KeySuper);
|
||||||
|
|
||||||
|
editor_context_.command_manager.ShowWhichKey();
|
||||||
|
|
||||||
|
// If CMD + R is pressed, reload the top result of recent files
|
||||||
|
if (IsKeyDown(ImGuiKey_R) && ctrl_or_super) {
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
manager.Load();
|
||||||
|
if (!manager.GetRecentFiles().empty()) {
|
||||||
|
auto front = manager.GetRecentFiles().front();
|
||||||
|
OpenRomOrProject(front);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_F1)) {
|
||||||
|
about_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If CMD + Q is pressed, quit the application
|
||||||
|
if (IsKeyDown(ImGuiKey_Q) && ctrl_or_super) {
|
||||||
|
quit_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If CMD + O is pressed, open a file dialog
|
||||||
|
if (IsKeyDown(ImGuiKey_O) && ctrl_or_super) {
|
||||||
|
LoadRom();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If CMD + S is pressed, save the current ROM
|
||||||
|
if (IsKeyDown(ImGuiKey_S) && ctrl_or_super) {
|
||||||
|
SaveRom();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_X) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Cut();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_C) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_V) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Paste();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_Z) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_Y) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Redo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsKeyDown(ImGuiKey_F) && ctrl_or_super) {
|
||||||
|
status_ = current_editor_->Find();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::DrawStatusPopup() {
|
||||||
|
static absl::Status prev_status;
|
||||||
|
if (!status_.ok()) {
|
||||||
|
show_status_ = true;
|
||||||
|
prev_status = status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_status_ && (BeginCentered("StatusWindow"))) {
|
||||||
|
Text("%s", ICON_MD_ERROR);
|
||||||
|
Text("%s", prev_status.ToString().c_str());
|
||||||
|
Spacing();
|
||||||
|
NextColumn();
|
||||||
|
Columns(1);
|
||||||
|
Separator();
|
||||||
|
NewLine();
|
||||||
|
SameLine(128);
|
||||||
|
if (Button("OK", gui::kDefaultModalSize) || IsKeyPressed(ImGuiKey_Space)) {
|
||||||
|
show_status_ = false;
|
||||||
|
status_ = absl::OkStatus();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CONTENT_COPY, ImVec2(50, 0))) {
|
||||||
|
SetClipboardText(prev_status.ToString().c_str());
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::DrawAboutPopup() {
|
||||||
|
if (about_) OpenPopup("About");
|
||||||
|
if (BeginPopupModal("About", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Yet Another Zelda3 Editor - v%s", version_.c_str());
|
||||||
|
Text("Written by: scawful");
|
||||||
|
Spacing();
|
||||||
|
Text("Special Thanks: Zarby89, JaredBrian");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
about_ = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::DrawInfoPopup() {
|
||||||
|
if (rom_info_) OpenPopup("ROM Information");
|
||||||
|
if (BeginPopupModal("ROM Information", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Title: %s", rom()->title().c_str());
|
||||||
|
Text("ROM Size: %s", core::HexLongLong(rom()->size()).c_str());
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize) ||
|
||||||
|
IsKeyPressed(ImGuiKey_Escape)) {
|
||||||
|
rom_info_ = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::DrawYazeMenu() {
|
||||||
|
static bool show_display_settings = false;
|
||||||
|
|
||||||
|
if (BeginMenuBar()) {
|
||||||
|
DrawYazeMenuBar();
|
||||||
|
SameLine(GetWindowWidth() - GetStyle().ItemSpacing.x -
|
||||||
|
CalcTextSize(ICON_MD_DISPLAY_SETTINGS).x - 110);
|
||||||
|
PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
|
||||||
|
if (Button(ICON_MD_DISPLAY_SETTINGS)) {
|
||||||
|
show_display_settings = !show_display_settings;
|
||||||
|
}
|
||||||
|
PopStyleColor();
|
||||||
|
Text("yaze v%s", version_.c_str());
|
||||||
|
EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_display_settings) {
|
||||||
|
Begin("Display Settings", &show_display_settings, ImGuiWindowFlags_None);
|
||||||
|
gui::DrawDisplaySettings();
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::DrawYazeMenuBar() {
|
||||||
|
static bool save_as_menu = false;
|
||||||
|
static bool new_project_menu = false;
|
||||||
|
|
||||||
|
if (BeginMenu("File")) {
|
||||||
|
if (MenuItem("Open", "Ctrl+O")) {
|
||||||
|
LoadRom();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Open Recent")) {
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
manager.Load();
|
||||||
|
if (manager.GetRecentFiles().empty()) {
|
||||||
|
MenuItem("No Recent Files", nullptr, false, false);
|
||||||
|
} else {
|
||||||
|
for (const auto &filePath : manager.GetRecentFiles()) {
|
||||||
|
if (MenuItem(filePath.c_str())) {
|
||||||
|
OpenRomOrProject(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
MENU_ITEM2("Save", "Ctrl+S") {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
SaveRom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MENU_ITEM("Save As..") { save_as_menu = true; }
|
||||||
|
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
MENU_ITEM("Close") {
|
||||||
|
status_ = rom()->Close();
|
||||||
|
rom_assets_loaded_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
if (BeginMenu("Project")) {
|
||||||
|
if (MenuItem("Create New Project")) {
|
||||||
|
// Create a new project
|
||||||
|
new_project_menu = true;
|
||||||
|
}
|
||||||
|
if (MenuItem("Open Project")) {
|
||||||
|
// Open an existing project
|
||||||
|
status_ = current_project_.Open(
|
||||||
|
core::FileDialogWrapper::ShowOpenFileDialog());
|
||||||
|
if (status_.ok()) {
|
||||||
|
status_ = OpenProject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (MenuItem("Save Project")) {
|
||||||
|
// Save the current project
|
||||||
|
status_ = current_project_.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Options")) {
|
||||||
|
MenuItem("Backup ROM", "", &backup_rom_);
|
||||||
|
MenuItem("Save New Auto", "", &save_new_auto_);
|
||||||
|
Separator();
|
||||||
|
if (BeginMenu("Experiment Flags")) {
|
||||||
|
static FlagsMenu flags_menu;
|
||||||
|
flags_menu.Draw();
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
if (MenuItem("Quit", "Ctrl+Q")) {
|
||||||
|
quit_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save_as_menu) {
|
||||||
|
static std::string save_as_filename = "";
|
||||||
|
Begin("Save As..", &save_as_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
||||||
|
InputText("Filename", &save_as_filename);
|
||||||
|
if (Button("Save", gui::kDefaultModalSize)) {
|
||||||
|
SaveRom();
|
||||||
|
save_as_menu = false;
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button("Cancel", gui::kDefaultModalSize)) {
|
||||||
|
save_as_menu = false;
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_project_menu) {
|
||||||
|
Begin("New Project", &new_project_menu, ImGuiWindowFlags_AlwaysAutoResize);
|
||||||
|
static std::string save_as_filename = "";
|
||||||
|
InputText("Project Name", &save_as_filename);
|
||||||
|
if (Button("Destination Filepath", gui::kDefaultModalSize)) {
|
||||||
|
current_project_.filepath = FileDialogWrapper::ShowOpenFolderDialog();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
Text("%s", current_project_.filepath.c_str());
|
||||||
|
if (Button("ROM File", gui::kDefaultModalSize)) {
|
||||||
|
current_project_.rom_filename_ = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
Text("%s", current_project_.rom_filename_.c_str());
|
||||||
|
if (Button("Labels File", gui::kDefaultModalSize)) {
|
||||||
|
current_project_.labels_filename_ =
|
||||||
|
FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
Text("%s", current_project_.labels_filename_.c_str());
|
||||||
|
if (Button("Code Folder", gui::kDefaultModalSize)) {
|
||||||
|
current_project_.code_folder_ = FileDialogWrapper::ShowOpenFolderDialog();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
Text("%s", current_project_.code_folder_.c_str());
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
if (Button("Create", gui::kDefaultModalSize)) {
|
||||||
|
new_project_menu = false;
|
||||||
|
status_ = current_project_.Create(save_as_filename);
|
||||||
|
if (status_.ok()) {
|
||||||
|
status_ = current_project_.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button("Cancel", gui::kDefaultModalSize)) {
|
||||||
|
new_project_menu = false;
|
||||||
|
}
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Edit")) {
|
||||||
|
MENU_ITEM2("Undo", "Ctrl+Z") { status_ = current_editor_->Undo(); }
|
||||||
|
MENU_ITEM2("Redo", "Ctrl+Y") { status_ = current_editor_->Redo(); }
|
||||||
|
Separator();
|
||||||
|
MENU_ITEM2("Cut", "Ctrl+X") { status_ = current_editor_->Cut(); }
|
||||||
|
MENU_ITEM2("Copy", "Ctrl+C") { status_ = current_editor_->Copy(); }
|
||||||
|
MENU_ITEM2("Paste", "Ctrl+V") { status_ = current_editor_->Paste(); }
|
||||||
|
Separator();
|
||||||
|
MENU_ITEM2("Find", "Ctrl+F") { status_ = current_editor_->Find(); }
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool show_imgui_metrics = false;
|
||||||
|
static bool show_memory_editor = false;
|
||||||
|
static bool show_asm_editor = false;
|
||||||
|
static bool show_imgui_demo = false;
|
||||||
|
static bool show_palette_editor = false;
|
||||||
|
static bool show_emulator = false;
|
||||||
|
|
||||||
|
if (show_imgui_demo) ShowDemoWindow();
|
||||||
|
if (show_imgui_metrics) ShowMetricsWindow(&show_imgui_metrics);
|
||||||
|
if (show_memory_editor) memory_editor_.Update(show_memory_editor);
|
||||||
|
if (show_asm_editor) assembly_editor_.Update(show_asm_editor);
|
||||||
|
|
||||||
|
if (show_emulator) {
|
||||||
|
Begin("Emulator", &show_emulator, ImGuiWindowFlags_MenuBar);
|
||||||
|
emulator_.Run();
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_palette_editor) {
|
||||||
|
Begin("Palette Editor", &show_palette_editor);
|
||||||
|
status_ = palette_editor_.Update();
|
||||||
|
End();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("View")) {
|
||||||
|
MenuItem("Emulator", nullptr, &show_emulator);
|
||||||
|
Separator();
|
||||||
|
MenuItem("Memory Editor", nullptr, &show_memory_editor);
|
||||||
|
MenuItem("Assembly Editor", nullptr, &show_asm_editor);
|
||||||
|
MenuItem("Palette Editor", nullptr, &show_palette_editor);
|
||||||
|
Separator();
|
||||||
|
MENU_ITEM("ROM Information") rom_info_ = true;
|
||||||
|
Separator();
|
||||||
|
MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
|
||||||
|
MenuItem("ImGui Metrics", nullptr, &show_imgui_metrics);
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool show_resource_label_manager = false;
|
||||||
|
if (current_project_.project_opened_) {
|
||||||
|
if (BeginMenu("Project")) {
|
||||||
|
Text("Name: %s", current_project_.name.c_str());
|
||||||
|
Text("ROM: %s", current_project_.rom_filename_.c_str());
|
||||||
|
Text("Labels: %s", current_project_.labels_filename_.c_str());
|
||||||
|
Text("Code: %s", current_project_.code_folder_.c_str());
|
||||||
|
Separator();
|
||||||
|
MenuItem("Resource Labels", nullptr, &show_resource_label_manager);
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool open_rom_help = false;
|
||||||
|
static bool open_supported_features = false;
|
||||||
|
static bool open_manage_project = false;
|
||||||
|
if (BeginMenu("Help")) {
|
||||||
|
if (MenuItem("How to open a ROM")) open_rom_help = true;
|
||||||
|
if (MenuItem("Supported Features")) open_supported_features = true;
|
||||||
|
if (MenuItem("How to manage a project")) open_manage_project = true;
|
||||||
|
|
||||||
|
if (MenuItem("About", "F1")) about_ = true;
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open_supported_features) OpenPopup("Supported Features");
|
||||||
|
if (BeginPopupModal("Supported Features", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Overworld");
|
||||||
|
BulletText("LW/DW/SW Tilemap Editing");
|
||||||
|
BulletText("LW/DW/SW Map Properties");
|
||||||
|
BulletText("Create/Delete/Update Entrances");
|
||||||
|
BulletText("Create/Delete/Update Exits");
|
||||||
|
BulletText("Create/Delete/Update Sprites");
|
||||||
|
BulletText("Create/Delete/Update Items");
|
||||||
|
|
||||||
|
Text("Dungeon");
|
||||||
|
BulletText("View Room Header Properties");
|
||||||
|
BulletText("View Entrance Properties");
|
||||||
|
|
||||||
|
Text("Graphics");
|
||||||
|
BulletText("View Decompressed Graphics Sheets");
|
||||||
|
BulletText("View/Update Graphics Groups");
|
||||||
|
|
||||||
|
Text("Palettes");
|
||||||
|
BulletText("View Palette Groups");
|
||||||
|
|
||||||
|
Text("Saveable");
|
||||||
|
BulletText("All Listed Overworld Features");
|
||||||
|
BulletText("Hex Editor Changes");
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
open_supported_features = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open_rom_help) OpenPopup("Open a ROM");
|
||||||
|
if (BeginPopupModal("Open a ROM", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("File -> Open");
|
||||||
|
Text("Select a ROM file to open");
|
||||||
|
Text("Supported ROMs (headered or unheadered):");
|
||||||
|
Text("The Legend of Zelda: A Link to the Past");
|
||||||
|
Text("US Version 1.0");
|
||||||
|
Text("JP Version 1.0");
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
open_rom_help = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open_manage_project) OpenPopup("Manage Project");
|
||||||
|
if (BeginPopupModal("Manage Project", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
Text("Project Menu");
|
||||||
|
Text("Create a new project or open an existing one.");
|
||||||
|
Text("Save the project to save the current state of the project.");
|
||||||
|
TextWrapped(
|
||||||
|
"To save a project, you need to first open a ROM and initialize your "
|
||||||
|
"code path and labels file. Label resource manager can be found in "
|
||||||
|
"the View menu. Code path is set in the Code editor after opening a "
|
||||||
|
"folder.");
|
||||||
|
|
||||||
|
if (Button("Close", gui::kDefaultModalSize)) {
|
||||||
|
open_manage_project = false;
|
||||||
|
CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_resource_label_manager) {
|
||||||
|
rom()->resource_label()->DisplayLabels(&show_resource_label_manager);
|
||||||
|
if (current_project_.project_opened_ &&
|
||||||
|
!current_project_.labels_filename_.empty()) {
|
||||||
|
current_project_.labels_filename_ = rom()->resource_label()->filename_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::LoadRom() {
|
||||||
|
auto file_name = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
auto load_rom = rom()->LoadFromFile(file_name);
|
||||||
|
if (load_rom.ok()) {
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
manager.Load();
|
||||||
|
manager.AddFile(file_name);
|
||||||
|
manager.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::SaveRom() {
|
||||||
|
if (core::ExperimentFlags::get().kSaveDungeonMaps) {
|
||||||
|
status_ = screen_editor_.SaveDungeonMaps();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_ = overworld_editor_.Save();
|
||||||
|
RETURN_VOID_IF_ERROR(status_);
|
||||||
|
|
||||||
|
status_ = rom()->SaveToFile(backup_rom_, save_new_auto_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditorManager::OpenRomOrProject(const std::string &filename) {
|
||||||
|
if (absl::StrContains(filename, ".yaze")) {
|
||||||
|
status_ = current_project_.Open(filename);
|
||||||
|
if (status_.ok()) {
|
||||||
|
status_ = OpenProject();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status_ = rom()->LoadFromFile(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status EditorManager::OpenProject() {
|
||||||
|
RETURN_IF_ERROR(rom()->LoadFromFile(current_project_.rom_filename_));
|
||||||
|
|
||||||
|
if (!rom()->resource_label()->LoadLabels(current_project_.labels_filename_)) {
|
||||||
|
return absl::InternalError(
|
||||||
|
"Could not load labels file, update your project file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static RecentFilesManager manager("recent_files.txt");
|
||||||
|
manager.Load();
|
||||||
|
manager.AddFile(current_project_.filepath + "/" + current_project_.name +
|
||||||
|
".yaze");
|
||||||
|
manager.Save();
|
||||||
|
|
||||||
|
assembly_editor_.OpenFolder(current_project_.code_folder_);
|
||||||
|
|
||||||
|
current_project_.project_opened_ = true;
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
112
src/app/editor/editor_manager.h
Normal file
112
src/app/editor/editor_manager.h
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_EDITOR_MANAGER_H
|
||||||
|
#define YAZE_APP_EDITOR_EDITOR_MANAGER_H
|
||||||
|
|
||||||
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/project.h"
|
||||||
|
#include "app/editor/code/assembly_editor.h"
|
||||||
|
#include "app/editor/code/memory_editor.h"
|
||||||
|
#include "app/editor/dungeon/dungeon_editor.h"
|
||||||
|
#include "app/editor/graphics/graphics_editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/graphics/screen_editor.h"
|
||||||
|
#include "app/editor/message/message_editor.h"
|
||||||
|
#include "app/editor/music/music_editor.h"
|
||||||
|
#include "app/editor/overworld/overworld_editor.h"
|
||||||
|
#include "app/editor/sprite/sprite_editor.h"
|
||||||
|
#include "app/editor/system/settings_editor.h"
|
||||||
|
#include "app/emu/emulator.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "yaze_config.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class EditorManager
|
||||||
|
* @brief The EditorManager controls the main editor window and manages the
|
||||||
|
* various editor classes.
|
||||||
|
*
|
||||||
|
* The EditorManager class contains instances of various editor classes such as
|
||||||
|
* AssemblyEditor, DungeonEditor, GraphicsEditor, MusicEditor, OverworldEditor,
|
||||||
|
* PaletteEditor, ScreenEditor, and SpriteEditor. The current_editor_ member
|
||||||
|
* variable points to the currently active editor in the tab view.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class EditorManager : public SharedRom {
|
||||||
|
public:
|
||||||
|
EditorManager() {
|
||||||
|
current_editor_ = &overworld_editor_;
|
||||||
|
active_editors_.push_back(&overworld_editor_);
|
||||||
|
active_editors_.push_back(&dungeon_editor_);
|
||||||
|
active_editors_.push_back(&graphics_editor_);
|
||||||
|
active_editors_.push_back(&palette_editor_);
|
||||||
|
active_editors_.push_back(&sprite_editor_);
|
||||||
|
active_editors_.push_back(&message_editor_);
|
||||||
|
active_editors_.push_back(&screen_editor_);
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << YAZE_VERSION_MAJOR << "." << YAZE_VERSION_MINOR << "."
|
||||||
|
<< YAZE_VERSION_PATCH;
|
||||||
|
ss >> version_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize(std::string filename = "");
|
||||||
|
absl::Status Update();
|
||||||
|
|
||||||
|
auto emulator() -> emu::Emulator & { return emulator_; }
|
||||||
|
auto quit() { return quit_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ManageActiveEditors();
|
||||||
|
void ManageKeyboardShortcuts();
|
||||||
|
|
||||||
|
void DrawStatusPopup();
|
||||||
|
void DrawAboutPopup();
|
||||||
|
void DrawInfoPopup();
|
||||||
|
|
||||||
|
void DrawYazeMenu();
|
||||||
|
void DrawYazeMenuBar();
|
||||||
|
|
||||||
|
void LoadRom();
|
||||||
|
void SaveRom();
|
||||||
|
|
||||||
|
void OpenRomOrProject(const std::string &filename);
|
||||||
|
absl::Status OpenProject();
|
||||||
|
|
||||||
|
bool quit_ = false;
|
||||||
|
bool about_ = false;
|
||||||
|
bool rom_info_ = false;
|
||||||
|
bool backup_rom_ = false;
|
||||||
|
bool save_new_auto_ = true;
|
||||||
|
bool show_status_ = false;
|
||||||
|
bool rom_assets_loaded_ = false;
|
||||||
|
|
||||||
|
std::string version_ = "";
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
emu::Emulator emulator_;
|
||||||
|
std::vector<Editor *> active_editors_;
|
||||||
|
|
||||||
|
Project current_project_;
|
||||||
|
EditorContext editor_context_;
|
||||||
|
|
||||||
|
Editor *current_editor_ = nullptr;
|
||||||
|
AssemblyEditor assembly_editor_;
|
||||||
|
DungeonEditor dungeon_editor_;
|
||||||
|
GraphicsEditor graphics_editor_;
|
||||||
|
MusicEditor music_editor_;
|
||||||
|
OverworldEditor overworld_editor_{*rom()};
|
||||||
|
PaletteEditor palette_editor_;
|
||||||
|
ScreenEditor screen_editor_;
|
||||||
|
SpriteEditor sprite_editor_;
|
||||||
|
SettingsEditor settings_editor_;
|
||||||
|
MessageEditor message_editor_;
|
||||||
|
MemoryEditorWithDiffChecker memory_editor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_EDITOR_MANAGER_H
|
||||||
299
src/app/editor/graphics/gfx_group_editor.cc
Normal file
299
src/app/editor/graphics/gfx_group_editor.cc
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
#include "gfx_group_editor.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginGroup;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndGroup;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::GetContentRegionAvail;
|
||||||
|
using ImGui::GetStyle;
|
||||||
|
using ImGui::IsItemClicked;
|
||||||
|
using ImGui::PopID;
|
||||||
|
using ImGui::PushID;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::SetNextItemWidth;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
using gfx::kPaletteGroupNames;
|
||||||
|
using gfx::PaletteCategory;
|
||||||
|
|
||||||
|
absl::Status GfxGroupEditor::Update() {
|
||||||
|
if (BeginTabBar("GfxGroupEditor")) {
|
||||||
|
if (BeginTabItem("Main")) {
|
||||||
|
gui::InputHexByte("Selected Blockset", &selected_blockset_,
|
||||||
|
(uint8_t)0x24);
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "blockset", "0x" + std::to_string(selected_blockset_),
|
||||||
|
"Blockset " + std::to_string(selected_blockset_));
|
||||||
|
DrawBlocksetViewer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem("Rooms")) {
|
||||||
|
gui::InputHexByte("Selected Blockset", &selected_roomset_, (uint8_t)81);
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "roomset", "0x" + std::to_string(selected_roomset_),
|
||||||
|
"Roomset " + std::to_string(selected_roomset_));
|
||||||
|
DrawRoomsetViewer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem("Sprites")) {
|
||||||
|
gui::InputHexByte("Selected Spriteset", &selected_spriteset_,
|
||||||
|
(uint8_t)143);
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "spriteset", "0x" + std::to_string(selected_spriteset_),
|
||||||
|
"Spriteset " + std::to_string(selected_spriteset_));
|
||||||
|
Text("Values");
|
||||||
|
DrawSpritesetViewer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem("Palettes")) {
|
||||||
|
DrawPaletteViewer();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxGroupEditor::DrawBlocksetViewer(bool sheet_only) {
|
||||||
|
if (BeginTable("##BlocksetTable", sheet_only ? 1 : 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
if (!sheet_only) {
|
||||||
|
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
}
|
||||||
|
|
||||||
|
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
if (!sheet_only) {
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
SetNextItemWidth(100.f);
|
||||||
|
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
|
||||||
|
&rom()->main_blockset_ids[selected_blockset_][i]);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
int sheet_id = rom()->main_blockset_ids[selected_blockset_][i];
|
||||||
|
auto sheet = rom()->gfx_sheets().at(sheet_id);
|
||||||
|
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
|
||||||
|
0x20, true, false, 22);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxGroupEditor::DrawRoomsetViewer() {
|
||||||
|
Text("Values - Overwrites 4 of main blockset");
|
||||||
|
if (BeginTable("##Roomstable", 3,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("List", ImGuiTableColumnFlags_WidthFixed, 100);
|
||||||
|
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginChild("##RoomsetList");
|
||||||
|
for (int i = 0; i < 0x51; i++) {
|
||||||
|
BeginGroup();
|
||||||
|
std::string roomset_label = absl::StrFormat("0x%02X", i);
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "roomset", roomset_label, "Roomset " + roomset_label);
|
||||||
|
if (IsItemClicked()) {
|
||||||
|
selected_roomset_ = i;
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SetNextItemWidth(100.f);
|
||||||
|
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
|
||||||
|
&rom()->room_blockset_ids[selected_roomset_][i]);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int sheet_id = rom()->room_blockset_ids[selected_roomset_][i];
|
||||||
|
auto sheet = rom()->gfx_sheets().at(sheet_id);
|
||||||
|
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
|
||||||
|
0x20, true, false, 23);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxGroupEditor::DrawSpritesetViewer(bool sheet_only) {
|
||||||
|
if (BeginTable("##SpritesTable", sheet_only ? 1 : 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
if (!sheet_only) {
|
||||||
|
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
}
|
||||||
|
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
if (!sheet_only) {
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
SetNextItemWidth(100.f);
|
||||||
|
gui::InputHexByte(("0x" + std::to_string(i)).c_str(),
|
||||||
|
&rom()->spriteset_ids[selected_spriteset_][i]);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableNextColumn();
|
||||||
|
{
|
||||||
|
BeginGroup();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int sheet_id = rom()->spriteset_ids[selected_spriteset_][i];
|
||||||
|
auto sheet = rom()->gfx_sheets().at(115 + sheet_id);
|
||||||
|
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256, 0x10 * 0x04,
|
||||||
|
0x20, true, false, 24);
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
}
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
void DrawPaletteFromPaletteGroup(gfx::SnesPalette &palette) {
|
||||||
|
if (palette.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (size_t n = 0; n < palette.size(); n++) {
|
||||||
|
PushID(n);
|
||||||
|
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
|
||||||
|
// Small icon of the color in the palette
|
||||||
|
if (gui::SnesColorButton(absl::StrCat("Palette", n), palette[n],
|
||||||
|
ImGuiColorEditFlags_NoAlpha |
|
||||||
|
ImGuiColorEditFlags_NoPicker |
|
||||||
|
ImGuiColorEditFlags_NoTooltip)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void GfxGroupEditor::DrawPaletteViewer() {
|
||||||
|
gui::InputHexByte("Selected Paletteset", &selected_paletteset_);
|
||||||
|
if (selected_paletteset_ >= 71) {
|
||||||
|
selected_paletteset_ = 71;
|
||||||
|
}
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, "paletteset", "0x" + std::to_string(selected_paletteset_),
|
||||||
|
"Paletteset " + std::to_string(selected_paletteset_));
|
||||||
|
|
||||||
|
uint8_t &dungeon_main_palette_val =
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][0];
|
||||||
|
uint8_t &dungeon_spr_pal_1_val =
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][1];
|
||||||
|
uint8_t &dungeon_spr_pal_2_val =
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][2];
|
||||||
|
uint8_t &dungeon_spr_pal_3_val =
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][3];
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon Main", &dungeon_main_palette_val);
|
||||||
|
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, kPaletteGroupNames[PaletteCategory::kDungeons].data(),
|
||||||
|
std::to_string(dungeon_main_palette_val), "Unnamed dungeon palette");
|
||||||
|
auto &palette = *rom()->mutable_palette_group()->dungeon_main.mutable_palette(
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][0]);
|
||||||
|
DrawPaletteFromPaletteGroup(palette);
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon Spr Pal 1", &dungeon_spr_pal_1_val);
|
||||||
|
auto &spr_aux_pal1 =
|
||||||
|
*rom()->mutable_palette_group()->sprites_aux1.mutable_palette(
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][1]);
|
||||||
|
DrawPaletteFromPaletteGroup(spr_aux_pal1);
|
||||||
|
SameLine();
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, kPaletteGroupNames[PaletteCategory::kSpritesAux1].data(),
|
||||||
|
std::to_string(dungeon_spr_pal_1_val), "Dungeon Spr Pal 1");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon Spr Pal 2", &dungeon_spr_pal_2_val);
|
||||||
|
auto &spr_aux_pal2 =
|
||||||
|
*rom()->mutable_palette_group()->sprites_aux2.mutable_palette(
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][2]);
|
||||||
|
DrawPaletteFromPaletteGroup(spr_aux_pal2);
|
||||||
|
SameLine();
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, kPaletteGroupNames[PaletteCategory::kSpritesAux2].data(),
|
||||||
|
std::to_string(dungeon_spr_pal_2_val), "Dungeon Spr Pal 2");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
gui::InputHexByte("Dungeon Spr Pal 3", &dungeon_spr_pal_3_val);
|
||||||
|
auto &spr_aux_pal3 =
|
||||||
|
*rom()->mutable_palette_group()->sprites_aux3.mutable_palette(
|
||||||
|
rom()->paletteset_ids[selected_paletteset_][3]);
|
||||||
|
DrawPaletteFromPaletteGroup(spr_aux_pal3);
|
||||||
|
SameLine();
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, kPaletteGroupNames[PaletteCategory::kSpritesAux3].data(),
|
||||||
|
std::to_string(dungeon_spr_pal_3_val), "Dungeon Spr Pal 3");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
46
src/app/editor/graphics/gfx_group_editor.h
Normal file
46
src/app/editor/graphics/gfx_group_editor.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class GfxGroupEditor
|
||||||
|
* @brief Manage graphics group configurations in a Rom.
|
||||||
|
*/
|
||||||
|
class GfxGroupEditor : public SharedRom {
|
||||||
|
public:
|
||||||
|
absl::Status Update();
|
||||||
|
|
||||||
|
void DrawBlocksetViewer(bool sheet_only = false);
|
||||||
|
void DrawRoomsetViewer();
|
||||||
|
void DrawSpritesetViewer(bool sheet_only = false);
|
||||||
|
void DrawPaletteViewer();
|
||||||
|
|
||||||
|
void SetSelectedBlockset(uint8_t blockset) { selected_blockset_ = blockset; }
|
||||||
|
void SetSelectedRoomset(uint8_t roomset) { selected_roomset_ = roomset; }
|
||||||
|
void SetSelectedSpriteset(uint8_t spriteset) {
|
||||||
|
selected_spriteset_ = spriteset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t selected_blockset_ = 0;
|
||||||
|
uint8_t selected_roomset_ = 0;
|
||||||
|
uint8_t selected_spriteset_ = 0;
|
||||||
|
uint8_t selected_paletteset_ = 0;
|
||||||
|
|
||||||
|
gui::Canvas blockset_canvas_;
|
||||||
|
gui::Canvas roomset_canvas_;
|
||||||
|
gui::Canvas spriteset_canvas_;
|
||||||
|
|
||||||
|
gfx::SnesPalette palette_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
#endif // YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||||
837
src/app/editor/graphics/graphics_editor.cc
Normal file
837
src/app/editor/graphics/graphics_editor.cc
Normal file
@@ -0,0 +1,837 @@
|
|||||||
|
#include "graphics_editor.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "app/core/platform/clipboard.h"
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/core/platform/renderer.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/compression.h"
|
||||||
|
#include "app/gfx/scad_format.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/gui/modules/asset_browser.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
#include "imgui_memory_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using core::Renderer;
|
||||||
|
|
||||||
|
using gfx::kPaletteGroupAddressesKeys;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::InputInt;
|
||||||
|
using ImGui::InputText;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kGfxEditTableFlags =
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
|
||||||
|
ImGuiTableFlags_SizingFixedFit;
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::Update() {
|
||||||
|
if (ImGui::BeginTabBar("##TabBar")) {
|
||||||
|
status_ = UpdateGfxEdit();
|
||||||
|
TAB_ITEM("Sheet Browser")
|
||||||
|
if (asset_browser_.Initialized == false) {
|
||||||
|
asset_browser_.Initialize(rom()->gfx_sheets());
|
||||||
|
}
|
||||||
|
asset_browser_.Draw(rom()->gfx_sheets());
|
||||||
|
END_TAB_ITEM()
|
||||||
|
status_ = UpdateScadView();
|
||||||
|
status_ = UpdateLinkGfxView();
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
CLEAR_AND_RETURN_STATUS(status_)
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::UpdateGfxEdit() {
|
||||||
|
if (ImGui::BeginTabItem("Sheet Editor")) {
|
||||||
|
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
for (const auto& name :
|
||||||
|
{"Tilesheets", "Current Graphics", "Palette Controls"})
|
||||||
|
ImGui::TableSetupColumn(name);
|
||||||
|
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
NEXT_COLUMN();
|
||||||
|
status_ = UpdateGfxSheetList();
|
||||||
|
|
||||||
|
NEXT_COLUMN();
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
DrawGfxEditToolset();
|
||||||
|
status_ = UpdateGfxTabView();
|
||||||
|
}
|
||||||
|
|
||||||
|
NEXT_COLUMN();
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
status_ = UpdatePaletteColumn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsEditor::DrawGfxEditToolset() {
|
||||||
|
if (ImGui::BeginTable("##GfxEditToolset", 9, ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
for (const auto& name :
|
||||||
|
{"Select", "Pencil", "Fill", "Copy Sheet", "Paste Sheet", "Zoom Out",
|
||||||
|
"Zoom In", "Current Color", "Tile Size"})
|
||||||
|
ImGui::TableSetupColumn(name);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_SELECT_ALL)) {
|
||||||
|
gfx_edit_mode_ = GfxEditMode::kSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_DRAW)) {
|
||||||
|
gfx_edit_mode_ = GfxEditMode::kPencil;
|
||||||
|
}
|
||||||
|
HOVER_HINT("Draw with current color");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
|
||||||
|
gfx_edit_mode_ = GfxEditMode::kFill;
|
||||||
|
}
|
||||||
|
HOVER_HINT("Fill with current color");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_CONTENT_COPY)) {
|
||||||
|
std::vector<uint8_t> png_data =
|
||||||
|
rom()->gfx_sheets().at(current_sheet_).GetPngData();
|
||||||
|
core::CopyImageToClipboard(png_data);
|
||||||
|
}
|
||||||
|
HOVER_HINT("Copy to Clipboard");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_CONTENT_PASTE)) {
|
||||||
|
std::vector<uint8_t> png_data;
|
||||||
|
int width, height;
|
||||||
|
core::GetImageFromClipboard(png_data, width, height);
|
||||||
|
if (png_data.size() > 0) {
|
||||||
|
rom()
|
||||||
|
->mutable_gfx_sheets()
|
||||||
|
->at(current_sheet_)
|
||||||
|
.Create(width, height, 8, png_data);
|
||||||
|
Renderer::GetInstance().UpdateBitmap(
|
||||||
|
&rom()->mutable_gfx_sheets()->at(current_sheet_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HOVER_HINT("Paste from Clipboard");
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_ZOOM_OUT)) {
|
||||||
|
if (current_scale_ >= 0.0f) {
|
||||||
|
current_scale_ -= 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_ZOOM_IN)) {
|
||||||
|
if (current_scale_ <= 16.0f) {
|
||||||
|
current_scale_ += 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
auto bitmap = rom()->gfx_sheets()[current_sheet_];
|
||||||
|
auto palette = bitmap.palette();
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
auto color =
|
||||||
|
ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
|
||||||
|
palette[i].rgb().z / 255.0f, 255.0f);
|
||||||
|
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
|
||||||
|
color)) {
|
||||||
|
current_color_ = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexByte("Tile Size", &tile_size_);
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::UpdateGfxSheetList() {
|
||||||
|
ImGui::BeginChild(
|
||||||
|
"##GfxEditChild", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
// TODO: Update the interaction for multi select on sheets
|
||||||
|
static ImGuiSelectionBasicStorage selection;
|
||||||
|
ImGuiMultiSelectFlags flags =
|
||||||
|
ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
|
||||||
|
ImGuiMultiSelectIO* ms_io =
|
||||||
|
ImGui::BeginMultiSelect(flags, selection.Size, kNumGfxSheets);
|
||||||
|
selection.ApplyRequests(ms_io);
|
||||||
|
ImGuiListClipper clipper;
|
||||||
|
clipper.Begin(kNumGfxSheets);
|
||||||
|
if (ms_io->RangeSrcItem != -1)
|
||||||
|
clipper.IncludeItemByIndex(
|
||||||
|
(int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
|
||||||
|
|
||||||
|
int key = 0;
|
||||||
|
for (auto& value : rom()->gfx_sheets()) {
|
||||||
|
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
|
||||||
|
ImVec2(0x100 + 1, 0x40 + 1), true,
|
||||||
|
ImGuiWindowFlags_NoDecoration);
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
|
graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1));
|
||||||
|
graphics_bin_canvas_.DrawContextMenu();
|
||||||
|
if (value.is_active()) {
|
||||||
|
auto texture = value.texture();
|
||||||
|
graphics_bin_canvas_.draw_list()->AddImage(
|
||||||
|
(ImTextureID)(intptr_t)texture,
|
||||||
|
ImVec2(graphics_bin_canvas_.zero_point().x + 2,
|
||||||
|
graphics_bin_canvas_.zero_point().y + 2),
|
||||||
|
ImVec2(graphics_bin_canvas_.zero_point().x +
|
||||||
|
value.width() * sheet_scale_,
|
||||||
|
graphics_bin_canvas_.zero_point().y +
|
||||||
|
value.height() * sheet_scale_));
|
||||||
|
|
||||||
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
|
current_sheet_ = key;
|
||||||
|
open_sheets_.insert(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a slightly transparent rectangle behind the text
|
||||||
|
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
|
||||||
|
graphics_bin_canvas_.zero_point().y + 2);
|
||||||
|
ImVec2 text_size =
|
||||||
|
ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str());
|
||||||
|
ImVec2 rent_min(text_pos.x, text_pos.y);
|
||||||
|
ImVec2 rent_max(text_pos.x + text_size.x, text_pos.y + text_size.y);
|
||||||
|
|
||||||
|
graphics_bin_canvas_.draw_list()->AddRectFilled(rent_min, rent_max,
|
||||||
|
IM_COL32(0, 125, 0, 128));
|
||||||
|
|
||||||
|
graphics_bin_canvas_.draw_list()->AddText(
|
||||||
|
text_pos, IM_COL32(125, 255, 125, 255),
|
||||||
|
absl::StrFormat("%02X", key).c_str());
|
||||||
|
|
||||||
|
key++;
|
||||||
|
}
|
||||||
|
graphics_bin_canvas_.DrawGrid(16.0f);
|
||||||
|
graphics_bin_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ms_io = ImGui::EndMultiSelect();
|
||||||
|
selection.ApplyRequests(ms_io);
|
||||||
|
ImGui::EndChild();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::UpdateGfxTabView() {
|
||||||
|
static int next_tab_id = 0;
|
||||||
|
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
|
||||||
|
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||||
|
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||||
|
ImGuiTabBarFlags_TabListPopupButton;
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
|
||||||
|
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
|
||||||
|
ImGuiTabItemFlags_NoTooltip)) {
|
||||||
|
open_sheets_.insert(next_tab_id++);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& sheet_id : open_sheets_) {
|
||||||
|
bool open = true;
|
||||||
|
if (ImGui::BeginTabItem(absl::StrFormat("%d", sheet_id).c_str(), &open,
|
||||||
|
ImGuiTabItemFlags_None)) {
|
||||||
|
current_sheet_ = sheet_id;
|
||||||
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||||
|
release_queue_.push(sheet_id);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
||||||
|
release_queue_.push(sheet_id);
|
||||||
|
child_window_sheets_.insert(sheet_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto child_id =
|
||||||
|
absl::StrFormat("##GfxEditPaletteChildWindow%d", sheet_id);
|
||||||
|
ImGui::BeginChild(child_id.c_str(), ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_NoDecoration |
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
||||||
|
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
||||||
|
|
||||||
|
gfx::Bitmap& current_bitmap = rom()->mutable_gfx_sheets()->at(sheet_id);
|
||||||
|
|
||||||
|
auto draw_tile_event = [&]() {
|
||||||
|
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, ¤t_bitmap,
|
||||||
|
current_color_);
|
||||||
|
Renderer::GetInstance().UpdateBitmap(¤t_bitmap);
|
||||||
|
};
|
||||||
|
|
||||||
|
current_sheet_canvas_.UpdateColorPainter(
|
||||||
|
rom()->mutable_gfx_sheets()->at(sheet_id), current_color_,
|
||||||
|
draw_tile_event, tile_size_, current_scale_);
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open) release_queue_.push(sheet_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release any tabs that were closed
|
||||||
|
while (!release_queue_.empty()) {
|
||||||
|
auto sheet_id = release_queue_.top();
|
||||||
|
open_sheets_.erase(sheet_id);
|
||||||
|
release_queue_.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw any child windows that were created
|
||||||
|
if (!child_window_sheets_.empty()) {
|
||||||
|
int id_to_release = -1;
|
||||||
|
for (const auto& id : child_window_sheets_) {
|
||||||
|
bool active = true;
|
||||||
|
ImGui::SetNextWindowPos(ImGui::GetIO().MousePos, ImGuiCond_Once);
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(0x100 + 1 * 16, 0x40 + 1 * 16),
|
||||||
|
ImGuiCond_Once);
|
||||||
|
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
|
||||||
|
&active, ImGuiWindowFlags_AlwaysUseWindowPadding);
|
||||||
|
current_sheet_ = id;
|
||||||
|
// ImVec2(0x100, 0x40),
|
||||||
|
current_sheet_canvas_.UpdateColorPainter(
|
||||||
|
rom()->mutable_gfx_sheets()->at(id), current_color_,
|
||||||
|
[&]() {
|
||||||
|
|
||||||
|
},
|
||||||
|
tile_size_, current_scale_);
|
||||||
|
ImGui::End();
|
||||||
|
|
||||||
|
if (active == false) {
|
||||||
|
id_to_release = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (id_to_release != -1) {
|
||||||
|
child_window_sheets_.erase(id_to_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::UpdatePaletteColumn() {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
auto palette_group = *rom()->palette_group().get_group(
|
||||||
|
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
|
||||||
|
auto palette = palette_group.palette(edit_palette_index_);
|
||||||
|
gui::TextWithSeparators("ROM Palette");
|
||||||
|
ImGui::SetNextItemWidth(100.f);
|
||||||
|
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
|
||||||
|
kPaletteGroupAddressesKeys,
|
||||||
|
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
||||||
|
ImGui::SetNextItemWidth(100.f);
|
||||||
|
gui::InputHex("Palette Group Index", &edit_palette_index_);
|
||||||
|
|
||||||
|
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
|
||||||
|
palette);
|
||||||
|
|
||||||
|
if (refresh_graphics_ && !open_sheets_.empty()) {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
rom()
|
||||||
|
->mutable_gfx_sheets()
|
||||||
|
->data()[current_sheet_]
|
||||||
|
.ApplyPaletteWithTransparent(palette, edit_palette_sub_index_));
|
||||||
|
Renderer::GetInstance().UpdateBitmap(
|
||||||
|
&rom()->mutable_gfx_sheets()->data()[current_sheet_]);
|
||||||
|
refresh_graphics_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::UpdateLinkGfxView() {
|
||||||
|
TAB_ITEM("Player Animations")
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("##PlayerAnimationTable", 3, kGfxEditTableFlags,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
for (const auto& name : {"Canvas", "Animation Steps", "Properties"})
|
||||||
|
ImGui::TableSetupColumn(name);
|
||||||
|
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
NEXT_COLUMN();
|
||||||
|
link_canvas_.DrawBackground();
|
||||||
|
link_canvas_.DrawGrid(16.0f);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (auto link_sheet : *rom()->mutable_link_graphics()) {
|
||||||
|
int x_offset = 0;
|
||||||
|
int y_offset = gfx::kTilesheetHeight * i * 4;
|
||||||
|
link_canvas_.DrawContextMenu(&link_sheet);
|
||||||
|
link_canvas_.DrawBitmap(link_sheet, x_offset, y_offset, 4);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
link_canvas_.DrawOverlay();
|
||||||
|
link_canvas_.DrawGrid();
|
||||||
|
|
||||||
|
NEXT_COLUMN();
|
||||||
|
ImGui::Text("Placeholder");
|
||||||
|
|
||||||
|
NEXT_COLUMN();
|
||||||
|
if (ImGui::Button("Load Link Graphics (Experimental)")) {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
// Load Links graphics from the ROM
|
||||||
|
RETURN_IF_ERROR(rom()->LoadLinkGraphics());
|
||||||
|
|
||||||
|
// Split it into the pose data frames
|
||||||
|
// Create an animation step display for the poses
|
||||||
|
// Allow the user to modify the frames used in an anim step
|
||||||
|
// LinkOAM_AnimationSteps:
|
||||||
|
// #_0D85FB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
|
||||||
|
END_TAB_ITEM()
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::UpdateScadView() {
|
||||||
|
TAB_ITEM("Prototype")
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(DrawToolset())
|
||||||
|
|
||||||
|
if (open_memory_editor_) {
|
||||||
|
ImGui::Begin("Memory Editor", &open_memory_editor_);
|
||||||
|
RETURN_IF_ERROR(DrawMemoryEditor())
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_SizingStretchSame;
|
||||||
|
|
||||||
|
BEGIN_TABLE("#gfxEditTable", 4, kGfxEditFlags)
|
||||||
|
SETUP_COLUMN("File Import (BIN, CGX, ROM)")
|
||||||
|
SETUP_COLUMN("Palette (COL)")
|
||||||
|
ImGui::TableSetupColumn("Tilemaps and Objects (SCR, PNL, OBJ)",
|
||||||
|
ImGuiTableColumnFlags_WidthFixed);
|
||||||
|
SETUP_COLUMN("Graphics Preview")
|
||||||
|
TABLE_HEADERS()
|
||||||
|
NEXT_COLUMN() {
|
||||||
|
status_ = DrawCgxImport();
|
||||||
|
status_ = DrawClipboardImport();
|
||||||
|
status_ = DrawFileImport();
|
||||||
|
status_ = DrawExperimentalFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
|
NEXT_COLUMN() { status_ = DrawPaletteControls(); }
|
||||||
|
|
||||||
|
NEXT_COLUMN()
|
||||||
|
gui::BitmapCanvasPipeline(scr_canvas_, scr_bitmap_, 0x200, 0x200, 0x20,
|
||||||
|
scr_loaded_, false, 0);
|
||||||
|
status_ = DrawScrImport();
|
||||||
|
|
||||||
|
NEXT_COLUMN()
|
||||||
|
if (super_donkey_) {
|
||||||
|
// TODO: Implement the Super Donkey 1 graphics decompression
|
||||||
|
// if (refresh_graphics_) {
|
||||||
|
// for (int i = 0; i < kNumGfxSheets; i++) {
|
||||||
|
// status_ = graphics_bin_[i].ApplyPalette(
|
||||||
|
// col_file_palette_group_[current_palette_index_]);
|
||||||
|
// Renderer::GetInstance().UpdateBitmap(&graphics_bin_[i]);
|
||||||
|
// }
|
||||||
|
// refresh_graphics_ = false;
|
||||||
|
// }
|
||||||
|
// Load the full graphics space from `super_donkey_1.bin`
|
||||||
|
// gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3,
|
||||||
|
// super_donkey_, graphics_bin_);
|
||||||
|
} else if (cgx_loaded_ && col_file_) {
|
||||||
|
// Load the CGX graphics
|
||||||
|
gui::BitmapCanvasPipeline(import_canvas_, cgx_bitmap_, 0x100, 16384, 0x20,
|
||||||
|
cgx_loaded_, true, 5);
|
||||||
|
} else {
|
||||||
|
// Load the BIN/Clipboard Graphics
|
||||||
|
gui::BitmapCanvasPipeline(import_canvas_, bin_bitmap_, 0x100, 16384, 0x20,
|
||||||
|
gfx_loaded_, true, 2);
|
||||||
|
}
|
||||||
|
END_TABLE()
|
||||||
|
|
||||||
|
END_TAB_ITEM()
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawToolset() {
|
||||||
|
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
|
||||||
|
"#memoryEditor",
|
||||||
|
"##separator_gfx1",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("GraphicsToolset", 2, ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
for (const auto& name : kGfxToolsetColumnNames)
|
||||||
|
ImGui::TableSetupColumn(name.data());
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(ICON_MD_MEMORY)) {
|
||||||
|
if (!open_memory_editor_) {
|
||||||
|
open_memory_editor_ = true;
|
||||||
|
} else {
|
||||||
|
open_memory_editor_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEXT_COLUMN("Open Memory Editor") // Separator
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawCgxImport() {
|
||||||
|
gui::TextWithSeparators("Cgx Import");
|
||||||
|
InputInt("BPP", ¤t_bpp_);
|
||||||
|
|
||||||
|
InputText("##CGXFile", &cgx_file_name_);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (ImGui::Button("Open CGX")) {
|
||||||
|
auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
cgx_file_name_ = filename;
|
||||||
|
cgx_file_path_ = std::filesystem::absolute(filename).string();
|
||||||
|
is_open_ = true;
|
||||||
|
cgx_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Copy CGX Path")) {
|
||||||
|
ImGui::SetClipboardText(cgx_file_path_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Load CGX Data")) {
|
||||||
|
status_ = gfx::scad_format::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
|
||||||
|
decoded_cgx_, extra_cgx_data_);
|
||||||
|
|
||||||
|
cgx_bitmap_.Create(0x80, 0x200, 8, decoded_cgx_);
|
||||||
|
if (col_file_) {
|
||||||
|
cgx_bitmap_.ApplyPalette(decoded_col_);
|
||||||
|
Renderer::GetInstance().RenderBitmap(&cgx_bitmap_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawScrImport() {
|
||||||
|
InputText("##ScrFile", &scr_file_name_);
|
||||||
|
|
||||||
|
if (ImGui::Button("Open SCR")) {
|
||||||
|
auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
scr_file_name_ = filename;
|
||||||
|
scr_file_path_ = std::filesystem::absolute(filename).string();
|
||||||
|
is_open_ = true;
|
||||||
|
scr_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputInt("SCR Mod", &scr_mod_value_);
|
||||||
|
|
||||||
|
if (ImGui::Button("Load Scr Data")) {
|
||||||
|
status_ =
|
||||||
|
gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
|
||||||
|
|
||||||
|
decoded_scr_data_.resize(0x100 * 0x100);
|
||||||
|
status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_,
|
||||||
|
decoded_scr_data_, decoded_cgx_);
|
||||||
|
|
||||||
|
scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_);
|
||||||
|
if (scr_loaded_) {
|
||||||
|
scr_bitmap_.ApplyPalette(decoded_col_);
|
||||||
|
Renderer::GetInstance().RenderBitmap(&scr_bitmap_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawPaletteControls() {
|
||||||
|
gui::TextWithSeparators("COL Import");
|
||||||
|
InputText("##ColFile", &col_file_name_);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (ImGui::Button("Open COL")) {
|
||||||
|
auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
col_file_name_ = filename;
|
||||||
|
col_file_path_ = std::filesystem::absolute(filename).string();
|
||||||
|
status_ = temp_rom_.LoadFromFile(col_file_path_,
|
||||||
|
/*z3_load=*/false);
|
||||||
|
auto col_data_ = gfx::GetColFileData(temp_rom_.mutable_data());
|
||||||
|
if (col_file_palette_group_.size() != 0) {
|
||||||
|
col_file_palette_group_.clear();
|
||||||
|
}
|
||||||
|
auto col_file_palette_group_status =
|
||||||
|
gfx::CreatePaletteGroupFromColFile(col_data_);
|
||||||
|
if (col_file_palette_group_status.ok()) {
|
||||||
|
col_file_palette_group_ = col_file_palette_group_status.value();
|
||||||
|
}
|
||||||
|
col_file_palette_ = gfx::SnesPalette(col_data_);
|
||||||
|
|
||||||
|
// gigaleak dev format based code
|
||||||
|
decoded_col_ = gfx::scad_format::DecodeColFile(col_file_path_);
|
||||||
|
col_file_ = true;
|
||||||
|
is_open_ = true;
|
||||||
|
}
|
||||||
|
HOVER_HINT(".COL, .BAK");
|
||||||
|
|
||||||
|
if (ImGui::Button("Copy Col Path")) {
|
||||||
|
ImGui::SetClipboardText(col_file_path_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
gui::TextWithSeparators("ROM Palette");
|
||||||
|
gui::InputHex("Palette Index", ¤t_palette_index_);
|
||||||
|
ImGui::Combo("Palette", ¤t_palette_, kPaletteGroupAddressesKeys,
|
||||||
|
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col_file_palette_.size() != 0) {
|
||||||
|
gui::SelectablePalettePipeline(current_palette_index_, refresh_graphics_,
|
||||||
|
col_file_palette_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawObjImport() {
|
||||||
|
gui::TextWithSeparators("OBJ Import");
|
||||||
|
|
||||||
|
InputText("##ObjFile", &obj_file_path_);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (ImGui::Button("Open OBJ")) {
|
||||||
|
auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
obj_file_path_ = std::filesystem::absolute(filename).string();
|
||||||
|
status_ = temp_rom_.LoadFromFile(obj_file_path_);
|
||||||
|
is_open_ = true;
|
||||||
|
obj_loaded_ = true;
|
||||||
|
}
|
||||||
|
HOVER_HINT(".OBJ, .BAK");
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawTilemapImport() {
|
||||||
|
gui::TextWithSeparators("Tilemap Import");
|
||||||
|
|
||||||
|
InputText("##TMapFile", &tilemap_file_path_);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (ImGui::Button("Open Tilemap")) {
|
||||||
|
auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
tilemap_file_path_ = std::filesystem::absolute(filename).string();
|
||||||
|
status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
|
||||||
|
status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
|
||||||
|
|
||||||
|
// Extract the high and low bytes from the file.
|
||||||
|
auto decomp_sheet = gfx::lc_lz2::DecompressV2(tilemap_rom_.data(),
|
||||||
|
gfx::lc_lz2::kNintendoMode1);
|
||||||
|
tilemap_loaded_ = true;
|
||||||
|
is_open_ = true;
|
||||||
|
}
|
||||||
|
HOVER_HINT(".DAT, .BIN, .HEX");
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawFileImport() {
|
||||||
|
gui::TextWithSeparators("BIN Import");
|
||||||
|
|
||||||
|
InputText("##ROMFile", &file_path_);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (ImGui::Button("Open BIN")) {
|
||||||
|
auto filename = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
file_path_ = filename;
|
||||||
|
status_ = temp_rom_.LoadFromFile(file_path_);
|
||||||
|
is_open_ = true;
|
||||||
|
}
|
||||||
|
HOVER_HINT(".BIN, .HEX");
|
||||||
|
|
||||||
|
if (Button("Copy File Path")) {
|
||||||
|
ImGui::SetClipboardText(file_path_.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
gui::InputHex("BIN Offset", ¤t_offset_);
|
||||||
|
gui::InputHex("BIN Size", &bin_size_);
|
||||||
|
|
||||||
|
if (Button("Decompress BIN")) {
|
||||||
|
if (file_path_.empty()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Please select a file before decompressing.");
|
||||||
|
}
|
||||||
|
RETURN_IF_ERROR(DecompressImportData(bin_size_))
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawClipboardImport() {
|
||||||
|
gui::TextWithSeparators("Clipboard Import");
|
||||||
|
if (Button("Paste From Clipboard")) {
|
||||||
|
const char* text = ImGui::GetClipboardText();
|
||||||
|
if (text) {
|
||||||
|
const auto clipboard_data =
|
||||||
|
std::vector<uint8_t>(text, text + strlen(text));
|
||||||
|
ImGui::MemFree((void*)text);
|
||||||
|
status_ = temp_rom_.LoadFromBytes(clipboard_data);
|
||||||
|
is_open_ = true;
|
||||||
|
open_memory_editor_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gui::InputHex("Offset", &clipboard_offset_);
|
||||||
|
gui::InputHex("Size", &clipboard_size_);
|
||||||
|
gui::InputHex("Num Sheets", &num_sheets_to_load_);
|
||||||
|
|
||||||
|
if (Button("Decompress Clipboard Data")) {
|
||||||
|
if (temp_rom_.is_loaded()) {
|
||||||
|
status_ = DecompressImportData(0x40000);
|
||||||
|
} else {
|
||||||
|
status_ = absl::InvalidArgumentError(
|
||||||
|
"Please paste data into the clipboard before "
|
||||||
|
"decompressing.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawExperimentalFeatures() {
|
||||||
|
gui::TextWithSeparators("Experimental");
|
||||||
|
if (Button("Decompress Super Donkey Full")) {
|
||||||
|
if (file_path_.empty()) {
|
||||||
|
return absl::InvalidArgumentError(
|
||||||
|
"Please select `super_donkey_1.bin` before "
|
||||||
|
"importing.");
|
||||||
|
}
|
||||||
|
RETURN_IF_ERROR(DecompressSuperDonkey())
|
||||||
|
}
|
||||||
|
ImGui::SetItemTooltip(
|
||||||
|
"Requires `super_donkey_1.bin` to be imported under the "
|
||||||
|
"BIN import section.");
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DrawMemoryEditor() {
|
||||||
|
std::string title = "Memory Editor";
|
||||||
|
if (is_open_) {
|
||||||
|
static MemoryEditor mem_edit;
|
||||||
|
mem_edit.DrawWindow(title.c_str(), temp_rom_.mutable_data(),
|
||||||
|
temp_rom_.size());
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DecompressImportData(int size) {
|
||||||
|
ASSIGN_OR_RETURN(import_data_, gfx::lc_lz2::DecompressV2(
|
||||||
|
temp_rom_.data(), current_offset_, size))
|
||||||
|
|
||||||
|
auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3);
|
||||||
|
bin_bitmap_.Create(gfx::kTilesheetWidth, 0x2000, gfx::kTilesheetDepth,
|
||||||
|
converted_sheet);
|
||||||
|
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
auto palette_group = rom()->palette_group().overworld_animated;
|
||||||
|
z3_rom_palette_ = palette_group[current_palette_];
|
||||||
|
if (col_file_) {
|
||||||
|
status_ = bin_bitmap_.ApplyPalette(col_file_palette_);
|
||||||
|
} else {
|
||||||
|
status_ = bin_bitmap_.ApplyPalette(z3_rom_palette_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer::GetInstance().RenderBitmap(&bin_bitmap_);
|
||||||
|
gfx_loaded_ = true;
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status GraphicsEditor::DecompressSuperDonkey() {
|
||||||
|
int i = 0;
|
||||||
|
for (const auto& offset : kSuperDonkeyTiles) {
|
||||||
|
int offset_value =
|
||||||
|
std::stoi(offset, nullptr, 16); // convert hex string to int
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
auto decompressed_data,
|
||||||
|
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
|
||||||
|
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
|
||||||
|
gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
|
||||||
|
gfx::kTilesheetDepth, converted_sheet);
|
||||||
|
if (col_file_) {
|
||||||
|
status_ = gfx_sheets_[i].ApplyPalette(
|
||||||
|
col_file_palette_group_[current_palette_index_]);
|
||||||
|
} else {
|
||||||
|
// ROM palette
|
||||||
|
|
||||||
|
auto palette_group = rom()->palette_group().get_group(
|
||||||
|
kPaletteGroupAddressesKeys[current_palette_]);
|
||||||
|
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
|
||||||
|
status_ = gfx_sheets_[i].ApplyPalette(z3_rom_palette_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer::GetInstance().RenderBitmap(&gfx_sheets_[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& offset : kSuperDonkeySprites) {
|
||||||
|
int offset_value =
|
||||||
|
std::stoi(offset, nullptr, 16); // convert hex string to int
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
auto decompressed_data,
|
||||||
|
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
|
||||||
|
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
|
||||||
|
gfx_sheets_[i] = gfx::Bitmap(gfx::kTilesheetWidth, gfx::kTilesheetHeight,
|
||||||
|
gfx::kTilesheetDepth, converted_sheet);
|
||||||
|
if (col_file_) {
|
||||||
|
status_ = gfx_sheets_[i].ApplyPalette(
|
||||||
|
col_file_palette_group_[current_palette_index_]);
|
||||||
|
} else {
|
||||||
|
// ROM palette
|
||||||
|
auto palette_group = rom()->palette_group().get_group(
|
||||||
|
kPaletteGroupAddressesKeys[current_palette_]);
|
||||||
|
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
|
||||||
|
status_ = gfx_sheets_[i].ApplyPalette(z3_rom_palette_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer::GetInstance().RenderBitmap(&gfx_sheets_[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
super_donkey_ = true;
|
||||||
|
num_sheets_to_load_ = i;
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
200
src/app/editor/graphics/graphics_editor.h
Normal file
200
src/app/editor/graphics/graphics_editor.h
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||||
|
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/modules/asset_browser.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui_memory_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
// "99973","A3D80",
|
||||||
|
|
||||||
|
const std::string kSuperDonkeyTiles[] = {
|
||||||
|
"97C05", "98219", "9871E", "98C00", "99084", "995AF", "99DE0", "9A27E",
|
||||||
|
"9A741", "9AC31", "9B07E", "9B55C", "9B963", "9BB99", "9C009", "9C4B4",
|
||||||
|
"9C92B", "9CDD6", "9D2C2", "9E037", "9E527", "9EA56", "9EF65", "9FCD1",
|
||||||
|
"A0193", "A059E", "A0B17", "A0FB6", "A14A5", "A1988", "A1E66", "A232B",
|
||||||
|
"A27F0", "A2B6E", "A302C", "A3453", "A38CA", "A42BB", "A470C", "A4BA9",
|
||||||
|
"A5089", "A5385", "A5742", "A5BCC", "A6017", "A6361", "A66F8"};
|
||||||
|
|
||||||
|
const std::string kSuperDonkeySprites[] = {
|
||||||
|
"A8E5D", "A9435", "A9934", "A9D83", "AA2F1", "AA6D4", "AABE4", "AB127",
|
||||||
|
"AB65A", "ABBDD", "AC38D", "AC797", "ACCC8", "AD0AE", "AD245", "AD554",
|
||||||
|
"ADAAC", "ADECC", "AE453", "AE9D2", "AEF40", "AF3C9", "AF92E", "AFE9D",
|
||||||
|
"B03D2", "B09AC", "B0F0C", "B1430", "B1859", "B1E01", "B229A", "B2854",
|
||||||
|
"B2D27", "B31D7", "B3B58", "B40B5", "B45A5", "B4D64", "B5031", "B555F",
|
||||||
|
"B5F30", "B6858", "B70DD", "B7526", "B79EC", "B7C83", "B80F7", "B85CC",
|
||||||
|
"B8A3F", "B8F97", "B94F2", "B9A20", "B9E9A", "BA3A2", "BA8F6", "BACDC",
|
||||||
|
"BB1F9", "BB781", "BBCCA", "BC26D", "BC7D4", "BCBB0", "BD082", "BD5FC",
|
||||||
|
"BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D",
|
||||||
|
"C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class GraphicsEditor
|
||||||
|
* @brief Allows the user to edit graphics sheets from the game or view
|
||||||
|
* prototype graphics.
|
||||||
|
*
|
||||||
|
* The GraphicsEditor class is responsible for providing functionality to edit
|
||||||
|
* graphics sheets from the game or view prototype graphics of Link to the Past
|
||||||
|
* from the CGX, SCR, and OBJ formats. It provides various methods to update
|
||||||
|
* different components of the graphics editor, such as the graphics edit tab,
|
||||||
|
* link graphics view, and prototype graphics viewer. It also includes import
|
||||||
|
* functions for different file formats, as well as other utility functions for
|
||||||
|
* drawing toolsets, palette controls, clipboard imports, experimental features,
|
||||||
|
* and memory editor.
|
||||||
|
*/
|
||||||
|
class GraphicsEditor : public SharedRom, public Editor {
|
||||||
|
public:
|
||||||
|
GraphicsEditor() { type_ = EditorType::kGraphics; }
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
|
||||||
|
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
|
||||||
|
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
|
||||||
|
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
|
||||||
|
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class GfxEditMode {
|
||||||
|
kSelect,
|
||||||
|
kPencil,
|
||||||
|
kFill,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Graphics Editor Tab
|
||||||
|
absl::Status UpdateGfxEdit();
|
||||||
|
absl::Status UpdateGfxSheetList();
|
||||||
|
absl::Status UpdateGfxTabView();
|
||||||
|
absl::Status UpdatePaletteColumn();
|
||||||
|
void DrawGfxEditToolset();
|
||||||
|
|
||||||
|
// Link Graphics Edit Tab
|
||||||
|
absl::Status UpdateLinkGfxView();
|
||||||
|
|
||||||
|
// Prototype Graphics Viewer
|
||||||
|
absl::Status UpdateScadView();
|
||||||
|
|
||||||
|
// Import Functions
|
||||||
|
absl::Status DrawCgxImport();
|
||||||
|
absl::Status DrawScrImport();
|
||||||
|
absl::Status DrawFileImport();
|
||||||
|
absl::Status DrawObjImport();
|
||||||
|
absl::Status DrawTilemapImport();
|
||||||
|
|
||||||
|
// Other Functions
|
||||||
|
absl::Status DrawToolset();
|
||||||
|
absl::Status DrawPaletteControls();
|
||||||
|
absl::Status DrawClipboardImport();
|
||||||
|
absl::Status DrawExperimentalFeatures();
|
||||||
|
absl::Status DrawMemoryEditor();
|
||||||
|
|
||||||
|
absl::Status DecompressImportData(int size);
|
||||||
|
absl::Status DecompressSuperDonkey();
|
||||||
|
|
||||||
|
// Member Variables
|
||||||
|
ImVec4 current_color_;
|
||||||
|
uint16_t current_sheet_ = 0;
|
||||||
|
uint8_t tile_size_ = 0x01;
|
||||||
|
std::set<uint16_t> open_sheets_;
|
||||||
|
std::set<uint16_t> child_window_sheets_;
|
||||||
|
std::stack<uint16_t> release_queue_;
|
||||||
|
uint64_t edit_palette_group_name_index_ = 0;
|
||||||
|
uint64_t edit_palette_group_index_ = 0;
|
||||||
|
uint64_t edit_palette_index_ = 0;
|
||||||
|
uint64_t edit_palette_sub_index_ = 0;
|
||||||
|
float sheet_scale_ = 2.0f;
|
||||||
|
float current_scale_ = 4.0f;
|
||||||
|
|
||||||
|
// Prototype Graphics Viewer
|
||||||
|
int current_palette_ = 0;
|
||||||
|
uint64_t current_offset_ = 0;
|
||||||
|
uint64_t current_size_ = 0;
|
||||||
|
uint64_t current_palette_index_ = 0;
|
||||||
|
int current_bpp_ = 0;
|
||||||
|
int scr_mod_value_ = 0;
|
||||||
|
|
||||||
|
uint64_t num_sheets_to_load_ = 1;
|
||||||
|
uint64_t bin_size_ = 0;
|
||||||
|
uint64_t clipboard_offset_ = 0;
|
||||||
|
uint64_t clipboard_size_ = 0;
|
||||||
|
|
||||||
|
bool refresh_graphics_ = false;
|
||||||
|
bool open_memory_editor_ = false;
|
||||||
|
bool gfx_loaded_ = false;
|
||||||
|
bool is_open_ = false;
|
||||||
|
bool super_donkey_ = false;
|
||||||
|
bool col_file_ = false;
|
||||||
|
bool cgx_loaded_ = false;
|
||||||
|
bool scr_loaded_ = false;
|
||||||
|
bool obj_loaded_ = false;
|
||||||
|
bool tilemap_loaded_ = false;
|
||||||
|
|
||||||
|
std::string file_path_ = "";
|
||||||
|
std::string col_file_path_ = "";
|
||||||
|
std::string col_file_name_ = "";
|
||||||
|
std::string cgx_file_path_ = "";
|
||||||
|
std::string cgx_file_name_ = "";
|
||||||
|
std::string scr_file_path_ = "";
|
||||||
|
std::string scr_file_name_ = "";
|
||||||
|
std::string obj_file_path_ = "";
|
||||||
|
std::string tilemap_file_path_ = "";
|
||||||
|
std::string tilemap_file_name_ = "";
|
||||||
|
|
||||||
|
gui::GfxSheetAssetBrowser asset_browser_;
|
||||||
|
|
||||||
|
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
|
||||||
|
|
||||||
|
Rom temp_rom_;
|
||||||
|
Rom tilemap_rom_;
|
||||||
|
zelda3::Overworld overworld_;
|
||||||
|
MemoryEditor cgx_memory_editor_;
|
||||||
|
MemoryEditor col_memory_editor_;
|
||||||
|
PaletteEditor palette_editor_;
|
||||||
|
std::vector<uint8_t> import_data_;
|
||||||
|
std::vector<uint8_t> graphics_buffer_;
|
||||||
|
std::vector<uint8_t> decoded_cgx_;
|
||||||
|
std::vector<uint8_t> cgx_data_;
|
||||||
|
std::vector<uint8_t> extra_cgx_data_;
|
||||||
|
std::vector<SDL_Color> decoded_col_;
|
||||||
|
std::vector<uint8_t> scr_data_;
|
||||||
|
std::vector<uint8_t> decoded_scr_data_;
|
||||||
|
gfx::Bitmap cgx_bitmap_;
|
||||||
|
gfx::Bitmap scr_bitmap_;
|
||||||
|
gfx::Bitmap bin_bitmap_;
|
||||||
|
gfx::Bitmap link_full_sheet_;
|
||||||
|
std::array<gfx::Bitmap, kNumGfxSheets> gfx_sheets_;
|
||||||
|
|
||||||
|
gfx::PaletteGroup col_file_palette_group_;
|
||||||
|
gfx::SnesPalette z3_rom_palette_;
|
||||||
|
gfx::SnesPalette col_file_palette_;
|
||||||
|
gfx::SnesPalette link_palette_;
|
||||||
|
gui::Canvas import_canvas_;
|
||||||
|
gui::Canvas scr_canvas_;
|
||||||
|
gui::Canvas super_donkey_canvas_;
|
||||||
|
gui::Canvas graphics_bin_canvas_;
|
||||||
|
gui::Canvas current_sheet_canvas_{"CurrentSheetCanvas", ImVec2(0x80, 0x20),
|
||||||
|
gui::CanvasGridSize::k8x8};
|
||||||
|
gui::Canvas link_canvas_{
|
||||||
|
"LinkCanvas",
|
||||||
|
ImVec2(gfx::kTilesheetWidth * 4, gfx::kTilesheetHeight * 0x10 * 4),
|
||||||
|
gui::CanvasGridSize::k16x16};
|
||||||
|
absl::Status status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||||
474
src/app/editor/graphics/palette_editor.cc
Normal file
474
src/app/editor/graphics/palette_editor.cc
Normal file
@@ -0,0 +1,474 @@
|
|||||||
|
#include "palette_editor.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::AcceptDragDropPayload;
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginDragDropTarget;
|
||||||
|
using ImGui::BeginGroup;
|
||||||
|
using ImGui::BeginPopup;
|
||||||
|
using ImGui::BeginPopupContextItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::ColorButton;
|
||||||
|
using ImGui::ColorPicker4;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndDragDropTarget;
|
||||||
|
using ImGui::EndGroup;
|
||||||
|
using ImGui::EndPopup;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::GetContentRegionAvail;
|
||||||
|
using ImGui::GetStyle;
|
||||||
|
using ImGui::OpenPopup;
|
||||||
|
using ImGui::PopID;
|
||||||
|
using ImGui::PushID;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Selectable;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::SetClipboardText;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetColumnIndex;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
using ImGui::TreeNode;
|
||||||
|
using ImGui::TreePop;
|
||||||
|
|
||||||
|
using namespace gfx;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kPaletteTableFlags =
|
||||||
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Hideable;
|
||||||
|
|
||||||
|
constexpr ImGuiColorEditFlags kPalNoAlpha = ImGuiColorEditFlags_NoAlpha;
|
||||||
|
|
||||||
|
constexpr ImGuiColorEditFlags kPalButtonFlags2 = ImGuiColorEditFlags_NoAlpha |
|
||||||
|
ImGuiColorEditFlags_NoPicker |
|
||||||
|
ImGuiColorEditFlags_NoTooltip;
|
||||||
|
|
||||||
|
constexpr ImGuiColorEditFlags kColorPopupFlags =
|
||||||
|
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int CustomFormatString(char* buf, size_t buf_size, const char* fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
#ifdef IMGUI_USE_STB_SPRINTF
|
||||||
|
int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
|
||||||
|
#else
|
||||||
|
int w = vsnprintf(buf, buf_size, fmt, args);
|
||||||
|
#endif
|
||||||
|
va_end(args);
|
||||||
|
if (buf == nullptr) return w;
|
||||||
|
if (w == -1 || w >= (int)buf_size) w = (int)buf_size - 1;
|
||||||
|
buf[w] = 0;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float color_saturate(float f) {
|
||||||
|
return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define F32_TO_INT8_SAT(_VAL) \
|
||||||
|
((int)(color_saturate(_VAL) * 255.0f + \
|
||||||
|
0.5f)) // Saturated, always output 0..255
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
|
||||||
|
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
||||||
|
static ImVec4 current_palette[256] = {};
|
||||||
|
ImGuiColorEditFlags misc_flags = ImGuiColorEditFlags_AlphaPreview |
|
||||||
|
ImGuiColorEditFlags_NoDragDrop |
|
||||||
|
ImGuiColorEditFlags_NoOptions;
|
||||||
|
|
||||||
|
// Generate a default palette. The palette will persist and can be edited.
|
||||||
|
static bool init = false;
|
||||||
|
if (loaded && !init) {
|
||||||
|
for (int n = 0; n < palette.size(); n++) {
|
||||||
|
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
|
||||||
|
current_palette[n].x = color.rgb().x / 255;
|
||||||
|
current_palette[n].y = color.rgb().y / 255;
|
||||||
|
current_palette[n].z = color.rgb().z / 255;
|
||||||
|
current_palette[n].w = 255; // Alpha
|
||||||
|
}
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImVec4 backup_color;
|
||||||
|
bool open_popup = ColorButton("MyColor##3b", color, misc_flags);
|
||||||
|
SameLine(0, GetStyle().ItemInnerSpacing.x);
|
||||||
|
open_popup |= Button("Palette");
|
||||||
|
if (open_popup) {
|
||||||
|
OpenPopup("mypicker");
|
||||||
|
backup_color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginPopup("mypicker")) {
|
||||||
|
TEXT_WITH_SEPARATOR("Current Overworld Palette");
|
||||||
|
ColorPicker4("##picker", (float*)&color,
|
||||||
|
misc_flags | ImGuiColorEditFlags_NoSidePreview |
|
||||||
|
ImGuiColorEditFlags_NoSmallPreview);
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
BeginGroup(); // Lock X position
|
||||||
|
Text("Current ==>");
|
||||||
|
SameLine();
|
||||||
|
Text("Previous");
|
||||||
|
|
||||||
|
if (Button("Update Map Palette")) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorButton(
|
||||||
|
"##current", color,
|
||||||
|
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||||
|
ImVec2(60, 40));
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (ColorButton(
|
||||||
|
"##previous", backup_color,
|
||||||
|
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
||||||
|
ImVec2(60, 40)))
|
||||||
|
color = backup_color;
|
||||||
|
|
||||||
|
// List of Colors in Overworld Palette
|
||||||
|
Separator();
|
||||||
|
Text("Palette");
|
||||||
|
for (int n = 0; n < IM_ARRAYSIZE(current_palette); n++) {
|
||||||
|
PushID(n);
|
||||||
|
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
|
||||||
|
if (ColorButton("##palette", current_palette[n], kPalButtonFlags2,
|
||||||
|
ImVec2(20, 20)))
|
||||||
|
color = ImVec4(current_palette[n].x, current_palette[n].y,
|
||||||
|
current_palette[n].z, color.w); // Preserve alpha!
|
||||||
|
|
||||||
|
if (BeginDragDropTarget()) {
|
||||||
|
if (const ImGuiPayload* payload =
|
||||||
|
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||||
|
memcpy((float*)¤t_palette[n], payload->Data, sizeof(float) * 3);
|
||||||
|
if (const ImGuiPayload* payload =
|
||||||
|
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||||
|
memcpy((float*)¤t_palette[n], payload->Data, sizeof(float) * 4);
|
||||||
|
EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::Update() {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
// Initialize the labels
|
||||||
|
for (int i = 0; i < kNumPalettes; i++) {
|
||||||
|
rom()->resource_label()->CreateOrGetLabel(
|
||||||
|
"Palette Group Name", std::to_string(i),
|
||||||
|
std::string(kPaletteGroupNames[i]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return absl::NotFoundError("ROM not open, no palettes to display");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTable("paletteEditorTable", 2, kPaletteTableFlags, ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Palette Groups", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Palette Sets and Metadata",
|
||||||
|
ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
TableNextColumn();
|
||||||
|
DrawModifiedColors();
|
||||||
|
|
||||||
|
DrawCustomPalette();
|
||||||
|
Separator();
|
||||||
|
gui::SnesColorEdit4("Current Color Picker", ¤t_color_,
|
||||||
|
ImGuiColorEditFlags_NoAlpha);
|
||||||
|
Separator();
|
||||||
|
DisplayCategoryTable();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gfx_group_editor_.DrawPaletteViewer();
|
||||||
|
Separator();
|
||||||
|
static bool in_use = false;
|
||||||
|
ImGui::Checkbox("Palette in use? ", &in_use);
|
||||||
|
Separator();
|
||||||
|
static std::string palette_notes = "Notes about the palette";
|
||||||
|
ImGui::InputTextMultiline("Notes", palette_notes.data(), 1024,
|
||||||
|
ImVec2(-1, ImGui::GetTextLineHeight() * 4),
|
||||||
|
ImGuiInputTextFlags_AllowTabInput);
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
CLEAR_AND_RETURN_STATUS(status_)
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteEditor::DrawCustomPalette() {
|
||||||
|
if (BeginChild("ColorPalette", ImVec2(0, 40), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||||
|
for (int i = 0; i < custom_palette_.size(); i++) {
|
||||||
|
PushID(i);
|
||||||
|
SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
gui::SnesColorEdit4("##customPalette", &custom_palette_[i],
|
||||||
|
ImGuiColorEditFlags_NoInputs);
|
||||||
|
// Accept a drag drop target which adds a color to the custom_palette_
|
||||||
|
if (BeginDragDropTarget()) {
|
||||||
|
if (const ImGuiPayload* payload =
|
||||||
|
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F)) {
|
||||||
|
ImVec4 color = ImVec4(0, 0, 0, 1.0f);
|
||||||
|
memcpy((float*)&color, payload->Data, sizeof(float));
|
||||||
|
custom_palette_.push_back(SnesColor(color));
|
||||||
|
}
|
||||||
|
EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (ImGui::Button("Add Color")) {
|
||||||
|
custom_palette_.push_back(SnesColor(0x7FFF));
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (ImGui::Button("Export to Clipboard")) {
|
||||||
|
std::string clipboard;
|
||||||
|
for (const auto& color : custom_palette_) {
|
||||||
|
clipboard += absl::StrFormat("$%04X,", color.snes());
|
||||||
|
}
|
||||||
|
SetClipboardText(clipboard.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteEditor::DisplayCategoryTable() {
|
||||||
|
if (BeginTable("Category Table", 8,
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_SizingStretchSame |
|
||||||
|
ImGuiTableFlags_Hideable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Weapons and Gear");
|
||||||
|
TableSetupColumn("Overworld and Area Colors");
|
||||||
|
TableSetupColumn("Global Sprites");
|
||||||
|
TableSetupColumn("Sprites Aux1");
|
||||||
|
TableSetupColumn("Sprites Aux2");
|
||||||
|
TableSetupColumn("Sprites Aux3");
|
||||||
|
TableSetupColumn("Maps and Items");
|
||||||
|
TableSetupColumn("Dungeons");
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableSetColumnIndex(0);
|
||||||
|
if (TreeNode("Sword")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kSword);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Shield")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kShield);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Clothes")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kClothes, true);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableSetColumnIndex(1);
|
||||||
|
gui::BeginChildWithScrollbar("##WorldPaletteScrollRegion");
|
||||||
|
if (TreeNode("World Colors")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kWorldColors);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Area Colors")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kAreaColors);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableSetColumnIndex(2);
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kGlobalSprites, true);
|
||||||
|
|
||||||
|
TableSetColumnIndex(3);
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux1);
|
||||||
|
|
||||||
|
TableSetColumnIndex(4);
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux2);
|
||||||
|
|
||||||
|
TableSetColumnIndex(5);
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kSpritesAux3);
|
||||||
|
|
||||||
|
TableSetColumnIndex(6);
|
||||||
|
gui::BeginChildWithScrollbar("##MapPaletteScrollRegion");
|
||||||
|
if (TreeNode("World Map")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kWorldMap, true);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Dungeon Map")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kDungeonMap);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Triforce")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kTriforce);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
if (TreeNode("Crystal")) {
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kCrystal);
|
||||||
|
TreePop();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
TableSetColumnIndex(7);
|
||||||
|
gui::BeginChildWithScrollbar("##DungeonPaletteScrollRegion");
|
||||||
|
status_ = DrawPaletteGroup(PaletteCategory::kDungeons, true);
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::DrawPaletteGroup(int category, bool right_side) {
|
||||||
|
if (!rom()->is_loaded()) {
|
||||||
|
return absl::NotFoundError("ROM not open, no palettes to display");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto palette_group_name = kPaletteGroupNames[category];
|
||||||
|
gfx::PaletteGroup* palette_group =
|
||||||
|
rom()->mutable_palette_group()->get_group(palette_group_name.data());
|
||||||
|
const auto size = palette_group->size();
|
||||||
|
|
||||||
|
static bool edit_color = false;
|
||||||
|
for (int j = 0; j < size; j++) {
|
||||||
|
gfx::SnesPalette* palette = palette_group->mutable_palette(j);
|
||||||
|
auto pal_size = palette->size();
|
||||||
|
|
||||||
|
for (int n = 0; n < pal_size; n++) {
|
||||||
|
PushID(n);
|
||||||
|
if (!right_side) {
|
||||||
|
if ((n % 7) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
} else {
|
||||||
|
if ((n % 15) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto popup_id =
|
||||||
|
absl::StrCat(kPaletteCategoryNames[category].data(), j, "_", n);
|
||||||
|
|
||||||
|
// Small icon of the color in the palette
|
||||||
|
if (gui::SnesColorButton(popup_id, *palette->mutable_color(n),
|
||||||
|
kPalNoAlpha)) {
|
||||||
|
ASSIGN_OR_RETURN(current_color_, palette->GetColor(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginPopupContextItem(popup_id.c_str())) {
|
||||||
|
RETURN_IF_ERROR(HandleColorPopup(*palette, category, j, n))
|
||||||
|
}
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
false, palette_group_name.data(), /*key=*/std::to_string(j),
|
||||||
|
"Unnamed Palette");
|
||||||
|
if (right_side) Separator();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteEditor::DrawModifiedColors() {
|
||||||
|
if (BeginChild("ModifiedColors", ImVec2(0, 100), true,
|
||||||
|
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||||
|
for (int i = 0; i < history_.size(); i++) {
|
||||||
|
PushID(i);
|
||||||
|
gui::SnesColorEdit4("Original ", &history_.GetOriginalColor(i),
|
||||||
|
ImGuiColorEditFlags_NoInputs);
|
||||||
|
SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
gui::SnesColorEdit4("Modified ", &history_.GetModifiedColor(i),
|
||||||
|
ImGuiColorEditFlags_NoInputs);
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::HandleColorPopup(gfx::SnesPalette& palette, int i,
|
||||||
|
int j, int n) {
|
||||||
|
auto col = gfx::ToFloatArray(palette[n]);
|
||||||
|
auto original_color = palette[n];
|
||||||
|
if (gui::SnesColorEdit4("Edit Color", &palette[n], kColorPopupFlags)) {
|
||||||
|
history_.RecordChange(/*group_name=*/std::string(kPaletteGroupNames[i]),
|
||||||
|
/*palette_index=*/j, /*color_index=*/n,
|
||||||
|
original_color, palette[n]);
|
||||||
|
palette[n].set_modified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Button("Copy as..", ImVec2(-1, 0))) OpenPopup("Copy");
|
||||||
|
if (BeginPopup("Copy")) {
|
||||||
|
int cr = F32_TO_INT8_SAT(col[0]);
|
||||||
|
int cg = F32_TO_INT8_SAT(col[1]);
|
||||||
|
int cb = F32_TO_INT8_SAT(col[2]);
|
||||||
|
char buf[64];
|
||||||
|
|
||||||
|
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
|
||||||
|
col[1], col[2]);
|
||||||
|
if (Selectable(buf)) SetClipboardText(buf);
|
||||||
|
|
||||||
|
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
|
||||||
|
if (Selectable(buf)) SetClipboardText(buf);
|
||||||
|
|
||||||
|
CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
|
||||||
|
if (Selectable(buf)) SetClipboardText(buf);
|
||||||
|
|
||||||
|
// SNES Format
|
||||||
|
CustomFormatString(buf, IM_ARRAYSIZE(buf), "$%04X",
|
||||||
|
ConvertRgbToSnes(ImVec4(col[0], col[1], col[2], 1.0f)));
|
||||||
|
if (Selectable(buf)) SetClipboardText(buf);
|
||||||
|
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndPopup();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::EditColorInPalette(gfx::SnesPalette& palette,
|
||||||
|
int index) {
|
||||||
|
if (index >= palette.size()) {
|
||||||
|
return absl::InvalidArgumentError("Index out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current color
|
||||||
|
ASSIGN_OR_RETURN(auto color, palette.GetColor(index));
|
||||||
|
auto currentColor = color.rgb();
|
||||||
|
if (ColorPicker4("Color Picker", (float*)&palette[index])) {
|
||||||
|
// The color was modified, update it in the palette
|
||||||
|
palette(index, currentColor);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status PaletteEditor::ResetColorToOriginal(
|
||||||
|
gfx::SnesPalette& palette, int index,
|
||||||
|
const gfx::SnesPalette& originalPalette) {
|
||||||
|
if (index >= palette.size() || index >= originalPalette.size()) {
|
||||||
|
return absl::InvalidArgumentError("Index out of bounds");
|
||||||
|
}
|
||||||
|
ASSIGN_OR_RETURN(auto color, originalPalette.GetColor(index));
|
||||||
|
auto originalColor = color.rgb();
|
||||||
|
palette(index, originalColor);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
124
src/app/editor/graphics/palette_editor.h
Normal file
124
src/app/editor/graphics/palette_editor.h
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/graphics/gfx_group_editor.h"
|
||||||
|
#include "app/editor/editor.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_color.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
namespace palette_internal {
|
||||||
|
struct PaletteChange {
|
||||||
|
std::string group_name;
|
||||||
|
size_t palette_index;
|
||||||
|
size_t color_index;
|
||||||
|
gfx::SnesColor original_color;
|
||||||
|
gfx::SnesColor new_color;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PaletteEditorHistory {
|
||||||
|
public:
|
||||||
|
void RecordChange(const std::string& group_name, size_t palette_index,
|
||||||
|
size_t color_index, const gfx::SnesColor& original_color,
|
||||||
|
const gfx::SnesColor& new_color) {
|
||||||
|
if (recent_changes_.size() >= kMaxHistorySize) {
|
||||||
|
recent_changes_.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
recent_changes_.push_back(
|
||||||
|
{group_name, palette_index, color_index, original_color, new_color});
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::SnesColor RestoreOriginalColor(const std::string& group_name,
|
||||||
|
size_t palette_index,
|
||||||
|
size_t color_index) const {
|
||||||
|
for (const auto& change : recent_changes_) {
|
||||||
|
if (change.group_name == group_name &&
|
||||||
|
change.palette_index == palette_index &&
|
||||||
|
change.color_index == color_index) {
|
||||||
|
return change.original_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gfx::SnesColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size() const { return recent_changes_.size(); }
|
||||||
|
|
||||||
|
gfx::SnesColor& GetModifiedColor(size_t index) {
|
||||||
|
return recent_changes_[index].new_color;
|
||||||
|
}
|
||||||
|
gfx::SnesColor& GetOriginalColor(size_t index) {
|
||||||
|
return recent_changes_[index].original_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::deque<PaletteChange>& GetRecentChanges() const {
|
||||||
|
return recent_changes_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::deque<PaletteChange> recent_changes_;
|
||||||
|
static const size_t kMaxHistorySize = 50;
|
||||||
|
};
|
||||||
|
} // namespace palette_internal
|
||||||
|
|
||||||
|
absl::Status DisplayPalette(gfx::SnesPalette& palette, bool loaded);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PaletteEditor
|
||||||
|
* @brief Allows the user to view and edit in game palettes.
|
||||||
|
*/
|
||||||
|
class PaletteEditor : public SharedRom, public Editor {
|
||||||
|
public:
|
||||||
|
PaletteEditor() {
|
||||||
|
type_ = EditorType::kPalette;
|
||||||
|
custom_palette_.push_back(gfx::SnesColor(0x7FFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
absl::Status Cut() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Copy() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Paste() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Undo() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Redo() override { return absl::OkStatus(); }
|
||||||
|
absl::Status Find() override { return absl::OkStatus(); }
|
||||||
|
|
||||||
|
void DisplayCategoryTable();
|
||||||
|
|
||||||
|
absl::Status EditColorInPalette(gfx::SnesPalette& palette, int index);
|
||||||
|
absl::Status ResetColorToOriginal(gfx::SnesPalette& palette, int index,
|
||||||
|
const gfx::SnesPalette& originalPalette);
|
||||||
|
absl::Status DrawPaletteGroup(int category, bool right_side = false);
|
||||||
|
|
||||||
|
void DrawCustomPalette();
|
||||||
|
|
||||||
|
void DrawModifiedColors();
|
||||||
|
|
||||||
|
private:
|
||||||
|
absl::Status HandleColorPopup(gfx::SnesPalette& palette, int i, int j, int n);
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
gfx::SnesColor current_color_;
|
||||||
|
|
||||||
|
GfxGroupEditor gfx_group_editor_;
|
||||||
|
|
||||||
|
std::vector<gfx::SnesColor> custom_palette_;
|
||||||
|
|
||||||
|
ImVec4 saved_palette_[256] = {};
|
||||||
|
|
||||||
|
palette_internal::PaletteEditorHistory history_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
616
src/app/editor/graphics/screen_editor.cc
Normal file
616
src/app/editor/graphics/screen_editor.cc
Normal file
@@ -0,0 +1,616 @@
|
|||||||
|
#include "screen_editor.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/core/platform/renderer.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gfx/tilesheet.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using core::Renderer;
|
||||||
|
|
||||||
|
constexpr uint32_t kRedPen = 0xFF0000FF;
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::Update() {
|
||||||
|
if (ImGui::BeginTabBar("##ScreenEditorTabBar")) {
|
||||||
|
if (ImGui::BeginTabItem("Dungeon Maps")) {
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
DrawDungeonMapsEditor();
|
||||||
|
}
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
DrawInventoryMenuEditor();
|
||||||
|
DrawOverworldMapEditor();
|
||||||
|
DrawTitleScreenEditor();
|
||||||
|
DrawNamingScreenEditor();
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
return status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawInventoryMenuEditor() {
|
||||||
|
TAB_ITEM("Inventory Menu")
|
||||||
|
|
||||||
|
static bool create = false;
|
||||||
|
if (!create && rom()->is_loaded()) {
|
||||||
|
status_ = inventory_.Create();
|
||||||
|
palette_ = inventory_.Palette();
|
||||||
|
create = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawInventoryToolset();
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("InventoryScreen", 3, ImGuiTableFlags_Resizable)) {
|
||||||
|
ImGui::TableSetupColumn("Canvas");
|
||||||
|
ImGui::TableSetupColumn("Tiles");
|
||||||
|
ImGui::TableSetupColumn("Palette");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
screen_canvas_.DrawBackground();
|
||||||
|
screen_canvas_.DrawContextMenu();
|
||||||
|
screen_canvas_.DrawBitmap(inventory_.Bitmap(), 2, create);
|
||||||
|
screen_canvas_.DrawGrid(32.0f);
|
||||||
|
screen_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
tilesheet_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
|
||||||
|
tilesheet_canvas_.DrawContextMenu();
|
||||||
|
tilesheet_canvas_.DrawBitmap(inventory_.Tilesheet(), 2, create);
|
||||||
|
tilesheet_canvas_.DrawGrid(16.0f);
|
||||||
|
tilesheet_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
status_ = gui::DisplayPalette(palette_, create);
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
ImGui::Separator();
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawInventoryToolset() {
|
||||||
|
if (ImGui::BeginTable("InventoryToolset", 8, ImGuiTableFlags_SizingFixedFit,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
ImGui::TableSetupColumn("#drawTool");
|
||||||
|
ImGui::TableSetupColumn("#sep1");
|
||||||
|
ImGui::TableSetupColumn("#zoomOut");
|
||||||
|
ImGui::TableSetupColumn("#zoomIN");
|
||||||
|
ImGui::TableSetupColumn("#sep2");
|
||||||
|
ImGui::TableSetupColumn("#bg2Tool");
|
||||||
|
ImGui::TableSetupColumn("#bg3Tool");
|
||||||
|
ImGui::TableSetupColumn("#itemTool");
|
||||||
|
|
||||||
|
BUTTON_COLUMN(ICON_MD_UNDO)
|
||||||
|
BUTTON_COLUMN(ICON_MD_REDO)
|
||||||
|
TEXT_COLUMN(ICON_MD_MORE_VERT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_ZOOM_OUT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_ZOOM_IN)
|
||||||
|
TEXT_COLUMN(ICON_MD_MORE_VERT)
|
||||||
|
BUTTON_COLUMN(ICON_MD_DRAW)
|
||||||
|
BUTTON_COLUMN(ICON_MD_BUILD)
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::LoadDungeonMaps() {
|
||||||
|
std::vector<std::array<uint8_t, 25>> current_floor_rooms_d;
|
||||||
|
std::vector<std::array<uint8_t, 25>> current_floor_gfx_d;
|
||||||
|
int total_floors_d;
|
||||||
|
uint8_t nbr_floor_d;
|
||||||
|
uint8_t nbr_basement_d;
|
||||||
|
|
||||||
|
for (int d = 0; d < 14; d++) {
|
||||||
|
current_floor_rooms_d.clear();
|
||||||
|
current_floor_gfx_d.clear();
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
int ptr,
|
||||||
|
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
int ptr_gfx,
|
||||||
|
rom()->ReadWord(zelda3::screen::kDungeonMapGfxPtr + (d * 2)));
|
||||||
|
ptr |= 0x0A0000; // Add bank to the short ptr
|
||||||
|
ptr_gfx |= 0x0A0000; // Add bank to the short ptr
|
||||||
|
int pc_ptr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
|
||||||
|
int pc_ptr_gfx =
|
||||||
|
core::SnesToPc(ptr_gfx); // Contains data for the next 25 rooms
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
ushort boss_room_d,
|
||||||
|
rom()->ReadWord(zelda3::screen::kDungeonMapBossRooms + (d * 2)));
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
nbr_basement_d,
|
||||||
|
rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
|
||||||
|
nbr_basement_d &= 0x0F;
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
nbr_floor_d,
|
||||||
|
rom()->ReadByte(zelda3::screen::kDungeonMapFloors + (d * 2)));
|
||||||
|
nbr_floor_d &= 0xF0;
|
||||||
|
nbr_floor_d = nbr_floor_d >> 4;
|
||||||
|
|
||||||
|
total_floors_d = nbr_basement_d + nbr_floor_d;
|
||||||
|
|
||||||
|
dungeon_map_labels_.emplace_back();
|
||||||
|
|
||||||
|
// for each floor in the dungeon
|
||||||
|
for (int i = 0; i < total_floors_d; i++) {
|
||||||
|
dungeon_map_labels_[d].emplace_back();
|
||||||
|
|
||||||
|
std::array<uint8_t, 25> rdata;
|
||||||
|
std::array<uint8_t, 25> gdata;
|
||||||
|
|
||||||
|
// for each room on the floor
|
||||||
|
for (int j = 0; j < 25; j++) {
|
||||||
|
gdata[j] = 0xFF;
|
||||||
|
rdata[j] = rom()->data()[pc_ptr + j + (i * 25)]; // Set the rooms
|
||||||
|
|
||||||
|
if (rdata[j] == 0x0F) {
|
||||||
|
gdata[j] = 0xFF;
|
||||||
|
} else {
|
||||||
|
gdata[j] = rom()->data()[pc_ptr_gfx++];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label = core::HexByte(rdata[j]);
|
||||||
|
dungeon_map_labels_[d][i][j] = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_floor_gfx_d.push_back(gdata); // Add new floor gfx data
|
||||||
|
current_floor_rooms_d.push_back(rdata); // Add new floor data
|
||||||
|
}
|
||||||
|
|
||||||
|
dungeon_maps_.emplace_back(boss_room_d, nbr_floor_d, nbr_basement_d,
|
||||||
|
current_floor_rooms_d, current_floor_gfx_d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::SaveDungeonMaps() {
|
||||||
|
for (int d = 0; d < 14; d++) {
|
||||||
|
int ptr = zelda3::screen::kDungeonMapRoomsPtr + (d * 2);
|
||||||
|
int ptr_gfx = zelda3::screen::kDungeonMapGfxPtr + (d * 2);
|
||||||
|
int pc_ptr = core::SnesToPc(ptr);
|
||||||
|
int pc_ptr_gfx = core::SnesToPc(ptr_gfx);
|
||||||
|
|
||||||
|
const int nbr_floors = dungeon_maps_[d].nbr_of_floor;
|
||||||
|
const int nbr_basements = dungeon_maps_[d].nbr_of_basement;
|
||||||
|
for (int i = 0; i < nbr_floors + nbr_basements; i++) {
|
||||||
|
for (int j = 0; j < 25; j++) {
|
||||||
|
RETURN_IF_ERROR(rom()->WriteByte(pc_ptr + j + (i * 25),
|
||||||
|
dungeon_maps_[d].floor_rooms[i][j]));
|
||||||
|
RETURN_IF_ERROR(rom()->WriteByte(pc_ptr_gfx + j + (i * 25),
|
||||||
|
dungeon_maps_[d].floor_gfx[i][j]));
|
||||||
|
pc_ptr_gfx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::LoadDungeonMapTile16(
|
||||||
|
const std::vector<uint8_t>& gfx_data, bool bin_mode) {
|
||||||
|
tile16_sheet_.Init(256, 192, gfx::TileType::Tile16);
|
||||||
|
|
||||||
|
for (int i = 0; i < 186; i++) {
|
||||||
|
int addr = zelda3::screen::kDungeonMapTile16;
|
||||||
|
if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
|
||||||
|
addr = zelda3::screen::kDungeonMapTile16Expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(auto tl, rom()->ReadWord(addr + (i * 8)));
|
||||||
|
gfx::TileInfo t1 = gfx::WordToTileInfo(tl); // Top left
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(auto tr, rom()->ReadWord(addr + 2 + (i * 8)));
|
||||||
|
gfx::TileInfo t2 = gfx::WordToTileInfo(tr); // Top right
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(auto bl, rom()->ReadWord(addr + 4 + (i * 8)));
|
||||||
|
gfx::TileInfo t3 = gfx::WordToTileInfo(bl); // Bottom left
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(auto br, rom()->ReadWord(addr + 6 + (i * 8)));
|
||||||
|
gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right
|
||||||
|
|
||||||
|
int sheet_offset = 212;
|
||||||
|
if (bin_mode) {
|
||||||
|
sheet_offset = 0;
|
||||||
|
}
|
||||||
|
tile16_sheet_.ComposeTile16(gfx_data, t1, t2, t3, t4, sheet_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette(
|
||||||
|
*rom()->mutable_dungeon_palette(3)));
|
||||||
|
Renderer::GetInstance().RenderBitmap(&*tile16_sheet_.mutable_bitmap().get());
|
||||||
|
|
||||||
|
for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) {
|
||||||
|
auto tile = tile16_sheet_.GetTile16(i);
|
||||||
|
tile16_individual_[i] = tile;
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
tile16_individual_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3)));
|
||||||
|
Renderer::GetInstance().RenderBitmap(&tile16_individual_[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::SaveDungeonMapTile16() {
|
||||||
|
for (int i = 0; i < 186; i++) {
|
||||||
|
int addr = zelda3::screen::kDungeonMapTile16;
|
||||||
|
if (rom()->data()[zelda3::screen::kDungeonMapExpCheck] != 0xB9) {
|
||||||
|
addr = zelda3::screen::kDungeonMapTile16Expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
gfx::TileInfo t1 = tile16_sheet_.tile_info()[i].tiles[0];
|
||||||
|
gfx::TileInfo t2 = tile16_sheet_.tile_info()[i].tiles[1];
|
||||||
|
gfx::TileInfo t3 = tile16_sheet_.tile_info()[i].tiles[2];
|
||||||
|
gfx::TileInfo t4 = tile16_sheet_.tile_info()[i].tiles[3];
|
||||||
|
|
||||||
|
auto tl = gfx::TileInfoToWord(t1);
|
||||||
|
RETURN_IF_ERROR(rom()->WriteWord(addr + (i * 8), tl));
|
||||||
|
|
||||||
|
auto tr = gfx::TileInfoToWord(t2);
|
||||||
|
RETURN_IF_ERROR(rom()->WriteWord(addr + 2 + (i * 8), tr));
|
||||||
|
|
||||||
|
auto bl = gfx::TileInfoToWord(t3);
|
||||||
|
RETURN_IF_ERROR(rom()->WriteWord(addr + 4 + (i * 8), bl));
|
||||||
|
|
||||||
|
auto br = gfx::TileInfoToWord(t4);
|
||||||
|
RETURN_IF_ERROR(rom()->WriteWord(addr + 6 + (i * 8), br));
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawDungeonMapsTabs() {
|
||||||
|
auto& current_dungeon = dungeon_maps_[selected_dungeon];
|
||||||
|
if (ImGui::BeginTabBar("##DungeonMapTabs")) {
|
||||||
|
auto nbr_floors =
|
||||||
|
current_dungeon.nbr_of_floor + current_dungeon.nbr_of_basement;
|
||||||
|
for (int i = 0; i < nbr_floors; i++) {
|
||||||
|
int basement_num = current_dungeon.nbr_of_basement - i;
|
||||||
|
std::string tab_name = absl::StrFormat("Basement %d", basement_num);
|
||||||
|
if (i >= current_dungeon.nbr_of_basement) {
|
||||||
|
tab_name = absl::StrFormat("Floor %d",
|
||||||
|
i - current_dungeon.nbr_of_basement + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem(tab_name.c_str())) {
|
||||||
|
floor_number = i;
|
||||||
|
screen_canvas_.DrawBackground(ImVec2(325, 325));
|
||||||
|
screen_canvas_.DrawTileSelector(64.f);
|
||||||
|
|
||||||
|
auto boss_room = current_dungeon.boss_room;
|
||||||
|
for (int j = 0; j < 25; j++) {
|
||||||
|
if (current_dungeon.floor_rooms[floor_number][j] != 0x0F) {
|
||||||
|
int tile16_id = current_dungeon.floor_gfx[floor_number][j];
|
||||||
|
int posX = ((j % 5) * 32);
|
||||||
|
int posY = ((j / 5) * 32);
|
||||||
|
|
||||||
|
if (tile16_individual_.count(tile16_id) == 0) {
|
||||||
|
tile16_individual_[tile16_id] =
|
||||||
|
tile16_sheet_.GetTile16(tile16_id);
|
||||||
|
Renderer::GetInstance().RenderBitmap(
|
||||||
|
&tile16_individual_[tile16_id]);
|
||||||
|
}
|
||||||
|
screen_canvas_.DrawBitmap(tile16_individual_[tile16_id], (posX * 2),
|
||||||
|
(posY * 2), 4.0f);
|
||||||
|
|
||||||
|
if (current_dungeon.floor_rooms[floor_number][j] == boss_room) {
|
||||||
|
screen_canvas_.DrawOutlineWithColor((posX * 2), (posY * 2), 64,
|
||||||
|
64, kRedPen);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label =
|
||||||
|
dungeon_map_labels_[selected_dungeon][floor_number][j];
|
||||||
|
screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
|
||||||
|
std::string gfx_id = core::HexByte(tile16_id);
|
||||||
|
screen_canvas_.DrawText(gfx_id, (posX * 2), (posY * 2) + 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screen_canvas_.DrawGrid(64.f, 5);
|
||||||
|
screen_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
if (!screen_canvas_.points().empty()) {
|
||||||
|
int x = screen_canvas_.points().front().x / 64;
|
||||||
|
int y = screen_canvas_.points().front().y / 64;
|
||||||
|
selected_room = x + (y * 5);
|
||||||
|
}
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
gui::InputHexByte(
|
||||||
|
"Selected Room",
|
||||||
|
¤t_dungeon.floor_rooms[floor_number].at(selected_room));
|
||||||
|
|
||||||
|
gui::InputHexWord("Boss Room", ¤t_dungeon.boss_room);
|
||||||
|
|
||||||
|
const ImVec2 button_size = ImVec2(130, 0);
|
||||||
|
|
||||||
|
// Add Floor Button
|
||||||
|
if (ImGui::Button("Add Floor", button_size) &&
|
||||||
|
current_dungeon.nbr_of_floor < 8) {
|
||||||
|
current_dungeon.nbr_of_floor++;
|
||||||
|
dungeon_map_labels_[selected_dungeon].emplace_back();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Remove Floor", button_size) &&
|
||||||
|
current_dungeon.nbr_of_floor > 0) {
|
||||||
|
current_dungeon.nbr_of_floor--;
|
||||||
|
dungeon_map_labels_[selected_dungeon].pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Basement Button
|
||||||
|
if (ImGui::Button("Add Basement", button_size) &&
|
||||||
|
current_dungeon.nbr_of_basement < 8) {
|
||||||
|
current_dungeon.nbr_of_basement++;
|
||||||
|
dungeon_map_labels_[selected_dungeon].emplace_back();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Remove Basement", button_size) &&
|
||||||
|
current_dungeon.nbr_of_basement > 0) {
|
||||||
|
current_dungeon.nbr_of_basement--;
|
||||||
|
dungeon_map_labels_[selected_dungeon].pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button("Copy Floor", button_size)) {
|
||||||
|
copy_button_pressed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Paste Floor", button_size)) {
|
||||||
|
paste_button_pressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawDungeonMapsEditor() {
|
||||||
|
if (!dungeon_maps_loaded_) {
|
||||||
|
if (!LoadDungeonMaps().ok()) {
|
||||||
|
ImGui::Text("Failed to load dungeon maps");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LoadDungeonMapTile16(rom()->graphics_buffer()).ok()) {
|
||||||
|
// TODO: Load roomset gfx based on dungeon ID
|
||||||
|
sheets_.emplace(0, rom()->gfx_sheets()[212]);
|
||||||
|
sheets_.emplace(1, rom()->gfx_sheets()[213]);
|
||||||
|
sheets_.emplace(2, rom()->gfx_sheets()[214]);
|
||||||
|
sheets_.emplace(3, rom()->gfx_sheets()[215]);
|
||||||
|
int current_tile8 = 0;
|
||||||
|
int tile_data_offset = 0;
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
for (int j = 0; j < 32; j++) {
|
||||||
|
std::vector<uint8_t> tile_data(64, 0); // 8x8 tile (64 bytes
|
||||||
|
int tile_index = current_tile8 + j;
|
||||||
|
int x = (j % 8) * 8;
|
||||||
|
int y = (j / 8) * 8;
|
||||||
|
sheets_[i].Get8x8Tile(tile_index, 0, 0, tile_data, tile_data_offset);
|
||||||
|
tile8_individual_.emplace_back(gfx::Bitmap(8, 8, 4, tile_data));
|
||||||
|
RETURN_VOID_IF_ERROR(tile8_individual_.back().ApplyPalette(
|
||||||
|
*rom()->mutable_dungeon_palette(3)));
|
||||||
|
Renderer::GetInstance().RenderBitmap(&tile8_individual_.back());
|
||||||
|
}
|
||||||
|
tile_data_offset = 0;
|
||||||
|
}
|
||||||
|
dungeon_maps_loaded_ = true;
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Failed to load dungeon map tile16");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("##DungeonMapToolset", 2,
|
||||||
|
ImGuiTableFlags_SizingFixedFit)) {
|
||||||
|
ImGui::TableSetupColumn("Draw Mode");
|
||||||
|
ImGui::TableSetupColumn("Edit Mode");
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Button(ICON_MD_DRAW)) {
|
||||||
|
current_mode_ = EditingMode::DRAW;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::Button(ICON_MD_EDIT)) {
|
||||||
|
current_mode_ = EditingMode::EDIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string> dungeon_names = {
|
||||||
|
"Sewers/Sanctuary", "Hyrule Castle", "Eastern Palace",
|
||||||
|
"Desert Palace", "Tower of Hera", "Agahnim's Tower",
|
||||||
|
"Palace of Darkness", "Swamp Palace", "Skull Woods",
|
||||||
|
"Thieves' Town", "Ice Palace", "Misery Mire",
|
||||||
|
"Turtle Rock", "Ganon's Tower"};
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("DungeonMapsTable", 4,
|
||||||
|
ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable)) {
|
||||||
|
ImGui::TableSetupColumn("Dungeon");
|
||||||
|
ImGui::TableSetupColumn("Map");
|
||||||
|
ImGui::TableSetupColumn("Rooms Gfx");
|
||||||
|
ImGui::TableSetupColumn("Tiles Gfx");
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Dungeon column
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
for (int i = 0; i < dungeon_names.size(); i++) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
selected_dungeon == i, "Dungeon Names", absl::StrFormat("%d", i),
|
||||||
|
dungeon_names[i]);
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
selected_dungeon = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map column
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
DrawDungeonMapsTabs();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (ImGui::BeginChild("##DungeonMapTiles", ImVec2(0, 0), true)) {
|
||||||
|
tilesheet_canvas_.DrawBackground(ImVec2((256 * 2) + 2, (192 * 2) + 4));
|
||||||
|
tilesheet_canvas_.DrawContextMenu();
|
||||||
|
tilesheet_canvas_.DrawTileSelector(32.f);
|
||||||
|
tilesheet_canvas_.DrawBitmap(*tile16_sheet_.bitmap(), 2, true);
|
||||||
|
tilesheet_canvas_.DrawGrid(32.f);
|
||||||
|
tilesheet_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
if (!tilesheet_canvas_.points().empty()) {
|
||||||
|
selected_tile16_ = tilesheet_canvas_.points().front().x / 32 +
|
||||||
|
(tilesheet_canvas_.points().front().y / 32) * 16;
|
||||||
|
current_tile16_info = tile16_sheet_.tile_info().at(selected_tile16_);
|
||||||
|
|
||||||
|
// Draw the selected tile
|
||||||
|
if (!screen_canvas_.points().empty()) {
|
||||||
|
dungeon_maps_[selected_dungeon]
|
||||||
|
.floor_gfx[floor_number][selected_room] = selected_tile16_;
|
||||||
|
tilesheet_canvas_.mutable_points()->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
current_tile_canvas_
|
||||||
|
.DrawBackground(); // ImVec2(64 * 2 + 2, 64 * 2 + 4));
|
||||||
|
current_tile_canvas_.DrawContextMenu();
|
||||||
|
if (current_tile_canvas_.DrawTilePainter(
|
||||||
|
tile8_individual_[selected_tile8_], 16)) {
|
||||||
|
// Modify the tile16 based on the selected tile and current_tile16_info
|
||||||
|
}
|
||||||
|
current_tile_canvas_.DrawBitmap(tile16_individual_[selected_tile16_], 2,
|
||||||
|
4.0f);
|
||||||
|
current_tile_canvas_.DrawGrid(16.f);
|
||||||
|
current_tile_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
gui::InputTileInfo("TL", ¤t_tile16_info.tiles[0]);
|
||||||
|
ImGui::SameLine();
|
||||||
|
gui::InputTileInfo("TR", ¤t_tile16_info.tiles[1]);
|
||||||
|
gui::InputTileInfo("BL", ¤t_tile16_info.tiles[2]);
|
||||||
|
ImGui::SameLine();
|
||||||
|
gui::InputTileInfo("BR", ¤t_tile16_info.tiles[3]);
|
||||||
|
|
||||||
|
if (ImGui::Button("Modify Tile16")) {
|
||||||
|
tile16_sheet_.ModifyTile16(
|
||||||
|
rom()->graphics_buffer(), current_tile16_info.tiles[0],
|
||||||
|
current_tile16_info.tiles[1], current_tile16_info.tiles[2],
|
||||||
|
current_tile16_info.tiles[3], selected_tile16_, 212);
|
||||||
|
tile16_individual_[selected_tile16_] =
|
||||||
|
tile16_sheet_.GetTile16(selected_tile16_);
|
||||||
|
RETURN_VOID_IF_ERROR(tile16_individual_[selected_tile16_].ApplyPalette(
|
||||||
|
*rom()->mutable_dungeon_palette(3)));
|
||||||
|
Renderer::GetInstance().RenderBitmap(
|
||||||
|
&tile16_individual_[selected_tile16_]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
tilemap_canvas_.DrawBackground();
|
||||||
|
tilemap_canvas_.DrawContextMenu();
|
||||||
|
if (tilemap_canvas_.DrawTileSelector(16.f)) {
|
||||||
|
// Get the tile8 ID to use for the tile16 drawing above
|
||||||
|
selected_tile8_ = tilemap_canvas_.GetTileIdFromMousePos();
|
||||||
|
}
|
||||||
|
tilemap_canvas_.DrawBitmapTable(sheets_);
|
||||||
|
tilemap_canvas_.DrawGrid();
|
||||||
|
tilemap_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::Text("Selected tile8: %d", selected_tile8_);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("For use with custom inserted graphics assembly patches.");
|
||||||
|
if (ImGui::Button("Load GFX from BIN file")) LoadBinaryGfx();
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::LoadBinaryGfx() {
|
||||||
|
std::string bin_file = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
if (!bin_file.empty()) {
|
||||||
|
std::ifstream file(bin_file, std::ios::binary);
|
||||||
|
if (file.is_open()) {
|
||||||
|
// Read the gfx data into a buffer
|
||||||
|
std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
|
||||||
|
std::istreambuf_iterator<char>());
|
||||||
|
auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
|
||||||
|
gfx_bin_data_ = converted_bin;
|
||||||
|
tile16_sheet_.clear();
|
||||||
|
if (LoadDungeonMapTile16(converted_bin, true).ok()) {
|
||||||
|
sheets_.clear();
|
||||||
|
std::vector<std::vector<uint8_t>> gfx_sheets;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
|
||||||
|
converted_bin.begin() + ((i + 1) * 0x1000));
|
||||||
|
sheets_.emplace(i, gfx::Bitmap(128, 32, 8, gfx_sheets[i]));
|
||||||
|
status_ = sheets_[i].ApplyPalette(*rom()->mutable_dungeon_palette(3));
|
||||||
|
if (status_.ok()) {
|
||||||
|
Renderer::GetInstance().RenderBitmap(&sheets_[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binary_gfx_loaded_ = true;
|
||||||
|
} else {
|
||||||
|
status_ = absl::InternalError("Failed to load dungeon map tile16");
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawTitleScreenEditor() {
|
||||||
|
if (ImGui::BeginTabItem("Title Screen")) {
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawNamingScreenEditor() {
|
||||||
|
if (ImGui::BeginTabItem("Naming Screen")) {
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawOverworldMapEditor() {
|
||||||
|
if (ImGui::BeginTabItem("Overworld Map")) {
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawToolset() {
|
||||||
|
static bool show_bg1 = true;
|
||||||
|
static bool show_bg2 = true;
|
||||||
|
static bool show_bg3 = true;
|
||||||
|
|
||||||
|
static bool drawing_bg1 = true;
|
||||||
|
static bool drawing_bg2 = false;
|
||||||
|
static bool drawing_bg3 = false;
|
||||||
|
|
||||||
|
ImGui::Checkbox("Show BG1", &show_bg1);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Show BG2", &show_bg2);
|
||||||
|
|
||||||
|
ImGui::Checkbox("Draw BG1", &drawing_bg1);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Draw BG2", &drawing_bg2);
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Checkbox("Draw BG3", &drawing_bg3);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
116
src/app/editor/graphics/screen_editor.h
Normal file
116
src/app/editor/graphics/screen_editor.h
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/tilesheet.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/screen/dungeon_map.h"
|
||||||
|
#include "app/zelda3/screen/inventory.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The ScreenEditor class allows the user to edit a variety of screens in
|
||||||
|
* the game or create a custom menu.
|
||||||
|
*
|
||||||
|
* This class is currently a work in progress (WIP) and provides functionality
|
||||||
|
* for updating the screens, saving dungeon maps, drawing different types of
|
||||||
|
* screens, loading dungeon maps, and managing various properties related to the
|
||||||
|
* editor.
|
||||||
|
*
|
||||||
|
* The screens that can be edited include the title screen, naming screen,
|
||||||
|
* overworld map, inventory menu, and more.
|
||||||
|
*
|
||||||
|
* The class inherits from the SharedRom class.
|
||||||
|
*/
|
||||||
|
class ScreenEditor : public SharedRom, public Editor {
|
||||||
|
public:
|
||||||
|
ScreenEditor() {
|
||||||
|
screen_canvas_.SetCanvasSize(ImVec2(512, 512));
|
||||||
|
type_ = EditorType::kScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
|
||||||
|
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
|
||||||
|
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
|
||||||
|
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
|
||||||
|
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
|
||||||
|
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
|
||||||
|
absl::Status Find() override { return absl::UnimplementedError("Find"); }
|
||||||
|
|
||||||
|
absl::Status SaveDungeonMaps();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DrawTitleScreenEditor();
|
||||||
|
void DrawNamingScreenEditor();
|
||||||
|
void DrawOverworldMapEditor();
|
||||||
|
|
||||||
|
void DrawInventoryMenuEditor();
|
||||||
|
void DrawToolset();
|
||||||
|
void DrawInventoryToolset();
|
||||||
|
|
||||||
|
absl::Status LoadDungeonMaps();
|
||||||
|
absl::Status LoadDungeonMapTile16(const std::vector<uint8_t> &gfx_data,
|
||||||
|
bool bin_mode = false);
|
||||||
|
absl::Status SaveDungeonMapTile16();
|
||||||
|
void DrawDungeonMapsTabs();
|
||||||
|
void DrawDungeonMapsEditor();
|
||||||
|
|
||||||
|
void LoadBinaryGfx();
|
||||||
|
|
||||||
|
enum class EditingMode { DRAW, EDIT };
|
||||||
|
|
||||||
|
EditingMode current_mode_ = EditingMode::DRAW;
|
||||||
|
|
||||||
|
bool dungeon_maps_loaded_ = false;
|
||||||
|
bool binary_gfx_loaded_ = false;
|
||||||
|
|
||||||
|
uint8_t selected_room = 0;
|
||||||
|
uint8_t boss_room = 0;
|
||||||
|
|
||||||
|
int selected_tile16_ = 0;
|
||||||
|
int selected_tile8_ = 0;
|
||||||
|
int selected_dungeon = 0;
|
||||||
|
int floor_number = 1;
|
||||||
|
|
||||||
|
bool copy_button_pressed = false;
|
||||||
|
bool paste_button_pressed = false;
|
||||||
|
|
||||||
|
std::array<uint16_t, 4> current_tile16_data_;
|
||||||
|
std::unordered_map<int, gfx::Bitmap> tile16_individual_;
|
||||||
|
std::vector<gfx::Bitmap> tile8_individual_;
|
||||||
|
std::vector<uint8_t> all_gfx_;
|
||||||
|
std::vector<uint8_t> gfx_bin_data_;
|
||||||
|
std::vector<zelda3::screen::DungeonMap> dungeon_maps_;
|
||||||
|
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
|
||||||
|
gfx::SnesPalette palette_;
|
||||||
|
gfx::BitmapTable sheets_;
|
||||||
|
gfx::Tilesheet tile16_sheet_;
|
||||||
|
gfx::InternalTile16 current_tile16_info;
|
||||||
|
|
||||||
|
gui::Canvas current_tile_canvas_{"##CurrentTileCanvas", ImVec2(32, 32),
|
||||||
|
gui::CanvasGridSize::k16x16, 2.0f};
|
||||||
|
gui::Canvas screen_canvas_;
|
||||||
|
gui::Canvas tilesheet_canvas_;
|
||||||
|
gui::Canvas tilemap_canvas_{"##TilemapCanvas", ImVec2(128 + 2, (192) + 4),
|
||||||
|
gui::CanvasGridSize::k8x8, 2.f};
|
||||||
|
|
||||||
|
zelda3::screen::Inventory inventory_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
396
src/app/editor/graphics/tile16_editor.cc
Normal file
396
src/app/editor/graphics/tile16_editor.cc
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
#include "tile16_editor.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/core/platform/renderer.h"
|
||||||
|
#include "app/editor/editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gfx/tilesheet.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using core::Renderer;
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginMenu;
|
||||||
|
using ImGui::BeginMenuBar;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::Checkbox;
|
||||||
|
using ImGui::Combo;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndMenu;
|
||||||
|
using ImGui::EndMenuBar;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::GetContentRegionAvail;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::InitBlockset(
|
||||||
|
const gfx::Bitmap& tile16_blockset_bmp, const gfx::Bitmap& current_gfx_bmp,
|
||||||
|
const std::vector<gfx::Bitmap>& tile16_individual,
|
||||||
|
std::array<uint8_t, 0x200>& all_tiles_types) {
|
||||||
|
all_tiles_types_ = all_tiles_types;
|
||||||
|
tile16_blockset_bmp_ = tile16_blockset_bmp;
|
||||||
|
tile16_individual_ = tile16_individual;
|
||||||
|
current_gfx_bmp_ = current_gfx_bmp;
|
||||||
|
RETURN_IF_ERROR(LoadTile8());
|
||||||
|
ImVector<std::string> tile16_names;
|
||||||
|
for (int i = 0; i < 0x200; ++i) {
|
||||||
|
std::string str = core::HexByte(all_tiles_types_[i]);
|
||||||
|
tile16_names.push_back(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
*tile8_source_canvas_.mutable_labels(0) = tile16_names;
|
||||||
|
*tile8_source_canvas_.custom_labels_enabled() = true;
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::Update() {
|
||||||
|
if (!map_blockset_loaded_) {
|
||||||
|
return absl::InvalidArgumentError("Blockset not initialized, open a ROM.");
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(DrawMenu());
|
||||||
|
if (BeginTabBar("Tile16 Editor Tabs")) {
|
||||||
|
DrawTile16Editor();
|
||||||
|
RETURN_IF_ERROR(UpdateTile16Transfer());
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::DrawMenu() {
|
||||||
|
if (BeginMenuBar()) {
|
||||||
|
if (BeginMenu("View")) {
|
||||||
|
Checkbox("Show Collision Types",
|
||||||
|
tile8_source_canvas_.custom_labels_enabled());
|
||||||
|
EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Tile16Editor::DrawTile16Editor() {
|
||||||
|
if (BeginTabItem("Tile16 Editing")) {
|
||||||
|
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Blockset", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
GetContentRegionAvail().x);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
TableNextColumn();
|
||||||
|
status_ = UpdateBlockset();
|
||||||
|
if (!status_.ok()) {
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
status_ = UpdateTile16Edit();
|
||||||
|
if (status_ != absl::OkStatus()) {
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
status_ = DrawTileEditControls();
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::UpdateBlockset() {
|
||||||
|
gui::BeginPadding(2);
|
||||||
|
gui::BeginChildWithScrollbar("##Tile16EditorBlocksetScrollRegion");
|
||||||
|
blockset_canvas_.DrawBackground();
|
||||||
|
gui::EndPadding();
|
||||||
|
blockset_canvas_.DrawContextMenu();
|
||||||
|
blockset_canvas_.DrawTileSelector(32);
|
||||||
|
blockset_canvas_.DrawBitmap(tile16_blockset_bmp_, 0, map_blockset_loaded_);
|
||||||
|
blockset_canvas_.DrawGrid();
|
||||||
|
blockset_canvas_.DrawOverlay();
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (!blockset_canvas_.points().empty()) {
|
||||||
|
notify_tile16.mutable_get() = blockset_canvas_.GetTileIdFromMousePos();
|
||||||
|
notify_tile16.apply_changes();
|
||||||
|
|
||||||
|
if (notify_tile16.modified()) {
|
||||||
|
current_tile16_ = notify_tile16.get();
|
||||||
|
current_tile16_bmp_ = tile16_individual_[notify_tile16];
|
||||||
|
auto ow_main_pal_group = rom()->palette_group().overworld_main;
|
||||||
|
RETURN_IF_ERROR(current_tile16_bmp_.ApplyPalette(
|
||||||
|
ow_main_pal_group[current_palette_]));
|
||||||
|
Renderer::GetInstance().RenderBitmap(¤t_tile16_bmp_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) {
|
||||||
|
constexpr int tile8_size = 8;
|
||||||
|
constexpr int tile16_size = 16;
|
||||||
|
|
||||||
|
// Calculate the tile index for x and y based on the click_position
|
||||||
|
// Adjusting for Tile16 (16x16) which contains 4 Tile8 (8x8)
|
||||||
|
int tile_index_x = static_cast<int>(click_position.x) / tile8_size;
|
||||||
|
int tile_index_y = static_cast<int>(click_position.y) / tile8_size;
|
||||||
|
std::cout << "Tile Index X: " << tile_index_x << std::endl;
|
||||||
|
std::cout << "Tile Index Y: " << tile_index_y << std::endl;
|
||||||
|
|
||||||
|
// Calculate the pixel start position within the Tile16
|
||||||
|
ImVec2 start_position;
|
||||||
|
start_position.x = tile_index_x * 0x40;
|
||||||
|
start_position.y = tile_index_y * 0x40;
|
||||||
|
std::cout << "Start Position X: " << start_position.x << std::endl;
|
||||||
|
std::cout << "Start Position Y: " << start_position.y << std::endl;
|
||||||
|
|
||||||
|
// Draw the Tile8 to the correct position within the Tile16
|
||||||
|
for (int y = 0; y < tile8_size; ++y) {
|
||||||
|
for (int x = 0; x < tile8_size; ++x) {
|
||||||
|
int pixel_index =
|
||||||
|
(start_position.y + y) * tile16_size + ((start_position.x) + x);
|
||||||
|
int gfx_pixel_index = y * tile8_size + x;
|
||||||
|
current_tile16_bmp_.WriteToPixel(
|
||||||
|
pixel_index,
|
||||||
|
current_gfx_individual_[current_tile8_].data()[gfx_pixel_index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::UpdateTile16Edit() {
|
||||||
|
auto ow_main_pal_group = rom()->palette_group().overworld_main;
|
||||||
|
|
||||||
|
if (BeginChild("Tile8 Selector", ImVec2(GetContentRegionAvail().x, 0x175),
|
||||||
|
true)) {
|
||||||
|
tile8_source_canvas_.DrawBackground();
|
||||||
|
tile8_source_canvas_.DrawContextMenu(¤t_gfx_bmp_);
|
||||||
|
if (tile8_source_canvas_.DrawTileSelector(32)) {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
|
||||||
|
ow_main_pal_group[0], current_palette_));
|
||||||
|
Renderer::GetInstance().UpdateBitmap(
|
||||||
|
¤t_gfx_individual_[current_tile8_]);
|
||||||
|
}
|
||||||
|
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 4.0f);
|
||||||
|
tile8_source_canvas_.DrawGrid();
|
||||||
|
tile8_source_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
// The user selected a tile8
|
||||||
|
if (!tile8_source_canvas_.points().empty()) {
|
||||||
|
uint16_t x = tile8_source_canvas_.points().front().x / 16;
|
||||||
|
uint16_t y = tile8_source_canvas_.points().front().y / 16;
|
||||||
|
|
||||||
|
current_tile8_ = x + (y * 8);
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_gfx_individual_[current_tile8_].ApplyPaletteWithTransparent(
|
||||||
|
ow_main_pal_group[0], current_palette_));
|
||||||
|
Renderer::GetInstance().UpdateBitmap(
|
||||||
|
¤t_gfx_individual_[current_tile8_]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginChild("Tile16 Editor Options",
|
||||||
|
ImVec2(GetContentRegionAvail().x, 0x50), true)) {
|
||||||
|
tile16_edit_canvas_.DrawBackground();
|
||||||
|
tile16_edit_canvas_.DrawContextMenu(¤t_tile16_bmp_);
|
||||||
|
tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
|
||||||
|
if (!tile8_source_canvas_.points().empty()) {
|
||||||
|
if (tile16_edit_canvas_.DrawTilePainter(
|
||||||
|
current_gfx_individual_[current_tile8_], 16, 2.0f)) {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
DrawToCurrentTile16(tile16_edit_canvas_.drawn_tile_position()));
|
||||||
|
Renderer::GetInstance().UpdateBitmap(¤t_tile16_bmp_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tile16_edit_canvas_.DrawGrid();
|
||||||
|
tile16_edit_canvas_.DrawOverlay();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::DrawTileEditControls() {
|
||||||
|
Separator();
|
||||||
|
Text("Tile16 ID: %d", current_tile16_);
|
||||||
|
Text("Tile8 ID: %d", current_tile8_);
|
||||||
|
Text("Options:");
|
||||||
|
gui::InputHexByte("Palette", ¬ify_palette.mutable_get());
|
||||||
|
notify_palette.apply_changes();
|
||||||
|
if (notify_palette.modified()) {
|
||||||
|
auto palette = palettesets_[current_palette_].main_;
|
||||||
|
auto value = notify_palette.get();
|
||||||
|
if (notify_palette.get() > 0x04 && notify_palette.get() < 0x06) {
|
||||||
|
palette = palettesets_[current_palette_].aux1;
|
||||||
|
value -= 0x04;
|
||||||
|
} else if (notify_palette.get() > 0x06) {
|
||||||
|
palette = palettesets_[current_palette_].aux2;
|
||||||
|
value -= 0x06;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value > 0x00) {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_gfx_bmp_.ApplyPaletteWithTransparent(palette, value));
|
||||||
|
Renderer::GetInstance().UpdateBitmap(¤t_gfx_bmp_);
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_tile16_bmp_.ApplyPaletteWithTransparent(palette, value));
|
||||||
|
Renderer::GetInstance().UpdateBitmap(¤t_tile16_bmp_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkbox("X Flip", &x_flip);
|
||||||
|
Checkbox("Y Flip", &y_flip);
|
||||||
|
Checkbox("Priority Tile", &priority_tile);
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::LoadTile8() {
|
||||||
|
auto ow_main_pal_group = rom()->palette_group().overworld_main;
|
||||||
|
|
||||||
|
current_gfx_individual_.reserve(1024);
|
||||||
|
|
||||||
|
for (int index = 0; index < 1024; index++) {
|
||||||
|
std::vector<uint8_t> tile_data(0x40, 0x00);
|
||||||
|
|
||||||
|
// Copy the pixel data for the current tile into the vector
|
||||||
|
for (int ty = 0; ty < 8; ty++) {
|
||||||
|
for (int tx = 0; tx < 8; tx++) {
|
||||||
|
// Current Gfx Data is 16 sheets of 8x8 tiles ordered 16 wide by 4 tall
|
||||||
|
|
||||||
|
// Calculate the position in the tile data vector
|
||||||
|
int position = tx + (ty * 0x08);
|
||||||
|
|
||||||
|
// Calculate the position in the current gfx data
|
||||||
|
int num_columns = current_gfx_bmp_.width() / 8;
|
||||||
|
int x = (index % num_columns) * 8 + tx;
|
||||||
|
int y = (index / num_columns) * 8 + ty;
|
||||||
|
int gfx_position = x + (y * 0x100);
|
||||||
|
|
||||||
|
// Get the pixel value from the current gfx data
|
||||||
|
uint8_t value = current_gfx_bmp_.data()[gfx_position];
|
||||||
|
|
||||||
|
if (value & 0x80) {
|
||||||
|
value -= 0x88;
|
||||||
|
}
|
||||||
|
|
||||||
|
tile_data[position] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_gfx_individual_.emplace_back();
|
||||||
|
current_gfx_individual_[index].Create(0x08, 0x08, 0x08, tile_data);
|
||||||
|
RETURN_IF_ERROR(current_gfx_individual_[index].ApplyPaletteWithTransparent(
|
||||||
|
ow_main_pal_group[0], current_palette_));
|
||||||
|
Renderer::GetInstance().RenderBitmap(¤t_gfx_individual_[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
map_blockset_loaded_ = true;
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::SetCurrentTile(int id) {
|
||||||
|
current_tile16_ = id;
|
||||||
|
current_tile16_bmp_ = tile16_individual_[id];
|
||||||
|
auto ow_main_pal_group = rom()->palette_group().overworld_main;
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_tile16_bmp_.ApplyPalette(ow_main_pal_group[current_palette_]));
|
||||||
|
Renderer::GetInstance().RenderBitmap(¤t_tile16_bmp_);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Tile16Transfer
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::UpdateTile16Transfer() {
|
||||||
|
if (BeginTabItem("Tile16 Transfer")) {
|
||||||
|
if (BeginTable("#Tile16TransferTable", 2, TABLE_BORDERS_RESIZABLE,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
GetContentRegionAvail().x / 2);
|
||||||
|
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
GetContentRegionAvail().x / 2);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
RETURN_IF_ERROR(UpdateBlockset());
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
RETURN_IF_ERROR(UpdateTransferTileCanvas());
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Tile16Editor::UpdateTransferTileCanvas() {
|
||||||
|
// Create a button for loading another ROM
|
||||||
|
if (Button("Load ROM")) {
|
||||||
|
auto file_name = core::FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
transfer_status_ = transfer_rom_.LoadFromFile(file_name);
|
||||||
|
transfer_started_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement tile16 transfer
|
||||||
|
if (transfer_started_ && !transfer_blockset_loaded_) {
|
||||||
|
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
|
||||||
|
|
||||||
|
// Load the Link to the Past overworld.
|
||||||
|
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
|
||||||
|
transfer_overworld_.set_current_map(0);
|
||||||
|
palette_ = transfer_overworld_.current_area_palette();
|
||||||
|
|
||||||
|
// Create the tile16 blockset image
|
||||||
|
RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
|
||||||
|
0x80, 0x2000, 0x80, transfer_overworld_.tile16_blockset_data(),
|
||||||
|
transfer_blockset_bmp_, palette_));
|
||||||
|
transfer_blockset_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a canvas for holding the tiles which will be exported
|
||||||
|
gui::BitmapCanvasPipeline(transfer_canvas_, transfer_blockset_bmp_, 0x100,
|
||||||
|
(8192 * 2), 0x20, transfer_blockset_loaded_, true,
|
||||||
|
3);
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
103
src/app/editor/graphics/tile16_editor.h
Normal file
103
src/app/editor/graphics/tile16_editor.h
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Popup window to edit Tile16 data
|
||||||
|
*/
|
||||||
|
class Tile16Editor : public gfx::GfxContext, public SharedRom {
|
||||||
|
public:
|
||||||
|
absl::Status InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
|
||||||
|
const gfx::Bitmap& current_gfx_bmp,
|
||||||
|
const std::vector<gfx::Bitmap>& tile16_individual,
|
||||||
|
std::array<uint8_t, 0x200>& all_tiles_types);
|
||||||
|
|
||||||
|
absl::Status Update();
|
||||||
|
absl::Status DrawMenu();
|
||||||
|
|
||||||
|
void DrawTile16Editor();
|
||||||
|
absl::Status UpdateTile16Transfer();
|
||||||
|
absl::Status UpdateBlockset();
|
||||||
|
|
||||||
|
absl::Status DrawToCurrentTile16(ImVec2 pos);
|
||||||
|
|
||||||
|
absl::Status UpdateTile16Edit();
|
||||||
|
|
||||||
|
absl::Status DrawTileEditControls();
|
||||||
|
|
||||||
|
absl::Status UpdateTransferTileCanvas();
|
||||||
|
|
||||||
|
absl::Status LoadTile8();
|
||||||
|
|
||||||
|
absl::Status SetCurrentTile(int id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool map_blockset_loaded_ = false;
|
||||||
|
bool transfer_started_ = false;
|
||||||
|
bool transfer_blockset_loaded_ = false;
|
||||||
|
|
||||||
|
int current_tile16_ = 0;
|
||||||
|
int current_tile8_ = 0;
|
||||||
|
uint8_t current_palette_ = 0;
|
||||||
|
|
||||||
|
core::NotifyValue<uint32_t> notify_tile16;
|
||||||
|
core::NotifyValue<uint8_t> notify_palette;
|
||||||
|
|
||||||
|
// Various options for the Tile16 Editor
|
||||||
|
bool x_flip;
|
||||||
|
bool y_flip;
|
||||||
|
bool priority_tile;
|
||||||
|
int tile_size;
|
||||||
|
|
||||||
|
std::array<uint8_t, 0x200> all_tiles_types_;
|
||||||
|
|
||||||
|
// Tile16 blockset for selecting the tile to edit
|
||||||
|
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
|
||||||
|
gui::CanvasGridSize::k32x32};
|
||||||
|
gfx::Bitmap tile16_blockset_bmp_;
|
||||||
|
|
||||||
|
// Canvas for editing the selected tile
|
||||||
|
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
|
||||||
|
gui::CanvasGridSize::k64x64};
|
||||||
|
gfx::Bitmap current_tile16_bmp_;
|
||||||
|
|
||||||
|
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
|
||||||
|
gui::Canvas tile8_source_canvas_{
|
||||||
|
"Tile8SourceCanvas",
|
||||||
|
ImVec2(gfx::kTilesheetWidth * 4, gfx::kTilesheetHeight * 0x10 * 4),
|
||||||
|
gui::CanvasGridSize::k32x32};
|
||||||
|
gfx::Bitmap current_gfx_bmp_;
|
||||||
|
|
||||||
|
gui::Canvas transfer_canvas_;
|
||||||
|
gfx::Bitmap transfer_blockset_bmp_;
|
||||||
|
|
||||||
|
std::vector<gfx::Bitmap> tile16_individual_;
|
||||||
|
std::vector<gfx::Bitmap> current_gfx_individual_;
|
||||||
|
|
||||||
|
PaletteEditor palette_editor_;
|
||||||
|
|
||||||
|
gfx::SnesPalette palette_;
|
||||||
|
zelda3::Overworld transfer_overworld_;
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
|
||||||
|
Rom transfer_rom_;
|
||||||
|
absl::Status transfer_status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
#endif // YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user