Compare commits
3 Commits
pre-0.2.2-
...
pre-0.2.2-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75bf38fa71 | ||
|
|
92cc574e15 | ||
|
|
546093360f |
3
.github/workflows/cmake.yml
vendored
3
.github/workflows/cmake.yml
vendored
@@ -34,6 +34,9 @@ jobs:
|
|||||||
- name: Install Audio Libs
|
- name: Install Audio Libs
|
||||||
run: sudo apt install libwavpack-dev
|
run: sudo apt install libwavpack-dev
|
||||||
|
|
||||||
|
- name: Install Abseil-cpp
|
||||||
|
run: sudo apt install libabsl-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
|
||||||
12
.gitmodules
vendored
12
.gitmodules
vendored
@@ -7,9 +7,6 @@
|
|||||||
[submodule "src/lib/ImGuiColorTextEdit"]
|
[submodule "src/lib/ImGuiColorTextEdit"]
|
||||||
path = src/lib/ImGuiColorTextEdit
|
path = src/lib/ImGuiColorTextEdit
|
||||||
url = https://github.com/BalazsJako/ImGuiColorTextEdit.git
|
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 +19,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
|
|
||||||
|
|||||||
@@ -3,55 +3,32 @@ cmake_minimum_required(VERSION 3.10)
|
|||||||
|
|
||||||
# Yet Another Zelda3 Editor
|
# Yet Another Zelda3 Editor
|
||||||
# by scawful
|
# by scawful
|
||||||
project(yaze VERSION 0.01)
|
project(yaze VERSION 0.10)
|
||||||
|
|
||||||
# C++ Standard Specifications
|
# C++ Standard Specifications
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
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)
|
||||||
|
|
||||||
# Abseil Standard Specifications
|
# Abseil Standard Specifications
|
||||||
include(cmake/absl.cmake)
|
include(cmake/absl.cmake)
|
||||||
add_subdirectory(src/lib/abseil-cpp)
|
|
||||||
|
|
||||||
include(cmake/openssl.cmake)
|
|
||||||
|
|
||||||
# Video Libraries
|
# Video Libraries
|
||||||
find_package(PNG REQUIRED)
|
find_package(PNG REQUIRED)
|
||||||
find_package(OpenGL REQUIRED)
|
|
||||||
include(cmake/sdl2.cmake)
|
include(cmake/sdl2.cmake)
|
||||||
|
|
||||||
# Asar
|
# Asar
|
||||||
# add_subdirectory(src/lib/asar/src)
|
add_subdirectory(src/lib/asar/src)
|
||||||
# include(cmake/asar.cmake)
|
include(cmake/asar.cmake)
|
||||||
|
|
||||||
# snes-spc
|
|
||||||
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/zelda3/music/spc700.def)
|
|
||||||
|
|
||||||
# ImGui
|
# ImGui
|
||||||
include(cmake/imgui.cmake)
|
include(cmake/imgui.cmake)
|
||||||
|
|
||||||
# Project Files
|
# Project Files
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(test)
|
|
||||||
@@ -34,7 +34,7 @@ Building and installation
|
|||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze.
|
- For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze.
|
||||||
- For developers, please refer to [infrastructure.md](docs/infrastructure.md) for information on the project's infrastructure.
|
- For developers, please refer to the [documentation](https://scawful.github.io/yaze/index.html) for information on the project's infrastructure.
|
||||||
|
|
||||||
License
|
License
|
||||||
--------
|
--------
|
||||||
@@ -44,9 +44,10 @@ SDL2, ImGui and Abseil are subject to respective licenses.
|
|||||||
|
|
||||||
Screenshots
|
Screenshots
|
||||||
--------
|
--------
|
||||||

|

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

|

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

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

|
|
||||||
|
|
||||||
|
|||||||
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
find_package(absl)
|
||||||
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)
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH})
|
|||||||
target_compile_definitions(ImGuiColorTextEdit PUBLIC
|
target_compile_definitions(ImGuiColorTextEdit 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_TEST_ENGINE_PATH ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine/imgui_test_engine)
|
||||||
|
file(GLOB IMGUI_TEST_ENGINE_SOURCES ${IMGUI_TEST_ENGINE_PATH}/*.cpp)
|
||||||
|
add_library("ImGuiTestEngine" STATIC ${IMGUI_TEST_ENGINE_SOURCES})
|
||||||
|
target_include_directories(ImGuiTestEngine PUBLIC ${IMGUI_PATH})
|
||||||
|
target_link_libraries(ImGuiTestEngine PUBLIC ImGui)
|
||||||
|
target_compile_definitions(ImGuiTestEngine PUBLIC
|
||||||
|
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
|
||||||
|
|
||||||
set(
|
set(
|
||||||
IMGUI_SRC
|
IMGUI_SRC
|
||||||
${IMGUI_PATH}/imgui.cpp
|
${IMGUI_PATH}/imgui.cpp
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -1,19 +1,6 @@
|
|||||||
# SDL2, SDL2_image and SDL2_mixer
|
# SDL2
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
add_subdirectory(src/lib/SDL)
|
add_subdirectory(src/lib/SDL)
|
||||||
else()
|
else()
|
||||||
find_package(SDL2)
|
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)
|
|
||||||
find_package(SDL2_image)
|
|
||||||
@@ -1,17 +1,27 @@
|
|||||||
|
# Build Instructions
|
||||||
For VSCode users, use the following CMake extensions with MinGW-w64
|
|
||||||
|
## Windows
|
||||||
https://marketplace.visualstudio.com/items?itemName=twxs.cmake
|
|
||||||
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
|
For VSCode users, use the following CMake extensions with MinGW-w64
|
||||||
|
|
||||||
https://www.msys2.org/
|
https://marketplace.visualstudio.com/items?itemName=twxs.cmake
|
||||||
|
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
|
||||||
Add to environment variables `C:\msys64\mingw64\bin`
|
|
||||||
|
https://www.msys2.org/
|
||||||
Install the following packages using `pacman -S <package-name>`
|
|
||||||
|
Add to environment variables `C:\msys64\mingw64\bin`
|
||||||
`mingw-w64-x86_64-gcc`
|
|
||||||
`mingw-w64-x86_64-gcc-libs`
|
Install the following packages using `pacman -S <package-name>`
|
||||||
`mingw-w64-x86_64-cmake`
|
|
||||||
`mingw-w64-x86_64-glew`
|
`mingw-w64-x86_64-gcc`
|
||||||
`mingw-w64-x86_64-lib-png`
|
`mingw-w64-x86_64-gcc-libs`
|
||||||
|
`mingw-w64-x86_64-cmake`
|
||||||
|
`mingw-w64-x86_64-glew`
|
||||||
|
`mingw-w64-x86_64-lib-png`
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
|
||||||
|
- Clang 15.0.1 x86_64-apple-darrwin22.5.0
|
||||||
|
- SDL2 Source v2.26.5
|
||||||
|
- Removed snes_spc
|
||||||
|
- Removed asar_static
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
The compression algorithm has multiple implementations with varying levels of quality, based primarily on the implementations made in skarsnik/sneshacking, Zarby89/ZScreamDungeon and ZCompress with optimizations made for C++.
|
The compression algorithm has multiple implementations with varying levels of quality, based primarily on the implementations made in skarsnik/sneshacking, Zarby89/ZScreamDungeon and ZCompress with optimizations made for C++.
|
||||||
|
|
||||||
|
Currently, the Compress and Uncompress methods from Hyrule Magic are used and all other compression methods are considered deprecated.
|
||||||
|
|
||||||
## Key Definitions
|
## Key Definitions
|
||||||
|
|
||||||
### Constants and Macros:
|
### Constants and Macros:
|
||||||
@@ -62,6 +64,3 @@ Using `CompressionContext` to handle compression.
|
|||||||
- **Compression String Creation**: `CreateCompressionString`
|
- **Compression String Creation**: `CreateCompressionString`
|
||||||
- **Compression Result Validation**: Such as `ValidateCompressionResult` and its V3 variant.
|
- **Compression Result Validation**: Such as `ValidateCompressionResult` and its V3 variant.
|
||||||
- **Compression Piece Manipulation**: Like `SplitCompressionPiece` and its V3 variant.
|
- **Compression Piece Manipulation**: Like `SplitCompressionPiece` and its V3 variant.
|
||||||
|
|
||||||
## Final Notes
|
|
||||||
The YAZE's LC_LZ2 compression scheme provides three versions of compression methodologies with comprehensive support for various commands and modes. It ensures versatility and adaptability for different compression needs.
|
|
||||||
|
|||||||
@@ -10,19 +10,10 @@ Before you start using YAZE, make sure you have the following:
|
|||||||
- A copy of "The Legend of Zelda: A Link to the Past" ROM file (US or JP)
|
- A copy of "The Legend of Zelda: A Link to the Past" ROM file (US or JP)
|
||||||
- Basic knowledge of hexadecimal and binary data
|
- Basic knowledge of hexadecimal and binary data
|
||||||
|
|
||||||
## Installation
|
|
||||||
To install YAZE, follow these steps based on your platform:
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
### MacOS
|
|
||||||
|
|
||||||
### GNU/Linux
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
To use the Link to the Past ROM Editor, follow these steps:
|
To use the Link to the Past ROM Editor, follow these steps:
|
||||||
|
|
||||||
Open the "ALTTP.sfc" ROM file using the "File" menu.
|
Open the ROM file using the "File" menu.
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|||||||
@@ -23,59 +23,6 @@ For developers to reference.
|
|||||||
- SDL2
|
- SDL2
|
||||||
- **test**: Contains testing interface `yaze_test`
|
- **test**: Contains testing interface `yaze_test`
|
||||||
|
|
||||||
## App Organization
|
|
||||||
|
|
||||||
- **Core Namespace**:
|
|
||||||
- Contains fundamental functionalities.
|
|
||||||
- [Common](../src/app/core/common.h)
|
|
||||||
- [Constants](../src/app/core/constants.h)
|
|
||||||
- [Controller](../src/app/core/controller.h)
|
|
||||||
- [Editor](../src/app/core/editor.h)
|
|
||||||
- [Pipeline](../src/app/gui/pipeline.h)
|
|
||||||
- **Editor Namespace**:
|
|
||||||
- Editors are responsible for representing the GUI view and handling user input.
|
|
||||||
- These classes are all controlled by [MasterEditor](../src/app/editor/master_editor.h)
|
|
||||||
- [DungeonEditor](../src/app/editor/dungeon_editor.h)
|
|
||||||
- [GraphicsEditor](../src/app/editor/graphics_editor.h)
|
|
||||||
- [OverworldEditor](../src/app/editor/overworld_editor.h)
|
|
||||||
- [ScreenEditor](../src/app/editor/screen_editor.h)
|
|
||||||
- [SpriteEditor](../src/app/editor/sprite_editor.h)
|
|
||||||
- **Modules**
|
|
||||||
- [AssemblyEditor](../src/app/editor/modules/assembly_editor.h)
|
|
||||||
- [MusicEditor](../src/app/editor/modules/music_editor.h)
|
|
||||||
- [GfxGroupEditor](../src/app/editor/modules/gfx_group_editor.h)
|
|
||||||
- [Tile16Editor](../src/app/editor/modules/tile16_editor.h)
|
|
||||||
- **Emu Namespace**:
|
|
||||||
- Contains business logic for `core::emulator`
|
|
||||||
- [Audio](../src/app/emu/audio/)
|
|
||||||
- [Debug](../src/app/emu/debug/)
|
|
||||||
- [Memory](../src/app/emu/memory/)
|
|
||||||
- [Video](../src/app/emu/video/)
|
|
||||||
- [Emulator](../src/app/emu/emulator.h)
|
|
||||||
- **Gfx Namespace**:
|
|
||||||
- Handles graphics related tasks.
|
|
||||||
- [Bitmap](../src/app/gfx/bitmap.h)
|
|
||||||
- [Compression](../src/app/gfx/compression.h)
|
|
||||||
- [SCAD Format](../src/app/gfx/scad_format.h)
|
|
||||||
- [SNES Palette](../src/app/gfx/snes_palette.h)
|
|
||||||
- [SNES Tile](../src/app/gfx/snes_tile.h)
|
|
||||||
- **Gui Namespace**:
|
|
||||||
- Manages GUI elements.
|
|
||||||
- [Canvas](../src/app/gui/canvas.h)
|
|
||||||
- [Color](../src/app/gui/color.h)
|
|
||||||
- [Icons](../src/app/gui/icons.h)
|
|
||||||
- [Input](../src/app/gui/input.h)
|
|
||||||
- [Style](../src/app/gui/style.h)
|
|
||||||
- [Widgets](../src/app/gui/widgets.h)
|
|
||||||
- **Zelda3 Namespace**:
|
|
||||||
- Holds business logic specific to Zelda3.
|
|
||||||
- [Dungeon](../src/app/zelda3/dungeon/)
|
|
||||||
- [Music](../src/app/zelda3/music/)
|
|
||||||
- [Screen](../src/app/zelda3/screen/)
|
|
||||||
- [Sprite](../src/app/zelda3/sprite/)
|
|
||||||
- [OverworldMap](../src/app/zelda3/overworld_map.h)
|
|
||||||
- [Overworld](../src/app/zelda3/overworld.h)
|
|
||||||
|
|
||||||
### Flow of Control
|
### Flow of Control
|
||||||
|
|
||||||
- [app/yaze.cc](../src/app/yaze.cc)
|
- [app/yaze.cc](../src/app/yaze.cc)
|
||||||
@@ -142,22 +89,6 @@ This `ROM` class provides methods to manipulate and access data from a ROM.
|
|||||||
- Specific Zelda 3 data can be loaded if specified.
|
- Specific Zelda 3 data can be loaded if specified.
|
||||||
- Palettes are categorized into multiple groups (e.g., `ow_main`, `ow_aux`, `hud`, etc.) and loaded accordingly.
|
- Palettes are categorized into multiple groups (e.g., `ow_main`, `ow_aux`, `hud`, etc.) and loaded accordingly.
|
||||||
|
|
||||||
|
|
||||||
## Overworld
|
|
||||||
|
|
||||||
- [app/zelda3/overworld.cc](../src/app/zelda3/overworld.cc)
|
|
||||||
- [app/zelda3/overworld.h](../src/app/zelda3/overworld.h)
|
|
||||||
- [app/zelda3/overworld_map.cc](../src/app/zelda3/overworld_map.cc)
|
|
||||||
- [app/zelda3/overworld_map.h](../src/app/zelda3/overworld_map.h)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- **Construction of Tile16 and Tile32**
|
|
||||||
- **Save and Load Resources**
|
|
||||||
- Sprites
|
|
||||||
- Entrances
|
|
||||||
- Tilemaps
|
|
||||||
|
|
||||||
## Bitmap
|
## Bitmap
|
||||||
|
|
||||||
- [app/gfx/bitmap.cc](../src/app/gfx/bitmap.cc)
|
- [app/gfx/bitmap.cc](../src/app/gfx/bitmap.cc)
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
# macOS Build Settings
|
|
||||||
|
|
||||||
- Clang 15.0.1 x86_64-apple-darrwin22.5.0
|
|
||||||
- SDL2 Source v2.26.5
|
|
||||||
- Removed snes_spc
|
|
||||||
- Removed asar_static
|
|
||||||
@@ -2,22 +2,8 @@ set(
|
|||||||
YAZE_APP_CORE_SRC
|
YAZE_APP_CORE_SRC
|
||||||
app/core/common.cc
|
app/core/common.cc
|
||||||
app/core/controller.cc
|
app/core/controller.cc
|
||||||
)
|
app/core/labeling.cc
|
||||||
|
app/emu/emulator.cc
|
||||||
set(
|
|
||||||
YAZE_APP_EDITOR_SRC
|
|
||||||
app/editor/dungeon_editor.cc
|
|
||||||
app/editor/graphics_editor.cc
|
|
||||||
app/editor/master_editor.cc
|
|
||||||
app/editor/overworld_editor.cc
|
|
||||||
app/editor/screen_editor.cc
|
|
||||||
app/editor/sprite_editor.cc
|
|
||||||
app/editor/modules/music_editor.cc
|
|
||||||
app/editor/modules/palette_editor.cc
|
|
||||||
app/editor/modules/assembly_editor.cc
|
|
||||||
app/editor/modules/tile16_editor.cc
|
|
||||||
app/editor/modules/gfx_group_editor.cc
|
|
||||||
app/editor/context/gfx_context.cc
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(
|
set(
|
||||||
@@ -27,33 +13,22 @@ set(
|
|||||||
app/gfx/scad_format.cc
|
app/gfx/scad_format.cc
|
||||||
app/gfx/snes_palette.cc
|
app/gfx/snes_palette.cc
|
||||||
app/gfx/snes_tile.cc
|
app/gfx/snes_tile.cc
|
||||||
)
|
app/gfx/snes_color.cc
|
||||||
|
app/gfx/tilesheet.cc
|
||||||
set(
|
|
||||||
YAZE_APP_ZELDA3_SRC
|
|
||||||
app/zelda3/overworld_map.cc
|
|
||||||
app/zelda3/overworld.cc
|
|
||||||
app/zelda3/screen/inventory.cc
|
|
||||||
app/zelda3/screen/title_screen.cc
|
|
||||||
app/zelda3/sprite/sprite.cc
|
|
||||||
app/zelda3/music/tracker.cc
|
|
||||||
app/zelda3/dungeon/room.cc
|
|
||||||
app/zelda3/dungeon/room_object.cc
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(
|
set(
|
||||||
YAZE_GUI_SRC
|
YAZE_GUI_SRC
|
||||||
|
app/gui/asset_browser.cc
|
||||||
app/gui/canvas.cc
|
app/gui/canvas.cc
|
||||||
app/gui/input.cc
|
app/gui/input.cc
|
||||||
app/gui/style.cc
|
app/gui/style.cc
|
||||||
app/gui/widgets.cc
|
|
||||||
app/gui/color.cc
|
app/gui/color.cc
|
||||||
app/gui/pipeline.cc
|
app/gui/zeml.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
set(
|
set(
|
||||||
YAZE_APP_EMU_SRC
|
YAZE_APP_EMU_SRC
|
||||||
app/emu/emulator.cc
|
|
||||||
app/emu/audio/apu.cc
|
app/emu/audio/apu.cc
|
||||||
app/emu/audio/spc700.cc
|
app/emu/audio/spc700.cc
|
||||||
app/emu/audio/dsp.cc
|
app/emu/audio/dsp.cc
|
||||||
@@ -75,9 +50,10 @@ if(WIN32 OR MINGW)
|
|||||||
add_definitions(-DSDL_MAIN_HANDLED)
|
add_definitions(-DSDL_MAIN_HANDLED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32 OR MINGW OR UNIX)
|
if (WIN32 OR MINGW OR UNIX AND NOT APPLE)
|
||||||
list(APPEND YAZE_APP_CORE_SRC
|
list(APPEND YAZE_APP_CORE_SRC
|
||||||
app/core/platform/font_loader.cc
|
app/core/platform/font_loader.cc
|
||||||
|
app/core/platform/clipboard.cc
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -87,6 +63,7 @@ if(APPLE)
|
|||||||
app/core/platform/app_delegate.mm
|
app/core/platform/app_delegate.mm
|
||||||
app/core/platform/font_loader.mm
|
app/core/platform/font_loader.mm
|
||||||
app/core/platform/clipboard.mm
|
app/core/platform/clipboard.mm
|
||||||
|
app/core/platform/file_path.mm
|
||||||
)
|
)
|
||||||
|
|
||||||
find_library(COCOA_LIBRARY Cocoa)
|
find_library(COCOA_LIBRARY Cocoa)
|
||||||
@@ -97,7 +74,7 @@ if(APPLE)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(app/CMakeLists.txt)
|
include(app/CMakeLists.txt)
|
||||||
include(cli/CMakeLists.txt)
|
# include(cli/CMakeLists.txt) Excluded for now, macOS include breaks action build
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
target_compile_definitions(yaze PRIVATE "linux")
|
target_compile_definitions(yaze PRIVATE "linux")
|
||||||
@@ -130,4 +107,6 @@ set_target_properties(yaze
|
|||||||
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}/yaze.res"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(test)
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
include(app/editor/CMakeLists.txt)
|
||||||
|
include(app/zelda3/CMakeLists.txt)
|
||||||
|
|
||||||
add_executable(
|
add_executable(
|
||||||
yaze
|
yaze
|
||||||
app/yaze.cc
|
app/yaze.cc
|
||||||
@@ -9,14 +12,15 @@ add_executable(
|
|||||||
${YAZE_APP_ZELDA3_SRC}
|
${YAZE_APP_ZELDA3_SRC}
|
||||||
${YAZE_GUI_SRC}
|
${YAZE_GUI_SRC}
|
||||||
${IMGUI_SRC}
|
${IMGUI_SRC}
|
||||||
|
${IMGUI_TEST_ENGINE_SOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
yaze PUBLIC
|
yaze PUBLIC
|
||||||
lib/
|
lib/
|
||||||
app/
|
app/
|
||||||
lib/SDL_mixer/include/
|
|
||||||
${CMAKE_SOURCE_DIR}/src/
|
${CMAKE_SOURCE_DIR}/src/
|
||||||
|
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
|
||||||
${PNG_INCLUDE_DIRS}
|
${PNG_INCLUDE_DIRS}
|
||||||
${SDL2_INCLUDE_DIR}
|
${SDL2_INCLUDE_DIR}
|
||||||
)
|
)
|
||||||
@@ -27,7 +31,7 @@ target_link_libraries(
|
|||||||
${SDL_TARGETS}
|
${SDL_TARGETS}
|
||||||
${PNG_LIBRARIES}
|
${PNG_LIBRARIES}
|
||||||
${CMAKE_DL_LIBS}
|
${CMAKE_DL_LIBS}
|
||||||
SDL2_mixer
|
ImGuiTestEngine
|
||||||
ImGui
|
ImGui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -9,12 +9,31 @@
|
|||||||
#include <stack>
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_;
|
std::shared_ptr<ExperimentFlags::Flags> ExperimentFlags::flags_;
|
||||||
|
|
||||||
|
std::string UppercaseHexByte(uint8_t byte, bool leading) {
|
||||||
|
if (leading) {
|
||||||
|
std::string result = absl::StrFormat("0x%02X", byte);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
std::string result = absl::StrFormat("%02X", byte);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
std::string UppercaseHexWord(uint16_t word) {
|
||||||
|
std::string result = absl::StrFormat("0x%04X", word);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
std::string UppercaseHexLong(uint32_t dword) {
|
||||||
|
std::string result = absl::StrFormat("0x%06X", dword);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t SnesToPc(uint32_t addr) {
|
uint32_t SnesToPc(uint32_t addr) {
|
||||||
if (addr >= 0x808000) {
|
if (addr >= 0x808000) {
|
||||||
addr -= 0x808000;
|
addr -= 0x808000;
|
||||||
@@ -24,8 +43,15 @@ uint32_t SnesToPc(uint32_t addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t PcToSnes(uint32_t addr) {
|
uint32_t PcToSnes(uint32_t addr) {
|
||||||
if (addr >= 0x400000) return -1;
|
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
|
||||||
addr = ((addr << 1) & 0x7F0000) | (addr & 0x7FFF) | 0x8000;
|
b[2] = static_cast<uint8_t>(b[2] * 2);
|
||||||
|
|
||||||
|
if (b[1] >= 0x80) {
|
||||||
|
b[2] += 1;
|
||||||
|
} else {
|
||||||
|
b[1] += 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +174,14 @@ uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
|
|||||||
// Initialize the static member
|
// Initialize the static member
|
||||||
std::stack<ImGuiID> ImGuiIdIssuer::idStack;
|
std::stack<ImGuiID> ImGuiIdIssuer::idStack;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
@@ -1,41 +1,52 @@
|
|||||||
#ifndef YAZE_CORE_COMMON_H
|
#ifndef YAZE_CORE_COMMON_H
|
||||||
#define YAZE_CORE_COMMON_H
|
#define YAZE_CORE_COMMON_H
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace yaze::app::core
|
||||||
|
* @brief Core application logic and utilities.
|
||||||
|
*/
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ExperimentFlags
|
||||||
|
* @brief A class to manage experimental feature flags.
|
||||||
|
*/
|
||||||
class ExperimentFlags {
|
class ExperimentFlags {
|
||||||
public:
|
public:
|
||||||
struct Flags {
|
struct Flags {
|
||||||
// Load and render overworld sprites to the screen. Unstable.
|
// Bitmap manager abstraction to manage graphics bin of Rom.
|
||||||
bool kDrawOverworldSprites = false;
|
|
||||||
|
|
||||||
// Bitmap manager abstraction to manage graphics bin of ROM.
|
|
||||||
bool kUseBitmapManager = true;
|
bool kUseBitmapManager = true;
|
||||||
|
|
||||||
// Log instructions to the GUI debugger.
|
// Log instructions to the GUI debugger.
|
||||||
bool kLogInstructions = false;
|
bool kLogInstructions = true;
|
||||||
|
|
||||||
// Flag to enable ImGui input config flags. Currently is
|
// Flag to enable ImGui input config flags. Currently is
|
||||||
// handled manually by controller class but should be
|
// handled manually by controller class but should be
|
||||||
// ported away from that eventually.
|
// ported away from that eventually.
|
||||||
bool kUseNewImGuiInput = false;
|
bool kUseNewImGuiInput = false;
|
||||||
|
|
||||||
// Flag to enable the saving of all palettes to the ROM.
|
// Flag to enable the saving of all palettes to the Rom.
|
||||||
bool kSaveAllPalettes = false;
|
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
|
// Flag to enable the change queue, which could have any anonymous
|
||||||
// save routine for the ROM. In practice, just the overworld tilemap
|
// save routine for the Rom. In practice, just the overworld tilemap
|
||||||
// and tile32 save.
|
// and tile32 save.
|
||||||
bool kSaveWithChangeQueue = false;
|
bool kSaveWithChangeQueue = false;
|
||||||
|
|
||||||
@@ -49,7 +60,38 @@ class ExperimentFlags {
|
|||||||
// only supports macOS.
|
// only supports macOS.
|
||||||
bool kLoadSystemFonts = true;
|
bool kLoadSystemFonts = true;
|
||||||
|
|
||||||
bool kLoadTexturesAsStreaming = false;
|
// Uses texture streaming from SDL for my dynamic updates.
|
||||||
|
bool kLoadTexturesAsStreaming = true;
|
||||||
|
|
||||||
|
// Save dungeon map edits to the Rom.
|
||||||
|
bool kSaveDungeonMaps = false;
|
||||||
|
|
||||||
|
// Log to the console.
|
||||||
|
bool kLogToConsole = false;
|
||||||
|
|
||||||
|
// Load audio device for emulator
|
||||||
|
bool kLoadAudioDevice = 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;
|
||||||
|
} overworld;
|
||||||
};
|
};
|
||||||
|
|
||||||
ExperimentFlags() = default;
|
ExperimentFlags() = default;
|
||||||
@@ -72,6 +114,11 @@ class ExperimentFlags {
|
|||||||
static std::shared_ptr<Flags> flags_;
|
static std::shared_ptr<Flags> flags_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class NotifyValue
|
||||||
|
* @brief A class to manage a value that can be modified and notify when it
|
||||||
|
* changes.
|
||||||
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class NotifyValue {
|
class NotifyValue {
|
||||||
public:
|
public:
|
||||||
@@ -113,81 +160,6 @@ class NotifyValue {
|
|||||||
T temp_value_;
|
T temp_value_;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TaskCheckpoint {
|
|
||||||
int task_index = 0;
|
|
||||||
bool complete = false;
|
|
||||||
// You can add more internal data or state-related variables here as needed
|
|
||||||
};
|
|
||||||
|
|
||||||
class TaskTimer {
|
|
||||||
public:
|
|
||||||
// Starts the timer
|
|
||||||
void StartTimer() { start_time_ = std::chrono::steady_clock::now(); }
|
|
||||||
|
|
||||||
// Checks if the task should finish based on the given timeout in seconds
|
|
||||||
bool ShouldFinishTask(int timeout_seconds) {
|
|
||||||
auto current_time = std::chrono::steady_clock::now();
|
|
||||||
auto elapsed_time = std::chrono::duration_cast<std::chrono::seconds>(
|
|
||||||
current_time - start_time_);
|
|
||||||
return elapsed_time.count() >= timeout_seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::chrono::steady_clock::time_point start_time_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename TFunc>
|
|
||||||
class TaskManager {
|
|
||||||
public:
|
|
||||||
TaskManager() = default;
|
|
||||||
~TaskManager() = default;
|
|
||||||
|
|
||||||
TaskManager(int totalTasks, int timeoutSeconds)
|
|
||||||
: total_tasks_(totalTasks),
|
|
||||||
timeout_seconds_(timeoutSeconds),
|
|
||||||
task_index_(0),
|
|
||||||
task_complete_(false) {}
|
|
||||||
|
|
||||||
void ExecuteTasks(const TFunc &taskFunc) {
|
|
||||||
if (task_complete_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StartTimer();
|
|
||||||
|
|
||||||
for (; task_index_ < total_tasks_; ++task_index_) {
|
|
||||||
taskFunc(task_index_);
|
|
||||||
|
|
||||||
if (ShouldFinishTask()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (task_index_ == total_tasks_) {
|
|
||||||
task_complete_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsTaskComplete() const { return task_complete_; }
|
|
||||||
void SetTimeout(int timeout) { timeout_seconds_ = timeout; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int total_tasks_;
|
|
||||||
int timeout_seconds_;
|
|
||||||
int task_index_;
|
|
||||||
bool task_complete_;
|
|
||||||
std::chrono::steady_clock::time_point start_time_;
|
|
||||||
|
|
||||||
void StartTimer() { start_time_ = std::chrono::steady_clock::now(); }
|
|
||||||
|
|
||||||
bool ShouldFinishTask() {
|
|
||||||
auto current_time = std::chrono::steady_clock::now();
|
|
||||||
auto elapsed_time = std::chrono::duration_cast<std::chrono::seconds>(
|
|
||||||
current_time - start_time_);
|
|
||||||
return elapsed_time.count() >= timeout_seconds_;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImGuiIdIssuer {
|
class ImGuiIdIssuer {
|
||||||
private:
|
private:
|
||||||
static std::stack<ImGuiID> idStack;
|
static std::stack<ImGuiID> idStack;
|
||||||
@@ -209,6 +181,23 @@ class ImGuiIdIssuer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
public:
|
||||||
|
static void log(std::string message) {
|
||||||
|
static std::ofstream fout("log.txt", std::ios::out | std::ios::app);
|
||||||
|
fout << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// log to console
|
||||||
|
static void logc(std::string message) { logs.emplace_back(message); }
|
||||||
|
|
||||||
|
static std::vector<std::string> logs;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string UppercaseHexByte(uint8_t byte, bool leading = false);
|
||||||
|
std::string UppercaseHexWord(uint16_t word);
|
||||||
|
std::string UppercaseHexLong(uint32_t dword);
|
||||||
|
|
||||||
uint32_t SnesToPc(uint32_t addr);
|
uint32_t SnesToPc(uint32_t addr);
|
||||||
uint32_t PcToSnes(uint32_t addr);
|
uint32_t PcToSnes(uint32_t addr);
|
||||||
|
|
||||||
@@ -224,14 +213,22 @@ void stle16b_i(uint8_t *const p_arr, size_t const p_index,
|
|||||||
uint16_t const p_val);
|
uint16_t const p_val);
|
||||||
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
|
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index);
|
||||||
|
|
||||||
void stle16b(uint8_t *const p_arr, uint16_t const p_val);
|
uint16_t ldle16b(uint8_t const *const p_arr);
|
||||||
void stle32b(uint8_t *const p_arr, uint32_t const p_val);
|
|
||||||
|
|
||||||
void stle32b_i(uint8_t *const p_arr, size_t const p_index,
|
void stle16b(uint8_t *const p_arr, uint16_t const p_val);
|
||||||
uint32_t const p_val);
|
|
||||||
|
struct FolderItem {
|
||||||
|
std::string name;
|
||||||
|
std::vector<FolderItem> subfolders;
|
||||||
|
std::vector<std::string> files;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct FolderItem FolderItem;
|
||||||
|
|
||||||
|
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc = true);
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
|
|
||||||
#include "absl/strings/string_view.h"
|
#include "absl/strings/string_view.h"
|
||||||
|
|
||||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
||||||
|
|
||||||
#define BASIC_BUTTON(w) if (ImGui::Button(w))
|
|
||||||
|
|
||||||
#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
|
#define TAB_BAR(w) if (ImGui::BeginTabBar(w)) {
|
||||||
#define END_TAB_BAR() \
|
#define END_TAB_BAR() \
|
||||||
ImGui::EndTabBar(); \
|
ImGui::EndTabBar(); \
|
||||||
@@ -19,11 +15,6 @@
|
|||||||
ImGui::EndTabItem(); \
|
ImGui::EndTabItem(); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define MENU_BAR() if (ImGui::BeginMenuBar()) {
|
|
||||||
#define END_MENU_BAR() \
|
|
||||||
ImGui::EndMenuBar(); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define MENU_ITEM(w) if (ImGui::MenuItem(w))
|
#define MENU_ITEM(w) if (ImGui::MenuItem(w))
|
||||||
#define MENU_ITEM2(w, v) if (ImGui::MenuItem(w, v))
|
#define MENU_ITEM2(w, v) if (ImGui::MenuItem(w, v))
|
||||||
|
|
||||||
@@ -68,6 +59,15 @@
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define RETURN_VOID_IF_ERROR(expression) \
|
||||||
|
{ \
|
||||||
|
auto error = expression; \
|
||||||
|
if (!error.ok()) { \
|
||||||
|
std::cout << error.ToString() << std::endl; \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
#define RETURN_IF_ERROR(expression) \
|
#define RETURN_IF_ERROR(expression) \
|
||||||
{ \
|
{ \
|
||||||
auto error = expression; \
|
auto error = expression; \
|
||||||
@@ -121,9 +121,9 @@
|
|||||||
using ushort = unsigned short;
|
using ushort = unsigned short;
|
||||||
using uint = unsigned int;
|
using uint = unsigned int;
|
||||||
using uchar = unsigned char;
|
using uchar = unsigned char;
|
||||||
using Bytes = std::vector<uchar>;
|
using Bytes = std::vector<uint8_t>;
|
||||||
|
|
||||||
using OWBlockset = std::vector<std::vector<ushort>>;
|
using OWBlockset = std::vector<std::vector<uint16_t>>;
|
||||||
struct OWMapTiles {
|
struct OWMapTiles {
|
||||||
OWBlockset light_world; // 64 maps
|
OWBlockset light_world; // 64 maps
|
||||||
OWBlockset dark_world; // 64 maps
|
OWBlockset dark_world; // 64 maps
|
||||||
@@ -135,7 +135,8 @@ namespace yaze {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
constexpr float kYazeVersion = 0.05;
|
constexpr uint32_t kRedPen = 0xFF0000FF;
|
||||||
|
constexpr float kYazeVersion = 0.2;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Magic numbers
|
// Magic numbers
|
||||||
@@ -185,96 +186,7 @@ constexpr int text_data2 = 0x75F40;
|
|||||||
constexpr int pointers_dictionaries = 0x74703;
|
constexpr int pointers_dictionaries = 0x74703;
|
||||||
constexpr int characters_width = 0x74ADF;
|
constexpr int characters_width = 0x74ADF;
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Dungeon Entrances Related Variables
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// 0x14577 word value for each room
|
|
||||||
constexpr int entrance_room = 0x14813;
|
|
||||||
|
|
||||||
// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
|
|
||||||
constexpr int entrance_scrolledge = 0x1491D; // 0x14681
|
|
||||||
constexpr int entrance_yscroll = 0x14D45; // 0x14AA9 2 bytes each room
|
|
||||||
constexpr int entrance_xscroll = 0x14E4F; // 0x14BB3 2 bytes
|
|
||||||
constexpr int entrance_yposition = 0x14F59; // 0x14CBD 2bytes
|
|
||||||
constexpr int entrance_xposition = 0x15063; // 0x14DC7 2bytes
|
|
||||||
constexpr int entrance_camerayposition = 0x1516D; // 0x14ED1 2bytes
|
|
||||||
constexpr int entrance_cameraxposition = 0x15277; // 0x14FDB 2bytes
|
|
||||||
|
|
||||||
constexpr int entrance_gfx_group = 0x5D97;
|
constexpr int entrance_gfx_group = 0x5D97;
|
||||||
constexpr int entrance_blockset = 0x15381; // 0x150E5 1byte
|
|
||||||
constexpr int entrance_floor = 0x15406; // 0x1516A 1byte
|
|
||||||
constexpr int entrance_dungeon = 0x1548B; // 0x151EF 1byte (dungeon id)
|
|
||||||
constexpr int entrance_door = 0x15510; // 0x15274 1byte
|
|
||||||
|
|
||||||
// 1 byte, ---b ---a b = bg2, a = need to check
|
|
||||||
constexpr int entrance_ladderbg = 0x15595; // 0x152F9
|
|
||||||
constexpr int entrance_scrolling = 0x1561A; // 0x1537E 1byte --h- --v-
|
|
||||||
constexpr int entrance_scrollquadrant = 0x1569F; // 0x15403 1byte
|
|
||||||
constexpr int entrance_exit = 0x15724; // 0x15488 2byte word
|
|
||||||
constexpr int entrance_music = 0x1582E; // 0x15592
|
|
||||||
|
|
||||||
// word value for each room
|
|
||||||
constexpr int startingentrance_room = 0x15B6E; // 0x158D2
|
|
||||||
|
|
||||||
// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
|
|
||||||
constexpr int startingentrance_scrolledge = 0x15B7C; // 0x158E0
|
|
||||||
constexpr int startingentrance_yscroll = 0x15BB4; // 0x14AA9 //2bytes each room
|
|
||||||
constexpr int startingentrance_xscroll = 0x15BC2; // 0x14BB3 //2bytes
|
|
||||||
constexpr int startingentrance_yposition = 0x15BD0; // 0x14CBD 2bytes
|
|
||||||
constexpr int startingentrance_xposition = 0x15BDE; // 0x14DC7 2bytes
|
|
||||||
constexpr int startingentrance_camerayposition = 0x15BEC; // 0x14ED1 2bytes
|
|
||||||
constexpr int startingentrance_cameraxposition = 0x15BFA; // 0x14FDB 2bytes
|
|
||||||
|
|
||||||
constexpr int startingentrance_blockset = 0x15C08; // 0x150E5 1byte
|
|
||||||
constexpr int startingentrance_floor = 0x15C0F; // 0x1516A 1byte
|
|
||||||
constexpr int startingentrance_dungeon = 0x15C16; // 0x151EF 1byte (dungeon id)
|
|
||||||
|
|
||||||
constexpr int startingentrance_door = 0x15C2B; // 0x15274 1byte
|
|
||||||
|
|
||||||
// 1 byte, ---b ---a b = bg2, a = need to check
|
|
||||||
constexpr int startingentrance_ladderbg = 0x15C1D; // 0x152F9
|
|
||||||
// 1byte --h- --v-
|
|
||||||
constexpr int startingentrance_scrolling = 0x15C24; // 0x1537E
|
|
||||||
constexpr int startingentrance_scrollquadrant = 0x15C2B; // 0x15403 1byte
|
|
||||||
constexpr int startingentrance_exit = 0x15C32; // 0x15488 //2byte word
|
|
||||||
constexpr int startingentrance_music = 0x15C4E; // 0x15592
|
|
||||||
constexpr int startingentrance_entrance = 0x15C40;
|
|
||||||
|
|
||||||
constexpr int items_data_start = 0xDDE9; // save purpose
|
|
||||||
constexpr int items_data_end = 0xE6B2; // save purpose
|
|
||||||
constexpr int initial_equipement = 0x271A6;
|
|
||||||
constexpr int messages_id_dungeon = 0x3F61D;
|
|
||||||
|
|
||||||
// item id you get instead if you already have that item
|
|
||||||
constexpr int chests_backupitems = 0x3B528;
|
|
||||||
constexpr int chests_yoffset = 0x4836C;
|
|
||||||
constexpr int chests_xoffset = 0x4836C + (76 * 1);
|
|
||||||
constexpr int chests_itemsgfx = 0x4836C + (76 * 2);
|
|
||||||
constexpr int chests_itemswide = 0x4836C + (76 * 3);
|
|
||||||
constexpr int chests_itemsproperties = 0x4836C + (76 * 4);
|
|
||||||
constexpr int chests_sramaddress = 0x4836C + (76 * 5);
|
|
||||||
constexpr int chests_sramvalue = 0x4836C + (76 * 7);
|
|
||||||
constexpr int chests_msgid = 0x442DD;
|
|
||||||
|
|
||||||
constexpr int dungeons_startrooms = 0x7939;
|
|
||||||
constexpr int dungeons_endrooms = 0x792D;
|
|
||||||
constexpr int dungeons_bossrooms = 0x10954; // short value
|
|
||||||
|
|
||||||
// Bed Related Values (Starting location)
|
|
||||||
constexpr int bedPositionX = 0x039A37; // short value
|
|
||||||
constexpr int bedPositionY = 0x039A32; // short value
|
|
||||||
|
|
||||||
// short value (on 2 different bytes)
|
|
||||||
constexpr int bedPositionResetXLow = 0x02DE53;
|
|
||||||
constexpr int bedPositionResetXHigh = 0x02DE58;
|
|
||||||
|
|
||||||
// short value (on 2 different bytes)
|
|
||||||
constexpr int bedPositionResetYLow = 0x02DE5D;
|
|
||||||
constexpr int bedPositionResetYHigh = 0x02DE62;
|
|
||||||
|
|
||||||
constexpr int bedSheetPositionX = 0x0480BD; // short value
|
|
||||||
constexpr int bedSheetPositionY = 0x0480B8; // short value
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Gravestones related variables
|
// Gravestones related variables
|
||||||
@@ -292,146 +204,90 @@ constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
|
|||||||
constexpr int GraveLinkSpecialHole = 0x46DD9; // short
|
constexpr int GraveLinkSpecialHole = 0x46DD9; // short
|
||||||
constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
|
constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Palettes Related Variables - This contain all the palettes of the game
|
|
||||||
// ============================================================================
|
|
||||||
constexpr int overworldPaletteMain = 0xDE6C8;
|
|
||||||
constexpr int overworldPaletteAuxialiary = 0xDE86C;
|
|
||||||
constexpr int overworldPaletteAnimated = 0xDE604;
|
|
||||||
constexpr int globalSpritePalettesLW = 0xDD218;
|
|
||||||
constexpr int globalSpritePalettesDW = 0xDD290;
|
|
||||||
// Green, Blue, Red, Bunny, Electrocuted (15 colors each)
|
|
||||||
constexpr int armorPalettes = 0xDD308;
|
|
||||||
constexpr int spritePalettesAux1 = 0xDD39E; // 7 colors each
|
|
||||||
constexpr int spritePalettesAux2 = 0xDD446; // 7 colors each
|
|
||||||
constexpr int spritePalettesAux3 = 0xDD4E0; // 7 colors each
|
|
||||||
constexpr int swordPalettes = 0xDD630; // 3 colors each - 4 entries
|
|
||||||
constexpr int shieldPalettes = 0xDD648; // 4 colors each - 3 entries
|
|
||||||
constexpr int hudPalettes = 0xDD660;
|
|
||||||
constexpr int dungeonMapPalettes = 0xDD70A; // 21 colors
|
|
||||||
constexpr int dungeonMainPalettes = 0xDD734; //(15*6) colors each - 20 entries
|
|
||||||
constexpr int dungeonMapBgPalettes = 0xDE544; // 16*6
|
|
||||||
// Mirrored Value at 0x75645 : 0x75625
|
|
||||||
constexpr int hardcodedGrassLW = 0x5FEA9;
|
|
||||||
constexpr int hardcodedGrassDW = 0x05FEB3; // 0x7564F
|
|
||||||
constexpr int hardcodedGrassSpecial = 0x75640;
|
|
||||||
constexpr int overworldMiniMapPalettes = 0x55B27;
|
|
||||||
constexpr int triforcePalette = 0x64425;
|
|
||||||
constexpr int crystalPalette = 0xF4CD3;
|
|
||||||
constexpr int customAreaSpecificBGPalette =
|
|
||||||
0x140000; // 2 bytes for each overworld area (320)
|
|
||||||
constexpr int customAreaSpecificBGASM = 0x140150;
|
|
||||||
constexpr int customAreaSpecificBGEnabled =
|
|
||||||
0x140140; // 1 byte, not 0 if enabled
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Dungeon Map Related Variables
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
constexpr int dungeonMap_rooms_ptr = 0x57605; // 14 pointers of map data
|
|
||||||
constexpr int dungeonMap_floors = 0x575D9; // 14 words values
|
|
||||||
|
|
||||||
constexpr int dungeonMap_gfx_ptr = 0x57BE4; // 14 pointers of gfx data
|
|
||||||
|
|
||||||
// data start for floors/gfx MUST skip 575D9 to 57621 (pointers)
|
|
||||||
constexpr int dungeonMap_datastart = 0x57039;
|
|
||||||
|
|
||||||
// IF Byte = 0xB9 dungeon maps are not expanded
|
|
||||||
constexpr int dungeonMap_expCheck = 0x56652;
|
|
||||||
constexpr int dungeonMap_tile16 = 0x57009;
|
|
||||||
constexpr int dungeonMap_tile16Exp = 0x109010;
|
|
||||||
|
|
||||||
// 14 words values 0x000F = no boss
|
|
||||||
constexpr int dungeonMap_bossrooms = 0x56807;
|
|
||||||
constexpr int triforceVertices = 0x04FFD2; // group of 3, X, Y ,Z
|
|
||||||
constexpr int TriforceFaces = 0x04FFE4; // group of 5
|
|
||||||
|
|
||||||
constexpr int crystalVertices = 0x04FF98;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Names
|
// Names
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
static const absl::string_view RoomEffect[] = {"Nothing",
|
static const std::string RoomEffect[] = {"Nothing",
|
||||||
"Nothing",
|
"Nothing",
|
||||||
"Moving Floor",
|
"Moving Floor",
|
||||||
"Moving Water",
|
"Moving Water",
|
||||||
"Trinexx Shell",
|
"Trinexx Shell",
|
||||||
"Red Flashes",
|
"Red Flashes",
|
||||||
"Light Torch to See Floor",
|
"Light Torch to See Floor",
|
||||||
"Ganon's Darkness"};
|
"Ganon's Darkness"};
|
||||||
|
|
||||||
static const absl::string_view RoomTag[] = {"Nothing",
|
static const std::string RoomTag[] = {"Nothing",
|
||||||
|
|
||||||
"NW Kill Enemy to Open",
|
"NW Kill Enemy to Open",
|
||||||
"NE Kill Enemy to Open",
|
"NE Kill Enemy to Open",
|
||||||
"SW Kill Enemy to Open",
|
"SW Kill Enemy to Open",
|
||||||
"SE Kill Enemy to Open",
|
"SE Kill Enemy to Open",
|
||||||
"W Kill Enemy to Open",
|
"W Kill Enemy to Open",
|
||||||
"E Kill Enemy to Open",
|
"E Kill Enemy to Open",
|
||||||
"N Kill Enemy to Open",
|
"N Kill Enemy to Open",
|
||||||
"S Kill Enemy to Open",
|
"S Kill Enemy to Open",
|
||||||
"Clear Quadrant to Open",
|
"Clear Quadrant to Open",
|
||||||
"Clear Full Tile to Open",
|
"Clear Full Tile to Open",
|
||||||
|
|
||||||
"NW Push Block to Open",
|
"NW Push Block to Open",
|
||||||
"NE Push Block to Open",
|
"NE Push Block to Open",
|
||||||
"SW Push Block to Open",
|
"SW Push Block to Open",
|
||||||
"SE Push Block to Open",
|
"SE Push Block to Open",
|
||||||
"W Push Block to Open",
|
"W Push Block to Open",
|
||||||
"E Push Block to Open",
|
"E Push Block to Open",
|
||||||
"N Push Block to Open",
|
"N Push Block to Open",
|
||||||
"S Push Block to Open",
|
"S Push Block to Open",
|
||||||
"Push Block to Open",
|
"Push Block to Open",
|
||||||
"Pull Lever to Open",
|
"Pull Lever to Open",
|
||||||
"Collect Prize to Open",
|
"Collect Prize to Open",
|
||||||
|
|
||||||
"Hold Switch Open Door",
|
"Hold Switch Open Door",
|
||||||
"Toggle Switch to Open Door",
|
"Toggle Switch to Open Door",
|
||||||
"Turn off Water",
|
"Turn off Water",
|
||||||
"Turn on Water",
|
"Turn on Water",
|
||||||
"Water Gate",
|
"Water Gate",
|
||||||
"Water Twin",
|
"Water Twin",
|
||||||
"Moving Wall Right",
|
"Moving Wall Right",
|
||||||
"Moving Wall Left",
|
"Moving Wall Left",
|
||||||
"Crash",
|
"Crash",
|
||||||
"Crash",
|
"Crash",
|
||||||
"Push Switch Exploding Wall",
|
"Push Switch Exploding Wall",
|
||||||
"Holes 0",
|
"Holes 0",
|
||||||
"Open Chest (Holes 0)",
|
"Open Chest (Holes 0)",
|
||||||
"Holes 1",
|
"Holes 1",
|
||||||
"Holes 2",
|
"Holes 2",
|
||||||
"Defeat Boss for Dungeon Prize",
|
"Defeat Boss for Dungeon Prize",
|
||||||
|
|
||||||
"SE Kill Enemy to Push Block",
|
"SE Kill Enemy to Push Block",
|
||||||
"Trigger Switch Chest",
|
"Trigger Switch Chest",
|
||||||
"Pull Lever Exploding Wall",
|
"Pull Lever Exploding Wall",
|
||||||
"NW Kill Enemy for Chest",
|
"NW Kill Enemy for Chest",
|
||||||
"NE Kill Enemy for Chest",
|
"NE Kill Enemy for Chest",
|
||||||
"SW Kill Enemy for Chest",
|
"SW Kill Enemy for Chest",
|
||||||
"SE Kill Enemy for Chest",
|
"SE Kill Enemy for Chest",
|
||||||
"W Kill Enemy for Chest",
|
"W Kill Enemy for Chest",
|
||||||
"E Kill Enemy for Chest",
|
"E Kill Enemy for Chest",
|
||||||
"N Kill Enemy for Chest",
|
"N Kill Enemy for Chest",
|
||||||
"S Kill Enemy for Chest",
|
"S Kill Enemy for Chest",
|
||||||
"Clear Quadrant for Chest",
|
"Clear Quadrant for Chest",
|
||||||
"Clear Full Tile for Chest",
|
"Clear Full Tile for Chest",
|
||||||
|
|
||||||
"Light Torches to Open",
|
"Light Torches to Open",
|
||||||
"Holes 3",
|
"Holes 3",
|
||||||
"Holes 4",
|
"Holes 4",
|
||||||
"Holes 5",
|
"Holes 5",
|
||||||
"Holes 6",
|
"Holes 6",
|
||||||
"Agahnim Room",
|
"Agahnim Room",
|
||||||
"Holes 7",
|
"Holes 7",
|
||||||
"Holes 8",
|
"Holes 8",
|
||||||
"Open Chest for Holes 8",
|
"Open Chest for Holes 8",
|
||||||
"Push Block for Chest",
|
"Push Block for Chest",
|
||||||
"Clear Room for Triforce Door",
|
"Clear Room for Triforce Door",
|
||||||
"Light Torches for Chest",
|
"Light Torches for Chest",
|
||||||
"Kill Boss Again"};
|
"Kill Boss Again"};
|
||||||
|
|
||||||
static const absl::string_view SecretItemNames[] = {
|
static const std::string SecretItemNames[] = {
|
||||||
"Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
|
"Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
|
||||||
"Bomb", "Heart ", "Blue Rupee",
|
"Bomb", "Heart ", "Blue Rupee",
|
||||||
|
|
||||||
@@ -443,7 +299,7 @@ static const absl::string_view SecretItemNames[] = {
|
|||||||
|
|
||||||
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
|
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
|
||||||
|
|
||||||
static const absl::string_view TileTypeNames[] = {
|
static const std::string TileTypeNames[] = {
|
||||||
"$00 Nothing (standard floor)",
|
"$00 Nothing (standard floor)",
|
||||||
"$01 Collision",
|
"$01 Collision",
|
||||||
"$02 Collision",
|
"$02 Collision",
|
||||||
@@ -701,7 +557,7 @@ static const absl::string_view TileTypeNames[] = {
|
|||||||
"$FE Door X top? (unused?)",
|
"$FE Door X top? (unused?)",
|
||||||
"$FF Door X top? (unused?)"};
|
"$FF Door X top? (unused?)"};
|
||||||
|
|
||||||
static const absl::string_view kSpriteDefaultNames[]{
|
static const std::string kSpriteDefaultNames[]{
|
||||||
"00 Raven",
|
"00 Raven",
|
||||||
"01 Vulture",
|
"01 Vulture",
|
||||||
"02 Flying Stalfos Head",
|
"02 Flying Stalfos Head",
|
||||||
@@ -960,7 +816,7 @@ static const absl::string_view kSpriteDefaultNames[]{
|
|||||||
"FF",
|
"FF",
|
||||||
};
|
};
|
||||||
|
|
||||||
static const absl::string_view overlordnames[] = {
|
static const std::string overlordnames[] = {
|
||||||
"Overlord_SpritePositionTarget",
|
"Overlord_SpritePositionTarget",
|
||||||
"Overlord_AllDirectionMetalBallFactory",
|
"Overlord_AllDirectionMetalBallFactory",
|
||||||
"Overlord_CascadeMetalBallFactory",
|
"Overlord_CascadeMetalBallFactory",
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <SDL_mixer.h>
|
|
||||||
#include <imgui/backends/imgui_impl_sdl2.h>
|
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||||
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
|
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
#include <imgui/imgui_internal.h>
|
#include "imgui/imgui_internal.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1
|
||||||
|
#include "imgui/backends/imgui_impl_metal.h"
|
||||||
|
#elif TARGET_OS_IPHONE == 1
|
||||||
|
#include "imgui/backends/imgui_impl_metal.h"
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -22,15 +33,100 @@ namespace core {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr ImGuiWindowFlags kMainEditorFlags =
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
|
||||||
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
|
||||||
|
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
|
||||||
|
|
||||||
|
using ::ImVec2;
|
||||||
|
using ImGui::Begin;
|
||||||
|
using ImGui::End;
|
||||||
|
using ImGui::GetIO;
|
||||||
|
using ImGui::NewFrame;
|
||||||
|
using ImGui::SetNextWindowPos;
|
||||||
|
using ImGui::SetNextWindowSize;
|
||||||
|
|
||||||
|
void NewMasterFrame() {
|
||||||
|
const ImGuiIO &io = GetIO();
|
||||||
|
ImGui_ImplSDLRenderer2_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
NewFrame();
|
||||||
|
SetNextWindowPos(gui::kZeroPos);
|
||||||
|
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
|
||||||
|
SetNextWindowSize(dimensions, ImGuiCond_Always);
|
||||||
|
|
||||||
|
if (!Begin("##YazeMain", nullptr, kMainEditorFlags)) {
|
||||||
|
End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InitializeKeymap() {
|
void InitializeKeymap() {
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.KeyMap[ImGuiKey_LeftSuper] = SDL_GetScancodeFromKey(SDLK_LGUI);
|
||||||
io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
|
io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
|
||||||
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
|
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
|
||||||
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
|
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
|
||||||
io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
|
io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
|
||||||
io.KeyMap[ImGuiKey_DownArrow] = SDL_GetScancodeFromKey(SDLK_DOWN);
|
io.KeyMap[ImGuiKey_DownArrow] = SDL_GetScancodeFromKey(SDLK_DOWN);
|
||||||
|
io.KeyMap[ImGuiKey_LeftArrow] = SDL_GetScancodeFromKey(SDLK_LEFT);
|
||||||
|
io.KeyMap[ImGuiKey_RightArrow] = SDL_GetScancodeFromKey(SDLK_RIGHT);
|
||||||
|
io.KeyMap[ImGuiKey_Delete] = SDL_GetScancodeFromKey(SDLK_DELETE);
|
||||||
|
io.KeyMap[ImGuiKey_Escape] = SDL_GetScancodeFromKey(SDLK_ESCAPE);
|
||||||
io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
|
io.KeyMap[ImGuiKey_Tab] = SDL_GetScancodeFromKey(SDLK_TAB);
|
||||||
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
|
io.KeyMap[ImGuiKey_LeftCtrl] = SDL_GetScancodeFromKey(SDLK_LCTRL);
|
||||||
|
io.KeyMap[ImGuiKey_PageUp] = SDL_GetScancodeFromKey(SDLK_PAGEUP);
|
||||||
|
io.KeyMap[ImGuiKey_PageDown] = SDL_GetScancodeFromKey(SDLK_PAGEDOWN);
|
||||||
|
io.KeyMap[ImGuiKey_Home] = SDL_GetScancodeFromKey(SDLK_HOME);
|
||||||
|
io.KeyMap[ImGuiKey_Space] = SDL_GetScancodeFromKey(SDLK_SPACE);
|
||||||
|
io.KeyMap[ImGuiKey_1] = SDL_GetScancodeFromKey(SDLK_1);
|
||||||
|
io.KeyMap[ImGuiKey_2] = SDL_GetScancodeFromKey(SDLK_2);
|
||||||
|
io.KeyMap[ImGuiKey_3] = SDL_GetScancodeFromKey(SDLK_3);
|
||||||
|
io.KeyMap[ImGuiKey_4] = SDL_GetScancodeFromKey(SDLK_4);
|
||||||
|
io.KeyMap[ImGuiKey_5] = SDL_GetScancodeFromKey(SDLK_5);
|
||||||
|
io.KeyMap[ImGuiKey_6] = SDL_GetScancodeFromKey(SDLK_6);
|
||||||
|
io.KeyMap[ImGuiKey_7] = SDL_GetScancodeFromKey(SDLK_7);
|
||||||
|
io.KeyMap[ImGuiKey_8] = SDL_GetScancodeFromKey(SDLK_8);
|
||||||
|
io.KeyMap[ImGuiKey_9] = SDL_GetScancodeFromKey(SDLK_9);
|
||||||
|
io.KeyMap[ImGuiKey_0] = SDL_GetScancodeFromKey(SDLK_0);
|
||||||
|
io.KeyMap[ImGuiKey_A] = SDL_GetScancodeFromKey(SDLK_a);
|
||||||
|
io.KeyMap[ImGuiKey_B] = SDL_GetScancodeFromKey(SDLK_b);
|
||||||
|
io.KeyMap[ImGuiKey_C] = SDL_GetScancodeFromKey(SDLK_c);
|
||||||
|
io.KeyMap[ImGuiKey_D] = SDL_GetScancodeFromKey(SDLK_d);
|
||||||
|
io.KeyMap[ImGuiKey_E] = SDL_GetScancodeFromKey(SDLK_e);
|
||||||
|
io.KeyMap[ImGuiKey_F] = SDL_GetScancodeFromKey(SDLK_f);
|
||||||
|
io.KeyMap[ImGuiKey_G] = SDL_GetScancodeFromKey(SDLK_g);
|
||||||
|
io.KeyMap[ImGuiKey_H] = SDL_GetScancodeFromKey(SDLK_h);
|
||||||
|
io.KeyMap[ImGuiKey_I] = SDL_GetScancodeFromKey(SDLK_i);
|
||||||
|
io.KeyMap[ImGuiKey_J] = SDL_GetScancodeFromKey(SDLK_j);
|
||||||
|
io.KeyMap[ImGuiKey_K] = SDL_GetScancodeFromKey(SDLK_k);
|
||||||
|
io.KeyMap[ImGuiKey_L] = SDL_GetScancodeFromKey(SDLK_l);
|
||||||
|
io.KeyMap[ImGuiKey_M] = SDL_GetScancodeFromKey(SDLK_m);
|
||||||
|
io.KeyMap[ImGuiKey_N] = SDL_GetScancodeFromKey(SDLK_n);
|
||||||
|
io.KeyMap[ImGuiKey_O] = SDL_GetScancodeFromKey(SDLK_o);
|
||||||
|
io.KeyMap[ImGuiKey_P] = SDL_GetScancodeFromKey(SDLK_p);
|
||||||
|
io.KeyMap[ImGuiKey_Q] = SDL_GetScancodeFromKey(SDLK_q);
|
||||||
|
io.KeyMap[ImGuiKey_R] = SDL_GetScancodeFromKey(SDLK_r);
|
||||||
|
io.KeyMap[ImGuiKey_S] = SDL_GetScancodeFromKey(SDLK_s);
|
||||||
|
io.KeyMap[ImGuiKey_T] = SDL_GetScancodeFromKey(SDLK_t);
|
||||||
|
io.KeyMap[ImGuiKey_U] = SDL_GetScancodeFromKey(SDLK_u);
|
||||||
|
io.KeyMap[ImGuiKey_V] = SDL_GetScancodeFromKey(SDLK_v);
|
||||||
|
io.KeyMap[ImGuiKey_W] = SDL_GetScancodeFromKey(SDLK_w);
|
||||||
|
io.KeyMap[ImGuiKey_X] = SDL_GetScancodeFromKey(SDLK_x);
|
||||||
|
io.KeyMap[ImGuiKey_Y] = SDL_GetScancodeFromKey(SDLK_y);
|
||||||
|
io.KeyMap[ImGuiKey_Z] = SDL_GetScancodeFromKey(SDLK_z);
|
||||||
|
io.KeyMap[ImGuiKey_F1] = SDL_GetScancodeFromKey(SDLK_F1);
|
||||||
|
io.KeyMap[ImGuiKey_F2] = SDL_GetScancodeFromKey(SDLK_F2);
|
||||||
|
io.KeyMap[ImGuiKey_F3] = SDL_GetScancodeFromKey(SDLK_F3);
|
||||||
|
io.KeyMap[ImGuiKey_F4] = SDL_GetScancodeFromKey(SDLK_F4);
|
||||||
|
io.KeyMap[ImGuiKey_F5] = SDL_GetScancodeFromKey(SDLK_F5);
|
||||||
|
io.KeyMap[ImGuiKey_F6] = SDL_GetScancodeFromKey(SDLK_F6);
|
||||||
|
io.KeyMap[ImGuiKey_F7] = SDL_GetScancodeFromKey(SDLK_F7);
|
||||||
|
io.KeyMap[ImGuiKey_F8] = SDL_GetScancodeFromKey(SDLK_F8);
|
||||||
|
io.KeyMap[ImGuiKey_F9] = SDL_GetScancodeFromKey(SDLK_F9);
|
||||||
|
io.KeyMap[ImGuiKey_F10] = SDL_GetScancodeFromKey(SDLK_F10);
|
||||||
|
io.KeyMap[ImGuiKey_F11] = SDL_GetScancodeFromKey(SDLK_F11);
|
||||||
|
io.KeyMap[ImGuiKey_F12] = SDL_GetScancodeFromKey(SDLK_F12);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
|
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
|
||||||
@@ -48,24 +144,63 @@ void InitializeClipboard() {
|
|||||||
io.ClipboardUserData = nullptr;
|
io.ClipboardUserData = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleKeyDown(SDL_Event &event) {
|
void HandleKeyDown(SDL_Event &event, editor::MasterEditor &editor) {
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.KeysDown[event.key.keysym.scancode] = (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);
|
||||||
|
|
||||||
switch (event.key.keysym.sym) {
|
switch (event.key.keysym.sym) {
|
||||||
case SDLK_UP:
|
|
||||||
case SDLK_DOWN:
|
|
||||||
case SDLK_RETURN:
|
|
||||||
case SDLK_BACKSPACE:
|
case SDLK_BACKSPACE:
|
||||||
case SDLK_LSHIFT:
|
case SDLK_LSHIFT:
|
||||||
case SDLK_LCTRL:
|
case SDLK_LCTRL:
|
||||||
case SDLK_TAB:
|
case SDLK_TAB:
|
||||||
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
|
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
|
||||||
break;
|
break;
|
||||||
|
case SDLK_z:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 0, true);
|
||||||
|
break;
|
||||||
|
case SDLK_a:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 1, true);
|
||||||
|
break;
|
||||||
|
case SDLK_RSHIFT:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 2, true);
|
||||||
|
break;
|
||||||
|
case SDLK_RETURN:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 3, true);
|
||||||
|
break;
|
||||||
|
case SDLK_UP:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 4, true);
|
||||||
|
break;
|
||||||
|
case SDLK_DOWN:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 5, true);
|
||||||
|
break;
|
||||||
|
case SDLK_LEFT:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 6, true);
|
||||||
|
break;
|
||||||
|
case SDLK_RIGHT:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 7, true);
|
||||||
|
break;
|
||||||
|
case SDLK_x:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 8, true);
|
||||||
|
break;
|
||||||
|
case SDLK_s:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 9, true);
|
||||||
|
break;
|
||||||
|
case SDLK_d:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 10, true);
|
||||||
|
break;
|
||||||
|
case SDLK_c:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 11, true);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleKeyUp(SDL_Event &event) {
|
void HandleKeyUp(SDL_Event &event, editor::MasterEditor &editor) {
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
int key = event.key.keysym.scancode;
|
int key = event.key.keysym.scancode;
|
||||||
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
|
IM_ASSERT(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown));
|
||||||
@@ -74,6 +209,47 @@ void HandleKeyUp(SDL_Event &event) {
|
|||||||
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
|
io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0);
|
||||||
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
|
io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0);
|
||||||
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
|
io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0);
|
||||||
|
|
||||||
|
switch (event.key.keysym.sym) {
|
||||||
|
case SDLK_z:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 0, false);
|
||||||
|
break;
|
||||||
|
case SDLK_a:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 1, false);
|
||||||
|
break;
|
||||||
|
case SDLK_RSHIFT:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 2, false);
|
||||||
|
break;
|
||||||
|
case SDLK_RETURN:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 3, false);
|
||||||
|
break;
|
||||||
|
case SDLK_UP:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 4, false);
|
||||||
|
break;
|
||||||
|
case SDLK_DOWN:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 5, false);
|
||||||
|
break;
|
||||||
|
case SDLK_LEFT:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 6, false);
|
||||||
|
break;
|
||||||
|
case SDLK_RIGHT:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 7, false);
|
||||||
|
break;
|
||||||
|
case SDLK_x:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 8, false);
|
||||||
|
break;
|
||||||
|
case SDLK_s:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 9, false);
|
||||||
|
break;
|
||||||
|
case SDLK_d:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 10, false);
|
||||||
|
break;
|
||||||
|
case SDLK_c:
|
||||||
|
editor.emulator().snes().SetButtonState(1, 11, false);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChangeWindowSizeEvent(SDL_Event &event) {
|
void ChangeWindowSizeEvent(SDL_Event &event) {
|
||||||
@@ -92,17 +268,42 @@ 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
|
} // namespace
|
||||||
|
|
||||||
absl::Status Controller::OnEntry() {
|
absl::Status Controller::OnEntry(std::string filename) {
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1
|
||||||
|
/* iOS in Xcode simulator */
|
||||||
|
platform_ = Platform::kiOS;
|
||||||
|
#elif TARGET_OS_IPHONE == 1
|
||||||
|
/* iOS */
|
||||||
|
platform_ = Platform::kiOS;
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
/* macOS */
|
||||||
|
platform_ = Platform::kMacOS;
|
||||||
|
#endif
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
platform_ = Platform::kWindows;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
platform_ = Platform::kLinux;
|
||||||
|
#else
|
||||||
|
platform_ = Platform::kUnknown;
|
||||||
|
#endif
|
||||||
|
|
||||||
RETURN_IF_ERROR(CreateSDL_Window())
|
RETURN_IF_ERROR(CreateSDL_Window())
|
||||||
RETURN_IF_ERROR(CreateRenderer())
|
RETURN_IF_ERROR(CreateRenderer())
|
||||||
RETURN_IF_ERROR(CreateGuiContext())
|
RETURN_IF_ERROR(CreateGuiContext())
|
||||||
|
if (flags()->kLoadAudioDevice) {
|
||||||
|
RETURN_IF_ERROR(LoadAudioDevice())
|
||||||
|
master_editor_.emulator().set_audio_buffer(audio_buffer_);
|
||||||
|
master_editor_.emulator().set_audio_device_id(audio_device_);
|
||||||
|
}
|
||||||
InitializeKeymap();
|
InitializeKeymap();
|
||||||
master_editor_.SetupScreen(renderer_);
|
master_editor_.SetupScreen(renderer_, filename);
|
||||||
active_ = true;
|
active_ = true;
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -115,10 +316,10 @@ void Controller::OnInput() {
|
|||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SDL_KEYDOWN:
|
case SDL_KEYDOWN:
|
||||||
HandleKeyDown(event);
|
HandleKeyDown(event, master_editor_);
|
||||||
break;
|
break;
|
||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
HandleKeyUp(event);
|
HandleKeyUp(event, master_editor_);
|
||||||
break;
|
break;
|
||||||
case SDL_TEXTINPUT:
|
case SDL_TEXTINPUT:
|
||||||
io.AddInputCharactersUTF8(event.text.text);
|
io.AddInputCharactersUTF8(event.text.text);
|
||||||
@@ -146,26 +347,57 @@ void Controller::OnInput() {
|
|||||||
HandleMouseMovement(wheel);
|
HandleMouseMovement(wheel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::OnLoad() { PRINT_IF_ERROR(master_editor_.Update()); }
|
absl::Status Controller::OnLoad() {
|
||||||
|
if (master_editor_.quit()) {
|
||||||
|
active_ = false;
|
||||||
|
}
|
||||||
|
NewMasterFrame();
|
||||||
|
RETURN_IF_ERROR(master_editor_.Update());
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
void Controller::DoRender() const {
|
void Controller::DoRender() const {
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
SDL_RenderClear(renderer_.get());
|
SDL_RenderClear(renderer_.get());
|
||||||
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
|
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get());
|
||||||
SDL_RenderPresent(renderer_.get());
|
SDL_RenderPresent(renderer_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::OnExit() {
|
void Controller::OnExit() {
|
||||||
master_editor_.Shutdown();
|
ImGui::DestroyContext();
|
||||||
Mix_CloseAudio();
|
if (flags()->kLoadAudioDevice) {
|
||||||
ImGui_ImplSDLRenderer2_Shutdown();
|
SDL_PauseAudioDevice(audio_device_, 1);
|
||||||
ImGui_ImplSDL2_Shutdown();
|
SDL_CloseAudioDevice(audio_device_);
|
||||||
|
delete audio_buffer_;
|
||||||
|
}
|
||||||
|
switch (platform_) {
|
||||||
|
case Platform::kMacOS:
|
||||||
|
case Platform::kWindows:
|
||||||
|
case Platform::kLinux:
|
||||||
|
ImGui_ImplSDLRenderer2_Shutdown();
|
||||||
|
ImGui_ImplSDL2_Shutdown();
|
||||||
|
break;
|
||||||
|
case Platform::kiOS:
|
||||||
|
// Deferred
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::Status Controller::CreateSDL_Window() {
|
absl::Status Controller::CreateSDL_Window() {
|
||||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
|
auto sdl_flags = SDL_INIT_VIDEO | SDL_INIT_TIMER;
|
||||||
|
if (flags()->kUseNewImGuiInput) {
|
||||||
|
sdl_flags |= SDL_INIT_GAMECONTROLLER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags()->kLoadAudioDevice) {
|
||||||
|
sdl_flags |= SDL_INIT_AUDIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
@@ -186,11 +418,6 @@ absl::Status Controller::CreateSDL_Window() {
|
|||||||
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(32000, MIX_DEFAULT_FORMAT, 2, 1024) < 0) {
|
|
||||||
printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
|
|
||||||
Mix_GetError());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -215,8 +442,9 @@ absl::Status Controller::CreateGuiContext() {
|
|||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
|
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
|
|
||||||
if (flags()->kUseNewImGuiInput) {
|
if (flags()->kUseNewImGuiInput) {
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +463,7 @@ absl::Status Controller::CreateGuiContext() {
|
|||||||
|
|
||||||
// Build a new ImGui frame
|
// Build a new ImGui frame
|
||||||
ImGui_ImplSDLRenderer2_NewFrame();
|
ImGui_ImplSDLRenderer2_NewFrame();
|
||||||
ImGui_ImplSDL2_NewFrame(window_.get());
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -243,13 +471,13 @@ absl::Status Controller::CreateGuiContext() {
|
|||||||
absl::Status Controller::LoadFontFamilies() const {
|
absl::Status Controller::LoadFontFamilies() const {
|
||||||
ImGuiIO &io = ImGui::GetIO();
|
ImGuiIO &io = ImGui::GetIO();
|
||||||
|
|
||||||
// Define constants
|
const char *font_path = "assets/font/";
|
||||||
static const char *KARLA_REGULAR = "assets/font/Karla-Regular.ttf";
|
static const char *KARLA_REGULAR = "Karla-Regular.ttf";
|
||||||
static const char *ROBOTO_MEDIUM = "assets/font/Roboto-Medium.ttf";
|
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
|
||||||
static const char *COUSINE_REGULAR = "assets/font/Cousine-Regular.ttf";
|
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
|
||||||
static const char *DROID_SANS = "assets/font/DroidSans.ttf";
|
static const char *DROID_SANS = "DroidSans.ttf";
|
||||||
static const char *NOTO_SANS_JP = "assets/font/NotoSansJP.ttf";
|
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
|
||||||
static const char *IBM_PLEX_JP = "assets/font/IBMPlexSansJP-Bold.ttf";
|
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
|
||||||
static const float FONT_SIZE_DEFAULT = 14.0f;
|
static const float FONT_SIZE_DEFAULT = 14.0f;
|
||||||
static const float FONT_SIZE_DROID_SANS = 16.0f;
|
static const float FONT_SIZE_DROID_SANS = 16.0f;
|
||||||
static const float ICON_FONT_SIZE = 18.0f;
|
static const float ICON_FONT_SIZE = 18.0f;
|
||||||
@@ -278,23 +506,78 @@ absl::Status Controller::LoadFontFamilies() const {
|
|||||||
float font_size =
|
float font_size =
|
||||||
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
|
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
|
||||||
|
|
||||||
if (!io.Fonts->AddFontFromFileTTF(font_path, font_size)) {
|
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 = std::filesystem::absolute(font_path).string();
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
actual_font_path = font_path;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!io.Fonts->AddFontFromFileTTF(actual_font_path.data(), font_size)) {
|
||||||
return absl::InternalError(
|
return absl::InternalError(
|
||||||
absl::StrFormat("Failed to load font from %s", font_path));
|
absl::StrFormat("Failed to load font from %s", actual_font_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge icon set
|
// Merge icon set
|
||||||
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE,
|
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 = std::filesystem::absolute(icon_font_path).string();
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
|
io.Fonts->AddFontFromFileTTF(actual_icon_font_path.data(), ICON_FONT_SIZE,
|
||||||
&icons_config, icons_ranges);
|
&icons_config, icons_ranges);
|
||||||
|
|
||||||
// Merge Japanese font
|
// Merge Japanese font
|
||||||
io.Fonts->AddFontFromFileTTF(NOTO_SANS_JP, 18.0f, &japanese_font_config,
|
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 =
|
||||||
|
std::filesystem::absolute(japanese_font_path).string();
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
|
io.Fonts->AddFontFromFileTTF(actual_japanese_font_path.data(), 18.0f,
|
||||||
|
&japanese_font_config,
|
||||||
io.Fonts->GetGlyphRangesJapanese());
|
io.Fonts->GetGlyphRangesJapanese());
|
||||||
}
|
}
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
master_editor_.emulator().set_audio_buffer(audio_buffer_);
|
||||||
|
SDL_PauseAudioDevice(audio_device_, 0);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
@@ -2,17 +2,18 @@
|
|||||||
#define YAZE_APP_CORE_CONTROLLER_H
|
#define YAZE_APP_CORE_CONTROLLER_H
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <imgui/backends/imgui_impl_sdl2.h>
|
#include "imgui/backends/imgui_impl_sdl2.h"
|
||||||
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
|
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imconfig.h"
|
||||||
#include <imgui/imgui_internal.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/core/common.h"
|
#include "app/core/common.h"
|
||||||
#include "app/core/editor.h"
|
|
||||||
#include "app/editor/master_editor.h"
|
#include "app/editor/master_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "app/gui/style.h"
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
@@ -22,15 +23,31 @@ namespace yaze {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
enum class Platform { kUnknown, kMacOS, kiOS, kWindows, kLinux };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 {
|
class Controller : public ExperimentFlags {
|
||||||
public:
|
public:
|
||||||
bool IsActive() const { return active_; }
|
bool IsActive() const { return active_; }
|
||||||
absl::Status OnEntry();
|
absl::Status OnEntry(std::string filename = "");
|
||||||
void OnInput();
|
void OnInput();
|
||||||
void OnLoad();
|
absl::Status OnLoad();
|
||||||
void DoRender() const;
|
void DoRender() const;
|
||||||
void OnExit();
|
void OnExit();
|
||||||
|
|
||||||
|
absl::Status CreateSDL_Window();
|
||||||
|
absl::Status CreateRenderer();
|
||||||
|
absl::Status CreateGuiContext();
|
||||||
|
absl::Status LoadFontFamilies() const;
|
||||||
|
absl::Status LoadAudioDevice();
|
||||||
|
|
||||||
|
auto master_editor() -> editor::MasterEditor & { return master_editor_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct sdl_deleter {
|
struct sdl_deleter {
|
||||||
void operator()(SDL_Window *p) const {
|
void operator()(SDL_Window *p) const {
|
||||||
@@ -46,16 +63,17 @@ class Controller : public ExperimentFlags {
|
|||||||
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
|
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
|
||||||
};
|
};
|
||||||
|
|
||||||
absl::Status CreateSDL_Window();
|
|
||||||
absl::Status CreateRenderer();
|
|
||||||
absl::Status CreateGuiContext();
|
|
||||||
absl::Status LoadFontFamilies() const;
|
|
||||||
void CloseWindow() { active_ = false; }
|
void CloseWindow() { active_ = false; }
|
||||||
|
|
||||||
friend int ::main(int argc, char **argv);
|
friend int ::main(int argc, char **argv);
|
||||||
|
|
||||||
bool active_;
|
bool active_;
|
||||||
|
Platform platform_;
|
||||||
editor::MasterEditor master_editor_;
|
editor::MasterEditor master_editor_;
|
||||||
|
|
||||||
|
int audio_frequency_ = 48000;
|
||||||
|
int16_t *audio_buffer_;
|
||||||
|
SDL_AudioDeviceID audio_device_;
|
||||||
std::shared_ptr<SDL_Window> window_;
|
std::shared_ptr<SDL_Window> window_;
|
||||||
std::shared_ptr<SDL_Renderer> renderer_;
|
std::shared_ptr<SDL_Renderer> renderer_;
|
||||||
};
|
};
|
||||||
@@ -64,4 +82,4 @@ class Controller : public ExperimentFlags {
|
|||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_APP_CORE_CONTROLLER_H
|
#endif // YAZE_APP_CORE_CONTROLLER_H
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
#ifndef YAZE_APP_CORE_EDITOR_H
|
|
||||||
#define YAZE_APP_CORE_EDITOR_H
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // YAZE_APP_CORE_EDITOR_H
|
|
||||||
141
src/app/core/labeling.cc
Normal file
141
src/app/core/labeling.cc
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#include "app/core/labeling.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
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::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 core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
53
src/app/core/labeling.h
Normal file
53
src/app/core/labeling.h
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_LABELING_H_
|
||||||
|
#define YAZE_APP_CORE_LABELING_H_
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
// 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 CreateOrGetLabel(const std::string& type, const std::string& key,
|
||||||
|
const std::string& defaultValue);
|
||||||
|
std::string CreateOrGetLabel(const std::string& type, const std::string& key,
|
||||||
|
const absl::string_view& 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_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_LABELING_H_
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||||
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||||
|
|
||||||
|
#ifdef TARGET_OS_MAC
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@@ -11,4 +13,6 @@ void InitializeCocoa();
|
|||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif // TARGET_OS_MAC
|
||||||
|
|
||||||
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
|
||||||
|
|||||||
@@ -1,12 +1,26 @@
|
|||||||
// AppDelegate.mm
|
// AppDelegate.mm
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
#import "app/core/controller.h"
|
|
||||||
#import "app/core/editor.h"
|
|
||||||
#import "app/core/platform/app_delegate.h"
|
#import "app/core/platform/app_delegate.h"
|
||||||
|
#import "app/core/controller.h"
|
||||||
#import "app/core/platform/file_dialog.h"
|
#import "app/core/platform/file_dialog.h"
|
||||||
|
#import "app/editor/utils/editor.h"
|
||||||
#import "app/rom.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
|
||||||
|
/* iOS in Xcode simulator */
|
||||||
|
|
||||||
|
#elif TARGET_OS_IPHONE == 1
|
||||||
|
/* iOS */
|
||||||
|
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
/* macOS */
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||||
- (void)setupMenus;
|
- (void)setupMenus;
|
||||||
// - (void)changeApplicationIcon;
|
// - (void)changeApplicationIcon;
|
||||||
@@ -195,7 +209,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)openFileAction:(id)sender {
|
- (void)openFileAction:(id)sender {
|
||||||
yaze::app::SharedROM::shared_rom_->LoadFromFile(FileDialogWrapper::ShowOpenFileDialog());
|
if (!yaze::app::SharedRom::shared_rom_->LoadFromFile(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 {
|
- (void)cutAction:(id)sender {
|
||||||
@@ -215,4 +236,8 @@ extern "C" void InitializeCocoa() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
8
src/app/core/platform/clipboard.cc
Normal file
8
src/app/core/platform/clipboard.cc
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#include "app/core/platform/clipboard.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
void CopyImageToClipboard(const std::vector<uint8_t>& data) {}
|
||||||
|
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
|
||||||
|
int& height) {}
|
||||||
@@ -8,6 +8,7 @@ void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
|
|||||||
|
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
void CopyImageToClipboard(const std::vector<uint8_t>& data);
|
void CopyImageToClipboard(const std::vector<uint8_t>& data);
|
||||||
@@ -15,17 +16,11 @@ void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
|
|||||||
|
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
void CopyImageToClipboard(const std::vector<uint8_t>& data) {
|
void CopyImageToClipboard(const std::vector<uint8_t>& data);
|
||||||
std::cout << "CopyImageToClipboard() is not implemented on Linux."
|
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
|
||||||
<< std::endl;
|
|
||||||
}
|
|
||||||
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
|
|
||||||
int& height) {
|
|
||||||
std::cout << "GetImageFromClipboard() is not implemented on Linux."
|
|
||||||
<< std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef TARGET_OS_MAC
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
|
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
|
||||||
@@ -39,4 +40,6 @@ void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& he
|
|||||||
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||||
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
||||||
CGContextRelease(context);
|
CGContextRelease(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -42,13 +45,39 @@ class FileDialogWrapper {
|
|||||||
|
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
|
#include "TargetConditionals.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef TARGET_OS_MAC
|
||||||
|
// Other kinds of Mac OS
|
||||||
|
|
||||||
class FileDialogWrapper {
|
class FileDialogWrapper {
|
||||||
public:
|
public:
|
||||||
static std::string ShowOpenFileDialog();
|
static std::string ShowOpenFileDialog();
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#elif TARGET_OS_IPHONE
|
||||||
|
|
||||||
|
// iOS
|
||||||
|
class FileDialogWrapper {
|
||||||
|
public:
|
||||||
|
static std::string ShowOpenFileDialog();
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
|
|
||||||
class FileDialogWrapper {
|
class FileDialogWrapper {
|
||||||
@@ -62,4 +91,6 @@ class FileDialogWrapper {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
#error "Unsupported platform."
|
#error "Unsupported platform."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
|
||||||
|
|||||||
@@ -1,6 +1,49 @@
|
|||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "app/core/platform/file_dialog.h"
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
/* Apple OSX and iOS (Darwin). */
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#import <CoreText/CoreText.h>
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1
|
||||||
|
/* iOS in Xcode simulator */
|
||||||
|
std::string FileDialogWrapper::ShowOpenFileDialog() { return ""; }
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
|
||||||
|
|
||||||
|
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif TARGET_OS_IPHONE == 1
|
||||||
|
/* iOS */
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowOpenFileDialog() { return ""; }
|
||||||
|
|
||||||
|
std::string FileDialogWrapper::ShowOpenFolderDialog() { return ""; }
|
||||||
|
|
||||||
|
std::vector<std::string> FileDialogWrapper::GetFilesInFolder(const std::string& folder) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> FileDialogWrapper::GetSubdirectoriesInFolder(const std::string& folder) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
/* macOS */
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
std::string FileDialogWrapper::ShowOpenFileDialog() {
|
||||||
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
|
||||||
[openPanel setCanChooseFiles:YES];
|
[openPanel setCanChooseFiles:YES];
|
||||||
@@ -15,3 +58,59 @@ std::string FileDialogWrapper::ShowOpenFileDialog() {
|
|||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string 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> 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> 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__
|
||||||
|
|||||||
6
src/app/core/platform/file_path.h
Normal file
6
src/app/core/platform/file_path.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PLATFORM_FILE_PATH_H
|
||||||
|
#define YAZE_APP_CORE_PLATFORM_FILE_PATH_H
|
||||||
|
|
||||||
|
std::string GetBundleResourcePath();
|
||||||
|
|
||||||
|
#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 <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
#include <Foundation/Foundation.h>
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1
|
||||||
|
std::string GetBundleResourcePath() {}
|
||||||
|
#elif TARGET_OS_IPHONE == 1
|
||||||
|
std::string GetBundleResourcePath() {
|
||||||
|
NSBundle* bundle = [NSBundle mainBundle];
|
||||||
|
NSString* resourceDirectoryPath = [bundle bundlePath];
|
||||||
|
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
|
||||||
|
return [path UTF8String];
|
||||||
|
}
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
std::string GetBundleResourcePath() {
|
||||||
|
NSBundle* bundle = [NSBundle mainBundle];
|
||||||
|
NSString* resourceDirectoryPath = [bundle bundlePath];
|
||||||
|
NSString* path = [resourceDirectoryPath stringByAppendingString:@"/"];
|
||||||
|
return [path UTF8String];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "app/core/platform/font_loader.h"
|
#include "app/core/platform/font_loader.h"
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|||||||
@@ -2,14 +2,25 @@
|
|||||||
#ifndef FONTLOADER_H
|
#ifndef FONTLOADER_H
|
||||||
#define FONTLOADER_H
|
#define FONTLOADER_H
|
||||||
|
|
||||||
// Function declaration for loading system fonts into ImGui
|
#include "TargetConditionals.h"
|
||||||
void LoadSystemFonts();
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
// Windows specific function declaration for loading system fonts into ImGui
|
// Windows specific function declaration for loading system fonts into ImGui
|
||||||
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
|
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
|
||||||
DWORD FontType, LPARAM lParam);
|
DWORD FontType, LPARAM lParam);
|
||||||
|
#elif __APPLE__
|
||||||
|
|
||||||
|
#ifdef TARGET_OS_MAC
|
||||||
|
|
||||||
|
void LoadSystemFonts();
|
||||||
|
|
||||||
|
#elif TARGET_OS_IPHONE
|
||||||
|
|
||||||
|
void LoadSystemFonts();
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // FONTLOADER_H
|
#endif // FONTLOADER_H
|
||||||
|
|||||||
@@ -1,11 +1,30 @@
|
|||||||
// FontLoader.mm
|
// FontLoader.mm
|
||||||
#include "app/core/platform/font_loader.h"
|
#include "app/core/platform/font_loader.h"
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import <CoreText/CoreText.h>
|
#include "imgui/imgui.h"
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__MACH__)
|
||||||
|
/* Apple OSX and iOS (Darwin). */
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
#import <CoreText/CoreText.h>
|
||||||
|
|
||||||
|
#if TARGET_IPHONE_SIMULATOR == 1
|
||||||
|
/* iOS in Xcode simulator */
|
||||||
|
void LoadSystemFonts() {}
|
||||||
|
|
||||||
|
#elif TARGET_OS_IPHONE == 1
|
||||||
|
/* iOS */
|
||||||
|
void LoadSystemFonts() {}
|
||||||
|
|
||||||
|
#elif TARGET_OS_MAC == 1
|
||||||
|
/* macOS */
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
// MacOS Implementation
|
||||||
void LoadSystemFonts() {
|
void LoadSystemFonts() {
|
||||||
// List of common macOS system fonts
|
// List of common macOS system fonts
|
||||||
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
|
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
|
||||||
@@ -47,4 +66,9 @@ void LoadSystemFonts() {
|
|||||||
CFRelease(fontURL);
|
CFRelease(fontURL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// Unsupported platform
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
138
src/app/core/project.h
Normal file
138
src/app/core/project.h
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_PROJECT_H
|
||||||
|
#define YAZE_APP_CORE_PROJECT_H
|
||||||
|
|
||||||
|
#include "absl/strings/match.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
constexpr absl::string_view kProjectFileExtension = ".yaze";
|
||||||
|
constexpr absl::string_view kProjectFileFilter =
|
||||||
|
"Yaze Project Files (*.yaze)\0*.yaze\0";
|
||||||
|
constexpr absl::string_view kPreviousRomFilenameDelimiter =
|
||||||
|
"PreviousRomFilename";
|
||||||
|
constexpr absl::string_view 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 : public core::ExperimentFlags {
|
||||||
|
/**
|
||||||
|
* @brief Creates a new project.
|
||||||
|
*
|
||||||
|
* @param project_name The name of the project.
|
||||||
|
* @param project_path The path to the project.
|
||||||
|
* @return An absl::Status indicating the success or failure of the project
|
||||||
|
* creation.
|
||||||
|
*/
|
||||||
|
absl::Status Create(const std::string &project_name) {
|
||||||
|
name = project_name;
|
||||||
|
project_opened_ = true;
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status 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_);
|
||||||
|
|
||||||
|
while (std::getline(in, line)) {
|
||||||
|
if (line == kEndOfProjectFile) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absl::StrContains(line, kPreviousRomFilenameDelimiter)) {
|
||||||
|
previous_rom_filenames_.push_back(
|
||||||
|
line.substr(line.find(kPreviousRomFilenameDelimiter) +
|
||||||
|
kPreviousRomFilenameDelimiter.size() + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
in.close();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status 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;
|
||||||
|
|
||||||
|
for (const auto &filename : previous_rom_filenames_) {
|
||||||
|
out << kPreviousRomFilenameDelimiter << " " << filename << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << kEndOfProjectFile << std::endl;
|
||||||
|
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
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 SerializeExperimentFlags() {
|
||||||
|
auto flags = mutable_flags();
|
||||||
|
// TODO: Serialize flags
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool project_opened_ = false;
|
||||||
|
std::string name;
|
||||||
|
std::string filepath;
|
||||||
|
std::string rom_filename_ = "";
|
||||||
|
std::string code_folder_ = "";
|
||||||
|
std::string labels_filename_ = "";
|
||||||
|
std::vector<std::string> previous_rom_filenames_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_PROJECT_H
|
||||||
19
src/app/core/testable.h
Normal file
19
src/app/core/testable.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_TESTABLE_H
|
||||||
|
#define YAZE_APP_CORE_TESTABLE_H
|
||||||
|
|
||||||
|
#include <imgui_test_engine/imgui_te_context.h>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace core {
|
||||||
|
class GuiTestable {
|
||||||
|
public:
|
||||||
|
virtual void RegisterTests(ImGuiTestEngine* e) = 0;
|
||||||
|
|
||||||
|
ImGuiTestEngine* test_engine;
|
||||||
|
};
|
||||||
|
} // namespace core
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_TESTABLE_H
|
||||||
21
src/app/editor/CMakeLists.txt
Normal file
21
src/app/editor/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
set(
|
||||||
|
YAZE_APP_EDITOR_SRC
|
||||||
|
app/editor/dungeon/dungeon_editor.cc
|
||||||
|
app/editor/master_editor.cc
|
||||||
|
app/editor/master_editor_test.cc
|
||||||
|
app/editor/settings_editor.cc
|
||||||
|
app/editor/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_editor_test.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/utils/gfx_context.cc
|
||||||
|
app/editor/overworld/refresh.cc
|
||||||
|
app/editor/overworld/entity.cc
|
||||||
|
)
|
||||||
367
src/app/editor/code/assembly_editor.cc
Normal file
367
src/app/editor/code/assembly_editor.cc
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
#include "assembly_editor.h"
|
||||||
|
|
||||||
|
#include "ImGuiColorTextEdit/TextEditor.h"
|
||||||
|
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "core/constants.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
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& folder :
|
||||||
|
FileDialogWrapper::GetSubdirectoriesInFolder(current_folder.name)) {
|
||||||
|
core::FolderItem folder_item;
|
||||||
|
folder_item.name = folder;
|
||||||
|
std::string full_folder = current_folder.name + "/" + folder;
|
||||||
|
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")) {
|
||||||
|
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);
|
||||||
|
} 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 app
|
||||||
|
} // namespace yaze
|
||||||
79
src/app/editor/code/assembly_editor.h
Normal file
79
src/app/editor/code/assembly_editor.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#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>
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
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 app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
88
src/app/editor/code/memory_editor.h
Normal file
88
src/app/editor/code/memory_editor.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_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/common.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/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_editor.h"
|
||||||
|
#include "app/editor/sprite/sprite_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/editor/utils/gfx_context.h"
|
||||||
|
#include "app/editor/utils/recent_files.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"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
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 = 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 app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_CODE_MEMORY_EDITOR_H
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#include "app/editor/context/gfx_context.h"
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "absl/status/statusor.h"
|
|
||||||
#include "app/core/editor.h"
|
|
||||||
#include "app/gui/pipeline.h"
|
|
||||||
#include "app/editor/modules/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/gui/icons.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "app/zelda3/overworld.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
absl::Status GfxContext::Update() { return absl::OkStatus(); }
|
|
||||||
|
|
||||||
gfx::Bitmap GfxContext::current_ow_gfx_bmp_;
|
|
||||||
gfx::SNESPalette GfxContext::current_ow_palette_;
|
|
||||||
gfx::Bitmap GfxContext::tile16_blockset_bmp_;
|
|
||||||
gfx::Bitmap GfxContext::tile8_blockset_bmp_;
|
|
||||||
std::vector<gfx::Bitmap> GfxContext::tile16_individual_bmp_;
|
|
||||||
std::vector<gfx::Bitmap> GfxContext::tile8_individual_bmp_;
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
|
||||||
#define YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "absl/status/statusor.h"
|
|
||||||
#include "app/core/editor.h"
|
|
||||||
#include "app/gui/pipeline.h"
|
|
||||||
#include "app/editor/modules/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/gui/icons.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "app/zelda3/overworld.h"
|
|
||||||
|
|
||||||
// Create a class which manages the current VRAM state of Link to the Past,
|
|
||||||
// including static members for the Bitmaps and Palettes as well as the current
|
|
||||||
// blockset as to update all of the overworld maps with the new blockset when it
|
|
||||||
// is changed. This class will also manage the current tile16 and tile8
|
|
||||||
// selection, as well as the current palette selection.
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
class GfxContext {
|
|
||||||
public:
|
|
||||||
absl::Status Update();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
static gfx::Bitmap current_ow_gfx_bmp_;
|
|
||||||
|
|
||||||
static gfx::SNESPalette current_ow_palette_;
|
|
||||||
|
|
||||||
static gfx::Bitmap tile16_blockset_bmp_;
|
|
||||||
|
|
||||||
static gfx::Bitmap tile8_blockset_bmp_;
|
|
||||||
|
|
||||||
// Bitmaps for the tile16 individual tiles
|
|
||||||
static std::vector<gfx::Bitmap> tile16_individual_bmp_;
|
|
||||||
|
|
||||||
// Bitmaps for the tile8 individual tiles
|
|
||||||
static std::vector<gfx::Bitmap> tile8_individual_bmp_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif // YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
|
||||||
830
src/app/editor/dungeon/dungeon_editor.cc
Normal file
830
src/app/editor/dungeon/dungeon_editor.cc
Normal file
@@ -0,0 +1,830 @@
|
|||||||
|
#include "dungeon_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/labeling.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 "app/zelda3/dungeon/room_names.h"
|
||||||
|
#include "zelda3/dungeon/room.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
TAB_BAR("##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()
|
||||||
|
END_TAB_BAR()
|
||||||
|
|
||||||
|
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::dungeon::Room(i));
|
||||||
|
rooms_[i].LoadHeader();
|
||||||
|
rooms_[i].LoadRoomFromROM();
|
||||||
|
if (flags()->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::dungeon::RoomEntrance(*rom(), i, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 0x85; ++i) {
|
||||||
|
entrances_.emplace_back(zelda3::dungeon::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()->mutable_bitmap_manager();
|
||||||
|
// 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() {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
int block = rooms_[current_room_id_].blocks()[i];
|
||||||
|
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
|
||||||
|
current_palette_group_[current_palette_id_], 0));
|
||||||
|
rom()->UpdateBitmap(&graphics_bin_[block], true);
|
||||||
|
}
|
||||||
|
auto sprites_aux1_pal_group = rom()->palette_group().sprites_aux1;
|
||||||
|
for (int i = 9; i < 16; i++) {
|
||||||
|
int block = rooms_[current_room_id_].blocks()[i];
|
||||||
|
RETURN_IF_ERROR(graphics_bin_[block].ApplyPaletteWithTransparent(
|
||||||
|
sprites_aux1_pal_group[current_palette_id_], 0));
|
||||||
|
rom()->UpdateBitmap(&graphics_bin_[block], true);
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
TAB_BAR("##DungeonRoomTabBar");
|
||||||
|
TAB_ITEM("Rooms");
|
||||||
|
DrawRoomSelector();
|
||||||
|
END_TAB_ITEM();
|
||||||
|
TAB_ITEM("Entrances");
|
||||||
|
DrawEntranceSelector();
|
||||||
|
END_TAB_ITEM();
|
||||||
|
END_TAB_BAR();
|
||||||
|
|
||||||
|
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::dungeon::kRoomNames) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_room_id_ == i, "Dungeon Room Names",
|
||||||
|
core::UppercaseHexByte(i), zelda3::dungeon::kRoomNames[i].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::UppercaseHexByte(i),
|
||||||
|
zelda3::dungeon::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::dungeon::kRoomNames) / 4) {
|
||||||
|
active_rooms_.erase(active_rooms_.Data + n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTabItem(zelda3::dungeon::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(
|
||||||
|
(void*)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::dungeon::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());
|
||||||
|
rom()->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 app
|
||||||
|
} // namespace yaze
|
||||||
153
src/app/editor/dungeon/dungeon_editor.h
Normal file
153
src/app/editor/dungeon/dungeon_editor.h
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/labeling.h"
|
||||||
|
#include "app/editor/graphics/gfx_group_editor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "zelda3/dungeon/room.h"
|
||||||
|
#include "zelda3/dungeon/room_entrance.h"
|
||||||
|
#include "zelda3/dungeon/room_object.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
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 core::ExperimentFlags {
|
||||||
|
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;
|
||||||
|
bool show_object_render_ = 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_;
|
||||||
|
gfx::BitmapManager graphics_bin_;
|
||||||
|
|
||||||
|
std::vector<gfx::Bitmap*> room_gfx_sheets_;
|
||||||
|
std::vector<zelda3::dungeon::Room> rooms_;
|
||||||
|
std::vector<zelda3::dungeon::RoomEntrance> entrances_;
|
||||||
|
std::vector<gfx::BitmapManager> room_graphics_;
|
||||||
|
zelda3::dungeon::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 app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,380 +0,0 @@
|
|||||||
#include "dungeon_editor.h"
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include "app/core/common.h"
|
|
||||||
#include "app/gfx/snes_palette.h"
|
|
||||||
#include "app/gui/canvas.h"
|
|
||||||
#include "app/gui/icons.h"
|
|
||||||
#include "app/gui/input.h"
|
|
||||||
#include "app/gui/pipeline.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "app/zelda3/dungeon/object_names.h"
|
|
||||||
#include "app/zelda3/dungeon/room_names.h"
|
|
||||||
#include "zelda3/dungeon/room.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
constexpr ImGuiTableFlags kDungeonObjectTableFlags =
|
|
||||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
|
||||||
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
|
||||||
ImGuiTableFlags_BordersV;
|
|
||||||
|
|
||||||
using ImGui::TableHeadersRow;
|
|
||||||
using ImGui::TableNextColumn;
|
|
||||||
using ImGui::TableNextRow;
|
|
||||||
using ImGui::TableSetupColumn;
|
|
||||||
|
|
||||||
absl::Status DungeonEditor::Update() {
|
|
||||||
if (!is_loaded_ && rom()->isLoaded()) {
|
|
||||||
for (int i = 0; i < 0x100; i++) {
|
|
||||||
rooms_.emplace_back(zelda3::dungeon::Room(i));
|
|
||||||
rooms_[i].LoadHeader();
|
|
||||||
rooms_[i].LoadRoomFromROM();
|
|
||||||
if (flags()->kDrawDungeonRoomGraphics) {
|
|
||||||
rooms_[i].LoadRoomGraphics();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
graphics_bin_ = rom()->graphics_bin();
|
|
||||||
full_palette_ =
|
|
||||||
rom()->palette_group("dungeon_main")[current_palette_group_id_];
|
|
||||||
current_palette_group_ =
|
|
||||||
gfx::CreatePaletteGroupFromLargePalette(full_palette_);
|
|
||||||
|
|
||||||
// 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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
is_loaded_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refresh_graphics_) {
|
|
||||||
for (int block : rooms_[current_room_id_].blocks()) {
|
|
||||||
graphics_bin_[block].ApplyPalette(
|
|
||||||
current_palette_group_[current_palette_id_]);
|
|
||||||
rom()->UpdateBitmap(&graphics_bin_[block]);
|
|
||||||
}
|
|
||||||
refresh_graphics_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawToolset();
|
|
||||||
|
|
||||||
if (palette_showing_) {
|
|
||||||
ImGui::Begin("Palette Editor", &palette_showing_, 0);
|
|
||||||
current_palette_ =
|
|
||||||
rom()->palette_group("dungeon_main")[current_palette_group_id_];
|
|
||||||
gui::SelectablePalettePipeline(current_palette_id_, refresh_graphics_,
|
|
||||||
current_palette_);
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginTable("#DungeonEditTable", 3, kDungeonTableFlags,
|
|
||||||
ImVec2(0, 0))) {
|
|
||||||
TableSetupColumn("Room Selector");
|
|
||||||
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
TableSetupColumn("Object Selector");
|
|
||||||
TableHeadersRow();
|
|
||||||
TableNextRow();
|
|
||||||
|
|
||||||
TableNextColumn();
|
|
||||||
DrawRoomSelector();
|
|
||||||
|
|
||||||
TableNextColumn();
|
|
||||||
DrawDungeonTabView();
|
|
||||||
|
|
||||||
TableNextColumn();
|
|
||||||
DrawTileSelector();
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DungeonEditor::DrawToolset() {
|
|
||||||
if (ImGui::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");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::Button(ICON_MD_UNDO)) {
|
|
||||||
PRINT_IF_ERROR(Undo());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::Button(ICON_MD_REDO)) {
|
|
||||||
PRINT_IF_ERROR(Redo());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ICON_MD_MORE_VERT);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::RadioButton(ICON_MD_FILTER_NONE,
|
|
||||||
background_type_ == kBackgroundAny)) {
|
|
||||||
background_type_ = kBackgroundAny;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::RadioButton(ICON_MD_FILTER_1,
|
|
||||||
background_type_ == kBackground1)) {
|
|
||||||
background_type_ = kBackground1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::RadioButton(ICON_MD_FILTER_2,
|
|
||||||
background_type_ == kBackground2)) {
|
|
||||||
background_type_ = kBackground2;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::RadioButton(ICON_MD_FILTER_3,
|
|
||||||
background_type_ == kBackground3)) {
|
|
||||||
background_type_ = kBackground3;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text(ICON_MD_MORE_VERT);
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::RadioButton(ICON_MD_PEST_CONTROL, placement_type_ == kSprite)) {
|
|
||||||
placement_type_ = kSprite;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Sprites");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::RadioButton(ICON_MD_GRASS, placement_type_ == kItem)) {
|
|
||||||
placement_type_ = kItem;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Items");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::RadioButton(ICON_MD_SENSOR_DOOR, placement_type_ == kDoor)) {
|
|
||||||
placement_type_ = kDoor;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Doors");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::RadioButton(ICON_MD_SQUARE, placement_type_ == kBlock)) {
|
|
||||||
placement_type_ = kBlock;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Blocks");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
if (ImGui::Button(ICON_MD_PALETTE)) {
|
|
||||||
palette_showing_ = !palette_showing_;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DungeonEditor::DrawRoomSelector() {
|
|
||||||
if (rom()->isLoaded()) {
|
|
||||||
gui::InputHexWord("Room ID", ¤t_room_id_);
|
|
||||||
gui::InputHex("Palette ID", ¤t_palette_id_);
|
|
||||||
|
|
||||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)9);
|
|
||||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
|
||||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
|
||||||
int i = 0;
|
|
||||||
for (const auto each_room_name : zelda3::dungeon::kRoomNames) {
|
|
||||||
ImGui::Selectable(each_room_name.data(), current_room_id_ == i,
|
|
||||||
ImGuiSelectableFlags_AllowDoubleClick);
|
|
||||||
if (ImGui::IsItemClicked()) {
|
|
||||||
active_rooms_.push_back(i);
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DungeonEditor::DrawDungeonTabView() {
|
|
||||||
static int next_tab_id = 0;
|
|
||||||
|
|
||||||
if (ImGui::BeginTabBar("MyTabBar", kDungeonTabBarFlags)) {
|
|
||||||
// TODO: Manage the room that is being added to the tab bar.
|
|
||||||
if (ImGui::TabItemButton("+", kDungeonTabFlags)) {
|
|
||||||
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 (ImGui::BeginTabItem(
|
|
||||||
zelda3::dungeon::kRoomNames[active_rooms_[n]].data(), &open,
|
|
||||||
ImGuiTabItemFlags_None)) {
|
|
||||||
DrawDungeonCanvas(active_rooms_[n]);
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!open)
|
|
||||||
active_rooms_.erase(active_rooms_.Data + n);
|
|
||||||
else
|
|
||||||
n++;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTabBar();
|
|
||||||
}
|
|
||||||
ImGui::Separator();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
|
||||||
ImGui::BeginGroup();
|
|
||||||
|
|
||||||
gui::InputHexByte("Layout", &rooms_[room_id].layout);
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
gui::InputHexByte("Blockset", &rooms_[room_id].blockset);
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
gui::InputHexByte("Spriteset", &rooms_[room_id].spriteset);
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
gui::InputHexByte("Palette", &rooms_[room_id].palette);
|
|
||||||
|
|
||||||
gui::InputHexByte("Floor1", &rooms_[room_id].floor1);
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
gui::InputHexByte("Floor2", &rooms_[room_id].floor2);
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
gui::InputHexWord("Message ID", &rooms_[room_id].message_id_);
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
ImGui::EndGroup();
|
|
||||||
|
|
||||||
canvas_.DrawBackground();
|
|
||||||
canvas_.DrawContextMenu();
|
|
||||||
canvas_.DrawGrid();
|
|
||||||
canvas_.DrawOverlay();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DungeonEditor::DrawRoomGraphics() {
|
|
||||||
const auto height = 0x40;
|
|
||||||
room_gfx_canvas_.DrawBackground(ImVec2(256 + 1, 0x10 * 0x40 + 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_.GetDrawList()->AddImage(
|
|
||||||
(void*)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 (ImGui::BeginTabBar("##TabBar", ImGuiTabBarFlags_FittingPolicyScroll)) {
|
|
||||||
if (ImGui::BeginTabItem("Room Graphics")) {
|
|
||||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)3);
|
|
||||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
|
||||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
|
||||||
DrawRoomGraphics();
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginTabItem("Object Renderer")) {
|
|
||||||
DrawObjectRenderer();
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
ImGui::EndTabBar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DungeonEditor::DrawObjectRenderer() {
|
|
||||||
if (ImGui::BeginTable("DungeonObjectEditorTable", 2, kDungeonObjectTableFlags,
|
|
||||||
ImVec2(0, 0))) {
|
|
||||||
TableSetupColumn("Dungeon Objects", ImGuiTableColumnFlags_WidthStretch,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
TableSetupColumn("Canvas");
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::BeginChild("DungeonObjectButtons", ImVec2(250, 0), true);
|
|
||||||
|
|
||||||
int selected_object = 0;
|
|
||||||
int i = 0;
|
|
||||||
for (const auto object_name : zelda3::dungeon::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());
|
|
||||||
rom()->RenderBitmap(object_renderer_.bitmap());
|
|
||||||
object_loaded_ = true;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
|
|
||||||
// Right side of the table - Canvas
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::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();
|
|
||||||
|
|
||||||
ImGui::EndChild();
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (object_loaded_) {
|
|
||||||
// ImGui::Begin("Memory Viewer", &object_loaded_, 0);
|
|
||||||
// auto memory = object_renderer_.memory();
|
|
||||||
// static MemoryEditor mem_edit;
|
|
||||||
// mem_edit.DrawContents((void*)object_renderer_.memory_ptr(),
|
|
||||||
// memory.size());
|
|
||||||
// ImGui::End();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
|
||||||
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include "app/core/common.h"
|
|
||||||
#include "app/core/editor.h"
|
|
||||||
#include "app/editor/modules/palette_editor.h"
|
|
||||||
#include "app/gui/canvas.h"
|
|
||||||
#include "app/gui/icons.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "zelda3/dungeon/room.h"
|
|
||||||
#include "zelda3/dungeon/room_object.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
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;
|
|
||||||
|
|
||||||
class DungeonEditor : public Editor,
|
|
||||||
public SharedROM,
|
|
||||||
public core::ExperimentFlags {
|
|
||||||
public:
|
|
||||||
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(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void DrawToolset();
|
|
||||||
void DrawRoomSelector();
|
|
||||||
|
|
||||||
void DrawDungeonTabView();
|
|
||||||
void DrawDungeonCanvas(int room_id);
|
|
||||||
|
|
||||||
void DrawRoomGraphics();
|
|
||||||
void DrawTileSelector();
|
|
||||||
void DrawObjectRenderer();
|
|
||||||
|
|
||||||
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;
|
|
||||||
bool show_object_render_ = false;
|
|
||||||
|
|
||||||
uint16_t current_room_id_ = 0;
|
|
||||||
uint64_t current_palette_id_ = 0;
|
|
||||||
uint64_t current_palette_group_id_ = 0;
|
|
||||||
|
|
||||||
ImVector<int> active_rooms_;
|
|
||||||
|
|
||||||
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_;
|
|
||||||
gfx::BitmapTable graphics_bin_;
|
|
||||||
|
|
||||||
std::vector<gfx::Bitmap*> room_gfx_sheets_;
|
|
||||||
std::vector<zelda3::dungeon::Room> rooms_;
|
|
||||||
std::vector<gfx::BitmapManager> room_graphics_;
|
|
||||||
zelda3::dungeon::DungeonObjectRenderer object_renderer_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
311
src/app/editor/graphics/gfx_group_editor.cc
Normal file
311
src/app/editor/graphics/gfx_group_editor.cc
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
#include "gfx_group_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/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/gui/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginGroup;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::ColorButton;
|
||||||
|
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::Selectable;
|
||||||
|
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()->bitmap_manager()[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()->bitmap_manager()[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()->bitmap_manager()[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 (int 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 app
|
||||||
|
} // namespace yaze
|
||||||
@@ -1,33 +1,48 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
#ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||||
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "absl/status/statusor.h"
|
#include "absl/status/statusor.h"
|
||||||
#include "app/core/editor.h"
|
#include "app/editor/utils/editor.h"
|
||||||
#include "app/gui/pipeline.h"
|
|
||||||
#include "app/editor/modules/palette_editor.h"
|
|
||||||
#include "app/gfx/bitmap.h"
|
#include "app/gfx/bitmap.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "app/gfx/snes_tile.h"
|
#include "app/gfx/snes_tile.h"
|
||||||
#include "app/gui/canvas.h"
|
#include "app/gui/canvas.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "app/gui/widgets.h"
|
#include "app/gui/style.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "app/zelda3/overworld.h"
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace editor {
|
namespace editor {
|
||||||
|
|
||||||
class GfxGroupEditor : public SharedROM {
|
/**
|
||||||
|
* @class GfxGroupEditor
|
||||||
|
* @brief Manage graphics group configurations in a Rom.
|
||||||
|
*/
|
||||||
|
class GfxGroupEditor : public SharedRom {
|
||||||
public:
|
public:
|
||||||
absl::Status Update();
|
absl::Status Update();
|
||||||
|
|
||||||
void InitBlockset(gfx::Bitmap tile16_blockset);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitBlockset(gfx::Bitmap* tile16_blockset) {
|
||||||
|
tile16_blockset_bmp_ = tile16_blockset;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int preview_palette_id_ = 0;
|
int preview_palette_id_ = 0;
|
||||||
@@ -35,20 +50,21 @@ class GfxGroupEditor : public SharedROM {
|
|||||||
uint8_t selected_blockset_ = 0;
|
uint8_t selected_blockset_ = 0;
|
||||||
uint8_t selected_roomset_ = 0;
|
uint8_t selected_roomset_ = 0;
|
||||||
uint8_t selected_spriteset_ = 0;
|
uint8_t selected_spriteset_ = 0;
|
||||||
|
uint8_t selected_paletteset_ = 0;
|
||||||
|
|
||||||
gui::Canvas blockset_canvas_;
|
gui::Canvas blockset_canvas_;
|
||||||
gui::Canvas roomset_canvas_;
|
gui::Canvas roomset_canvas_;
|
||||||
gui::Canvas spriteset_canvas_;
|
gui::Canvas spriteset_canvas_;
|
||||||
|
|
||||||
gfx::SNESPalette palette_;
|
gfx::SnesPalette palette_;
|
||||||
gfx::PaletteGroup palette_group_;
|
gfx::PaletteGroup palette_group_;
|
||||||
gfx::Bitmap tile16_blockset_bmp_;
|
gfx::Bitmap* tile16_blockset_bmp_;
|
||||||
|
|
||||||
std::vector<Bytes> tile16_individual_data_;
|
std::vector<Bytes> tile16_individual_data_;
|
||||||
std::vector<gfx::Bitmap> tile16_individual_;
|
std::vector<gfx::Bitmap> tile16_individual_;
|
||||||
|
|
||||||
gui::BitmapViewer gfx_group_viewer_;
|
gui::BitmapViewer gfx_group_viewer_;
|
||||||
zelda3::Overworld overworld_;
|
zelda3::overworld::Overworld overworld_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
@@ -1,22 +1,23 @@
|
|||||||
#include "app/editor/graphics_editor.h"
|
#include "graphics_editor.h"
|
||||||
|
|
||||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
#include "ImGuiFileDialog/ImGuiFileDialog.h"
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
#include <imgui_memory_editor.h>
|
#include "imgui_memory_editor.h"
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "absl/status/statusor.h"
|
#include "absl/status/statusor.h"
|
||||||
#include "app/core/platform/clipboard.h"
|
#include "app/core/platform/clipboard.h"
|
||||||
#include "app/editor/modules/palette_editor.h"
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
#include "app/gfx/bitmap.h"
|
#include "app/gfx/bitmap.h"
|
||||||
#include "app/gfx/compression.h"
|
#include "app/gfx/compression.h"
|
||||||
#include "app/gfx/scad_format.h"
|
#include "app/gfx/scad_format.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "app/gfx/snes_tile.h"
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/asset_browser.h"
|
||||||
#include "app/gui/canvas.h"
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/color.h"
|
||||||
#include "app/gui/input.h"
|
#include "app/gui/input.h"
|
||||||
#include "app/gui/pipeline.h"
|
|
||||||
#include "app/gui/style.h"
|
#include "app/gui/style.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
|
|
||||||
@@ -24,10 +25,17 @@ namespace yaze {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace editor {
|
namespace editor {
|
||||||
|
|
||||||
|
using gfx::kPaletteGroupAddressesKeys;
|
||||||
using ImGui::Button;
|
using ImGui::Button;
|
||||||
using ImGui::InputInt;
|
using ImGui::InputInt;
|
||||||
using ImGui::InputText;
|
using ImGui::InputText;
|
||||||
using ImGui::SameLine;
|
using ImGui::SameLine;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
|
||||||
|
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
|
||||||
|
"#memoryEditor",
|
||||||
|
"##separator_gfx1",
|
||||||
|
};
|
||||||
|
|
||||||
constexpr ImGuiTableFlags kGfxEditTableFlags =
|
constexpr ImGuiTableFlags kGfxEditTableFlags =
|
||||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
|
||||||
@@ -39,9 +47,20 @@ constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
|
|||||||
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||||
ImGuiTabBarFlags_TabListPopupButton;
|
ImGuiTabBarFlags_TabListPopupButton;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_SizingStretchSame;
|
||||||
|
|
||||||
absl::Status GraphicsEditor::Update() {
|
absl::Status GraphicsEditor::Update() {
|
||||||
TAB_BAR("##TabBar")
|
TAB_BAR("##TabBar")
|
||||||
status_ = UpdateGfxEdit();
|
status_ = UpdateGfxEdit();
|
||||||
|
TAB_ITEM("Sheet Browser")
|
||||||
|
if (asset_browser_.Initialized == false) {
|
||||||
|
asset_browser_.Initialize(rom()->mutable_bitmap_manager());
|
||||||
|
}
|
||||||
|
asset_browser_.Draw(rom()->mutable_bitmap_manager());
|
||||||
|
|
||||||
|
END_TAB_ITEM()
|
||||||
status_ = UpdateScadView();
|
status_ = UpdateScadView();
|
||||||
status_ = UpdateLinkGfxView();
|
status_ = UpdateLinkGfxView();
|
||||||
END_TAB_BAR()
|
END_TAB_BAR()
|
||||||
@@ -50,12 +69,13 @@ absl::Status GraphicsEditor::Update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
absl::Status GraphicsEditor::UpdateGfxEdit() {
|
absl::Status GraphicsEditor::UpdateGfxEdit() {
|
||||||
TAB_ITEM("Graphics Editor")
|
TAB_ITEM("Sheet Editor")
|
||||||
|
|
||||||
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
|
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
|
||||||
ImVec2(0, 0))) {
|
ImVec2(0, 0))) {
|
||||||
for (const auto& name : kGfxEditColumnNames)
|
for (const auto& name :
|
||||||
ImGui::TableSetupColumn(name.data());
|
{"Tilesheets", "Current Graphics", "Palette Controls"})
|
||||||
|
ImGui::TableSetupColumn(name);
|
||||||
|
|
||||||
ImGui::TableHeadersRow();
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
@@ -63,13 +83,13 @@ absl::Status GraphicsEditor::UpdateGfxEdit() {
|
|||||||
status_ = UpdateGfxSheetList();
|
status_ = UpdateGfxSheetList();
|
||||||
|
|
||||||
NEXT_COLUMN();
|
NEXT_COLUMN();
|
||||||
if (rom()->isLoaded()) {
|
if (rom()->is_loaded()) {
|
||||||
DrawGfxEditToolset();
|
DrawGfxEditToolset();
|
||||||
status_ = UpdateGfxTabView();
|
status_ = UpdateGfxTabView();
|
||||||
}
|
}
|
||||||
|
|
||||||
NEXT_COLUMN();
|
NEXT_COLUMN();
|
||||||
if (rom()->isLoaded()) {
|
if (rom()->is_loaded()) {
|
||||||
status_ = UpdatePaletteColumn();
|
status_ = UpdatePaletteColumn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,76 +107,77 @@ void GraphicsEditor::DrawGfxEditToolset() {
|
|||||||
"Zoom In", "Current Color", "Tile Size"})
|
"Zoom In", "Current Color", "Tile Size"})
|
||||||
ImGui::TableSetupColumn(name);
|
ImGui::TableSetupColumn(name);
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
if (Button(ICON_MD_SELECT_ALL)) {
|
if (Button(ICON_MD_SELECT_ALL)) {
|
||||||
gfx_edit_mode_ = GfxEditMode::kSelect;
|
gfx_edit_mode_ = GfxEditMode::kSelect;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
if (Button(ICON_MD_DRAW)) {
|
if (Button(ICON_MD_DRAW)) {
|
||||||
gfx_edit_mode_ = GfxEditMode::kPencil;
|
gfx_edit_mode_ = GfxEditMode::kPencil;
|
||||||
}
|
}
|
||||||
|
HOVER_HINT("Draw with current color");
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
|
if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
|
||||||
gfx_edit_mode_ = GfxEditMode::kFill;
|
gfx_edit_mode_ = GfxEditMode::kFill;
|
||||||
}
|
}
|
||||||
|
HOVER_HINT("Fill with current color");
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
if (Button(ICON_MD_CONTENT_COPY)) {
|
if (Button(ICON_MD_CONTENT_COPY)) {
|
||||||
std::vector<uint8_t> png_data =
|
std::vector<uint8_t> png_data =
|
||||||
rom()->bitmap_manager().GetBitmap(current_sheet_)->GetPngData();
|
rom()->bitmap_manager().shared_bitmap(current_sheet_).GetPngData();
|
||||||
CopyImageToClipboard(png_data);
|
CopyImageToClipboard(png_data);
|
||||||
}
|
}
|
||||||
|
HOVER_HINT("Copy to Clipboard");
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
if (Button(ICON_MD_CONTENT_PASTE)) {
|
if (Button(ICON_MD_CONTENT_PASTE)) {
|
||||||
std::vector<uint8_t> png_data;
|
std::vector<uint8_t> png_data;
|
||||||
int width, height;
|
int width, height;
|
||||||
GetImageFromClipboard(png_data, width, height);
|
GetImageFromClipboard(png_data, width, height);
|
||||||
if (png_data.size() > 0) {
|
if (png_data.size() > 0) {
|
||||||
rom()
|
rom()
|
||||||
->bitmap_manager()
|
->mutable_bitmap_manager()
|
||||||
.GetBitmap(current_sheet_)
|
->mutable_bitmap(current_sheet_)
|
||||||
->LoadFromPngData(png_data, width, height);
|
->Create(width, height, 8, png_data);
|
||||||
rom()->UpdateBitmap(rom()
|
rom()->UpdateBitmap(
|
||||||
->mutable_bitmap_manager()
|
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_));
|
||||||
->mutable_bitmap(current_sheet_)
|
|
||||||
.get());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HOVER_HINT("Paste from Clipboard");
|
HOVER_HINT("Paste from Clipboard");
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
if (Button(ICON_MD_ZOOM_OUT)) {
|
if (Button(ICON_MD_ZOOM_OUT)) {
|
||||||
if (current_scale_ >= 0.0f) {
|
if (current_scale_ >= 0.0f) {
|
||||||
current_scale_ -= 1.0f;
|
current_scale_ -= 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
if (Button(ICON_MD_ZOOM_IN)) {
|
if (Button(ICON_MD_ZOOM_IN)) {
|
||||||
if (current_scale_ <= 16.0f) {
|
if (current_scale_ <= 16.0f) {
|
||||||
current_scale_ += 1.0f;
|
current_scale_ += 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
auto bitmap = rom()->bitmap_manager()[current_sheet_];
|
auto bitmap = rom()->bitmap_manager()[current_sheet_];
|
||||||
auto palette = bitmap->palette();
|
auto palette = bitmap.palette();
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
auto color =
|
auto color =
|
||||||
ImVec4(palette[i].GetRGB().x / 255.0f, palette[i].GetRGB().y / 255.0f,
|
ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
|
||||||
palette[i].GetRGB().z / 255.0f, 255.0f);
|
palette[i].rgb().z / 255.0f, 255.0f);
|
||||||
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
|
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
|
||||||
color)) {
|
color)) {
|
||||||
current_color_ = color;
|
current_color_ = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
gui::InputHexByte("Tile Size", &tile_size_, 0x01);
|
gui::InputHexByte("Tile Size", &tile_size_);
|
||||||
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
@@ -167,53 +188,68 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
|
|||||||
"##GfxEditChild", ImVec2(0, 0), true,
|
"##GfxEditChild", ImVec2(0, 0), true,
|
||||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
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, rom()->bitmap_manager().size());
|
||||||
|
selection.ApplyRequests(ms_io);
|
||||||
|
ImGuiListClipper clipper;
|
||||||
|
clipper.Begin(rom()->bitmap_manager().size());
|
||||||
|
if (ms_io->RangeSrcItem != -1)
|
||||||
|
clipper.IncludeItemByIndex(
|
||||||
|
(int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
|
||||||
|
|
||||||
for (auto& [key, value] : rom()->bitmap_manager()) {
|
for (auto& [key, value] : rom()->bitmap_manager()) {
|
||||||
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
|
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
|
||||||
ImVec2(0x100 + 1, 0x40 + 1), true,
|
ImVec2(0x100 + 1, 0x40 + 1), true,
|
||||||
ImGuiWindowFlags_NoDecoration);
|
ImGuiWindowFlags_NoDecoration);
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
gui::Canvas graphics_bin_canvas_;
|
gui::Canvas graphics_bin_canvas_;
|
||||||
auto select_tile_event = [&]() {
|
|
||||||
if (value.get()->IsActive()) {
|
|
||||||
auto texture = value.get()->texture();
|
|
||||||
graphics_bin_canvas_.GetDrawList()->AddImage(
|
|
||||||
(void*)texture,
|
|
||||||
ImVec2(graphics_bin_canvas_.zero_point().x + 2,
|
|
||||||
graphics_bin_canvas_.zero_point().y + 2),
|
|
||||||
ImVec2(graphics_bin_canvas_.zero_point().x +
|
|
||||||
value.get()->width() * sheet_scale_,
|
|
||||||
graphics_bin_canvas_.zero_point().y +
|
|
||||||
value.get()->height() * sheet_scale_));
|
|
||||||
|
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1));
|
||||||
current_sheet_ = key;
|
graphics_bin_canvas_.DrawContextMenu();
|
||||||
open_sheets_.insert(key);
|
if (value.is_active()) {
|
||||||
}
|
auto texture = value.texture();
|
||||||
|
graphics_bin_canvas_.draw_list()->AddImage(
|
||||||
|
(void*)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_));
|
||||||
|
|
||||||
// Add a slightly transparent rectangle behind the text
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
|
current_sheet_ = key;
|
||||||
graphics_bin_canvas_.zero_point().y + 2);
|
open_sheets_.insert(key);
|
||||||
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_.GetDrawList()->AddRectFilled(
|
|
||||||
rent_min, rent_max, IM_COL32(0, 125, 0, 128));
|
|
||||||
|
|
||||||
graphics_bin_canvas_.GetDrawList()->AddText(
|
|
||||||
text_pos, IM_COL32(125, 255, 125, 255),
|
|
||||||
absl::StrFormat("%02X", key).c_str());
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
graphics_bin_canvas_.UpdateEvent(
|
// Add a slightly transparent rectangle behind the text
|
||||||
select_tile_event, ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_,
|
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
|
||||||
/*grid_size=*/16.0f);
|
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());
|
||||||
|
}
|
||||||
|
graphics_bin_canvas_.DrawGrid(16.0f);
|
||||||
|
graphics_bin_canvas_.DrawOverlay();
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
|
ms_io = ImGui::EndMultiSelect();
|
||||||
|
selection.ApplyRequests(ms_io);
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -222,8 +258,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
|
|||||||
static int next_tab_id = 0;
|
static int next_tab_id = 0;
|
||||||
|
|
||||||
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
|
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
|
||||||
if (ImGui::TabItemButton(
|
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
|
||||||
"+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
|
ImGuiTabItemFlags_NoTooltip)) {
|
||||||
open_sheets_.insert(next_tab_id++);
|
open_sheets_.insert(next_tab_id++);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,18 +285,19 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
|
|||||||
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
||||||
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
||||||
|
|
||||||
|
gfx::Bitmap& current_bitmap =
|
||||||
|
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
|
||||||
|
|
||||||
auto draw_tile_event = [&]() {
|
auto draw_tile_event = [&]() {
|
||||||
gfx::Bitmap& current_bitmap =
|
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, ¤t_bitmap,
|
||||||
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
|
|
||||||
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, current_bitmap,
|
|
||||||
current_color_);
|
current_color_);
|
||||||
rom()->UpdateBitmap(¤t_bitmap);
|
rom()->UpdateBitmap(¤t_bitmap, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto size = ImVec2(0x80, 0x20);
|
|
||||||
current_sheet_canvas_.UpdateColorPainter(
|
current_sheet_canvas_.UpdateColorPainter(
|
||||||
*rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
|
rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
|
||||||
size, tile_size_, current_scale_, 8.0f);
|
tile_size_, current_scale_);
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
@@ -289,12 +326,13 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
|
|||||||
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
|
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
|
||||||
&active, ImGuiWindowFlags_AlwaysUseWindowPadding);
|
&active, ImGuiWindowFlags_AlwaysUseWindowPadding);
|
||||||
current_sheet_ = id;
|
current_sheet_ = id;
|
||||||
|
// ImVec2(0x100, 0x40),
|
||||||
current_sheet_canvas_.UpdateColorPainter(
|
current_sheet_canvas_.UpdateColorPainter(
|
||||||
*rom()->bitmap_manager()[id], current_color_,
|
rom()->bitmap_manager()[id], current_color_,
|
||||||
[&]() {
|
[&]() {
|
||||||
|
|
||||||
},
|
},
|
||||||
ImVec2(0x100, 0x40), tile_size_, current_scale_, 8.0f);
|
tile_size_, current_scale_);
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
if (active == false) {
|
if (active == false) {
|
||||||
@@ -310,12 +348,10 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
absl::Status GraphicsEditor::UpdatePaletteColumn() {
|
absl::Status GraphicsEditor::UpdatePaletteColumn() {
|
||||||
auto palette_group = rom()->palette_group(
|
if (rom()->is_loaded()) {
|
||||||
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
|
auto palette_group = *rom()->palette_group().get_group(
|
||||||
|
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
|
||||||
auto palette = palette_group[edit_palette_index_];
|
auto palette = palette_group.palette(edit_palette_index_);
|
||||||
|
|
||||||
if (rom()->isLoaded()) {
|
|
||||||
gui::TextWithSeparators("ROM Palette");
|
gui::TextWithSeparators("ROM Palette");
|
||||||
ImGui::SetNextItemWidth(100.f);
|
ImGui::SetNextItemWidth(100.f);
|
||||||
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
|
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
|
||||||
@@ -323,36 +359,66 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
|
|||||||
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
|
||||||
ImGui::SetNextItemWidth(100.f);
|
ImGui::SetNextItemWidth(100.f);
|
||||||
gui::InputHex("Palette Group Index", &edit_palette_index_);
|
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()->bitmap_manager()[current_sheet_].ApplyPaletteWithTransparent(
|
||||||
|
palette, edit_palette_sub_index_));
|
||||||
|
rom()->UpdateBitmap(
|
||||||
|
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_),
|
||||||
|
true);
|
||||||
|
refresh_graphics_ = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
|
|
||||||
palette);
|
|
||||||
|
|
||||||
if (refresh_graphics_) {
|
|
||||||
rom()->bitmap_manager()[current_sheet_]->ApplyPaletteWithTransparent(
|
|
||||||
palette, edit_palette_sub_index_);
|
|
||||||
rom()->UpdateBitmap(
|
|
||||||
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_).get());
|
|
||||||
refresh_graphics_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::Status GraphicsEditor::UpdateLinkGfxView() {
|
absl::Status GraphicsEditor::UpdateLinkGfxView() {
|
||||||
TAB_ITEM("Player Animations")
|
TAB_ITEM("Player Animations")
|
||||||
|
|
||||||
const auto link_gfx_offset = 0x80000;
|
if (ImGui::BeginTable("##PlayerAnimationTable", 3, kGfxEditTableFlags,
|
||||||
const auto link_gfx_length = 0x7000;
|
ImVec2(0, 0))) {
|
||||||
|
for (const auto& name : {"Canvas", "Animation Steps", "Properties"})
|
||||||
|
ImGui::TableSetupColumn(name);
|
||||||
|
|
||||||
// Load Links graphics from the ROM
|
ImGui::TableHeadersRow();
|
||||||
RETURN_IF_ERROR(rom()->LoadLinkGraphics());
|
|
||||||
|
|
||||||
// Split it into the pose data frames
|
NEXT_COLUMN();
|
||||||
// Create an animation step display for the poses
|
link_canvas_.DrawBackground();
|
||||||
// Allow the user to modify the frames used in an anim step
|
link_canvas_.DrawGrid(16.0f);
|
||||||
// LinkOAM_AnimationSteps:
|
|
||||||
// #_0D85FB
|
int i = 0;
|
||||||
|
for (auto [key, link_sheet] : *rom()->mutable_link_graphics()) {
|
||||||
|
int x_offset = 0;
|
||||||
|
int y_offset = core::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()
|
END_TAB_ITEM()
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
@@ -394,7 +460,7 @@ absl::Status GraphicsEditor::UpdateScadView() {
|
|||||||
if (super_donkey_) {
|
if (super_donkey_) {
|
||||||
if (refresh_graphics_) {
|
if (refresh_graphics_) {
|
||||||
for (int i = 0; i < graphics_bin_.size(); i++) {
|
for (int i = 0; i < graphics_bin_.size(); i++) {
|
||||||
graphics_bin_[i].ApplyPalette(
|
status_ = graphics_bin_[i].ApplyPalette(
|
||||||
col_file_palette_group_[current_palette_index_]);
|
col_file_palette_group_[current_palette_index_]);
|
||||||
rom()->UpdateBitmap(&graphics_bin_[i]);
|
rom()->UpdateBitmap(&graphics_bin_[i]);
|
||||||
}
|
}
|
||||||
@@ -424,7 +490,7 @@ absl::Status GraphicsEditor::DrawToolset() {
|
|||||||
for (const auto& name : kGfxToolsetColumnNames)
|
for (const auto& name : kGfxToolsetColumnNames)
|
||||||
ImGui::TableSetupColumn(name.data());
|
ImGui::TableSetupColumn(name.data());
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
TableNextColumn();
|
||||||
if (Button(ICON_MD_MEMORY)) {
|
if (Button(ICON_MD_MEMORY)) {
|
||||||
if (!open_memory_editor_) {
|
if (!open_memory_editor_) {
|
||||||
open_memory_editor_ = true;
|
open_memory_editor_ = true;
|
||||||
@@ -457,19 +523,21 @@ absl::Status GraphicsEditor::DrawCgxImport() {
|
|||||||
is_open_ = true;
|
is_open_ = true;
|
||||||
cgx_loaded_ = true;
|
cgx_loaded_ = true;
|
||||||
});
|
});
|
||||||
gui::ButtonPipe("Copy CGX Path",
|
|
||||||
[this]() { ImGui::SetClipboardText(cgx_file_path_); });
|
|
||||||
|
|
||||||
gui::ButtonPipe("Load CGX Data", [this]() {
|
if (ImGui::Button("Copy CGX Path")) {
|
||||||
status_ = gfx::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
|
ImGui::SetClipboardText(cgx_file_path_);
|
||||||
decoded_cgx_, extra_cgx_data_);
|
}
|
||||||
|
|
||||||
cgx_bitmap_.InitializeFromData(0x80, 0x200, 8, decoded_cgx_);
|
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_) {
|
if (col_file_) {
|
||||||
cgx_bitmap_.ApplyPalette(decoded_col_);
|
cgx_bitmap_.ApplyPalette(decoded_col_);
|
||||||
rom()->RenderBitmap(&cgx_bitmap_);
|
rom()->RenderBitmap(&cgx_bitmap_);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -491,19 +559,20 @@ absl::Status GraphicsEditor::DrawScrImport() {
|
|||||||
|
|
||||||
InputInt("SCR Mod", &scr_mod_value_);
|
InputInt("SCR Mod", &scr_mod_value_);
|
||||||
|
|
||||||
gui::ButtonPipe("Load Scr Data", [this]() {
|
if (ImGui::Button("Load Scr Data")) {
|
||||||
status_ = gfx::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
|
status_ =
|
||||||
|
gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
|
||||||
|
|
||||||
decoded_scr_data_.resize(0x100 * 0x100);
|
decoded_scr_data_.resize(0x100 * 0x100);
|
||||||
status_ = gfx::DrawScrWithCgx(current_bpp_, scr_data_, decoded_scr_data_,
|
status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_,
|
||||||
decoded_cgx_);
|
decoded_scr_data_, decoded_cgx_);
|
||||||
|
|
||||||
scr_bitmap_.InitializeFromData(0x100, 0x100, 8, decoded_scr_data_);
|
scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_);
|
||||||
if (scr_loaded_) {
|
if (scr_loaded_) {
|
||||||
scr_bitmap_.ApplyPalette(decoded_col_);
|
scr_bitmap_.ApplyPalette(decoded_col_);
|
||||||
rom()->RenderBitmap(&scr_bitmap_);
|
rom()->RenderBitmap(&scr_bitmap_);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -527,19 +596,24 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
|
|||||||
if (col_file_palette_group_.size() != 0) {
|
if (col_file_palette_group_.size() != 0) {
|
||||||
col_file_palette_group_.Clear();
|
col_file_palette_group_.Clear();
|
||||||
}
|
}
|
||||||
col_file_palette_group_ = gfx::CreatePaletteGroupFromColFile(col_data_);
|
auto col_file_palette_group_status =
|
||||||
col_file_palette_ = gfx::SNESPalette(col_data_);
|
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
|
// gigaleak dev format based code
|
||||||
decoded_col_ = gfx::DecodeColFile(col_file_path_);
|
decoded_col_ = gfx::scad_format::DecodeColFile(col_file_path_);
|
||||||
col_file_ = true;
|
col_file_ = true;
|
||||||
is_open_ = true;
|
is_open_ = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
gui::ButtonPipe("Copy COL Path",
|
if (ImGui::Button("Copy Col Path")) {
|
||||||
[this]() { ImGui::SetClipboardText(col_file_path_); });
|
ImGui::SetClipboardText(col_file_path_);
|
||||||
|
}
|
||||||
|
|
||||||
if (rom()->isLoaded()) {
|
if (rom()->is_loaded()) {
|
||||||
gui::TextWithSeparators("ROM Palette");
|
gui::TextWithSeparators("ROM Palette");
|
||||||
gui::InputHex("Palette Index", ¤t_palette_index_);
|
gui::InputHex("Palette Index", ¤t_palette_index_);
|
||||||
ImGui::Combo("Palette", ¤t_palette_, kPaletteGroupAddressesKeys,
|
ImGui::Combo("Palette", ¤t_palette_, kPaletteGroupAddressesKeys,
|
||||||
@@ -609,8 +683,9 @@ absl::Status GraphicsEditor::DrawFileImport() {
|
|||||||
is_open_ = true;
|
is_open_ = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
gui::ButtonPipe("Copy File Path",
|
if (Button("Copy File Path")) {
|
||||||
[this]() { ImGui::SetClipboardText(file_path_); });
|
ImGui::SetClipboardText(file_path_);
|
||||||
|
}
|
||||||
|
|
||||||
gui::InputHex("BIN Offset", ¤t_offset_);
|
gui::InputHex("BIN Offset", ¤t_offset_);
|
||||||
gui::InputHex("BIN Size", &bin_size_);
|
gui::InputHex("BIN Size", &bin_size_);
|
||||||
@@ -629,7 +704,7 @@ absl::Status GraphicsEditor::DrawFileImport() {
|
|||||||
|
|
||||||
absl::Status GraphicsEditor::DrawClipboardImport() {
|
absl::Status GraphicsEditor::DrawClipboardImport() {
|
||||||
gui::TextWithSeparators("Clipboard Import");
|
gui::TextWithSeparators("Clipboard Import");
|
||||||
gui::ButtonPipe("Paste from Clipboard", [this]() {
|
if (Button("Paste From Clipboard")) {
|
||||||
const char* text = ImGui::GetClipboardText();
|
const char* text = ImGui::GetClipboardText();
|
||||||
if (text) {
|
if (text) {
|
||||||
const auto clipboard_data = Bytes(text, text + strlen(text));
|
const auto clipboard_data = Bytes(text, text + strlen(text));
|
||||||
@@ -638,20 +713,20 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
|
|||||||
is_open_ = true;
|
is_open_ = true;
|
||||||
open_memory_editor_ = true;
|
open_memory_editor_ = true;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
gui::InputHex("Offset", &clipboard_offset_);
|
gui::InputHex("Offset", &clipboard_offset_);
|
||||||
gui::InputHex("Size", &clipboard_size_);
|
gui::InputHex("Size", &clipboard_size_);
|
||||||
gui::InputHex("Num Sheets", &num_sheets_to_load_);
|
gui::InputHex("Num Sheets", &num_sheets_to_load_);
|
||||||
|
|
||||||
gui::ButtonPipe("Decompress Clipboard Data", [this]() {
|
if (Button("Decompress Clipboard Data")) {
|
||||||
if (temp_rom_.isLoaded()) {
|
if (temp_rom_.is_loaded()) {
|
||||||
status_ = DecompressImportData(0x40000);
|
status_ = DecompressImportData(0x40000);
|
||||||
} else {
|
} else {
|
||||||
status_ = absl::InvalidArgumentError(
|
status_ = absl::InvalidArgumentError(
|
||||||
"Please paste data into the clipboard before "
|
"Please paste data into the clipboard before "
|
||||||
"decompressing.");
|
"decompressing.");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -690,13 +765,13 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
|
|||||||
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth,
|
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth,
|
||||||
converted_sheet);
|
converted_sheet);
|
||||||
|
|
||||||
if (rom()->isLoaded()) {
|
if (rom()->is_loaded()) {
|
||||||
auto palette_group = rom()->palette_group("ow_main");
|
auto palette_group = rom()->palette_group().overworld_animated;
|
||||||
z3_rom_palette_ = palette_group[current_palette_];
|
z3_rom_palette_ = palette_group[current_palette_];
|
||||||
if (col_file_) {
|
if (col_file_) {
|
||||||
bin_bitmap_.ApplyPalette(col_file_palette_);
|
status_ = bin_bitmap_.ApplyPalette(col_file_palette_);
|
||||||
} else {
|
} else {
|
||||||
bin_bitmap_.ApplyPalette(z3_rom_palette_);
|
status_ = bin_bitmap_.ApplyPalette(z3_rom_palette_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,14 +794,15 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
|
|||||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||||
core::kTilesheetDepth, converted_sheet);
|
core::kTilesheetDepth, converted_sheet);
|
||||||
if (col_file_) {
|
if (col_file_) {
|
||||||
graphics_bin_[i].ApplyPalette(
|
status_ = graphics_bin_[i].ApplyPalette(
|
||||||
col_file_palette_group_[current_palette_index_]);
|
col_file_palette_group_[current_palette_index_]);
|
||||||
} else {
|
} else {
|
||||||
// ROM palette
|
// ROM palette
|
||||||
auto palette_group =
|
|
||||||
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]);
|
auto palette_group = rom()->palette_group().get_group(
|
||||||
z3_rom_palette_ = palette_group[current_palette_index_];
|
kPaletteGroupAddressesKeys[current_palette_]);
|
||||||
graphics_bin_[i].ApplyPalette(z3_rom_palette_);
|
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
|
||||||
|
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
|
||||||
}
|
}
|
||||||
|
|
||||||
rom()->RenderBitmap(&graphics_bin_[i]);
|
rom()->RenderBitmap(&graphics_bin_[i]);
|
||||||
@@ -744,14 +820,14 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
|
|||||||
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
|
||||||
core::kTilesheetDepth, converted_sheet);
|
core::kTilesheetDepth, converted_sheet);
|
||||||
if (col_file_) {
|
if (col_file_) {
|
||||||
graphics_bin_[i].ApplyPalette(
|
status_ = graphics_bin_[i].ApplyPalette(
|
||||||
col_file_palette_group_[current_palette_index_]);
|
col_file_palette_group_[current_palette_index_]);
|
||||||
} else {
|
} else {
|
||||||
// ROM palette
|
// ROM palette
|
||||||
auto palette_group =
|
auto palette_group = rom()->palette_group().get_group(
|
||||||
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]);
|
kPaletteGroupAddressesKeys[current_palette_]);
|
||||||
z3_rom_palette_ = palette_group[current_palette_index_];
|
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
|
||||||
graphics_bin_[i].ApplyPalette(z3_rom_palette_);
|
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
|
||||||
}
|
}
|
||||||
|
|
||||||
rom()->RenderBitmap(&graphics_bin_[i]);
|
rom()->RenderBitmap(&graphics_bin_[i]);
|
||||||
@@ -765,4 +841,4 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
|
|||||||
|
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||||
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||||
|
|
||||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
#include "ImGuiFileDialog/ImGuiFileDialog.h"
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
#include <imgui_memory_editor.h>
|
#include "imgui_memory_editor.h"
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "absl/status/statusor.h"
|
#include "absl/status/statusor.h"
|
||||||
#include "app/editor/modules/palette_editor.h"
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
#include "app/gfx/bitmap.h"
|
#include "app/gfx/bitmap.h"
|
||||||
#include "app/gfx/snes_tile.h"
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gui/asset_browser.h"
|
||||||
#include "app/gui/canvas.h"
|
#include "app/gui/canvas.h"
|
||||||
#include "app/gui/input.h"
|
#include "app/gui/input.h"
|
||||||
#include "app/gui/pipeline.h"
|
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "app/zelda3/overworld.h"
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
@@ -43,28 +44,32 @@ const std::string kSuperDonkeySprites[] = {
|
|||||||
"BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D",
|
"BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D",
|
||||||
"C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"};
|
"C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"};
|
||||||
|
|
||||||
constexpr const char* kPaletteGroupAddressesKeys[] = {
|
/**
|
||||||
"ow_main", "ow_aux", "ow_animated", "hud",
|
* @class GraphicsEditor
|
||||||
"global_sprites", "armors", "swords", "shields",
|
* @brief Allows the user to edit graphics sheets from the game or view
|
||||||
"sprites_aux1", "sprites_aux2", "sprites_aux3", "dungeon_main",
|
* prototype graphics.
|
||||||
"grass", "3d_object", "ow_mini_map",
|
*
|
||||||
};
|
* The GraphicsEditor class is responsible for providing functionality to edit
|
||||||
|
* graphics sheets from the game or view prototype graphics of Link to the Past
|
||||||
static constexpr std::string_view kGfxEditColumnNames[] = {
|
* from the CGX, SCR, and OBJ formats. It provides various methods to update
|
||||||
"Tilesheets", "Current Graphics", "Palette Controls"};
|
* different components of the graphics editor, such as the graphics edit tab,
|
||||||
|
* link graphics view, and prototype graphics viewer. It also includes import
|
||||||
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
|
* functions for different file formats, as well as other utility functions for
|
||||||
"#memoryEditor",
|
* drawing toolsets, palette controls, clipboard imports, experimental features,
|
||||||
"##separator_gfx1",
|
* and memory editor.
|
||||||
};
|
*/
|
||||||
|
class GraphicsEditor : public SharedRom, public Editor {
|
||||||
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
|
|
||||||
ImGuiTableFlags_Resizable |
|
|
||||||
ImGuiTableFlags_SizingStretchSame;
|
|
||||||
|
|
||||||
class GraphicsEditor : public SharedROM {
|
|
||||||
public:
|
public:
|
||||||
absl::Status Update();
|
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:
|
private:
|
||||||
enum class GfxEditMode {
|
enum class GfxEditMode {
|
||||||
@@ -152,11 +157,13 @@ class GraphicsEditor : public SharedROM {
|
|||||||
char tilemap_file_path_[256] = "";
|
char tilemap_file_path_[256] = "";
|
||||||
char tilemap_file_name_[256] = "";
|
char tilemap_file_name_[256] = "";
|
||||||
|
|
||||||
|
gui::GfxSheetAssetBrowser asset_browser_;
|
||||||
|
|
||||||
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
|
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
|
||||||
|
|
||||||
ROM temp_rom_;
|
Rom temp_rom_;
|
||||||
ROM tilemap_rom_;
|
Rom tilemap_rom_;
|
||||||
zelda3::Overworld overworld_;
|
zelda3::overworld::Overworld overworld_;
|
||||||
MemoryEditor cgx_memory_editor_;
|
MemoryEditor cgx_memory_editor_;
|
||||||
MemoryEditor col_memory_editor_;
|
MemoryEditor col_memory_editor_;
|
||||||
PaletteEditor palette_editor_;
|
PaletteEditor palette_editor_;
|
||||||
@@ -176,13 +183,18 @@ class GraphicsEditor : public SharedROM {
|
|||||||
gfx::BitmapTable clipboard_graphics_bin_;
|
gfx::BitmapTable clipboard_graphics_bin_;
|
||||||
gfx::BitmapTable link_graphics_;
|
gfx::BitmapTable link_graphics_;
|
||||||
gfx::PaletteGroup col_file_palette_group_;
|
gfx::PaletteGroup col_file_palette_group_;
|
||||||
gfx::SNESPalette z3_rom_palette_;
|
gfx::SnesPalette z3_rom_palette_;
|
||||||
gfx::SNESPalette col_file_palette_;
|
gfx::SnesPalette col_file_palette_;
|
||||||
gfx::SNESPalette link_palette_;
|
gfx::SnesPalette link_palette_;
|
||||||
gui::Canvas import_canvas_;
|
gui::Canvas import_canvas_;
|
||||||
gui::Canvas scr_canvas_;
|
gui::Canvas scr_canvas_;
|
||||||
gui::Canvas super_donkey_canvas_;
|
gui::Canvas super_donkey_canvas_;
|
||||||
gui::Canvas current_sheet_canvas_;
|
gui::Canvas current_sheet_canvas_{"CurrentSheetCanvas", ImVec2(0x80, 0x20),
|
||||||
|
gui::CanvasGridSize::k8x8};
|
||||||
|
gui::Canvas link_canvas_{
|
||||||
|
"LinkCanvas",
|
||||||
|
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
|
||||||
|
gui::CanvasGridSize::k16x16};
|
||||||
absl::Status status_;
|
absl::Status status_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -190,4 +202,4 @@ class GraphicsEditor : public SharedROM {
|
|||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
|
||||||
469
src/app/editor/graphics/palette_editor.cc
Normal file
469
src/app/editor/graphics/palette_editor.cc
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
#include "palette_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.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/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteEditor::DisplayPalette(gfx::SnesPalette& palette, bool loaded) {
|
||||||
|
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
||||||
|
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) {
|
||||||
|
status_ = InitializeSavedPalette(palette);
|
||||||
|
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(saved_palette_); n++) {
|
||||||
|
PushID(n);
|
||||||
|
if ((n % 8) != 0) SameLine(0.0f, GetStyle().ItemSpacing.y);
|
||||||
|
|
||||||
|
if (ColorButton("##palette", saved_palette_[n], kPalButtonFlags2,
|
||||||
|
ImVec2(20, 20)))
|
||||||
|
color = ImVec4(saved_palette_[n].x, saved_palette_[n].y,
|
||||||
|
saved_palette_[n].z, color.w); // Preserve alpha!
|
||||||
|
|
||||||
|
if (BeginDragDropTarget()) {
|
||||||
|
if (const ImGuiPayload* payload =
|
||||||
|
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
||||||
|
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
|
||||||
|
if (const ImGuiPayload* payload =
|
||||||
|
AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
||||||
|
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
|
||||||
|
EndDragDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
PopID();
|
||||||
|
}
|
||||||
|
EndGroup();
|
||||||
|
EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 app
|
||||||
|
} // namespace yaze
|
||||||
140
src/app/editor/graphics/palette_editor.h
Normal file
140
src/app/editor/graphics/palette_editor.h
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/graphics/gfx_group_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
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:
|
||||||
|
// Record a change in the palette editor
|
||||||
|
void RecordChange(const std::string& groupName, size_t paletteIndex,
|
||||||
|
size_t colorIndex, const gfx::SnesColor& originalColor,
|
||||||
|
const gfx::SnesColor& newColor) {
|
||||||
|
// Check size and remove the oldest if necessary
|
||||||
|
if (recentChanges.size() >= maxHistorySize) {
|
||||||
|
recentChanges.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the new change
|
||||||
|
recentChanges.push_back(
|
||||||
|
{groupName, paletteIndex, colorIndex, originalColor, newColor});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get recent changes for display in the palette editor
|
||||||
|
const std::deque<PaletteChange>& GetRecentChanges() const {
|
||||||
|
return recentChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the original color
|
||||||
|
gfx::SnesColor RestoreOriginalColor(const std::string& groupName,
|
||||||
|
size_t paletteIndex,
|
||||||
|
size_t colorIndex) const {
|
||||||
|
for (const auto& change : recentChanges) {
|
||||||
|
if (change.group_name == groupName &&
|
||||||
|
change.palette_index == paletteIndex &&
|
||||||
|
change.color_index == colorIndex) {
|
||||||
|
return change.original_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle error or return default (this is just an example,
|
||||||
|
// handle as appropriate for your application)
|
||||||
|
return gfx::SnesColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size() const { return recentChanges.size(); }
|
||||||
|
|
||||||
|
gfx::SnesColor& GetModifiedColor(size_t index) {
|
||||||
|
return recentChanges[index].new_color;
|
||||||
|
}
|
||||||
|
gfx::SnesColor& GetOriginalColor(size_t index) {
|
||||||
|
return recentChanges[index].original_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::deque<PaletteChange> recentChanges;
|
||||||
|
static const size_t maxHistorySize = 50; // or any other number you deem fit
|
||||||
|
};
|
||||||
|
} // namespace palette_internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
void DisplayPalette(gfx::SnesPalette& palette, bool loaded);
|
||||||
|
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 InitializeSavedPalette(const gfx::SnesPalette& palette) {
|
||||||
|
for (int n = 0; n < palette.size(); n++) {
|
||||||
|
ASSIGN_OR_RETURN(auto color, palette.GetColor(n));
|
||||||
|
saved_palette_[n].x = color.rgb().x / 255;
|
||||||
|
saved_palette_[n].y = color.rgb().y / 255;
|
||||||
|
saved_palette_[n].z = color.rgb().z / 255;
|
||||||
|
saved_palette_[n].w = 255; // Alpha
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
437
src/app/editor/graphics/screen_editor.cc
Normal file
437
src/app/editor/graphics/screen_editor.cc
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
#include "screen_editor.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/core/constants.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/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/zelda3/dungeon/room.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::Update() {
|
||||||
|
TAB_BAR("##TabBar")
|
||||||
|
TAB_ITEM("Dungeon Maps")
|
||||||
|
if (rom()->is_loaded()) {
|
||||||
|
DrawDungeonMapsEditor();
|
||||||
|
}
|
||||||
|
END_TAB_ITEM()
|
||||||
|
DrawInventoryMenuEditor();
|
||||||
|
DrawOverworldMapEditor();
|
||||||
|
DrawTitleScreenEditor();
|
||||||
|
DrawNamingScreenEditor();
|
||||||
|
END_TAB_BAR()
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ptrGFX,
|
||||||
|
rom()->ReadWord(zelda3::screen::kDungeonMapRoomsPtr + (d * 2)));
|
||||||
|
ptr |= 0x0A0000; // Add bank to the short ptr
|
||||||
|
ptrGFX |= 0x0A0000; // Add bank to the short ptr
|
||||||
|
int pcPtr = core::SnesToPc(ptr); // Contains data for the next 25 rooms
|
||||||
|
int pcPtrGFX =
|
||||||
|
core::SnesToPc(ptrGFX); // Contains data for the next 25 rooms
|
||||||
|
|
||||||
|
ASSIGN_OR_RETURN(
|
||||||
|
ushort bossRoomD,
|
||||||
|
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++) {
|
||||||
|
// rdata[j] = 0x0F;
|
||||||
|
gdata[j] = 0xFF;
|
||||||
|
rdata[j] = rom()->data()[pcPtr + j + (i * 25)]; // Set the rooms
|
||||||
|
|
||||||
|
if (rdata[j] == 0x0F) {
|
||||||
|
gdata[j] = 0xFF;
|
||||||
|
} else {
|
||||||
|
gdata[j] = rom()->data()[pcPtrGFX++];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label = core::UppercaseHexByte(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(bossRoomD, 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 ptrGFX = zelda3::screen::kDungeonMapGfxPtr + (d * 2);
|
||||||
|
int pcPtr = core::SnesToPc(ptr);
|
||||||
|
int pcPtrGFX = core::SnesToPc(ptrGFX);
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
// rom()->data()[pcPtr + j + (i * 25)] =
|
||||||
|
// dungeon_maps_[d].floor_rooms[i][j];
|
||||||
|
// rom()->data()[pcPtrGFX++] = dungeon_maps_[d].floor_gfx[i][j];
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(rom()->WriteByte(ptr + j + (i * 25),
|
||||||
|
dungeon_maps_[d].floor_rooms[i][j]));
|
||||||
|
RETURN_IF_ERROR(rom()->WriteByte(ptrGFX + j + (i * 25),
|
||||||
|
dungeon_maps_[d].floor_gfx[i][j]));
|
||||||
|
pcPtrGFX++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status ScreenEditor::LoadDungeonMapTile16() {
|
||||||
|
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
|
||||||
|
|
||||||
|
tile16_sheet_.ComposeTile16(rom()->graphics_buffer(), t1, t2, t3, t4);
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(tile16_sheet_.mutable_bitmap()->ApplyPalette(
|
||||||
|
*rom()->mutable_dungeon_palette(3)));
|
||||||
|
rom()->RenderBitmap(&*tile16_sheet_.mutable_bitmap().get());
|
||||||
|
|
||||||
|
for (int i = 0; i < tile16_sheet_.num_tiles(); ++i) {
|
||||||
|
if (tile16_individual_.count(i) == 0) {
|
||||||
|
auto tile = tile16_sheet_.GetTile16(i);
|
||||||
|
tile16_individual_[i] = tile;
|
||||||
|
rom()->RenderBitmap(&tile16_individual_[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
std::string tab_name = absl::StrFormat("Floor %d", i + 1);
|
||||||
|
if (i >= current_dungeon.nbr_of_floor) {
|
||||||
|
tab_name = absl::StrFormat("Basement %d",
|
||||||
|
i - current_dungeon.nbr_of_floor + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem(tab_name.c_str())) {
|
||||||
|
floor_number = i;
|
||||||
|
// screen_canvas_.LoadCustomLabels(dungeon_map_labels_[selected_dungeon]);
|
||||||
|
// screen_canvas_.set_current_labels(floor_number);
|
||||||
|
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_rooms[floor_number][j];
|
||||||
|
int tile_x = (tile16_id % 16) * 16;
|
||||||
|
int tile_y = (tile16_id / 16) * 16;
|
||||||
|
int posX = ((j % 5) * 32);
|
||||||
|
int posY = ((j / 5) * 32);
|
||||||
|
|
||||||
|
if (tile16_individual_.count(tile16_id) == 0) {
|
||||||
|
auto tile = tile16_sheet_.GetTile16(tile16_id);
|
||||||
|
std::cout << "Tile16: " << tile16_id << std::endl;
|
||||||
|
rom()->RenderBitmap(&tile);
|
||||||
|
tile16_individual_[tile16_id] = tile;
|
||||||
|
}
|
||||||
|
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, core::kRedPen);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string label =
|
||||||
|
dungeon_map_labels_[selected_dungeon][floor_number][j];
|
||||||
|
screen_canvas_.DrawText(label, (posX * 2), (posY * 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (ImGui::Button("Copy Floor", ImVec2(100, 0))) {
|
||||||
|
copy_button_pressed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Paste Floor", ImVec2(100, 0))) {
|
||||||
|
paste_button_pressed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawDungeonMapsEditor() {
|
||||||
|
if (!dungeon_maps_loaded_) {
|
||||||
|
if (LoadDungeonMaps().ok()) {
|
||||||
|
if (LoadDungeonMapTile16().ok()) {
|
||||||
|
auto bitmap_manager = rom()->mutable_bitmap_manager();
|
||||||
|
sheets_.emplace(0, *bitmap_manager->mutable_bitmap(212));
|
||||||
|
sheets_.emplace(1, *bitmap_manager->mutable_bitmap(213));
|
||||||
|
sheets_.emplace(2, *bitmap_manager->mutable_bitmap(214));
|
||||||
|
sheets_.emplace(3, *bitmap_manager->mutable_bitmap(215));
|
||||||
|
dungeon_maps_loaded_ = true;
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Failed to load dungeon map tile16");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Failed to load dungeon maps");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
tilemap_canvas_.DrawBackground(ImVec2(128 * 2 + 2, (192 * 2) + 4));
|
||||||
|
tilemap_canvas_.DrawContextMenu();
|
||||||
|
tilemap_canvas_.DrawBitmapTable(sheets_);
|
||||||
|
tilemap_canvas_.DrawGrid();
|
||||||
|
tilemap_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenEditor::DrawTitleScreenEditor() {
|
||||||
|
TAB_ITEM("Title Screen")
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
void ScreenEditor::DrawNamingScreenEditor() {
|
||||||
|
TAB_ITEM("Naming Screen")
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
void ScreenEditor::DrawOverworldMapEditor() {
|
||||||
|
TAB_ITEM("Overworld Map")
|
||||||
|
END_TAB_ITEM()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 app
|
||||||
|
} // namespace yaze
|
||||||
106
src/app/editor/graphics/screen_editor.h
Normal file
106
src/app/editor/graphics/screen_editor.h
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/core/constants.h"
|
||||||
|
#include "app/editor/utils/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/color.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/screen/dungeon_map.h"
|
||||||
|
#include "app/zelda3/screen/inventory.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
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();
|
||||||
|
void DrawDungeonMapsTabs();
|
||||||
|
void DrawDungeonMapsEditor();
|
||||||
|
|
||||||
|
std::vector<zelda3::screen::DungeonMap> dungeon_maps_;
|
||||||
|
std::vector<std::vector<std::array<std::string, 25>>> dungeon_map_labels_;
|
||||||
|
|
||||||
|
std::unordered_map<int, gfx::Bitmap> tile16_individual_;
|
||||||
|
|
||||||
|
bool dungeon_maps_loaded_ = false;
|
||||||
|
|
||||||
|
int selected_tile16_ = 0;
|
||||||
|
int selected_dungeon = 0;
|
||||||
|
uint8_t selected_room = 0;
|
||||||
|
uint8_t boss_room = 0;
|
||||||
|
int floor_number = 1;
|
||||||
|
|
||||||
|
bool copy_button_pressed = false;
|
||||||
|
bool paste_button_pressed = false;
|
||||||
|
|
||||||
|
Bytes all_gfx_;
|
||||||
|
zelda3::screen::Inventory inventory_;
|
||||||
|
gfx::SnesPalette palette_;
|
||||||
|
gui::Canvas screen_canvas_;
|
||||||
|
gui::Canvas tilesheet_canvas_;
|
||||||
|
gui::Canvas tilemap_canvas_;
|
||||||
|
|
||||||
|
gfx::BitmapTable sheets_;
|
||||||
|
|
||||||
|
gfx::Tilesheet tile16_sheet_;
|
||||||
|
|
||||||
|
absl::Status status_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif
|
||||||
400
src/app/editor/graphics/tile16_editor.cc
Normal file
400
src/app/editor/graphics/tile16_editor.cc
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
#include "tile16_editor.h"
|
||||||
|
|
||||||
|
#include "ImGuiFileDialog/ImGuiFileDialog.h"
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/status/statusor.h"
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/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"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
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(
|
||||||
|
gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp,
|
||||||
|
const std::vector<gfx::Bitmap>& tile16_individual,
|
||||||
|
uint8_t all_tiles_types[0x200]) {
|
||||||
|
all_tiles_types_ = all_tiles_types;
|
||||||
|
tile16_blockset_bmp_ = tile16_blockset_bmp;
|
||||||
|
tile16_individual_ = tile16_individual;
|
||||||
|
current_gfx_bmp_ = current_gfx_bmp;
|
||||||
|
tile8_gfx_data_ = current_gfx_bmp_.vector();
|
||||||
|
RETURN_IF_ERROR(LoadTile8());
|
||||||
|
ImVector<std::string> tile16_names;
|
||||||
|
for (int i = 0; i < 0x200; ++i) {
|
||||||
|
std::string str = core::UppercaseHexByte(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")) {
|
||||||
|
RETURN_IF_ERROR(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();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status 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();
|
||||||
|
RETURN_IF_ERROR(UpdateBlockset());
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
RETURN_IF_ERROR(UpdateTile16Edit());
|
||||||
|
RETURN_IF_ERROR(DrawTileEditControls());
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_]));
|
||||||
|
rom()->RenderBitmap(current_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) / 4) * 0x40;
|
||||||
|
start_position.y = ((tile_index_y) / 4) * 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_));
|
||||||
|
rom()->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_));
|
||||||
|
rom()->UpdateBitmap(¤t_gfx_individual_[current_tile8_]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginChild("Tile16 Editor Options",
|
||||||
|
ImVec2(GetContentRegionAvail().x, 0x50), true)) {
|
||||||
|
tile16_edit_canvas_.DrawBackground();
|
||||||
|
tile16_edit_canvas_.DrawContextMenu(current_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()));
|
||||||
|
rom()->UpdateBitmap(current_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));
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
current_tile16_bmp_->ApplyPaletteWithTransparent(palette, value));
|
||||||
|
rom()->UpdateBitmap(¤t_gfx_bmp_);
|
||||||
|
rom()->UpdateBitmap(current_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 num_rows = current_gfx_bmp_.height() / 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_));
|
||||||
|
rom()->RenderBitmap(¤t_gfx_individual_[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
map_blockset_loaded_ = true;
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Tile16 Transfer
|
||||||
|
|
||||||
|
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")) {
|
||||||
|
ImGuiFileDialog::Instance()->OpenDialog(
|
||||||
|
"ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", ".");
|
||||||
|
}
|
||||||
|
gui::FileDialogPipeline(
|
||||||
|
"ChooseTransferFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
||||||
|
std::string filePathName =
|
||||||
|
ImGuiFileDialog::Instance()->GetFilePathName();
|
||||||
|
transfer_status_ = transfer_rom_.LoadFromFile(filePathName);
|
||||||
|
transfer_started_ = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Implement tile16 transfer
|
||||||
|
if (transfer_started_ && !transfer_blockset_loaded_) {
|
||||||
|
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
|
||||||
|
graphics_bin_ = transfer_rom_.graphics_bin();
|
||||||
|
|
||||||
|
// Load the Link to the Past overworld.
|
||||||
|
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
|
||||||
|
transfer_overworld_.set_current_map(0);
|
||||||
|
palette_ = transfer_overworld_.AreaPalette();
|
||||||
|
|
||||||
|
// Create the tile16 blockset image
|
||||||
|
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
|
||||||
|
0x80, 0x2000, 0x80, transfer_overworld_.Tile16Blockset(),
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_]));
|
||||||
|
rom()->RenderBitmap(current_tile16_bmp_);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
@@ -1,44 +1,57 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
|
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||||
#define YAZE_APP_EDITOR_TILE16EDITOR_H
|
#define YAZE_APP_EDITOR_TILE16EDITOR_H
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "absl/status/statusor.h"
|
#include "absl/status/statusor.h"
|
||||||
#include "app/core/editor.h"
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
#include "app/gui/pipeline.h"
|
#include "app/editor/utils/editor.h"
|
||||||
#include "app/editor/modules/palette_editor.h"
|
#include "app/editor/utils/gfx_context.h"
|
||||||
#include "app/gfx/bitmap.h"
|
#include "app/gfx/bitmap.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "app/gfx/snes_tile.h"
|
#include "app/gfx/snes_tile.h"
|
||||||
|
#include "app/gfx/tilesheet.h"
|
||||||
#include "app/gui/canvas.h"
|
#include "app/gui/canvas.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "app/zelda3/overworld.h"
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace editor {
|
namespace editor {
|
||||||
|
|
||||||
class Tile16Editor : public SharedROM {
|
/**
|
||||||
|
* @brief Popup window to edit Tile16 data
|
||||||
|
*/
|
||||||
|
class Tile16Editor : public context::GfxContext, public SharedRom {
|
||||||
public:
|
public:
|
||||||
absl::Status Update();
|
absl::Status InitBlockset(gfx::Bitmap* tile16_blockset_bmp,
|
||||||
|
gfx::Bitmap current_gfx_bmp,
|
||||||
|
const std::vector<gfx::Bitmap>& tile16_individual,
|
||||||
|
uint8_t all_tiles_types[0x200]);
|
||||||
|
|
||||||
|
absl::Status Update();
|
||||||
|
absl::Status DrawMenu();
|
||||||
|
|
||||||
|
absl::Status DrawTile16Editor();
|
||||||
|
absl::Status UpdateTile16Transfer();
|
||||||
absl::Status UpdateBlockset();
|
absl::Status UpdateBlockset();
|
||||||
|
|
||||||
|
absl::Status DrawToCurrentTile16(ImVec2 pos);
|
||||||
|
|
||||||
absl::Status UpdateTile16Edit();
|
absl::Status UpdateTile16Edit();
|
||||||
|
|
||||||
void DrawTileEditControls();
|
absl::Status DrawTileEditControls();
|
||||||
|
|
||||||
absl::Status UpdateTransferTileCanvas();
|
absl::Status UpdateTransferTileCanvas();
|
||||||
|
|
||||||
absl::Status InitBlockset(const gfx::Bitmap& tile16_blockset_bmp,
|
|
||||||
gfx::Bitmap current_gfx_bmp,
|
|
||||||
const std::vector<gfx::Bitmap>& tile16_individual);
|
|
||||||
|
|
||||||
absl::Status LoadTile8();
|
absl::Status LoadTile8();
|
||||||
|
|
||||||
|
absl::Status SetCurrentTile(int id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool map_blockset_loaded_ = false;
|
bool map_blockset_loaded_ = false;
|
||||||
bool transfer_started_ = false;
|
bool transfer_started_ = false;
|
||||||
@@ -48,38 +61,36 @@ class Tile16Editor : public SharedROM {
|
|||||||
int current_tile8_ = 0;
|
int current_tile8_ = 0;
|
||||||
uint8_t current_palette_ = 0;
|
uint8_t current_palette_ = 0;
|
||||||
|
|
||||||
core::NotifyValue<uint8_t> notify_tile16;
|
core::NotifyValue<uint32_t> notify_tile16;
|
||||||
core::NotifyValue<uint8_t> notify_palette;
|
core::NotifyValue<uint8_t> notify_palette;
|
||||||
|
|
||||||
// Canvas dimensions
|
|
||||||
int canvas_width;
|
|
||||||
int canvas_height;
|
|
||||||
|
|
||||||
// Texture ID for the canvas
|
|
||||||
int texture_id;
|
|
||||||
|
|
||||||
// Various options for the Tile16 Editor
|
// Various options for the Tile16 Editor
|
||||||
bool x_flip;
|
bool x_flip;
|
||||||
bool y_flip;
|
bool y_flip;
|
||||||
bool priority_tile;
|
bool priority_tile;
|
||||||
int tile_size;
|
int tile_size;
|
||||||
|
|
||||||
|
uint8_t* all_tiles_types_;
|
||||||
|
|
||||||
// Tile16 blockset for selecting the tile to edit
|
// Tile16 blockset for selecting the tile to edit
|
||||||
gui::Canvas blockset_canvas_;
|
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
|
||||||
gfx::Bitmap tile16_blockset_bmp_;
|
gui::CanvasGridSize::k32x32};
|
||||||
|
gfx::Bitmap* tile16_blockset_bmp_;
|
||||||
|
|
||||||
// Canvas for editing the selected tile
|
// Canvas for editing the selected tile
|
||||||
gui::Canvas tile16_edit_canvas_;
|
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
|
||||||
gfx::Bitmap current_tile16_bmp_;
|
gui::CanvasGridSize::k64x64};
|
||||||
gfx::Bitmap current_tile8_bmp_;
|
gfx::Bitmap* current_tile16_bmp_;
|
||||||
|
|
||||||
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
|
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
|
||||||
gui::Canvas tile8_source_canvas_;
|
gui::Canvas tile8_source_canvas_{
|
||||||
|
"Tile8SourceCanvas",
|
||||||
|
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
|
||||||
|
gui::CanvasGridSize::k32x32};
|
||||||
gfx::Bitmap current_gfx_bmp_;
|
gfx::Bitmap current_gfx_bmp_;
|
||||||
|
|
||||||
gui::Canvas transfer_canvas_;
|
gui::Canvas transfer_canvas_;
|
||||||
gfx::Bitmap transfer_blockset_bmp_;
|
gfx::Bitmap transfer_blockset_bmp_;
|
||||||
gfx::Bitmap transfer_current_bmp_;
|
|
||||||
|
|
||||||
std::vector<Bytes> tile16_individual_data_;
|
std::vector<Bytes> tile16_individual_data_;
|
||||||
std::vector<gfx::Bitmap> tile16_individual_;
|
std::vector<gfx::Bitmap> tile16_individual_;
|
||||||
@@ -88,17 +99,17 @@ class Tile16Editor : public SharedROM {
|
|||||||
|
|
||||||
std::vector<uint8_t> current_tile16_data_;
|
std::vector<uint8_t> current_tile16_data_;
|
||||||
|
|
||||||
|
std::vector<uint8_t> tile8_gfx_data_;
|
||||||
|
|
||||||
PaletteEditor palette_editor_;
|
PaletteEditor palette_editor_;
|
||||||
|
|
||||||
gfx::SNESPalette palette_;
|
gfx::SnesPalette palette_;
|
||||||
zelda3::Overworld transfer_overworld_;
|
zelda3::overworld::Overworld transfer_overworld_;
|
||||||
|
|
||||||
gfx::BitmapTable graphics_bin_;
|
gfx::BitmapTable graphics_bin_;
|
||||||
|
|
||||||
ROM transfer_rom_;
|
Rom transfer_rom_;
|
||||||
absl::Status transfer_status_;
|
absl::Status transfer_status_;
|
||||||
|
|
||||||
core::TaskManager<std::function<void(int)>> task_manager_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,25 +1,30 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
|
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||||
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
|
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||||
|
|
||||||
#include <ImGuiColorTextEdit/TextEditor.h>
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
#include <ImGuiFileDialog/ImGuiFileDialog.h>
|
|
||||||
#include <imgui/imgui.h>
|
#include "ImGuiColorTextEdit/TextEditor.h"
|
||||||
#include <imgui/misc/cpp/imgui_stdlib.h>
|
#include "ImGuiFileDialog/ImGuiFileDialog.h"
|
||||||
#include <imgui_memory_editor.h>
|
#include "imgui/imgui.h"
|
||||||
|
#include "imgui/misc/cpp/imgui_stdlib.h"
|
||||||
|
#include "imgui_memory_editor.h"
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
#include "absl/status/status.h"
|
||||||
#include "app/core/common.h"
|
#include "app/core/common.h"
|
||||||
#include "app/core/constants.h"
|
#include "app/core/constants.h"
|
||||||
#include "app/gui/pipeline.h"
|
#include "app/core/project.h"
|
||||||
#include "app/editor/context/gfx_context.h"
|
#include "app/editor/code/assembly_editor.h"
|
||||||
#include "app/editor/dungeon_editor.h"
|
#include "app/editor/code/memory_editor.h"
|
||||||
#include "app/editor/graphics_editor.h"
|
#include "app/editor/dungeon/dungeon_editor.h"
|
||||||
#include "app/editor/modules/assembly_editor.h"
|
#include "app/editor/graphics/graphics_editor.h"
|
||||||
#include "app/editor/modules/music_editor.h"
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
#include "app/editor/modules/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_editor.h"
|
#include "app/editor/overworld_editor.h"
|
||||||
#include "app/editor/screen_editor.h"
|
#include "app/editor/settings_editor.h"
|
||||||
#include "app/editor/sprite_editor.h"
|
#include "app/editor/sprite/sprite_editor.h"
|
||||||
|
#include "app/editor/utils/gfx_context.h"
|
||||||
#include "app/emu/emulator.h"
|
#include "app/emu/emulator.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "app/gfx/snes_tile.h"
|
#include "app/gfx/snes_tile.h"
|
||||||
@@ -32,18 +37,51 @@ namespace yaze {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace editor {
|
namespace editor {
|
||||||
|
|
||||||
class MasterEditor : public SharedROM,
|
/**
|
||||||
public GfxContext,
|
* @class MasterEditor
|
||||||
|
* @brief The MasterEditor class represents the main editor for a Rom in the
|
||||||
|
* Yaze application.
|
||||||
|
*
|
||||||
|
* This class inherits from SharedRom, GfxContext, and ExperimentFlags, and
|
||||||
|
* provides functionality for setting up the screen, updating the editor, and
|
||||||
|
* shutting down the editor. It also includes methods for drawing various menus
|
||||||
|
* and popups, saving the Rom, and managing editor-specific flags.
|
||||||
|
*
|
||||||
|
* The MasterEditor 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.
|
||||||
|
*
|
||||||
|
* @note This class assumes the presence of an SDL_Renderer object for rendering
|
||||||
|
* graphics.
|
||||||
|
*/
|
||||||
|
class MasterEditor : public SharedRom,
|
||||||
|
public context::GfxContext,
|
||||||
public core::ExperimentFlags {
|
public core::ExperimentFlags {
|
||||||
public:
|
public:
|
||||||
MasterEditor() { current_editor_ = &overworld_editor_; }
|
MasterEditor() {
|
||||||
|
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_);
|
||||||
|
}
|
||||||
|
|
||||||
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer);
|
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer,
|
||||||
|
std::string filename = "");
|
||||||
absl::Status Update();
|
absl::Status Update();
|
||||||
|
|
||||||
void Shutdown() { overworld_editor_.Shutdown(); }
|
auto emulator() -> emu::Emulator& { return emulator_; }
|
||||||
|
auto quit() { return quit_; }
|
||||||
|
auto overworld_editor() -> OverworldEditor& { return overworld_editor_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void ManageActiveEditors();
|
||||||
|
void ManageKeyboardShortcuts();
|
||||||
|
void OpenRomOrProject(const std::string& filename);
|
||||||
|
|
||||||
void DrawFileDialog();
|
void DrawFileDialog();
|
||||||
void DrawStatusPopup();
|
void DrawStatusPopup();
|
||||||
void DrawAboutPopup();
|
void DrawAboutPopup();
|
||||||
@@ -53,11 +91,20 @@ class MasterEditor : public SharedROM,
|
|||||||
void DrawFileMenu();
|
void DrawFileMenu();
|
||||||
void DrawEditMenu();
|
void DrawEditMenu();
|
||||||
void DrawViewMenu();
|
void DrawViewMenu();
|
||||||
|
void DrawTestMenu();
|
||||||
|
void DrawProjectMenu();
|
||||||
void DrawHelpMenu();
|
void DrawHelpMenu();
|
||||||
|
|
||||||
|
void LoadRom();
|
||||||
|
void SaveRom();
|
||||||
|
|
||||||
|
absl::Status OpenProject();
|
||||||
|
|
||||||
|
bool quit_ = false;
|
||||||
bool about_ = false;
|
bool about_ = false;
|
||||||
bool rom_info_ = false;
|
bool rom_info_ = false;
|
||||||
bool backup_rom_ = true;
|
bool backup_rom_ = false;
|
||||||
|
bool save_new_auto_ = true;
|
||||||
bool show_status_ = false;
|
bool show_status_ = false;
|
||||||
bool rom_assets_loaded_ = false;
|
bool rom_assets_loaded_ = false;
|
||||||
|
|
||||||
@@ -68,6 +115,8 @@ class MasterEditor : public SharedROM,
|
|||||||
|
|
||||||
emu::Emulator emulator_;
|
emu::Emulator emulator_;
|
||||||
|
|
||||||
|
Project current_project_;
|
||||||
|
|
||||||
AssemblyEditor assembly_editor_;
|
AssemblyEditor assembly_editor_;
|
||||||
DungeonEditor dungeon_editor_;
|
DungeonEditor dungeon_editor_;
|
||||||
GraphicsEditor graphics_editor_;
|
GraphicsEditor graphics_editor_;
|
||||||
@@ -76,12 +125,17 @@ class MasterEditor : public SharedROM,
|
|||||||
PaletteEditor palette_editor_;
|
PaletteEditor palette_editor_;
|
||||||
ScreenEditor screen_editor_;
|
ScreenEditor screen_editor_;
|
||||||
SpriteEditor sprite_editor_;
|
SpriteEditor sprite_editor_;
|
||||||
|
SettingsEditor settings_editor_;
|
||||||
|
MessageEditor message_editor_;
|
||||||
|
MemoryEditorWithDiffChecker memory_editor_;
|
||||||
|
|
||||||
Editor *current_editor_ = nullptr;
|
ImVector<int> active_tabs_;
|
||||||
|
std::vector<Editor*> active_editors_;
|
||||||
|
Editor* current_editor_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
|
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
#include "sprite_editor.h"
|
#include "master_editor.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace editor {
|
namespace editor {
|
||||||
|
|
||||||
absl::Status SpriteEditor::Update() { return absl::OkStatus(); }
|
|
||||||
|
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
} // namespace app
|
} // namespace app
|
||||||
177
src/app/editor/message/message_data.h
Normal file
177
src/app/editor/message/message_data.h
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
|
||||||
|
#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
const uint8_t MESSAGETERMINATOR = 0x7F;
|
||||||
|
|
||||||
|
static std::string AddNewLinesToCommands(std::string str);
|
||||||
|
static std::string ReplaceAllDictionaryWords(std::string str);
|
||||||
|
static std::vector<uint8_t> ParseMessageToData(std::string str);
|
||||||
|
|
||||||
|
const std::string CHEESE = "\uBEBE"; // Inserted into commands to protect
|
||||||
|
// them from dictionary replacements.
|
||||||
|
|
||||||
|
struct MessageData {
|
||||||
|
int ID;
|
||||||
|
int Address;
|
||||||
|
std::string RawString;
|
||||||
|
std::string ContentsParsed;
|
||||||
|
std::vector<uint8_t> Data;
|
||||||
|
std::vector<uint8_t> DataParsed;
|
||||||
|
|
||||||
|
MessageData() = default;
|
||||||
|
MessageData(int id, int address, const std::string& rawString,
|
||||||
|
const std::vector<uint8_t>& rawData,
|
||||||
|
const std::string& parsedString,
|
||||||
|
const std::vector<uint8_t>& parsedData)
|
||||||
|
: ID(id),
|
||||||
|
Address(address),
|
||||||
|
RawString(rawString),
|
||||||
|
Data(rawData),
|
||||||
|
DataParsed(parsedData),
|
||||||
|
ContentsParsed(parsedString) {}
|
||||||
|
|
||||||
|
// Copy constructor
|
||||||
|
MessageData(const MessageData& other) {
|
||||||
|
ID = other.ID;
|
||||||
|
Address = other.Address;
|
||||||
|
RawString = other.RawString;
|
||||||
|
Data = other.Data;
|
||||||
|
DataParsed = other.DataParsed;
|
||||||
|
ContentsParsed = other.ContentsParsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetMessage(std::string messageString) {
|
||||||
|
ContentsParsed = messageString;
|
||||||
|
RawString = OptimizeMessageForDictionary(messageString);
|
||||||
|
RecalculateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToString() {
|
||||||
|
return absl::StrFormat("%0X - %s", ID, ContentsParsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetReadableDumpedContents() {
|
||||||
|
std::stringstream stringBuilder;
|
||||||
|
for (const auto& b : Data) {
|
||||||
|
stringBuilder << absl::StrFormat("%0X ", b);
|
||||||
|
}
|
||||||
|
stringBuilder << absl::StrFormat("%00X", MESSAGETERMINATOR);
|
||||||
|
|
||||||
|
return absl::StrFormat(
|
||||||
|
"[[[[\r\nMessage "
|
||||||
|
"%000X]]]]\r\n[Contents]\r\n%s\r\n\r\n[Data]\r\n%s"
|
||||||
|
"\r\n\r\n\r\n\r\n",
|
||||||
|
ID, AddNewLinesToCommands(ContentsParsed), stringBuilder.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetDumpedContents() {
|
||||||
|
return absl::StrFormat("%000X : %s\r\n\r\n", ID, ContentsParsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OptimizeMessageForDictionary(std::string messageString) {
|
||||||
|
std::stringstream protons;
|
||||||
|
bool command = false;
|
||||||
|
for (const auto& c : messageString) {
|
||||||
|
if (c == '[') {
|
||||||
|
command = true;
|
||||||
|
} else if (c == ']') {
|
||||||
|
command = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protons << c;
|
||||||
|
if (command) {
|
||||||
|
protons << CHEESE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string protonsString = protons.str();
|
||||||
|
std::string replacedString = ReplaceAllDictionaryWords(protonsString);
|
||||||
|
std::string finalString =
|
||||||
|
absl::StrReplaceAll(replacedString, {{CHEESE, ""}});
|
||||||
|
|
||||||
|
return finalString;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecalculateData() {
|
||||||
|
Data = ParseMessageToData(RawString);
|
||||||
|
DataParsed = ParseMessageToData(ContentsParsed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextElement {
|
||||||
|
uint8_t ID;
|
||||||
|
std::string Token;
|
||||||
|
std::string GenericToken;
|
||||||
|
std::string Pattern;
|
||||||
|
std::string StrictPattern;
|
||||||
|
std::string Description;
|
||||||
|
bool HasArgument;
|
||||||
|
|
||||||
|
TextElement() = default;
|
||||||
|
TextElement(uint8_t id, std::string token, bool arg,
|
||||||
|
std::string description) {
|
||||||
|
ID = id;
|
||||||
|
Token = token;
|
||||||
|
if (arg) {
|
||||||
|
GenericToken = absl::StrFormat("[%s:##]", Token);
|
||||||
|
} else {
|
||||||
|
GenericToken = absl::StrFormat("[%s]", Token);
|
||||||
|
}
|
||||||
|
HasArgument = arg;
|
||||||
|
Description = description;
|
||||||
|
Pattern =
|
||||||
|
arg ? "\\[" + Token + ":?([0-9A-F]{1,2})\\]" : "\\[" + Token + "\\]";
|
||||||
|
Pattern = absl::StrReplaceAll(Pattern, {{"[", "\\["}, {"]", "\\]"}});
|
||||||
|
StrictPattern = absl::StrCat("^", Pattern, "$");
|
||||||
|
StrictPattern = "^" + Pattern + "$";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetParameterizedToken(uint8_t value = 0) {
|
||||||
|
if (HasArgument) {
|
||||||
|
return absl::StrFormat("[%s:%02X]", Token, value);
|
||||||
|
} else {
|
||||||
|
return absl::StrFormat("[%s]", Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToString() {
|
||||||
|
return absl::StrFormat("%s %s", GenericToken, Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::smatch MatchMe(std::string dfrag) const {
|
||||||
|
std::regex pattern(StrictPattern);
|
||||||
|
std::smatch match;
|
||||||
|
std::regex_match(dfrag, match, pattern);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Empty() { return ID == 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedElement {
|
||||||
|
TextElement Parent;
|
||||||
|
uint8_t Value;
|
||||||
|
bool Active = false;
|
||||||
|
|
||||||
|
ParsedElement() = default;
|
||||||
|
ParsedElement(TextElement textElement, uint8_t value) {
|
||||||
|
Parent = textElement;
|
||||||
|
Value = value;
|
||||||
|
Active = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
|
||||||
770
src/app/editor/message/message_editor.cc
Normal file
770
src/app/editor/message/message_editor.cc
Normal file
@@ -0,0 +1,770 @@
|
|||||||
|
#include "message_editor.h"
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_replace.h"
|
||||||
|
#include "absl/strings/str_split.h"
|
||||||
|
#include "app/core/common.h"
|
||||||
|
#include "app/editor/utils/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/gui/icons.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::Begin;
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::End;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::InputText;
|
||||||
|
using ImGui::InputTextMultiline;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
using ImGui::TextWrapped;
|
||||||
|
using ImGui::TreeNode;
|
||||||
|
|
||||||
|
static ParsedElement FindMatchingElement(string str) {
|
||||||
|
std::smatch match;
|
||||||
|
for (auto& textElement : TextCommands) {
|
||||||
|
match = textElement.MatchMe(str);
|
||||||
|
if (match.size() > 0) {
|
||||||
|
if (textElement.HasArgument) {
|
||||||
|
return ParsedElement(textElement,
|
||||||
|
std::stoi(match[1].str(), nullptr, 16));
|
||||||
|
} else {
|
||||||
|
return ParsedElement(textElement, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match = DictionaryElement.MatchMe(str);
|
||||||
|
if (match.size() > 0) {
|
||||||
|
return ParsedElement(DictionaryElement,
|
||||||
|
DICTOFF + std::stoi(match[1].str(), nullptr, 16));
|
||||||
|
}
|
||||||
|
return ParsedElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
static string ReplaceAllDictionaryWords(string str) {
|
||||||
|
string temp = str;
|
||||||
|
for (const auto& entry : AllDictionaries) {
|
||||||
|
if (absl::StrContains(temp, entry.Contents)) {
|
||||||
|
temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<uint8_t> ParseMessageToData(string str) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
string tempString = str;
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
while (pos < tempString.size()) {
|
||||||
|
// Get next text fragment.
|
||||||
|
if (tempString[pos] == '[') {
|
||||||
|
int next = tempString.find(']', pos);
|
||||||
|
if (next == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedElement parsedElement =
|
||||||
|
FindMatchingElement(tempString.substr(pos, next - pos + 1));
|
||||||
|
if (!parsedElement.Active) {
|
||||||
|
break; // TODO: handle badness.
|
||||||
|
// } else if (parsedElement.Parent == DictionaryElement) {
|
||||||
|
// bytes.push_back(parsedElement.Value);
|
||||||
|
} else {
|
||||||
|
bytes.push_back(parsedElement.Parent.ID);
|
||||||
|
|
||||||
|
if (parsedElement.Parent.HasArgument) {
|
||||||
|
bytes.push_back(parsedElement.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = next + 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
uint8_t bb = MessageEditor::FindMatchingCharacter(tempString[pos++]);
|
||||||
|
|
||||||
|
if (bb != 0xFF) {
|
||||||
|
// TODO: handle badness.
|
||||||
|
bytes.push_back(bb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Update() {
|
||||||
|
if (rom()->is_loaded() && !data_loaded_) {
|
||||||
|
RETURN_IF_ERROR(Initialize());
|
||||||
|
CurrentMessage = ListOfTexts[1];
|
||||||
|
data_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginTable("##MessageEditor", 3,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
|
||||||
|
TableSetupColumn("List");
|
||||||
|
TableSetupColumn("Contents");
|
||||||
|
TableSetupColumn("Commands");
|
||||||
|
|
||||||
|
TableHeadersRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawMessageList();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawCurrentMessage();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawTextCommands();
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawMessageList() {
|
||||||
|
if (InputText("Search", &search_text_)) {
|
||||||
|
DisplayedMessages.clear();
|
||||||
|
for (const auto& message : ListOfTexts) {
|
||||||
|
if (absl::StrContains(message.ContentsParsed, search_text_)) {
|
||||||
|
DisplayedMessages.push_back(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginChild("##MessagesList", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
if (BeginTable("##MessagesTable", 3,
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders |
|
||||||
|
ImGuiTableFlags_Resizable)) {
|
||||||
|
TableSetupColumn("ID");
|
||||||
|
TableSetupColumn("Contents");
|
||||||
|
TableSetupColumn("Data");
|
||||||
|
|
||||||
|
TableHeadersRow();
|
||||||
|
|
||||||
|
for (const auto& message : ListOfTexts) {
|
||||||
|
TableNextColumn();
|
||||||
|
if (Button(core::UppercaseHexWord(message.ID).c_str())) {
|
||||||
|
CurrentMessage = message;
|
||||||
|
DrawMessagePreview();
|
||||||
|
}
|
||||||
|
TableNextColumn();
|
||||||
|
TextWrapped("%s", ParsedMessages[message.ID].c_str());
|
||||||
|
TableNextColumn();
|
||||||
|
TextWrapped(
|
||||||
|
"%s",
|
||||||
|
core::UppercaseHexLong(ListOfTexts[message.ID].Address).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawCurrentMessage() {
|
||||||
|
Button(absl::StrCat("Message ", CurrentMessage.ID).c_str());
|
||||||
|
if (InputTextMultiline("##MessageEditor", &ParsedMessages[CurrentMessage.ID],
|
||||||
|
ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
|
||||||
|
CurrentMessage.Data = ParseMessageToData(message_text_box_.text);
|
||||||
|
DrawMessagePreview();
|
||||||
|
}
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
Text("Font Graphics");
|
||||||
|
gui::BeginPadding(1);
|
||||||
|
BeginChild("MessageEditorCanvas", ImVec2(0, 130));
|
||||||
|
font_gfx_canvas_.DrawBackground();
|
||||||
|
font_gfx_canvas_.DrawContextMenu();
|
||||||
|
font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
|
||||||
|
font_gfx_canvas_.DrawGrid();
|
||||||
|
font_gfx_canvas_.DrawOverlay();
|
||||||
|
EndChild();
|
||||||
|
gui::EndPadding();
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
Text("Message Preview");
|
||||||
|
if (Button("Refresh Bitmap")) {
|
||||||
|
rom()->UpdateBitmap(¤t_font_gfx16_bitmap_);
|
||||||
|
}
|
||||||
|
gui::BeginPadding(1);
|
||||||
|
BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
current_font_gfx16_canvas_.DrawBackground();
|
||||||
|
gui::EndPadding();
|
||||||
|
current_font_gfx16_canvas_.DrawContextMenu();
|
||||||
|
current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
|
||||||
|
current_font_gfx16_canvas_.DrawGrid();
|
||||||
|
current_font_gfx16_canvas_.DrawOverlay();
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawTextCommands() {
|
||||||
|
if (BeginChild("##TextCommands", ImVec2(0, 0), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
||||||
|
for (const auto& text_element : TextCommands) {
|
||||||
|
if (Button(text_element.GenericToken.c_str())) {
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
TextWrapped("%s", text_element.Description.c_str());
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Initialize() {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
width_array[i] = rom()->data()[kCharactersWidth + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildDictionaryEntries();
|
||||||
|
ReadAllTextData();
|
||||||
|
|
||||||
|
font_preview_colors_.AddColor(0x7FFF); // White
|
||||||
|
font_preview_colors_.AddColor(0x7C00); // Red
|
||||||
|
font_preview_colors_.AddColor(0x03E0); // Green
|
||||||
|
font_preview_colors_.AddColor(0x001F); // Blue
|
||||||
|
|
||||||
|
std::vector<uint8_t> data(0x4000, 0);
|
||||||
|
for (int i = 0; i < 0x4000; i++) {
|
||||||
|
data[i] = rom()->data()[kGfxFont + i];
|
||||||
|
}
|
||||||
|
font_gfx16_data = gfx::SnesTo8bppSheet(data, /*bpp=*/2);
|
||||||
|
|
||||||
|
// 4bpp
|
||||||
|
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
|
||||||
|
128, 128, 8, font_gfx16_data, font_gfx_bitmap_, font_preview_colors_))
|
||||||
|
|
||||||
|
current_font_gfx16_data_.reserve(172 * 4096);
|
||||||
|
for (int i = 0; i < 172 * 4096; i++) {
|
||||||
|
current_font_gfx16_data_.push_back(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8bpp
|
||||||
|
RETURN_IF_ERROR(rom()->CreateAndRenderBitmap(
|
||||||
|
172, 4096, 64, current_font_gfx16_data_, current_font_gfx16_bitmap_,
|
||||||
|
font_preview_colors_))
|
||||||
|
|
||||||
|
gfx::SnesPalette color_palette = font_gfx_bitmap_.palette();
|
||||||
|
for (int i = 0; i < font_preview_colors_.size(); i++) {
|
||||||
|
*color_palette.mutable_color(i) = font_preview_colors_[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
*font_gfx_bitmap_.mutable_palette() = color_palette;
|
||||||
|
|
||||||
|
for (const auto& message : ListOfTexts) {
|
||||||
|
DisplayedMessages.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& each_message : ListOfTexts) {
|
||||||
|
// Each string has a [:XX] char encoded
|
||||||
|
// The corresponding character is found in CharEncoder unordered_map
|
||||||
|
std::string parsed_message = "";
|
||||||
|
for (const auto& byte : each_message.Data) {
|
||||||
|
// Find the char byte in the CharEncoder map
|
||||||
|
if (CharEncoder.contains(byte)) {
|
||||||
|
parsed_message.push_back(CharEncoder.at(byte));
|
||||||
|
} else {
|
||||||
|
// If the byte is not found in the CharEncoder map, it is a command
|
||||||
|
// or a dictionary entry
|
||||||
|
if (byte >= DICTOFF && byte < (DICTOFF + 97)) {
|
||||||
|
// Dictionary entry
|
||||||
|
auto dictionaryEntry = GetDictionaryFromID(byte - DICTOFF);
|
||||||
|
parsed_message.append(dictionaryEntry.Contents);
|
||||||
|
} else {
|
||||||
|
// Command
|
||||||
|
TextElement textElement = FindMatchingCommand(byte);
|
||||||
|
if (!textElement.Empty()) {
|
||||||
|
// If the element is line 2, 3 or V we add a newline
|
||||||
|
if (textElement.ID == kScrollVertical || textElement.ID == kLine2 ||
|
||||||
|
textElement.ID == kLine3)
|
||||||
|
parsed_message.append("\n");
|
||||||
|
|
||||||
|
parsed_message.append(textElement.GenericToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParsedMessages.push_back(parsed_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawMessagePreview();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::BuildDictionaryEntries() {
|
||||||
|
for (int i = 0; i < 97; i++) {
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
std::stringstream stringBuilder;
|
||||||
|
|
||||||
|
int address = core::SnesToPc(
|
||||||
|
0x0E0000 + (rom()->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
|
||||||
|
rom()->data()[kPointersDictionaries + (i * 2)]);
|
||||||
|
|
||||||
|
int temppush_backress = core::SnesToPc(
|
||||||
|
0x0E0000 +
|
||||||
|
(rom()->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
|
||||||
|
rom()->data()[kPointersDictionaries + ((i + 1) * 2)]);
|
||||||
|
|
||||||
|
while (address < temppush_backress) {
|
||||||
|
uint8_t uint8_tDictionary = rom()->data()[address++];
|
||||||
|
bytes.push_back(uint8_tDictionary);
|
||||||
|
stringBuilder << ParseTextDataByte(uint8_tDictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllDictionaries[i] = DictionaryEntry{(uint8_t)i, stringBuilder.str()};
|
||||||
|
AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllDictionaries.OrderByDescending(dictionary = > dictionary.Length);
|
||||||
|
AllDictionaries[0].Length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::ReadAllTextData() {
|
||||||
|
int messageID = 0;
|
||||||
|
uint8_t current_byte;
|
||||||
|
int pos = kTextData;
|
||||||
|
std::vector<uint8_t> temp_bytes_raw;
|
||||||
|
std::vector<uint8_t> temp_bytes_parsed;
|
||||||
|
|
||||||
|
std::string current_message_raw;
|
||||||
|
std::string current_message_parsed;
|
||||||
|
TextElement text_element;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
current_byte = rom()->data()[pos++];
|
||||||
|
|
||||||
|
if (current_byte == MESSAGETERMINATOR) {
|
||||||
|
auto message =
|
||||||
|
MessageData(messageID++, pos, current_message_raw, temp_bytes_raw,
|
||||||
|
current_message_parsed, temp_bytes_parsed);
|
||||||
|
|
||||||
|
ListOfTexts.push_back(message);
|
||||||
|
|
||||||
|
temp_bytes_raw.clear();
|
||||||
|
temp_bytes_parsed.clear();
|
||||||
|
current_message_raw.clear();
|
||||||
|
current_message_parsed.clear();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else if (current_byte == 0xFF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_bytes_raw.push_back(current_byte);
|
||||||
|
|
||||||
|
// Check for command.
|
||||||
|
text_element = FindMatchingCommand(current_byte);
|
||||||
|
|
||||||
|
if (!text_element.Empty()) {
|
||||||
|
temp_bytes_parsed.push_back(current_byte);
|
||||||
|
if (text_element.HasArgument) {
|
||||||
|
current_byte = rom()->data()[pos++];
|
||||||
|
temp_bytes_raw.push_back(current_byte);
|
||||||
|
temp_bytes_parsed.push_back(current_byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_message_raw.append(
|
||||||
|
text_element.GetParameterizedToken(current_byte));
|
||||||
|
current_message_parsed.append(
|
||||||
|
text_element.GetParameterizedToken(current_byte));
|
||||||
|
|
||||||
|
if (text_element.Token == BANKToken) {
|
||||||
|
pos = kTextData2;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for special characters.
|
||||||
|
text_element = FindMatchingSpecial(current_byte);
|
||||||
|
|
||||||
|
if (!text_element.Empty()) {
|
||||||
|
current_message_raw.append(text_element.GetParameterizedToken());
|
||||||
|
current_message_parsed.append(text_element.GetParameterizedToken());
|
||||||
|
temp_bytes_parsed.push_back(current_byte);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for dictionary.
|
||||||
|
int dictionary = FindDictionaryEntry(current_byte);
|
||||||
|
|
||||||
|
if (dictionary >= 0) {
|
||||||
|
current_message_raw.append("[");
|
||||||
|
current_message_raw.append(DICTIONARYTOKEN);
|
||||||
|
current_message_raw.append(":");
|
||||||
|
current_message_raw.append(core::UppercaseHexWord(dictionary));
|
||||||
|
current_message_raw.append("]");
|
||||||
|
|
||||||
|
uint32_t address = core::Get24LocalFromPC(
|
||||||
|
rom()->data(), kPointersDictionaries + (dictionary * 2));
|
||||||
|
uint32_t address_end = core::Get24LocalFromPC(
|
||||||
|
rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2));
|
||||||
|
|
||||||
|
for (uint32_t i = address; i < address_end; i++) {
|
||||||
|
temp_bytes_parsed.push_back(rom()->data()[i]);
|
||||||
|
current_message_parsed.append(ParseTextDataByte(rom()->data()[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else.
|
||||||
|
if (CharEncoder.contains(current_byte)) {
|
||||||
|
std::string str = "";
|
||||||
|
str.push_back(CharEncoder.at(current_byte));
|
||||||
|
current_message_raw.append(str);
|
||||||
|
current_message_parsed.append(str);
|
||||||
|
temp_bytes_parsed.push_back(current_byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextElement MessageEditor::FindMatchingCommand(uint8_t b) {
|
||||||
|
TextElement empty_element;
|
||||||
|
for (const auto text_element : TextCommands) {
|
||||||
|
if (text_element.ID == b) {
|
||||||
|
return text_element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return empty_element;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextElement MessageEditor::FindMatchingSpecial(uint8_t value) {
|
||||||
|
TextElement empty_element;
|
||||||
|
for (const auto text_element : SpecialChars) {
|
||||||
|
if (text_element.ID == value) {
|
||||||
|
return text_element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return empty_element;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageEditor::DictionaryEntry MessageEditor::GetDictionaryFromID(
|
||||||
|
uint8_t value) {
|
||||||
|
if (value < 0 || value >= AllDictionaries.size()) {
|
||||||
|
return DictionaryEntry();
|
||||||
|
}
|
||||||
|
return AllDictionaries[value];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MessageEditor::FindDictionaryEntry(uint8_t value) {
|
||||||
|
if (value < DICTOFF || value == 0xFF) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value - DICTOFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MessageEditor::FindMatchingCharacter(char value) {
|
||||||
|
for (const auto [key, char_value] : CharEncoder) {
|
||||||
|
if (value == char_value) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
string MessageEditor::ParseTextDataByte(uint8_t value) {
|
||||||
|
if (CharEncoder.contains(value)) {
|
||||||
|
char c = CharEncoder.at(value);
|
||||||
|
string str = "";
|
||||||
|
str.push_back(c);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for command.
|
||||||
|
TextElement textElement = FindMatchingCommand(value);
|
||||||
|
if (!textElement.Empty()) {
|
||||||
|
return textElement.GenericToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for special characters.
|
||||||
|
textElement = FindMatchingSpecial(value);
|
||||||
|
if (!textElement.Empty()) {
|
||||||
|
return textElement.GenericToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for dictionary.
|
||||||
|
int dictionary = FindDictionaryEntry(value);
|
||||||
|
if (dictionary >= 0) {
|
||||||
|
return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
|
||||||
|
int sizex, int sizey) {
|
||||||
|
int drawid = srcx + (srcy * 32);
|
||||||
|
for (int yl = 0; yl < sizey * 8; yl++) {
|
||||||
|
for (int xl = 0; xl < 4; xl++) {
|
||||||
|
int mx = xl;
|
||||||
|
int my = yl;
|
||||||
|
|
||||||
|
// Formula information to get tile index position in the array.
|
||||||
|
// ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
|
||||||
|
int tx = ((drawid / 16) * 512) + ((drawid - ((drawid / 16) * 16)) * 4);
|
||||||
|
uint8_t pixel = font_gfx16_data[tx + (yl * 64) + xl];
|
||||||
|
|
||||||
|
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
|
||||||
|
// position
|
||||||
|
int index = x + (y * 172) + (mx * 2) + (my * 172);
|
||||||
|
if ((pixel & 0x0F) != 0) {
|
||||||
|
current_font_gfx16_data_[index + 1] =
|
||||||
|
(uint8_t)((pixel & 0x0F) + (0 * 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((pixel >> 4) & 0x0F) != 0) {
|
||||||
|
current_font_gfx16_data_[index + 0] =
|
||||||
|
(uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawStringToPreview(string str) {
|
||||||
|
for (const auto c : str) {
|
||||||
|
DrawCharacterToPreview(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawCharacterToPreview(char c) {
|
||||||
|
DrawCharacterToPreview(FindMatchingCharacter(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
|
||||||
|
for (const uint8_t& value : text) {
|
||||||
|
if (skip_next) {
|
||||||
|
skip_next = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value < 100) {
|
||||||
|
int srcy = value / 16;
|
||||||
|
int srcx = value - (value & (~0xF));
|
||||||
|
|
||||||
|
if (text_pos >= 170) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawTileToPreview(text_pos, text_line * 16, srcx, srcy, 0, 1, 2);
|
||||||
|
text_pos += width_array[value];
|
||||||
|
} else if (value == kLine1) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line = 0;
|
||||||
|
} else if (value == kScrollVertical) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line += 1;
|
||||||
|
} else if (value == kLine2) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line = 1;
|
||||||
|
} else if (value == kLine3) {
|
||||||
|
text_pos = 0;
|
||||||
|
text_line = 2;
|
||||||
|
} else if (value == 0x6B || value == 0x6D || value == 0x6E ||
|
||||||
|
value == 0x77 || value == 0x78 || value == 0x79 ||
|
||||||
|
value == 0x7A) {
|
||||||
|
skip_next = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else if (value == 0x6C) // BCD numbers.
|
||||||
|
{
|
||||||
|
DrawCharacterToPreview('0');
|
||||||
|
skip_next = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else if (value == 0x6A) {
|
||||||
|
// Includes parentheses to be longer, since player names can be up to 6
|
||||||
|
// characters.
|
||||||
|
DrawStringToPreview("(NAME)");
|
||||||
|
} else if (value >= DICTOFF && value < (DICTOFF + 97)) {
|
||||||
|
auto dictionaryEntry = GetDictionaryFromID(value - DICTOFF);
|
||||||
|
DrawCharacterToPreview(dictionaryEntry.Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::DrawMessagePreview() // From Parsing.
|
||||||
|
{
|
||||||
|
text_line = 0;
|
||||||
|
for (int i = 0; i < (172 * 4096); i++) {
|
||||||
|
current_font_gfx16_data_[i] = 0;
|
||||||
|
}
|
||||||
|
text_pos = 0;
|
||||||
|
DrawCharacterToPreview(CurrentMessage.Data);
|
||||||
|
shown_lines = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Cut() {
|
||||||
|
// Ensure that text is currently selected in the text box.
|
||||||
|
if (!message_text_box_.text.empty()) {
|
||||||
|
// Cut the selected text in the control and paste it into the Clipboard.
|
||||||
|
message_text_box_.Cut();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Paste() {
|
||||||
|
// Determine if there is any text in the Clipboard to paste into the
|
||||||
|
if (ImGui::GetClipboardText() != nullptr) {
|
||||||
|
// Paste the text from the Clipboard into the text box.
|
||||||
|
message_text_box_.Paste();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Copy() {
|
||||||
|
// Ensure that text is selected in the text box.
|
||||||
|
if (message_text_box_.selection_length > 0) {
|
||||||
|
// Copy the selected text to the Clipboard.
|
||||||
|
message_text_box_.Copy();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Undo() {
|
||||||
|
// Determine if last operation can be undone in text box.
|
||||||
|
if (message_text_box_.can_undo) {
|
||||||
|
// Undo the last operation.
|
||||||
|
message_text_box_.Undo();
|
||||||
|
|
||||||
|
// clear the undo buffer to prevent last action from being redone.
|
||||||
|
message_text_box_.clearUndo();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status MessageEditor::Save() {
|
||||||
|
std::vector<uint8_t> backup = rom()->vector();
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
RETURN_IF_ERROR(rom()->Write(kCharactersWidth + i, width_array[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
int pos = kTextData;
|
||||||
|
bool in_second_bank = false;
|
||||||
|
|
||||||
|
for (const auto& message : ListOfTexts) {
|
||||||
|
for (const auto value : message.Data) {
|
||||||
|
RETURN_IF_ERROR(rom()->Write(pos, value));
|
||||||
|
|
||||||
|
if (value == kBlockTerminator) {
|
||||||
|
// Make sure we didn't go over the space available in the first block.
|
||||||
|
// 0x7FFF available.
|
||||||
|
if ((!in_second_bank & pos) > kTextDataEnd) {
|
||||||
|
return absl::InternalError(DisplayTextOverflowError(pos, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to the second block.
|
||||||
|
pos = kTextData2 - 1;
|
||||||
|
in_second_bank = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
rom()->Write(pos++, MESSAGETERMINATOR)); // , true, "Terminator text"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we didn't go over the space available for the second block.
|
||||||
|
// 0x14BF available.
|
||||||
|
if ((in_second_bank & pos) > kTextData2End) {
|
||||||
|
// rom()->data() = backup;
|
||||||
|
return absl::InternalError(DisplayTextOverflowError(pos, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(rom()->Write(pos, 0xFF)); // , true, "End of text"
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
|
||||||
|
int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
|
||||||
|
string bankSTR = bank ? "1st" : "2nd";
|
||||||
|
string posSTR = bank ? absl::StrFormat("%X4", pos & 0xFFFF)
|
||||||
|
: absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
|
||||||
|
std::string message = absl::StrFormat(
|
||||||
|
"There is too much text data in the %s block to save.\n"
|
||||||
|
"Available: %X4 | Used: %s",
|
||||||
|
bankSTR, space, posSTR);
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// push_backs a command to the text field when the push_back command button is
|
||||||
|
// pressed or the command is double clicked in the list.
|
||||||
|
void MessageEditor::InsertCommandButton_Click_1() {
|
||||||
|
// InsertSelectedText(
|
||||||
|
// TextCommands[TextCommandList.SelectedIndex].GetParameterizedToken(
|
||||||
|
// (uint8_t)ParamsBox.HexValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
// push_backs a special character to the text field when the push_back command
|
||||||
|
// button is pressed or the character is double clicked in the list.
|
||||||
|
void MessageEditor::InsertSpecialButton_Click() {
|
||||||
|
// InsertSelectedText(
|
||||||
|
// SpecialChars[SpecialsList.SelectedIndex].GetParameterizedToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::InsertSelectedText(string str) {
|
||||||
|
int textboxPos = message_text_box_.selection_start;
|
||||||
|
from_form = true;
|
||||||
|
// message_text_box_.Text = message_text_box_.Text.Insert(textboxPos, str);
|
||||||
|
from_form = false;
|
||||||
|
message_text_box_.selection_start = textboxPos + str.size();
|
||||||
|
message_text_box_.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::Delete() {
|
||||||
|
// Determine if any text is selected in the TextBox control.
|
||||||
|
if (message_text_box_.selection_length == 0) {
|
||||||
|
// clear all of the text in the textbox.
|
||||||
|
message_text_box_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageEditor::SelectAll() {
|
||||||
|
// Determine if any text is selected in the TextBox control.
|
||||||
|
if (message_text_box_.selection_length == 0) {
|
||||||
|
// Select all text in the text box.
|
||||||
|
message_text_box_.SelectAll();
|
||||||
|
|
||||||
|
// Move the cursor to the text box.
|
||||||
|
message_text_box_.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
366
src/app/editor/message/message_editor.h
Normal file
366
src/app/editor/message/message_editor.h
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_MESSAGE_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_MESSAGE_EDITOR_H
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <regex>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "absl/strings/str_format.h"
|
||||||
|
#include "absl/strings/str_replace.h"
|
||||||
|
#include "absl/strings/str_split.h"
|
||||||
|
#include "app/editor/message/message_data.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
// TEXT EDITOR RELATED CONSTANTS
|
||||||
|
const int kGfxFont = 0x70000; // 2bpp format
|
||||||
|
const int kTextData = 0xE0000;
|
||||||
|
const int kTextDataEnd = 0xE7FFF;
|
||||||
|
const int kTextData2 = 0x75F40;
|
||||||
|
const int kTextData2End = 0x773FF;
|
||||||
|
const int kPointersDictionaries = 0x74703;
|
||||||
|
const int kCharactersWidth = 0x74ADF;
|
||||||
|
|
||||||
|
const string DICTIONARYTOKEN = "D";
|
||||||
|
const uint8_t DICTOFF = 0x88;
|
||||||
|
const string BANKToken = "BANK";
|
||||||
|
const uint8_t BANKID = 0x80;
|
||||||
|
|
||||||
|
constexpr uint8_t kBlockTerminator = 0x80;
|
||||||
|
|
||||||
|
static std::vector<uint8_t> ParseMessageToData(string str);
|
||||||
|
|
||||||
|
static ParsedElement FindMatchingElement(string str);
|
||||||
|
|
||||||
|
constexpr uint8_t kScrollVertical = 0x73;
|
||||||
|
constexpr uint8_t kLine1 = 0x74;
|
||||||
|
constexpr uint8_t kLine2 = 0x75;
|
||||||
|
constexpr uint8_t kLine3 = 0x76;
|
||||||
|
|
||||||
|
static const TextElement TextCommands[] = {
|
||||||
|
TextElement(0x6B, "W", true, "Window border"),
|
||||||
|
TextElement(0x6D, "P", true, "Window position"),
|
||||||
|
TextElement(0x6E, "SPD", true, "Scroll speed"),
|
||||||
|
TextElement(0x7A, "S", true, "Text draw speed"),
|
||||||
|
TextElement(0x77, "C", true, "Text color"),
|
||||||
|
TextElement(0x6A, "L", false, "Player name"),
|
||||||
|
TextElement(0x74, "1", false, "Line 1"),
|
||||||
|
TextElement(0x75, "2", false, "Line 2"),
|
||||||
|
TextElement(0x76, "3", false, "Line 3"),
|
||||||
|
TextElement(0x7E, "K", false, "Wait for key"),
|
||||||
|
TextElement(0x73, "V", false, "Scroll text"),
|
||||||
|
TextElement(0x78, "WT", true, "Delay X"),
|
||||||
|
TextElement(0x6C, "N", true, "BCD number"),
|
||||||
|
TextElement(0x79, "SFX", true, "Sound effect"),
|
||||||
|
TextElement(0x71, "CH3", false, "Choose 3"),
|
||||||
|
TextElement(0x72, "CH2", false, "Choose 2 high"),
|
||||||
|
TextElement(0x6F, "CH2L", false, "Choose 2 low"),
|
||||||
|
TextElement(0x68, "CH2I", false, "Choose 2 indented"),
|
||||||
|
TextElement(0x69, "CHI", false, "Choose item"),
|
||||||
|
TextElement(0x67, "IMG", false, "Next attract image"),
|
||||||
|
TextElement(0x80, BANKToken, false, "Bank marker (automatic)"),
|
||||||
|
TextElement(0x70, "NONO", false, "Crash"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<TextElement> SpecialChars = {
|
||||||
|
TextElement(0x43, "...", false, "Ellipsis …"),
|
||||||
|
TextElement(0x4D, "UP", false, "Arrow ↑"),
|
||||||
|
TextElement(0x4E, "DOWN", false, "Arrow ↓"),
|
||||||
|
TextElement(0x4F, "LEFT", false, "Arrow ←"),
|
||||||
|
TextElement(0x50, "RIGHT", false, "Arrow →"),
|
||||||
|
TextElement(0x5B, "A", false, "Button Ⓐ"),
|
||||||
|
TextElement(0x5C, "B", false, "Button Ⓑ"),
|
||||||
|
TextElement(0x5D, "X", false, "Button ⓧ"),
|
||||||
|
TextElement(0x5E, "Y", false, "Button ⓨ"),
|
||||||
|
TextElement(0x52, "HP1L", false, "1 HP left"),
|
||||||
|
TextElement(0x53, "HP1R", false, "1 HP right"),
|
||||||
|
TextElement(0x54, "HP2L", false, "2 HP left"),
|
||||||
|
TextElement(0x55, "HP3L", false, "3 HP left"),
|
||||||
|
TextElement(0x56, "HP3R", false, "3 HP right"),
|
||||||
|
TextElement(0x57, "HP4L", false, "4 HP left"),
|
||||||
|
TextElement(0x58, "HP4R", false, "4 HP right"),
|
||||||
|
TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
|
||||||
|
TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
|
||||||
|
TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
|
||||||
|
TextElement(0x4A, "LFL", false, "Link face left"),
|
||||||
|
TextElement(0x4B, "LFR", false, "Link face right"),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
|
||||||
|
{0x00, 'A'},
|
||||||
|
{0x01, 'B'},
|
||||||
|
{0x02, 'C'},
|
||||||
|
{0x03, 'D'},
|
||||||
|
{0x04, 'E'},
|
||||||
|
{0x05, 'F'},
|
||||||
|
{0x06, 'G'},
|
||||||
|
{0x07, 'H'},
|
||||||
|
{0x08, 'I'},
|
||||||
|
{0x09, 'J'},
|
||||||
|
{0x0A, 'K'},
|
||||||
|
{0x0B, 'L'},
|
||||||
|
{0x0C, 'M'},
|
||||||
|
{0x0D, 'N'},
|
||||||
|
{0x0E, 'O'},
|
||||||
|
{0x0F, 'P'},
|
||||||
|
{0x10, 'Q'},
|
||||||
|
{0x11, 'R'},
|
||||||
|
{0x12, 'S'},
|
||||||
|
{0x13, 'T'},
|
||||||
|
{0x14, 'U'},
|
||||||
|
{0x15, 'V'},
|
||||||
|
{0x16, 'W'},
|
||||||
|
{0x17, 'X'},
|
||||||
|
{0x18, 'Y'},
|
||||||
|
{0x19, 'Z'},
|
||||||
|
{0x1A, 'a'},
|
||||||
|
{0x1B, 'b'},
|
||||||
|
{0x1C, 'c'},
|
||||||
|
{0x1D, 'd'},
|
||||||
|
{0x1E, 'e'},
|
||||||
|
{0x1F, 'f'},
|
||||||
|
{0x20, 'g'},
|
||||||
|
{0x21, 'h'},
|
||||||
|
{0x22, 'i'},
|
||||||
|
{0x23, 'j'},
|
||||||
|
{0x24, 'k'},
|
||||||
|
{0x25, 'l'},
|
||||||
|
{0x26, 'm'},
|
||||||
|
{0x27, 'n'},
|
||||||
|
{0x28, 'o'},
|
||||||
|
{0x29, 'p'},
|
||||||
|
{0x2A, 'q'},
|
||||||
|
{0x2B, 'r'},
|
||||||
|
{0x2C, 's'},
|
||||||
|
{0x2D, 't'},
|
||||||
|
{0x2E, 'u'},
|
||||||
|
{0x2F, 'v'},
|
||||||
|
{0x30, 'w'},
|
||||||
|
{0x31, 'x'},
|
||||||
|
{0x32, 'y'},
|
||||||
|
{0x33, 'z'},
|
||||||
|
{0x34, '0'},
|
||||||
|
{0x35, '1'},
|
||||||
|
{0x36, '2'},
|
||||||
|
{0x37, '3'},
|
||||||
|
{0x38, '4'},
|
||||||
|
{0x39, '5'},
|
||||||
|
{0x3A, '6'},
|
||||||
|
{0x3B, '7'},
|
||||||
|
{0x3C, '8'},
|
||||||
|
{0x3D, '9'},
|
||||||
|
{0x3E, '!'},
|
||||||
|
{0x3F, '?'},
|
||||||
|
{0x40, '-'},
|
||||||
|
{0x41, '.'},
|
||||||
|
{0x42, ','},
|
||||||
|
{0x44, '>'},
|
||||||
|
{0x45, '('},
|
||||||
|
{0x46, ')'},
|
||||||
|
{0x4C, '"'},
|
||||||
|
{0x51, '\''},
|
||||||
|
{0x59, ' '},
|
||||||
|
{0x5A, '<'},
|
||||||
|
// {0x5F, '¡'}, {0x60, '¡'}, {0x61, '¡'}, {0x62, ' '}, {0x63, ' '}, {0x64,
|
||||||
|
// ' '},
|
||||||
|
{0x65, ' '},
|
||||||
|
{0x66, '_'},
|
||||||
|
};
|
||||||
|
|
||||||
|
static TextElement DictionaryElement =
|
||||||
|
TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
|
||||||
|
|
||||||
|
class MessageEditor : public Editor, public SharedRom {
|
||||||
|
public:
|
||||||
|
struct DictionaryEntry {
|
||||||
|
uint8_t ID;
|
||||||
|
std::string Contents;
|
||||||
|
std::vector<uint8_t> Data;
|
||||||
|
int Length;
|
||||||
|
std::string Token;
|
||||||
|
|
||||||
|
DictionaryEntry() = default;
|
||||||
|
DictionaryEntry(uint8_t i, std::string s)
|
||||||
|
: Contents(s), ID(i), Length(s.length()) {
|
||||||
|
Token = absl::StrFormat("[%s:%00X]", DICTIONARYTOKEN, ID);
|
||||||
|
Data = ParseMessageToData(Contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ContainedInString(std::string s) {
|
||||||
|
return s.find(Contents) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ReplaceInstancesOfIn(std::string s) {
|
||||||
|
std::string replacedString = s;
|
||||||
|
size_t pos = replacedString.find(Contents);
|
||||||
|
while (pos != std::string::npos) {
|
||||||
|
replacedString.replace(pos, Contents.length(), Token);
|
||||||
|
pos = replacedString.find(Contents, pos + Token.length());
|
||||||
|
}
|
||||||
|
return replacedString;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
MessageEditor() { type_ = EditorType::kMessage; }
|
||||||
|
|
||||||
|
absl::Status Update() override;
|
||||||
|
void DrawMessageList();
|
||||||
|
void DrawCurrentMessage();
|
||||||
|
void DrawTextCommands();
|
||||||
|
|
||||||
|
absl::Status Initialize();
|
||||||
|
void ReadAllTextData();
|
||||||
|
void BuildDictionaryEntries();
|
||||||
|
|
||||||
|
absl::Status Cut() override;
|
||||||
|
absl::Status Copy() override;
|
||||||
|
absl::Status Paste() override;
|
||||||
|
absl::Status Undo() override;
|
||||||
|
absl::Status Redo() override {
|
||||||
|
return absl::UnimplementedError("Redo not implemented");
|
||||||
|
}
|
||||||
|
absl::Status Find() override {
|
||||||
|
return absl::UnimplementedError("Find not implemented");
|
||||||
|
}
|
||||||
|
absl::Status Save();
|
||||||
|
void Delete();
|
||||||
|
void SelectAll();
|
||||||
|
// void RegisterTests(ImGuiTestEngine* e) override;
|
||||||
|
|
||||||
|
TextElement FindMatchingCommand(uint8_t byte);
|
||||||
|
TextElement FindMatchingSpecial(uint8_t value);
|
||||||
|
string ParseTextDataByte(uint8_t value);
|
||||||
|
DictionaryEntry GetDictionaryFromID(uint8_t value);
|
||||||
|
|
||||||
|
static uint8_t FindDictionaryEntry(uint8_t value);
|
||||||
|
static uint8_t FindMatchingCharacter(char value);
|
||||||
|
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
|
||||||
|
int sizex = 1, int sizey = 1);
|
||||||
|
void DrawCharacterToPreview(char c);
|
||||||
|
void DrawCharacterToPreview(const std::vector<uint8_t>& text);
|
||||||
|
|
||||||
|
void DrawStringToPreview(string str);
|
||||||
|
void DrawMessagePreview();
|
||||||
|
std::string DisplayTextOverflowError(int pos, bool bank);
|
||||||
|
|
||||||
|
void InsertCommandButton_Click_1();
|
||||||
|
void InsertSpecialButton_Click();
|
||||||
|
void InsertSelectedText(string str);
|
||||||
|
|
||||||
|
static const std::vector<DictionaryEntry> AllDicts;
|
||||||
|
|
||||||
|
uint8_t width_array[100];
|
||||||
|
string romname = "";
|
||||||
|
|
||||||
|
int text_line = 0;
|
||||||
|
int text_pos = 0;
|
||||||
|
int shown_lines = 0;
|
||||||
|
int selected_tile = 0;
|
||||||
|
|
||||||
|
bool skip_next = false;
|
||||||
|
bool from_form = false;
|
||||||
|
|
||||||
|
std::vector<MessageData> ListOfTexts;
|
||||||
|
std::vector<MessageData> DisplayedMessages;
|
||||||
|
std::vector<std::string> ParsedMessages;
|
||||||
|
|
||||||
|
MessageData CurrentMessage;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const TextElement DictionaryElement;
|
||||||
|
|
||||||
|
bool data_loaded_ = false;
|
||||||
|
int current_message_id_ = 0;
|
||||||
|
|
||||||
|
std::string search_text_ = "";
|
||||||
|
|
||||||
|
gui::Canvas font_gfx_canvas_{"##FontGfxCanvas", ImVec2(128, 128)};
|
||||||
|
gui::Canvas current_font_gfx16_canvas_{"##CurrentMessageGfx",
|
||||||
|
ImVec2(172, 4096)};
|
||||||
|
|
||||||
|
gfx::Bitmap font_gfx_bitmap_;
|
||||||
|
gfx::Bitmap current_font_gfx16_bitmap_;
|
||||||
|
|
||||||
|
Bytes font_gfx16_data;
|
||||||
|
Bytes current_font_gfx16_data_;
|
||||||
|
|
||||||
|
gfx::SnesPalette font_preview_colors_;
|
||||||
|
|
||||||
|
struct TextBox {
|
||||||
|
std::string text;
|
||||||
|
std::string buffer;
|
||||||
|
int cursor_pos = 0;
|
||||||
|
int selection_start = 0;
|
||||||
|
int selection_end = 0;
|
||||||
|
int selection_length = 0;
|
||||||
|
bool has_selection = false;
|
||||||
|
bool has_focus = false;
|
||||||
|
bool changed = false;
|
||||||
|
bool can_undo = false;
|
||||||
|
|
||||||
|
void Undo() {
|
||||||
|
text = buffer;
|
||||||
|
cursor_pos = selection_start;
|
||||||
|
has_selection = false;
|
||||||
|
}
|
||||||
|
void clearUndo() { can_undo = false; }
|
||||||
|
void Copy() { ImGui::SetClipboardText(text.c_str()); }
|
||||||
|
void Cut() {
|
||||||
|
Copy();
|
||||||
|
text.erase(selection_start, selection_end - selection_start);
|
||||||
|
cursor_pos = selection_start;
|
||||||
|
has_selection = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
void Paste() {
|
||||||
|
text.erase(selection_start, selection_end - selection_start);
|
||||||
|
text.insert(selection_start, ImGui::GetClipboardText());
|
||||||
|
std::string str = ImGui::GetClipboardText();
|
||||||
|
cursor_pos = selection_start + str.size();
|
||||||
|
has_selection = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
void clear() {
|
||||||
|
text.clear();
|
||||||
|
buffer.clear();
|
||||||
|
cursor_pos = 0;
|
||||||
|
selection_start = 0;
|
||||||
|
selection_end = 0;
|
||||||
|
selection_length = 0;
|
||||||
|
has_selection = false;
|
||||||
|
has_focus = false;
|
||||||
|
changed = false;
|
||||||
|
can_undo = false;
|
||||||
|
}
|
||||||
|
void SelectAll() {
|
||||||
|
selection_start = 0;
|
||||||
|
selection_end = text.size();
|
||||||
|
selection_length = text.size();
|
||||||
|
has_selection = true;
|
||||||
|
}
|
||||||
|
void Focus() { has_focus = true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
TextBox message_text_box_;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<MessageEditor::DictionaryEntry> AllDictionaries;
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_MESSAGE_EDITOR_H
|
||||||
7
src/app/editor/message/message_editor_test.cc
Normal file
7
src/app/editor/message/message_editor_test.cc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "message_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
#include "assembly_editor.h"
|
|
||||||
|
|
||||||
#include "app/gui/widgets.h"
|
|
||||||
#include "core/constants.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
AssemblyEditor::AssemblyEditor() {
|
|
||||||
text_editor_.SetLanguageDefinition(gui::GetAssemblyLanguageDef());
|
|
||||||
text_editor_.SetPalette(TextEditor::GetDarkPalette());
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssemblyEditor::Update(bool& is_loaded) {
|
|
||||||
ImGui::Begin("Assembly Editor", &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_view& filename) {
|
|
||||||
current_file_ = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AssemblyEditor::DrawFileView() {
|
|
||||||
ImGui::BeginTable("##table_view", 4,
|
|
||||||
ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg);
|
|
||||||
|
|
||||||
// Table headers
|
|
||||||
ImGui::TableSetupColumn("Files", ImGuiTableColumnFlags_WidthFixed, 150.0f);
|
|
||||||
ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_WidthFixed, 60.0f);
|
|
||||||
ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthFixed, 100.0f);
|
|
||||||
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch);
|
|
||||||
|
|
||||||
ImGui::TableHeadersRow();
|
|
||||||
|
|
||||||
// Table data
|
|
||||||
ImGui::TableNextRow();
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
// TODO: Add tree view of files
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
// TODO: Add line number
|
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
// TODO: Add address per line
|
|
||||||
|
|
||||||
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::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,41 +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(bool &is_loaded);
|
|
||||||
void InlineUpdate();
|
|
||||||
void ChangeActiveFile(const std::string_view &);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void DrawFileMenu();
|
|
||||||
void DrawEditMenu();
|
|
||||||
|
|
||||||
void DrawFileView();
|
|
||||||
|
|
||||||
void SetEditorText();
|
|
||||||
|
|
||||||
bool file_is_loaded_ = false;
|
|
||||||
|
|
||||||
std::string current_file_;
|
|
||||||
TextEditor text_editor_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
#include "gfx_group_editor.h"
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "absl/status/statusor.h"
|
|
||||||
#include "app/core/editor.h"
|
|
||||||
#include "app/editor/modules/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/gui/icons.h"
|
|
||||||
#include "app/gui/input.h"
|
|
||||||
#include "app/gui/pipeline.h"
|
|
||||||
#include "app/gui/widgets.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "app/zelda3/overworld.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
using ImGui::SameLine;
|
|
||||||
using ImGui::TableHeadersRow;
|
|
||||||
using ImGui::TableNextColumn;
|
|
||||||
using ImGui::TableNextRow;
|
|
||||||
using ImGui::TableSetupColumn;
|
|
||||||
|
|
||||||
absl::Status GfxGroupEditor::Update() {
|
|
||||||
if (ImGui::BeginTabBar("GfxGroupEditor")) {
|
|
||||||
if (ImGui::BeginTabItem("Main")) {
|
|
||||||
gui::InputHexByte("Selected Blockset", &selected_blockset_);
|
|
||||||
ImGui::Text("Values");
|
|
||||||
if (ImGui::BeginTable("##BlocksetTable", 2, ImGuiTableFlags_Borders,
|
|
||||||
ImVec2(0, 0))) {
|
|
||||||
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
|
||||||
TableHeadersRow();
|
|
||||||
TableNextRow();
|
|
||||||
TableNextColumn();
|
|
||||||
{
|
|
||||||
ImGui::BeginGroup();
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
ImGui::SetNextItemWidth(100.f);
|
|
||||||
gui::InputHexByte(("##blockset0" + std::to_string(i)).c_str(),
|
|
||||||
&rom()->main_blockset_ids[selected_blockset_][i]);
|
|
||||||
if (i != 3 && i != 7) {
|
|
||||||
SameLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndGroup();
|
|
||||||
}
|
|
||||||
TableNextColumn();
|
|
||||||
{
|
|
||||||
ImGui::BeginGroup();
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
int sheet_id = rom()->main_blockset_ids[selected_blockset_][i];
|
|
||||||
auto &sheet = *rom()->bitmap_manager()[sheet_id];
|
|
||||||
if (sheet_id != last_sheet_id_) {
|
|
||||||
last_sheet_id_ = sheet_id;
|
|
||||||
auto palette_group = rom()->palette_group("ow_main");
|
|
||||||
auto palette = palette_group[preview_palette_id_];
|
|
||||||
sheet.ApplyPalette(palette);
|
|
||||||
rom()->UpdateBitmap(&sheet);
|
|
||||||
}
|
|
||||||
gui::BitmapCanvasPipeline(blockset_canvas_, sheet, 256, 0x10 * 0x04,
|
|
||||||
0x20, true, false, 22);
|
|
||||||
}
|
|
||||||
ImGui::EndGroup();
|
|
||||||
}
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginTabItem("Rooms")) {
|
|
||||||
gui::InputHexByte("Selected Blockset", &selected_roomset_);
|
|
||||||
|
|
||||||
ImGui::Text("Values - Overwrites 4 of main blockset");
|
|
||||||
if (ImGui::BeginTable("##Roomstable", 2, ImGuiTableFlags_Borders,
|
|
||||||
ImVec2(0, 0))) {
|
|
||||||
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
|
||||||
TableHeadersRow();
|
|
||||||
TableNextRow();
|
|
||||||
TableNextColumn();
|
|
||||||
{
|
|
||||||
ImGui::BeginGroup();
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
ImGui::SetNextItemWidth(100.f);
|
|
||||||
gui::InputHexByte(("##roomset0" + std::to_string(i)).c_str(),
|
|
||||||
&rom()->room_blockset_ids[selected_roomset_][i]);
|
|
||||||
if (i != 3 && i != 7) {
|
|
||||||
SameLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndGroup();
|
|
||||||
}
|
|
||||||
TableNextColumn();
|
|
||||||
{
|
|
||||||
ImGui::BeginGroup();
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
int sheet_id = rom()->room_blockset_ids[selected_roomset_][i];
|
|
||||||
auto &sheet = *rom()->bitmap_manager()[sheet_id];
|
|
||||||
gui::BitmapCanvasPipeline(roomset_canvas_, sheet, 256, 0x10 * 0x04,
|
|
||||||
0x20, true, false, 23);
|
|
||||||
}
|
|
||||||
ImGui::EndGroup();
|
|
||||||
}
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginTabItem("Sprites")) {
|
|
||||||
gui::InputHexByte("Selected Spriteset", &selected_spriteset_);
|
|
||||||
|
|
||||||
ImGui::Text("Values");
|
|
||||||
if (ImGui::BeginTable("##SpritesTable", 2, ImGuiTableFlags_Borders,
|
|
||||||
ImVec2(0, 0))) {
|
|
||||||
TableSetupColumn("Inputs", ImGuiTableColumnFlags_WidthStretch,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
TableSetupColumn("Sheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
|
||||||
TableHeadersRow();
|
|
||||||
TableNextRow();
|
|
||||||
TableNextColumn();
|
|
||||||
{
|
|
||||||
ImGui::BeginGroup();
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
ImGui::SetNextItemWidth(100.f);
|
|
||||||
gui::InputHexByte(("##spriteset0" + std::to_string(i)).c_str(),
|
|
||||||
&rom()->spriteset_ids[selected_spriteset_][i]);
|
|
||||||
if (i != 3 && i != 7) {
|
|
||||||
SameLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::EndGroup();
|
|
||||||
}
|
|
||||||
TableNextColumn();
|
|
||||||
{
|
|
||||||
ImGui::BeginGroup();
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
int sheet_id = rom()->spriteset_ids[selected_spriteset_][i];
|
|
||||||
auto sheet = *rom()->bitmap_manager()[sheet_id];
|
|
||||||
gui::BitmapCanvasPipeline(spriteset_canvas_, sheet, 256,
|
|
||||||
0x10 * 0x04, 0x20, true, false, 24);
|
|
||||||
}
|
|
||||||
ImGui::EndGroup();
|
|
||||||
}
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginTabItem("Palettes")) {
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTabBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text("Palette: ");
|
|
||||||
ImGui::InputInt("##PreviewPaletteID", &preview_palette_id_);
|
|
||||||
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GfxGroupEditor::InitBlockset(gfx::Bitmap tile16_blockset) {
|
|
||||||
tile16_blockset_bmp_ = tile16_blockset;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
#include "palette_editor.h"
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "app/gfx/snes_palette.h"
|
|
||||||
#include "app/gui/canvas.h"
|
|
||||||
#include "app/gui/color.h"
|
|
||||||
#include "app/gui/icons.h"
|
|
||||||
|
|
||||||
static inline float ImSaturate(float f) {
|
|
||||||
return (f < 0.0f) ? 0.0f : (f > 1.0f) ? 1.0f : f;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define IM_F32_TO_INT8_SAT(_VAL) \
|
|
||||||
((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
absl::Status PaletteEditor::Update() {
|
|
||||||
if (ImGui::BeginTable("paletteEditorTable", 2,
|
|
||||||
ImGuiTableFlags_Reorderable |
|
|
||||||
ImGuiTableFlags_Resizable |
|
|
||||||
ImGuiTableFlags_SizingStretchSame,
|
|
||||||
ImVec2(0, 0))) {
|
|
||||||
ImGui::TableSetupColumn("Palette Groups",
|
|
||||||
ImGuiTableColumnFlags_WidthStretch,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
ImGui::TableSetupColumn("Editor", ImGuiTableColumnFlags_WidthStretch,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
ImGui::TableHeadersRow();
|
|
||||||
ImGui::TableNextRow();
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
for (int category = 0; category < kNumPalettes; ++category) {
|
|
||||||
if (ImGui::TreeNode(kPaletteCategoryNames[category].data())) {
|
|
||||||
status_ = DrawPaletteGroup(category);
|
|
||||||
ImGui::TreePop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::TableNextColumn();
|
|
||||||
ImGui::Text("Test Column");
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
CLEAR_AND_RETURN_STATUS(status_)
|
|
||||||
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaletteEditor::EditColorInPalette(gfx::SNESPalette& palette, int index) {
|
|
||||||
if (index >= palette.size()) {
|
|
||||||
// Handle error: the index is out of bounds
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current color
|
|
||||||
auto currentColor = palette.GetColor(index).GetRGB();
|
|
||||||
if (ImGui::ColorPicker4("Color Picker", (float*)&palette[index])) {
|
|
||||||
// The color was modified, update it in the palette
|
|
||||||
palette(index, currentColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaletteEditor::ResetColorToOriginal(
|
|
||||||
gfx::SNESPalette& palette, int index,
|
|
||||||
const gfx::SNESPalette& originalPalette) {
|
|
||||||
if (index >= palette.size() || index >= originalPalette.size()) {
|
|
||||||
// Handle error: the index is out of bounds
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto originalColor = originalPalette.GetColor(index).GetRGB();
|
|
||||||
palette(index, originalColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status PaletteEditor::DrawPaletteGroup(int category) {
|
|
||||||
if (!rom()->isLoaded()) {
|
|
||||||
return absl::NotFoundError("ROM not open, no palettes to display");
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto size =
|
|
||||||
rom()->palette_group(kPaletteGroupNames[category].data()).size();
|
|
||||||
auto palettes = rom()->palette_group(kPaletteGroupNames[category].data());
|
|
||||||
static bool edit_color = false;
|
|
||||||
for (int j = 0; j < size; j++) {
|
|
||||||
ImGui::Text("%d", j);
|
|
||||||
|
|
||||||
auto palette = palettes[j];
|
|
||||||
auto pal_size = palette.size();
|
|
||||||
|
|
||||||
for (int n = 0; n < pal_size; n++) {
|
|
||||||
ImGui::PushID(n);
|
|
||||||
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::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[n], palette_button_flags)) {
|
|
||||||
edit_color = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginPopupContextItem(popup_id.c_str())) {
|
|
||||||
RETURN_IF_ERROR(HandleColorPopup(palette, category, j, n))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edit_color) {
|
|
||||||
// The color button was clicked, open the popup
|
|
||||||
if (ImGui::ColorEdit4(popup_id.c_str(),
|
|
||||||
gfx::ToFloatArray(palette[n]).data(),
|
|
||||||
palette_button_flags)) {
|
|
||||||
EditColorInPalette(palette, n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status PaletteEditor::HandleColorPopup(gfx::SNESPalette& palette, int i,
|
|
||||||
int j, int n) {
|
|
||||||
auto col = gfx::ToFloatArray(palette[n]);
|
|
||||||
if (ImGui::ColorEdit4("Edit Color", col.data(), color_popup_flags)) {
|
|
||||||
RETURN_IF_ERROR(rom()->UpdatePaletteColor(kPaletteGroupNames[i].data(), j,
|
|
||||||
n, palette[n]))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::Button("Copy as..", ImVec2(-1, 0))) ImGui::OpenPopup("Copy");
|
|
||||||
|
|
||||||
if (ImGui::BeginPopup("Copy")) {
|
|
||||||
int cr = IM_F32_TO_INT8_SAT(col[0]);
|
|
||||||
int cg = IM_F32_TO_INT8_SAT(col[1]);
|
|
||||||
int cb = IM_F32_TO_INT8_SAT(col[2]);
|
|
||||||
char buf[64];
|
|
||||||
|
|
||||||
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff)", col[0],
|
|
||||||
col[1], col[2]);
|
|
||||||
|
|
||||||
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
|
|
||||||
CustomFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d)", cr, cg, cb);
|
|
||||||
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
|
|
||||||
CustomFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
|
|
||||||
if (ImGui::Selectable(buf)) ImGui::SetClipboardText(buf);
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndPopup();
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaletteEditor::DisplayPalette(gfx::SNESPalette& palette, bool loaded) {
|
|
||||||
static ImVec4 color = ImVec4(0, 0, 0, 255.f);
|
|
||||||
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) {
|
|
||||||
InitializeSavedPalette(palette);
|
|
||||||
init = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ImVec4 backup_color;
|
|
||||||
bool open_popup = ImGui::ColorButton("MyColor##3b", color, misc_flags);
|
|
||||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
|
||||||
open_popup |= ImGui::Button("Palette");
|
|
||||||
if (open_popup) {
|
|
||||||
ImGui::OpenPopup("mypicker");
|
|
||||||
backup_color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::BeginPopup("mypicker")) {
|
|
||||||
TEXT_WITH_SEPARATOR("Current Overworld Palette");
|
|
||||||
ImGui::ColorPicker4("##picker", (float*)&color,
|
|
||||||
misc_flags | ImGuiColorEditFlags_NoSidePreview |
|
|
||||||
ImGuiColorEditFlags_NoSmallPreview);
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
ImGui::BeginGroup(); // Lock X position
|
|
||||||
ImGui::Text("Current ==>");
|
|
||||||
ImGui::SameLine();
|
|
||||||
ImGui::Text("Previous");
|
|
||||||
|
|
||||||
if (ImGui::Button("Update Map Palette")) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::ColorButton(
|
|
||||||
"##current", color,
|
|
||||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
|
||||||
ImVec2(60, 40));
|
|
||||||
ImGui::SameLine();
|
|
||||||
|
|
||||||
if (ImGui::ColorButton(
|
|
||||||
"##previous", backup_color,
|
|
||||||
ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_AlphaPreviewHalf,
|
|
||||||
ImVec2(60, 40)))
|
|
||||||
color = backup_color;
|
|
||||||
|
|
||||||
// List of Colors in Overworld Palette
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text("Palette");
|
|
||||||
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
|
|
||||||
ImGui::PushID(n);
|
|
||||||
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
|
||||||
|
|
||||||
if (ImGui::ColorButton("##palette", saved_palette_[n],
|
|
||||||
palette_button_flags_2, ImVec2(20, 20)))
|
|
||||||
color = ImVec4(saved_palette_[n].x, saved_palette_[n].y,
|
|
||||||
saved_palette_[n].z, color.w); // Preserve alpha!
|
|
||||||
|
|
||||||
if (ImGui::BeginDragDropTarget()) {
|
|
||||||
if (const ImGuiPayload* payload =
|
|
||||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
|
||||||
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
|
|
||||||
if (const ImGuiPayload* payload =
|
|
||||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
|
||||||
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
|
|
||||||
ImGui::EndDragDropTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::EndGroup();
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PaletteEditor::DrawPortablePalette(gfx::SNESPalette& palette) {
|
|
||||||
static bool init = false;
|
|
||||||
if (!init) {
|
|
||||||
InitializeSavedPalette(palette);
|
|
||||||
init = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGuiID child_id = ImGui::GetID((void*)(intptr_t)100);
|
|
||||||
ImGui::BeginChild(child_id, ImGui::GetContentRegionAvail(), true,
|
|
||||||
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
|
|
||||||
ImGui::BeginGroup(); // Lock X position
|
|
||||||
ImGui::Text("Palette");
|
|
||||||
for (int n = 0; n < IM_ARRAYSIZE(saved_palette_); n++) {
|
|
||||||
ImGui::PushID(n);
|
|
||||||
if ((n % 8) != 0) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
|
|
||||||
|
|
||||||
if (ImGui::ColorButton("##palette", saved_palette_[n],
|
|
||||||
palette_button_flags_2, ImVec2(20, 20)))
|
|
||||||
ImVec4(saved_palette_[n].x, saved_palette_[n].y, saved_palette_[n].z,
|
|
||||||
1.0f); // Preserve alpha!
|
|
||||||
|
|
||||||
if (ImGui::BeginDragDropTarget()) {
|
|
||||||
if (const ImGuiPayload* payload =
|
|
||||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
|
|
||||||
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 3);
|
|
||||||
if (const ImGuiPayload* payload =
|
|
||||||
ImGui::AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
|
|
||||||
memcpy((float*)&saved_palette_[n], payload->Data, sizeof(float) * 4);
|
|
||||||
ImGui::EndDragDropTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PopID();
|
|
||||||
}
|
|
||||||
ImGui::EndGroup();
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
|
||||||
#define YAZE_APP_EDITOR_PALETTE_EDITOR_H
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "app/gfx/snes_palette.h"
|
|
||||||
#include "app/gui/canvas.h"
|
|
||||||
#include "app/gui/icons.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
constexpr int kNumPalettes = 11;
|
|
||||||
|
|
||||||
static constexpr absl::string_view kPaletteCategoryNames[] = {
|
|
||||||
"Sword", "Shield", "Clothes", "World Colors",
|
|
||||||
"Area Colors", "Enemies", "Dungeons", "World Map",
|
|
||||||
"Dungeon Map", "Triforce", "Crystal"};
|
|
||||||
|
|
||||||
static constexpr absl::string_view kPaletteGroupNames[] = {
|
|
||||||
"swords", "shields", "armors", "ow_main",
|
|
||||||
"ow_aux", "global_sprites", "dungeon_main", "ow_mini_map",
|
|
||||||
"ow_mini_map", "3d_object", "3d_object"};
|
|
||||||
|
|
||||||
struct PaletteChange {
|
|
||||||
std::string groupName;
|
|
||||||
size_t paletteIndex;
|
|
||||||
size_t colorIndex;
|
|
||||||
gfx::SNESColor originalColor;
|
|
||||||
gfx::SNESColor newColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PaletteEditorHistory {
|
|
||||||
public:
|
|
||||||
// Record a change in the palette editor
|
|
||||||
void RecordChange(const std::string& groupName, size_t paletteIndex,
|
|
||||||
size_t colorIndex, const gfx::SNESColor& originalColor,
|
|
||||||
const gfx::SNESColor& newColor) {
|
|
||||||
// Check size and remove the oldest if necessary
|
|
||||||
if (recentChanges.size() >= maxHistorySize) {
|
|
||||||
recentChanges.pop_front();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the new change
|
|
||||||
recentChanges.push_back(
|
|
||||||
{groupName, paletteIndex, colorIndex, originalColor, newColor});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get recent changes for display in the palette editor
|
|
||||||
const std::deque<PaletteChange>& GetRecentChanges() const {
|
|
||||||
return recentChanges;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the original color
|
|
||||||
gfx::SNESColor GetOriginalColor(const std::string& groupName,
|
|
||||||
size_t paletteIndex,
|
|
||||||
size_t colorIndex) const {
|
|
||||||
for (const auto& change : recentChanges) {
|
|
||||||
if (change.groupName == groupName &&
|
|
||||||
change.paletteIndex == paletteIndex &&
|
|
||||||
change.colorIndex == colorIndex) {
|
|
||||||
return change.originalColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Handle error or return default (this is just an example,
|
|
||||||
// handle as appropriate for your application)
|
|
||||||
return gfx::SNESColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::deque<PaletteChange> recentChanges;
|
|
||||||
static const size_t maxHistorySize = 50; // or any other number you deem fit
|
|
||||||
};
|
|
||||||
|
|
||||||
class PaletteEditor : public SharedROM {
|
|
||||||
public:
|
|
||||||
absl::Status Update();
|
|
||||||
absl::Status DrawPaletteGroups();
|
|
||||||
|
|
||||||
void EditColorInPalette(gfx::SNESPalette& palette, int index);
|
|
||||||
void ResetColorToOriginal(gfx::SNESPalette& palette, int index,
|
|
||||||
const gfx::SNESPalette& originalPalette);
|
|
||||||
void DisplayPalette(gfx::SNESPalette& palette, bool loaded);
|
|
||||||
void DrawPortablePalette(gfx::SNESPalette& palette);
|
|
||||||
|
|
||||||
private:
|
|
||||||
absl::Status DrawPaletteGroup(int category);
|
|
||||||
absl::Status HandleColorPopup(gfx::SNESPalette& palette, int i, int j, int n);
|
|
||||||
|
|
||||||
void InitializeSavedPalette(const gfx::SNESPalette& palette) {
|
|
||||||
for (int n = 0; n < palette.size(); n++) {
|
|
||||||
saved_palette_[n].x = palette.GetColor(n).GetRGB().x / 255;
|
|
||||||
saved_palette_[n].y = palette.GetColor(n).GetRGB().y / 255;
|
|
||||||
saved_palette_[n].z = palette.GetColor(n).GetRGB().z / 255;
|
|
||||||
saved_palette_[n].w = 255; // Alpha
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status status_;
|
|
||||||
|
|
||||||
PaletteEditorHistory history_;
|
|
||||||
|
|
||||||
ImVec4 saved_palette_[256] = {};
|
|
||||||
ImVec4 current_color_;
|
|
||||||
|
|
||||||
ImGuiColorEditFlags color_popup_flags =
|
|
||||||
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoAlpha;
|
|
||||||
ImGuiColorEditFlags palette_button_flags =
|
|
||||||
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoTooltip;
|
|
||||||
ImGuiColorEditFlags palette_button_flags_2 = ImGuiColorEditFlags_NoAlpha |
|
|
||||||
ImGuiColorEditFlags_NoPicker |
|
|
||||||
ImGuiColorEditFlags_NoTooltip;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
#include "tile16_editor.h"
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
#include "absl/status/statusor.h"
|
|
||||||
#include "app/core/editor.h"
|
|
||||||
#include "app/editor/modules/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/gui/icons.h"
|
|
||||||
#include "app/gui/input.h"
|
|
||||||
#include "app/gui/pipeline.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "app/zelda3/overworld.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
using ImGui::BeginChild;
|
|
||||||
using ImGui::BeginTabBar;
|
|
||||||
using ImGui::BeginTabItem;
|
|
||||||
using ImGui::BeginTable;
|
|
||||||
using ImGui::Combo;
|
|
||||||
using ImGui::EndChild;
|
|
||||||
using ImGui::EndTabBar;
|
|
||||||
using ImGui::EndTabItem;
|
|
||||||
using ImGui::TableHeadersRow;
|
|
||||||
using ImGui::TableNextColumn;
|
|
||||||
using ImGui::TableNextRow;
|
|
||||||
using ImGui::TableSetupColumn;
|
|
||||||
|
|
||||||
absl::Status Tile16Editor::Update() {
|
|
||||||
// Create a tab for Tile16 Editing
|
|
||||||
static bool start_task = false;
|
|
||||||
if (ImGui::Button("Test")) {
|
|
||||||
start_task = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start_task && !map_blockset_loaded_) {
|
|
||||||
LoadTile8();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a tab bar for Tile16 Editing and Tile16 Transfer
|
|
||||||
if (BeginTabBar("Tile16 Editor Tabs")) {
|
|
||||||
if (BeginTabItem("Tile16 Editing")) {
|
|
||||||
if (BeginTable("#Tile16EditorTable", 2, TABLE_BORDERS_RESIZABLE,
|
|
||||||
ImVec2(0, 0))) {
|
|
||||||
TableSetupColumn("Tiles", ImGuiTableColumnFlags_WidthFixed,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthStretch,
|
|
||||||
ImGui::GetContentRegionAvail().x);
|
|
||||||
TableHeadersRow();
|
|
||||||
TableNextRow();
|
|
||||||
TableNextColumn();
|
|
||||||
RETURN_IF_ERROR(UpdateBlockset());
|
|
||||||
|
|
||||||
TableNextColumn();
|
|
||||||
RETURN_IF_ERROR(UpdateTile16Edit());
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a tab for Tile16 Transfer
|
|
||||||
if (BeginTabItem("Tile16 Transfer")) {
|
|
||||||
if (BeginTable("#Tile16TransferTable", 2,
|
|
||||||
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable,
|
|
||||||
ImVec2(0, 0))) {
|
|
||||||
TableSetupColumn("Current ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
|
||||||
ImGui::GetContentRegionAvail().x / 2);
|
|
||||||
TableSetupColumn("Transfer ROM Tiles", ImGuiTableColumnFlags_WidthFixed,
|
|
||||||
ImGui::GetContentRegionAvail().x / 2);
|
|
||||||
TableHeadersRow();
|
|
||||||
TableNextRow();
|
|
||||||
|
|
||||||
TableNextColumn();
|
|
||||||
RETURN_IF_ERROR(UpdateBlockset());
|
|
||||||
|
|
||||||
TableNextColumn();
|
|
||||||
RETURN_IF_ERROR(UpdateTransferTileCanvas());
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTabItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTabBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status Tile16Editor::UpdateBlockset() {
|
|
||||||
gui::BitmapCanvasPipeline(blockset_canvas_, tile16_blockset_bmp_, 0x100,
|
|
||||||
(8192 * 2), 0x20, map_blockset_loaded_, true, 55);
|
|
||||||
|
|
||||||
if (!blockset_canvas_.Points().empty()) {
|
|
||||||
uint16_t x = blockset_canvas_.Points().front().x / 32;
|
|
||||||
uint16_t y = blockset_canvas_.Points().front().y / 32;
|
|
||||||
|
|
||||||
notify_tile16.mutable_get() = x + (y * 8);
|
|
||||||
notify_tile16.apply_changes();
|
|
||||||
if (notify_tile16.modified()) {
|
|
||||||
current_tile16_bmp_ = tile16_individual_[notify_tile16];
|
|
||||||
current_tile16_bmp_.ApplyPalette(
|
|
||||||
rom()->palette_group("ow_main")[current_palette_]);
|
|
||||||
rom()->RenderBitmap(¤t_tile16_bmp_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status Tile16Editor::UpdateTile16Edit() {
|
|
||||||
if (ImGui::BeginChild("Tile8 Selector",
|
|
||||||
ImVec2(ImGui::GetContentRegionAvail().x, 0x100),
|
|
||||||
true)) {
|
|
||||||
tile8_source_canvas_.DrawBackground(
|
|
||||||
ImVec2(core::kTilesheetWidth * 2, core::kTilesheetHeight * 0x10 * 2));
|
|
||||||
tile8_source_canvas_.DrawContextMenu();
|
|
||||||
tile8_source_canvas_.DrawTileSelector(16);
|
|
||||||
tile8_source_canvas_.DrawBitmap(current_gfx_bmp_, 0, 0, 2.0f);
|
|
||||||
tile8_source_canvas_.DrawGrid(16.0f);
|
|
||||||
tile8_source_canvas_.DrawOverlay();
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
|
|
||||||
if (ImGui::BeginChild("Tile16 Editor Options",
|
|
||||||
ImVec2(ImGui::GetContentRegionAvail().x, 0x50), true)) {
|
|
||||||
tile16_edit_canvas_.DrawBackground(ImVec2(0x40, 0x40));
|
|
||||||
tile16_edit_canvas_.DrawContextMenu();
|
|
||||||
// if (current_tile8_bmp_.modified()) {
|
|
||||||
// rom()->UpdateBitmap(¤t_tile8_bmp_);
|
|
||||||
// current_tile8_bmp_.set_modified(false);
|
|
||||||
// }
|
|
||||||
tile16_edit_canvas_.DrawBitmap(current_tile16_bmp_, 0, 0, 4.0f);
|
|
||||||
tile16_edit_canvas_.HandleTileEdits(
|
|
||||||
tile8_source_canvas_, current_gfx_individual_, current_tile8_bmp_,
|
|
||||||
current_tile8_, 2.0f);
|
|
||||||
|
|
||||||
tile16_edit_canvas_.DrawGrid(128.0f);
|
|
||||||
tile16_edit_canvas_.DrawOverlay();
|
|
||||||
}
|
|
||||||
ImGui::EndChild();
|
|
||||||
DrawTileEditControls();
|
|
||||||
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tile16Editor::DrawTileEditControls() {
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text("Options:");
|
|
||||||
gui::InputHexByte("Palette", ¬ify_palette.mutable_get());
|
|
||||||
notify_palette.apply_changes();
|
|
||||||
if (notify_palette.modified()) {
|
|
||||||
current_gfx_bmp_.ApplyPalette(
|
|
||||||
rom()->palette_group("ow_main")[notify_palette.get()]);
|
|
||||||
current_tile16_bmp_.ApplyPalette(
|
|
||||||
rom()->palette_group("ow_main")[notify_palette.get()]);
|
|
||||||
rom()->UpdateBitmap(¤t_gfx_bmp_);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Checkbox("X Flip", &x_flip);
|
|
||||||
ImGui::Checkbox("Y Flip", &y_flip);
|
|
||||||
ImGui::Checkbox("Priority Tile", &priority_tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status Tile16Editor::UpdateTransferTileCanvas() {
|
|
||||||
// Create a button for loading another ROM
|
|
||||||
if (ImGui::Button("Load ROM")) {
|
|
||||||
ImGuiFileDialog::Instance()->OpenDialog(
|
|
||||||
"ChooseTransferFileDlgKey", "Open Transfer ROM", ".sfc,.smc", ".");
|
|
||||||
}
|
|
||||||
gui::FileDialogPipeline(
|
|
||||||
"ChooseTransferFileDlgKey", ".sfc,.smc", std::nullopt, [&]() {
|
|
||||||
std::string filePathName =
|
|
||||||
ImGuiFileDialog::Instance()->GetFilePathName();
|
|
||||||
transfer_status_ = transfer_rom_.LoadFromFile(filePathName);
|
|
||||||
transfer_started_ = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (transfer_started_ && !transfer_blockset_loaded_) {
|
|
||||||
PRINT_IF_ERROR(transfer_rom_.LoadAllGraphicsData())
|
|
||||||
graphics_bin_ = transfer_rom_.graphics_bin();
|
|
||||||
|
|
||||||
// Load the Link to the Past overworld.
|
|
||||||
PRINT_IF_ERROR(transfer_overworld_.Load(transfer_rom_))
|
|
||||||
transfer_overworld_.SetCurrentMap(0);
|
|
||||||
palette_ = transfer_overworld_.AreaPalette();
|
|
||||||
|
|
||||||
// Create the tile16 blockset image
|
|
||||||
gui::BuildAndRenderBitmapPipeline(0x80, 0x2000, 0x80,
|
|
||||||
transfer_overworld_.Tile16Blockset(),
|
|
||||||
*rom(), 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
using core::TaskManager;
|
|
||||||
|
|
||||||
absl::Status Tile16Editor::InitBlockset(
|
|
||||||
const gfx::Bitmap& tile16_blockset_bmp, gfx::Bitmap current_gfx_bmp,
|
|
||||||
const std::vector<gfx::Bitmap>& tile16_individual) {
|
|
||||||
tile16_blockset_bmp_ = tile16_blockset_bmp;
|
|
||||||
tile16_individual_ = tile16_individual;
|
|
||||||
current_gfx_bmp_ = current_gfx_bmp;
|
|
||||||
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::Status Tile16Editor::LoadTile8() {
|
|
||||||
current_gfx_individual_.reserve(128);
|
|
||||||
|
|
||||||
// Define the task function
|
|
||||||
std::function<void(int)> taskFunc = [&](int index) {
|
|
||||||
auto current_gfx_data = current_gfx_bmp_.mutable_data();
|
|
||||||
std::vector<uint8_t> tile_data;
|
|
||||||
tile_data.reserve(0x40);
|
|
||||||
for (int i = 0; i < 0x40; i++) {
|
|
||||||
tile_data.emplace_back(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++) {
|
|
||||||
int position = tx + (ty * 0x08);
|
|
||||||
uint8_t value =
|
|
||||||
current_gfx_data[(index % 16 * 32) + (index / 16 * 32 * 0x80) +
|
|
||||||
(ty * 0x80) + tx];
|
|
||||||
tile_data[position] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
current_gfx_individual_.emplace_back();
|
|
||||||
current_gfx_individual_[index].Create(0x08, 0x08, 0x80, tile_data);
|
|
||||||
current_gfx_individual_[index].ApplyPalette(
|
|
||||||
rom()->palette_group("ow_main")[current_palette_]);
|
|
||||||
rom()->RenderBitmap(¤t_gfx_individual_[index]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the task manager
|
|
||||||
static bool started = false;
|
|
||||||
if (!started) {
|
|
||||||
task_manager_ = TaskManager<std::function<void(int)>>(127, 1);
|
|
||||||
started = true;
|
|
||||||
}
|
|
||||||
task_manager_.ExecuteTasks(taskFunc);
|
|
||||||
|
|
||||||
if (task_manager_.IsTaskComplete()) {
|
|
||||||
// All tasks are complete
|
|
||||||
current_tile8_bmp_ = current_gfx_individual_[0];
|
|
||||||
map_blockset_loaded_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// auto current_gfx_data = current_gfx_bmp_.mutable_data();
|
|
||||||
// for (int i = 0; i < 128; i++) {
|
|
||||||
// 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++) {
|
|
||||||
// int position = tx + (ty * 0x10);
|
|
||||||
// uint8_t value = current_gfx_data[(i % 16 * 32) + (i / 16 * 32 * 0x80)
|
|
||||||
// +
|
|
||||||
// (ty * 0x80) + tx];
|
|
||||||
// tile_data[position] = value;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// current_gfx_individual_data_.emplace_back(tile_data);
|
|
||||||
// current_gfx_individual_.emplace_back();
|
|
||||||
// current_gfx_individual_[i].Create(0x08, 0x08, 0x80, tile_data);
|
|
||||||
// current_gfx_individual_[i].ApplyPalette(
|
|
||||||
// rom()->palette_group("ow_main")[current_palette_]);
|
|
||||||
// rom()->RenderBitmap(¤t_gfx_individual_[i]);
|
|
||||||
// }
|
|
||||||
return absl::OkStatus();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,71 +1,18 @@
|
|||||||
#include "music_editor.h"
|
#include "music_editor.h"
|
||||||
|
|
||||||
#include <SDL_mixer.h>
|
#include "imgui/imgui.h"
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
#include "app/editor/modules/assembly_editor.h"
|
#include "app/editor/code/assembly_editor.h"
|
||||||
#include "app/gui/canvas.h"
|
#include "app/gui/canvas.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "app/gui/input.h"
|
#include "app/gui/input.h"
|
||||||
// #include "snes_spc/demo/demo_util.h"
|
|
||||||
// #include "snes_spc/demo/wave_writer.h"
|
|
||||||
// #include "snes_spc/snes_spc/spc.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace editor {
|
namespace editor {
|
||||||
|
|
||||||
namespace {
|
absl::Status MusicEditor::Update() {
|
||||||
|
|
||||||
#define BUF_SIZE 2048
|
|
||||||
|
|
||||||
void PlaySPC() {
|
|
||||||
// /* Create emulator and filter */
|
|
||||||
// SNES_SPC* snes_spc = spc_new();
|
|
||||||
// SPC_Filter* filter = spc_filter_new();
|
|
||||||
// if (!snes_spc || !filter) error("Out of memory");
|
|
||||||
|
|
||||||
// /* Load SPC */
|
|
||||||
// {
|
|
||||||
// /* Load file into memory */
|
|
||||||
// long spc_size;
|
|
||||||
// void* spc = load_file("assets/music/hyrule_field.spc", &spc_size);
|
|
||||||
|
|
||||||
// /* Load SPC data into emulator */
|
|
||||||
// error(spc_load_spc(snes_spc, spc, spc_size));
|
|
||||||
// free(spc); /* emulator makes copy of data */
|
|
||||||
|
|
||||||
// /* Most SPC files have garbage data in the echo buffer, so clear that */
|
|
||||||
// spc_clear_echo(snes_spc);
|
|
||||||
|
|
||||||
// /* Clear filter before playing */
|
|
||||||
// spc_filter_clear(filter);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /* Record 20 seconds to wave file */
|
|
||||||
// wave_open(spc_sample_rate, "out.wav");
|
|
||||||
// wave_enable_stereo();
|
|
||||||
// while (wave_sample_count() < 30 * spc_sample_rate * 2) {
|
|
||||||
// /* Play into buffer */
|
|
||||||
// short buf[BUF_SIZE];
|
|
||||||
// error(spc_play(snes_spc, BUF_SIZE, buf));
|
|
||||||
|
|
||||||
// /* Filter samples */
|
|
||||||
// spc_filter_run(filter, buf, BUF_SIZE);
|
|
||||||
|
|
||||||
// wave_write(buf, BUF_SIZE);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /* Cleanup */
|
|
||||||
// spc_filter_delete(filter);
|
|
||||||
// spc_delete(snes_spc);
|
|
||||||
// wave_close();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void MusicEditor::Update() {
|
|
||||||
if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
|
if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
|
||||||
ImVec2(0, 0))) {
|
ImVec2(0, 0))) {
|
||||||
ImGui::TableSetupColumn("Assembly");
|
ImGui::TableSetupColumn("Assembly");
|
||||||
@@ -83,6 +30,8 @@ void MusicEditor::Update() {
|
|||||||
|
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MusicEditor::DrawChannels() {
|
void MusicEditor::DrawChannels() {
|
||||||
@@ -221,31 +170,8 @@ void MusicEditor::DrawToolset() {
|
|||||||
static bool has_loaded_song = false;
|
static bool has_loaded_song = false;
|
||||||
const int MAX_VOLUME = 100;
|
const int MAX_VOLUME = 100;
|
||||||
|
|
||||||
if (is_playing) {
|
if (is_playing && !has_loaded_song) {
|
||||||
if (!has_loaded_song) {
|
has_loaded_song = true;
|
||||||
// PlaySPC();
|
|
||||||
// current_song_ = Mix_LoadMUS("out.wav");
|
|
||||||
// Mix_PlayMusic(current_song_, -1);
|
|
||||||
// has_loaded_song = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// // If there is no music playing
|
|
||||||
// if (Mix_PlayingMusic() == 0) {
|
|
||||||
// Mix_PlayMusic(current_song_, -1);
|
|
||||||
// }
|
|
||||||
// // If music is being played
|
|
||||||
// else {
|
|
||||||
// // If the music is paused
|
|
||||||
// if (Mix_PausedMusic() == 1) {
|
|
||||||
// // Resume the music
|
|
||||||
// Mix_ResumeMusic();
|
|
||||||
// }
|
|
||||||
// // If the music is playing
|
|
||||||
// else {
|
|
||||||
// // Pause the music
|
|
||||||
// Mix_PauseMusic();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
|
gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
|
||||||
@@ -264,7 +190,6 @@ void MusicEditor::DrawToolset() {
|
|||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
|
if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
|
||||||
if (is_playing) {
|
if (is_playing) {
|
||||||
Mix_HaltMusic();
|
|
||||||
has_loaded_song = false;
|
has_loaded_song = false;
|
||||||
}
|
}
|
||||||
is_playing = !is_playing;
|
is_playing = !is_playing;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
#ifndef YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
||||||
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
#define YAZE_APP_EDITOR_MUSIC_EDITOR_H
|
||||||
|
|
||||||
#include <SDL_mixer.h>
|
#include "imgui/imgui.h"
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
#include "app/editor/modules/assembly_editor.h"
|
#include "app/editor/code/assembly_editor.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
#include "app/gui/canvas.h"
|
#include "app/gui/canvas.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "app/gui/input.h"
|
#include "app/gui/input.h"
|
||||||
@@ -54,9 +54,23 @@ static const char* kGameSongs[] = {"Title",
|
|||||||
static constexpr absl::string_view kSongNotes[] = {
|
static constexpr absl::string_view kSongNotes[] = {
|
||||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C",
|
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C",
|
||||||
"C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
|
"C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", "C"};
|
||||||
class MusicEditor : public SharedROM {
|
|
||||||
|
/**
|
||||||
|
* @class MusicEditor
|
||||||
|
* @brief A class for editing music data in a Rom.
|
||||||
|
*/
|
||||||
|
class MusicEditor : public SharedRom, public Editor {
|
||||||
public:
|
public:
|
||||||
void Update();
|
MusicEditor() { type_ = EditorType::kMusic; }
|
||||||
|
|
||||||
|
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:
|
private:
|
||||||
void DrawChannels();
|
void DrawChannels();
|
||||||
@@ -65,7 +79,7 @@ class MusicEditor : public SharedROM {
|
|||||||
void DrawSongToolset();
|
void DrawSongToolset();
|
||||||
void DrawToolset();
|
void DrawToolset();
|
||||||
|
|
||||||
zelda3::Tracker music_tracker_;
|
zelda3::music::Tracker music_tracker_;
|
||||||
|
|
||||||
// Mix_Music* current_song_ = NULL;
|
// Mix_Music* current_song_ = NULL;
|
||||||
|
|
||||||
490
src/app/editor/overworld/entity.cc
Normal file
490
src/app/editor/overworld/entity.cc
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
#include "app/editor/overworld/entity.h"
|
||||||
|
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
#include "app/gui/style.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginGroup;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::Checkbox;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::SameLine;
|
||||||
|
using ImGui::Selectable;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
|
||||||
|
ImVec2 canvas_p0, ImVec2 scrolling) {
|
||||||
|
// Get the mouse position relative to the canvas
|
||||||
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
|
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||||
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||||
|
|
||||||
|
// Check if the mouse is hovering over the entity
|
||||||
|
if (mouse_pos.x >= entity.x_ && mouse_pos.x <= entity.x_ + 16 &&
|
||||||
|
mouse_pos.y >= entity.y_ && mouse_pos.y <= entity.y_ + 16) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
|
||||||
|
ImVec2 scrolling, bool free_movement) {
|
||||||
|
// Get the mouse position relative to the canvas
|
||||||
|
const ImGuiIO &io = ImGui::GetIO();
|
||||||
|
const ImVec2 origin(canvas_p0.x + scrolling.x, canvas_p0.y + scrolling.y);
|
||||||
|
const ImVec2 mouse_pos(io.MousePos.x - origin.x, io.MousePos.y - origin.y);
|
||||||
|
|
||||||
|
// Calculate the new position on the 16x16 grid
|
||||||
|
int new_x = static_cast<int>(mouse_pos.x) / 16 * 16;
|
||||||
|
int new_y = static_cast<int>(mouse_pos.y) / 16 * 16;
|
||||||
|
if (free_movement) {
|
||||||
|
new_x = static_cast<int>(mouse_pos.x) / 8 * 8;
|
||||||
|
new_y = static_cast<int>(mouse_pos.y) / 8 * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the entity position
|
||||||
|
entity->set_x(new_x);
|
||||||
|
entity->set_y(new_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
|
||||||
|
ImVec2 scrolling, bool &is_dragging_entity,
|
||||||
|
zelda3::OverworldEntity *&dragged_entity,
|
||||||
|
zelda3::OverworldEntity *¤t_entity,
|
||||||
|
bool free_movement) {
|
||||||
|
std::string entity_type = "Entity";
|
||||||
|
if (entity->type_ == zelda3::OverworldEntity::EntityType::kExit) {
|
||||||
|
entity_type = "Exit";
|
||||||
|
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kEntrance) {
|
||||||
|
entity_type = "Entrance";
|
||||||
|
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kSprite) {
|
||||||
|
entity_type = "Sprite";
|
||||||
|
} else if (entity->type_ == zelda3::OverworldEntity::EntityType::kItem) {
|
||||||
|
entity_type = "Item";
|
||||||
|
}
|
||||||
|
const auto is_hovering =
|
||||||
|
IsMouseHoveringOverEntity(*entity, canvas_p0, scrolling);
|
||||||
|
|
||||||
|
const auto drag_or_clicked = ImGui::IsMouseDragging(ImGuiMouseButton_Left) ||
|
||||||
|
ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||||
|
|
||||||
|
if (is_hovering && drag_or_clicked && !is_dragging_entity) {
|
||||||
|
dragged_entity = entity;
|
||||||
|
is_dragging_entity = true;
|
||||||
|
} else if (is_hovering && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
|
current_entity = entity;
|
||||||
|
ImGui::OpenPopup(absl::StrFormat("%s editor", entity_type.c_str()).c_str());
|
||||||
|
} else if (is_dragging_entity && dragged_entity == entity &&
|
||||||
|
ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||||
|
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
|
||||||
|
entity->UpdateMapProperties(entity->map_id_);
|
||||||
|
is_dragging_entity = false;
|
||||||
|
dragged_entity = nullptr;
|
||||||
|
} else if (is_dragging_entity && dragged_entity == entity) {
|
||||||
|
if (ImGui::BeginDragDropSource()) {
|
||||||
|
ImGui::SetDragDropPayload("ENTITY_PAYLOAD", &entity,
|
||||||
|
sizeof(zelda3::OverworldEntity));
|
||||||
|
Text("Moving %s ID: %s", entity_type.c_str(),
|
||||||
|
core::UppercaseHexByte(entity->entity_id_).c_str());
|
||||||
|
ImGui::EndDragDropSource();
|
||||||
|
}
|
||||||
|
MoveEntityOnGrid(dragged_entity, canvas_p0, scrolling, free_movement);
|
||||||
|
entity->x_ = dragged_entity->x_;
|
||||||
|
entity->y_ = dragged_entity->y_;
|
||||||
|
entity->UpdateMapProperties(entity->map_id_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawEntranceInserterPopup() {
|
||||||
|
bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopup("Entrance Inserter")) {
|
||||||
|
static int entrance_id = 0;
|
||||||
|
gui::InputHex("Entrance ID", &entrance_id);
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deleting OverworldEntrance objects, currently only hides them
|
||||||
|
bool DrawOverworldEntrancePopup(
|
||||||
|
zelda3::overworld::OverworldEntrance &entrance) {
|
||||||
|
static bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Entrance editor", NULL,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
gui::InputHex("Map ID", &entrance.map_id_);
|
||||||
|
gui::InputHexByte("Entrance ID", &entrance.entrance_id_,
|
||||||
|
kInputFieldSize + 20);
|
||||||
|
gui::InputHex("X", &entrance.x_);
|
||||||
|
gui::InputHex("Y", &entrance.y_);
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_DELETE)) {
|
||||||
|
entrance.deleted = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deleting OverworldExit objects
|
||||||
|
void DrawExitInserterPopup() {
|
||||||
|
if (ImGui::BeginPopup("Exit Inserter")) {
|
||||||
|
static int exit_id = 0;
|
||||||
|
gui::InputHex("Exit ID", &exit_id);
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit) {
|
||||||
|
static bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Exit editor", NULL,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
// Normal door: None = 0, Wooden = 1, Bombable = 2
|
||||||
|
static int doorType = exit.door_type_1_;
|
||||||
|
// Fancy door: None = 0, Sanctuary = 1, Palace = 2
|
||||||
|
static int fancyDoorType = exit.door_type_2_;
|
||||||
|
|
||||||
|
static int xPos = 0;
|
||||||
|
static int yPos = 0;
|
||||||
|
|
||||||
|
// Special overworld exit properties
|
||||||
|
static int centerY = 0;
|
||||||
|
static int centerX = 0;
|
||||||
|
static int unk1 = 0;
|
||||||
|
static int unk2 = 0;
|
||||||
|
static int linkPosture = 0;
|
||||||
|
static int spriteGFX = 0;
|
||||||
|
static int bgGFX = 0;
|
||||||
|
static int palette = 0;
|
||||||
|
static int sprPal = 0;
|
||||||
|
static int top = 0;
|
||||||
|
static int bottom = 0;
|
||||||
|
static int left = 0;
|
||||||
|
static int right = 0;
|
||||||
|
static int leftEdgeOfMap = 0;
|
||||||
|
|
||||||
|
gui::InputHexWord("Room", &exit.room_id_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHex("Entity ID", &exit.entity_id_, 4);
|
||||||
|
gui::InputHex("Map", &exit.map_id_);
|
||||||
|
SameLine();
|
||||||
|
Checkbox("Automatic", &exit.is_automatic_);
|
||||||
|
|
||||||
|
gui::InputHex("X Positon", &exit.x_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHex("Y Position", &exit.y_);
|
||||||
|
|
||||||
|
gui::InputHexByte("X Camera", &exit.x_camera_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexByte("Y Camera", &exit.y_camera_);
|
||||||
|
|
||||||
|
gui::InputHexWord("X Scroll", &exit.x_scroll_);
|
||||||
|
SameLine();
|
||||||
|
gui::InputHexWord("Y Scroll", &exit.y_scroll_);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
static bool show_properties = false;
|
||||||
|
Checkbox("Show properties", &show_properties);
|
||||||
|
if (show_properties) {
|
||||||
|
Text("Deleted? %s", exit.deleted_ ? "true" : "false");
|
||||||
|
Text("Hole? %s", exit.is_hole_ ? "true" : "false");
|
||||||
|
Text("Large Map? %s", exit.large_map_ ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
gui::TextWithSeparators("Unimplemented below");
|
||||||
|
|
||||||
|
ImGui::RadioButton("None", &doorType, 0);
|
||||||
|
SameLine();
|
||||||
|
ImGui::RadioButton("Wooden", &doorType, 1);
|
||||||
|
SameLine();
|
||||||
|
ImGui::RadioButton("Bombable", &doorType, 2);
|
||||||
|
// If door type is not None, input positions
|
||||||
|
if (doorType != 0) {
|
||||||
|
gui::InputHex("Door X pos", &xPos);
|
||||||
|
gui::InputHex("Door Y pos", &yPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::RadioButton("None##Fancy", &fancyDoorType, 0);
|
||||||
|
SameLine();
|
||||||
|
ImGui::RadioButton("Sanctuary", &fancyDoorType, 1);
|
||||||
|
SameLine();
|
||||||
|
ImGui::RadioButton("Palace", &fancyDoorType, 2);
|
||||||
|
// If fancy door type is not None, input positions
|
||||||
|
if (fancyDoorType != 0) {
|
||||||
|
// Placeholder for fancy door's X position
|
||||||
|
gui::InputHex("Fancy Door X pos", &xPos);
|
||||||
|
// Placeholder for fancy door's Y position
|
||||||
|
gui::InputHex("Fancy Door Y pos", &yPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool special_exit = false;
|
||||||
|
Checkbox("Special exit", &special_exit);
|
||||||
|
if (special_exit) {
|
||||||
|
gui::InputHex("Center X", ¢erX);
|
||||||
|
|
||||||
|
gui::InputHex("Center Y", ¢erY);
|
||||||
|
gui::InputHex("Unk1", &unk1);
|
||||||
|
gui::InputHex("Unk2", &unk2);
|
||||||
|
|
||||||
|
gui::InputHex("Link's posture", &linkPosture);
|
||||||
|
gui::InputHex("Sprite GFX", &spriteGFX);
|
||||||
|
gui::InputHex("BG GFX", &bgGFX);
|
||||||
|
gui::InputHex("Palette", &palette);
|
||||||
|
gui::InputHex("Spr Pal", &sprPal);
|
||||||
|
|
||||||
|
gui::InputHex("Top", &top);
|
||||||
|
gui::InputHex("Bottom", &bottom);
|
||||||
|
gui::InputHex("Left", &left);
|
||||||
|
gui::InputHex("Right", &right);
|
||||||
|
|
||||||
|
gui::InputHex("Left edge of map", &leftEdgeOfMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_DELETE)) {
|
||||||
|
exit.deleted_ = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawItemInsertPopup() {
|
||||||
|
// Contents of the Context Menu
|
||||||
|
if (ImGui::BeginPopup("Item Inserter")) {
|
||||||
|
static int new_item_id = 0;
|
||||||
|
Text("Add Item");
|
||||||
|
BeginChild("ScrollRegion", ImVec2(150, 150), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
|
||||||
|
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(),
|
||||||
|
i == new_item_id)) {
|
||||||
|
new_item_id = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
// Add the new item to the overworld
|
||||||
|
new_item_id = 0;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deleting OverworldItem objects, currently only hides them
|
||||||
|
bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item) {
|
||||||
|
static bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Item editor", NULL,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
BeginChild("ScrollRegion", ImVec2(150, 150), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
for (int i = 0; i < zelda3::overworld::kSecretItemNames.size(); i++) {
|
||||||
|
if (Selectable(zelda3::overworld::kSecretItemNames[i].c_str(),
|
||||||
|
item.id == i)) {
|
||||||
|
item.id = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndGroup();
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CLOSE)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_DELETE)) {
|
||||||
|
item.deleted = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImGuiTableSortSpecs *SpriteItem::s_current_sort_specs = nullptr;
|
||||||
|
|
||||||
|
void DrawSpriteTable(std::function<void(int)> onSpriteSelect) {
|
||||||
|
static ImGuiTextFilter filter;
|
||||||
|
static int selected_id = 0;
|
||||||
|
static std::vector<SpriteItem> items;
|
||||||
|
|
||||||
|
// Initialize items if empty
|
||||||
|
if (items.empty()) {
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
items.push_back(SpriteItem{i, core::kSpriteDefaultNames[i].data()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.Draw("Filter", 180);
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("##sprites", 2,
|
||||||
|
ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable)) {
|
||||||
|
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_DefaultSort, 0.0f,
|
||||||
|
MyItemColumnID_ID);
|
||||||
|
ImGui::TableSetupColumn("Name", 0, 0.0f, MyItemColumnID_Name);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
// Handle sorting
|
||||||
|
if (ImGuiTableSortSpecs *sort_specs = ImGui::TableGetSortSpecs()) {
|
||||||
|
if (sort_specs->SpecsDirty) {
|
||||||
|
SpriteItem::SortWithSortSpecs(sort_specs, items);
|
||||||
|
sort_specs->SpecsDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display filtered and sorted items
|
||||||
|
for (const auto &item : items) {
|
||||||
|
if (filter.PassFilter(item.name)) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
Text("%d", item.id);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
|
||||||
|
if (Selectable(item.name, selected_id == item.id,
|
||||||
|
ImGuiSelectableFlags_SpanAllColumns)) {
|
||||||
|
selected_id = item.id;
|
||||||
|
onSpriteSelect(item.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Implement deleting OverworldSprite objects
|
||||||
|
void DrawSpriteInserterPopup() {
|
||||||
|
if (ImGui::BeginPopup("Sprite Inserter")) {
|
||||||
|
static int new_sprite_id = 0;
|
||||||
|
Text("Add Sprite");
|
||||||
|
BeginChild("ScrollRegion", ImVec2(250, 250), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
DrawSpriteTable([](int selected_id) { new_sprite_id = selected_id; });
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) {
|
||||||
|
// Add the new item to the overworld
|
||||||
|
new_sprite_id = 0;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_CANCEL)) {
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite) {
|
||||||
|
static bool set_done = false;
|
||||||
|
if (set_done) {
|
||||||
|
set_done = false;
|
||||||
|
}
|
||||||
|
if (ImGui::BeginPopupModal("Sprite editor", NULL,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
|
BeginChild("ScrollRegion", ImVec2(350, 350), true,
|
||||||
|
ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
Text("%s", sprite.name().c_str());
|
||||||
|
|
||||||
|
DrawSpriteTable([&sprite](int selected_id) {
|
||||||
|
sprite.set_id(selected_id);
|
||||||
|
sprite.UpdateMapProperties(sprite.map_id());
|
||||||
|
});
|
||||||
|
ImGui::EndGroup();
|
||||||
|
EndChild();
|
||||||
|
|
||||||
|
if (Button(ICON_MD_DONE)) ImGui::CloseCurrentPopup();
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_CLOSE)) {
|
||||||
|
set_done = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
SameLine();
|
||||||
|
if (Button(ICON_MD_DELETE)) {
|
||||||
|
sprite.set_deleted(true);
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
return set_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
88
src/app/editor/overworld/entity.h
Normal file
88
src/app/editor/overworld/entity.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
|
||||||
|
#define YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "app/editor/overworld_editor.h"
|
||||||
|
#include "app/zelda3/common.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
bool IsMouseHoveringOverEntity(const zelda3::OverworldEntity &entity,
|
||||||
|
ImVec2 canvas_p0, ImVec2 scrolling);
|
||||||
|
|
||||||
|
void MoveEntityOnGrid(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
|
||||||
|
ImVec2 scrolling, bool free_movement = false);
|
||||||
|
|
||||||
|
void HandleEntityDragging(zelda3::OverworldEntity *entity, ImVec2 canvas_p0,
|
||||||
|
ImVec2 scrolling, bool &is_dragging_entity,
|
||||||
|
zelda3::OverworldEntity *&dragged_entity,
|
||||||
|
zelda3::OverworldEntity *¤t_entity,
|
||||||
|
bool free_movement = false);
|
||||||
|
|
||||||
|
bool DrawEntranceInserterPopup();
|
||||||
|
bool DrawOverworldEntrancePopup(zelda3::overworld::OverworldEntrance &entrance);
|
||||||
|
|
||||||
|
void DrawExitInserterPopup();
|
||||||
|
bool DrawExitEditorPopup(zelda3::overworld::OverworldExit &exit);
|
||||||
|
|
||||||
|
void DrawItemInsertPopup();
|
||||||
|
|
||||||
|
bool DrawItemEditorPopup(zelda3::overworld::OverworldItem &item);
|
||||||
|
|
||||||
|
enum MyItemColumnID {
|
||||||
|
MyItemColumnID_ID,
|
||||||
|
MyItemColumnID_Name,
|
||||||
|
MyItemColumnID_Action,
|
||||||
|
MyItemColumnID_Quantity,
|
||||||
|
MyItemColumnID_Description
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteItem {
|
||||||
|
int id;
|
||||||
|
const char *name;
|
||||||
|
static const ImGuiTableSortSpecs *s_current_sort_specs;
|
||||||
|
|
||||||
|
static void SortWithSortSpecs(ImGuiTableSortSpecs *sort_specs,
|
||||||
|
std::vector<SpriteItem> &items) {
|
||||||
|
s_current_sort_specs =
|
||||||
|
sort_specs; // Store for access by the compare function.
|
||||||
|
if (items.size() > 1)
|
||||||
|
std::sort(items.begin(), items.end(), SpriteItem::CompareWithSortSpecs);
|
||||||
|
s_current_sort_specs = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool CompareWithSortSpecs(const SpriteItem &a, const SpriteItem &b) {
|
||||||
|
for (int n = 0; n < s_current_sort_specs->SpecsCount; n++) {
|
||||||
|
const ImGuiTableColumnSortSpecs *sort_spec =
|
||||||
|
&s_current_sort_specs->Specs[n];
|
||||||
|
int delta = 0;
|
||||||
|
switch (sort_spec->ColumnUserID) {
|
||||||
|
case MyItemColumnID_ID:
|
||||||
|
delta = (a.id - b.id);
|
||||||
|
break;
|
||||||
|
case MyItemColumnID_Name:
|
||||||
|
delta = strcmp(a.name + 2, b.name + 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (delta != 0)
|
||||||
|
return (sort_spec->SortDirection == ImGuiSortDirection_Ascending)
|
||||||
|
? delta < 0
|
||||||
|
: delta > 0;
|
||||||
|
}
|
||||||
|
return a.id < b.id; // Fallback
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void DrawSpriteTable(std::function<void(int)> onSpriteSelect);
|
||||||
|
void DrawSpriteInserterPopup();
|
||||||
|
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite);
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_OVERWORLD_ENTITY_H
|
||||||
157
src/app/editor/overworld/refresh.cc
Normal file
157
src/app/editor/overworld/refresh.cc
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
#include "app/editor/overworld_editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
void OverworldEditor::RefreshChildMap(int map_index) {
|
||||||
|
overworld_.mutable_overworld_map(map_index)->LoadAreaGraphics();
|
||||||
|
status_ = overworld_.mutable_overworld_map(map_index)->BuildTileset();
|
||||||
|
PRINT_IF_ERROR(status_);
|
||||||
|
status_ = overworld_.mutable_overworld_map(map_index)->BuildTiles16Gfx(
|
||||||
|
overworld_.tiles16().size());
|
||||||
|
PRINT_IF_ERROR(status_);
|
||||||
|
status_ = overworld_.mutable_overworld_map(map_index)->BuildBitmap(
|
||||||
|
overworld_.GetMapTiles(current_world_));
|
||||||
|
maps_bmp_[map_index].set_data(
|
||||||
|
overworld_.mutable_overworld_map(map_index)->bitmap_data());
|
||||||
|
maps_bmp_[map_index].set_modified(true);
|
||||||
|
PRINT_IF_ERROR(status_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldEditor::RefreshOverworldMap() {
|
||||||
|
std::vector<std::future<void>> futures;
|
||||||
|
int indices[4];
|
||||||
|
|
||||||
|
auto refresh_map_async = [this](int map_index) {
|
||||||
|
RefreshChildMap(map_index);
|
||||||
|
};
|
||||||
|
|
||||||
|
int source_map_id = current_map_;
|
||||||
|
bool is_large = overworld_.overworld_map(current_map_)->is_large_map();
|
||||||
|
if (is_large) {
|
||||||
|
source_map_id = current_parent_;
|
||||||
|
// We need to update the map and its siblings if it's a large map
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
int sibling_index = overworld_.overworld_map(source_map_id)->parent() + i;
|
||||||
|
if (i >= 2) sibling_index += 6;
|
||||||
|
futures.push_back(
|
||||||
|
std::async(std::launch::async, refresh_map_async, sibling_index));
|
||||||
|
indices[i] = sibling_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indices[0] = source_map_id;
|
||||||
|
futures.push_back(
|
||||||
|
std::async(std::launch::async, refresh_map_async, source_map_id));
|
||||||
|
|
||||||
|
for (auto &each : futures) {
|
||||||
|
each.get();
|
||||||
|
}
|
||||||
|
int n = is_large ? 4 : 1;
|
||||||
|
// We do texture updating on the main thread
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
rom()->UpdateBitmap(&maps_bmp_[indices[i]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status OverworldEditor::RefreshMapPalette() {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
overworld_.mutable_overworld_map(current_map_)->LoadPalette());
|
||||||
|
const auto current_map_palette = overworld_.AreaPalette();
|
||||||
|
|
||||||
|
if (overworld_.overworld_map(current_map_)->is_large_map()) {
|
||||||
|
// We need to update the map and its siblings if it's a large map
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
int sibling_index = overworld_.overworld_map(current_map_)->parent() + i;
|
||||||
|
if (i >= 2) sibling_index += 6;
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
overworld_.mutable_overworld_map(sibling_index)->LoadPalette());
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
maps_bmp_[sibling_index].ApplyPalette(current_map_palette));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RETURN_IF_ERROR(maps_bmp_[current_map_].ApplyPalette(current_map_palette));
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldEditor::RefreshMapProperties() {
|
||||||
|
auto ¤t_ow_map = *overworld_.mutable_overworld_map(current_map_);
|
||||||
|
if (current_ow_map.is_large_map()) {
|
||||||
|
// We need to copy the properties from the parent map to the children
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
int sibling_index = current_ow_map.parent() + i;
|
||||||
|
if (i >= 2) {
|
||||||
|
sibling_index += 6;
|
||||||
|
}
|
||||||
|
auto &map = *overworld_.mutable_overworld_map(sibling_index);
|
||||||
|
map.set_area_graphics(current_ow_map.area_graphics());
|
||||||
|
map.set_area_palette(current_ow_map.area_palette());
|
||||||
|
map.set_sprite_graphics(game_state_,
|
||||||
|
current_ow_map.sprite_graphics(game_state_));
|
||||||
|
map.set_sprite_palette(game_state_,
|
||||||
|
current_ow_map.sprite_palette(game_state_));
|
||||||
|
map.set_message_id(current_ow_map.message_id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status OverworldEditor::RefreshTile16Blockset() {
|
||||||
|
if (current_blockset_ ==
|
||||||
|
overworld_.overworld_map(current_map_)->area_graphics()) {
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
current_blockset_ = overworld_.overworld_map(current_map_)->area_graphics();
|
||||||
|
|
||||||
|
overworld_.set_current_map(current_map_);
|
||||||
|
palette_ = overworld_.AreaPalette();
|
||||||
|
// Create the tile16 blockset image
|
||||||
|
rom()->UpdateBitmap(&tile16_blockset_bmp_);
|
||||||
|
RETURN_IF_ERROR(tile16_blockset_bmp_.ApplyPalette(palette_));
|
||||||
|
|
||||||
|
// Copy the tile16 data into individual tiles.
|
||||||
|
auto tile16_data = overworld_.Tile16Blockset();
|
||||||
|
|
||||||
|
std::vector<std::future<void>> futures;
|
||||||
|
// Loop through the tiles and copy their pixel data into separate vectors
|
||||||
|
for (int i = 0; i < 4096; i++) {
|
||||||
|
futures.push_back(std::async(
|
||||||
|
std::launch::async,
|
||||||
|
[&](int index) {
|
||||||
|
// Create a new vector for the pixel data of the current tile
|
||||||
|
Bytes tile_data(16 * 16, 0x00); // More efficient initialization
|
||||||
|
|
||||||
|
// Copy the pixel data for the current tile into the vector
|
||||||
|
for (int ty = 0; ty < 16; ty++) {
|
||||||
|
for (int tx = 0; tx < 16; tx++) {
|
||||||
|
int position = tx + (ty * 0x10);
|
||||||
|
uint8_t value =
|
||||||
|
tile16_data[(index % 8 * 16) + (index / 8 * 16 * 0x80) +
|
||||||
|
(ty * 0x80) + tx];
|
||||||
|
tile_data[position] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the vector for the current tile to the vector of tile pixel
|
||||||
|
// data
|
||||||
|
tile16_individual_[index].set_data(tile_data);
|
||||||
|
},
|
||||||
|
i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &future : futures) {
|
||||||
|
future.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the bitmaps of each tile.
|
||||||
|
for (int id = 0; id < 4096; id++) {
|
||||||
|
RETURN_IF_ERROR(tile16_individual_[id].ApplyPalette(palette_));
|
||||||
|
rom()->UpdateBitmap(&tile16_individual_[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||||
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -11,18 +11,20 @@
|
|||||||
#include "absl/status/statusor.h"
|
#include "absl/status/statusor.h"
|
||||||
#include "absl/strings/str_format.h"
|
#include "absl/strings/str_format.h"
|
||||||
#include "app/core/common.h"
|
#include "app/core/common.h"
|
||||||
#include "app/core/editor.h"
|
#include "app/editor/graphics/gfx_group_editor.h"
|
||||||
#include "app/editor/modules/gfx_group_editor.h"
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
#include "app/editor/modules/palette_editor.h"
|
#include "app/editor/graphics/tile16_editor.h"
|
||||||
#include "app/editor/modules/tile16_editor.h"
|
#include "app/editor/overworld/entity.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/editor/utils/gfx_context.h"
|
||||||
#include "app/gfx/bitmap.h"
|
#include "app/gfx/bitmap.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "app/gfx/snes_tile.h"
|
#include "app/gfx/snes_tile.h"
|
||||||
#include "app/gui/canvas.h"
|
#include "app/gui/canvas.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "app/gui/pipeline.h"
|
#include "app/gui/zeml.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "app/zelda3/overworld.h"
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
@@ -36,12 +38,14 @@ static constexpr uint kTile8DisplayHeight = 64;
|
|||||||
static constexpr float kInputFieldSize = 30.f;
|
static constexpr float kInputFieldSize = 30.f;
|
||||||
|
|
||||||
static constexpr absl::string_view kToolsetColumnNames[] = {
|
static constexpr absl::string_view kToolsetColumnNames[] = {
|
||||||
"#undoTool", "#redoTool", "#drawTool", "#separator2",
|
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
|
||||||
"#zoomOutTool", "#zoomInTool", "#separator", "#history",
|
"#zoomInTool", "#separator", "#drawTool", "#history",
|
||||||
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
|
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
|
||||||
"#transportTool", "#musicTool", "#separator3", "#tilemapTool"};
|
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
|
||||||
|
"propertiesTool"};
|
||||||
|
|
||||||
constexpr ImGuiTableFlags kOWMapFlags = ImGuiTableFlags_Borders;
|
constexpr ImGuiTableFlags kOWMapFlags =
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
|
||||||
constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit;
|
constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit;
|
||||||
constexpr ImGuiTableFlags kOWEditFlags =
|
constexpr ImGuiTableFlags kOWEditFlags =
|
||||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
@@ -57,60 +61,131 @@ constexpr absl::string_view kTileSelectorTab = "##TileSelectorTabBar";
|
|||||||
constexpr absl::string_view kOWEditTable = "##OWEditTable";
|
constexpr absl::string_view kOWEditTable = "##OWEditTable";
|
||||||
constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
|
constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
|
||||||
|
|
||||||
|
class EntranceContext {
|
||||||
|
public:
|
||||||
|
absl::Status LoadEntranceTileTypes(Rom& rom) {
|
||||||
|
int offset_low = 0xDB8BF;
|
||||||
|
int offset_high = 0xDB917;
|
||||||
|
|
||||||
|
for (int i = 0; i < 0x2C; i++) {
|
||||||
|
// Load entrance tile types
|
||||||
|
ASSIGN_OR_RETURN(auto value_low, rom.ReadWord(offset_low + i));
|
||||||
|
entrance_tile_types_low_.push_back(value_low);
|
||||||
|
ASSIGN_OR_RETURN(auto value_high, rom.ReadWord(offset_high + i));
|
||||||
|
entrance_tile_types_low_.push_back(value_high);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint16_t> entrance_tile_types_low_;
|
||||||
|
std::vector<uint16_t> entrance_tile_types_high_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class OverworldEditor
|
||||||
|
* @brief Manipulates the Overworld and OverworldMap data in a Rom.
|
||||||
|
*
|
||||||
|
* The `OverworldEditor` class is responsible for managing the editing and
|
||||||
|
* manipulation of the overworld in a game. The user can drag and drop tiles,
|
||||||
|
* modify OverworldEntrance, OverworldExit, Sprite, and OverworldItem
|
||||||
|
* as well as change the gfx and palettes used in each overworld map.
|
||||||
|
*
|
||||||
|
* The Overworld itself is a series of bitmap images which exist inside each
|
||||||
|
* OverworldMap object. The drawing of the overworld is done using the Canvas
|
||||||
|
* class in conjunction with these underlying Bitmap objects.
|
||||||
|
*
|
||||||
|
* Provides access to the GfxGroupEditor and Tile16Editor through popup windows.
|
||||||
|
*
|
||||||
|
*/
|
||||||
class OverworldEditor : public Editor,
|
class OverworldEditor : public Editor,
|
||||||
public SharedROM,
|
public SharedRom,
|
||||||
|
public context::GfxContext,
|
||||||
|
public EntranceContext,
|
||||||
public core::ExperimentFlags {
|
public core::ExperimentFlags {
|
||||||
public:
|
public:
|
||||||
|
OverworldEditor() { type_ = EditorType::kOverworld; }
|
||||||
|
|
||||||
|
void InitializeZeml();
|
||||||
|
|
||||||
absl::Status Update() final;
|
absl::Status Update() final;
|
||||||
absl::Status Undo() { return absl::UnimplementedError("Undo"); }
|
absl::Status Undo() { return absl::UnimplementedError("Undo"); }
|
||||||
absl::Status Redo() { return absl::UnimplementedError("Redo"); }
|
absl::Status Redo() { return absl::UnimplementedError("Redo"); }
|
||||||
absl::Status Cut() { return absl::UnimplementedError("Cut"); }
|
absl::Status Cut() { return absl::UnimplementedError("Cut"); }
|
||||||
absl::Status Copy() { return absl::UnimplementedError("Copy"); }
|
absl::Status Copy() { return absl::UnimplementedError("Copy"); }
|
||||||
absl::Status Paste() { return absl::UnimplementedError("Paste"); }
|
absl::Status Paste() { return absl::UnimplementedError("Paste"); }
|
||||||
|
absl::Status Find() { return absl::UnimplementedError("Find Unused Tiles"); }
|
||||||
|
|
||||||
auto overworld() { return &overworld_; }
|
auto overworld() { return &overworld_; }
|
||||||
|
|
||||||
void Shutdown() {
|
/**
|
||||||
for (auto &bmp : tile16_individual_) {
|
* @brief
|
||||||
bmp.Cleanup();
|
*/
|
||||||
}
|
int jump_to_tab() { return jump_to_tab_; }
|
||||||
for (auto &[i, bmp] : maps_bmp_) {
|
int jump_to_tab_ = -1;
|
||||||
bmp.Cleanup();
|
|
||||||
}
|
|
||||||
for (auto &[i, bmp] : graphics_bin_) {
|
|
||||||
bmp.Cleanup();
|
|
||||||
}
|
|
||||||
for (auto &[i, bmp] : current_graphics_set_) {
|
|
||||||
bmp.Cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load the Bitmap objects for each OverworldMap.
|
||||||
|
*
|
||||||
|
* Calls the Overworld class to load the image data and palettes from the Rom,
|
||||||
|
* then renders the area graphics and tile16 blockset Bitmap objects before
|
||||||
|
* assembling the OverworldMap Bitmap objects.
|
||||||
|
*/
|
||||||
absl::Status LoadGraphics();
|
absl::Status LoadGraphics();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
absl::Status UpdateFullscreenCanvas();
|
||||||
|
|
||||||
absl::Status DrawToolset();
|
absl::Status DrawToolset();
|
||||||
void DrawOverworldMapSettings();
|
void DrawOverworldMapSettings();
|
||||||
|
|
||||||
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling);
|
void RefreshChildMap(int i);
|
||||||
void DrawOverworldMaps();
|
void RefreshOverworldMap();
|
||||||
|
absl::Status RefreshMapPalette();
|
||||||
|
void RefreshMapProperties();
|
||||||
|
absl::Status RefreshTile16Blockset();
|
||||||
|
|
||||||
|
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling,
|
||||||
|
bool holes = false);
|
||||||
|
void DrawOverworldExits(ImVec2 zero, ImVec2 scrolling);
|
||||||
|
void DrawOverworldItems();
|
||||||
void DrawOverworldSprites();
|
void DrawOverworldSprites();
|
||||||
|
|
||||||
|
void DrawOverworldMaps();
|
||||||
void DrawOverworldEdits();
|
void DrawOverworldEdits();
|
||||||
void RenderUpdatedMapBitmap(const ImVec2 &click_position,
|
void RenderUpdatedMapBitmap(const ImVec2& click_position,
|
||||||
const Bytes &tile_data);
|
const Bytes& tile_data);
|
||||||
void SaveOverworldChanges();
|
|
||||||
void DetermineActiveMap(const ImVec2 &mouse_position);
|
|
||||||
|
|
||||||
void CheckForOverworldEdits();
|
void CheckForOverworldEdits();
|
||||||
void CheckForCurrentMap();
|
void CheckForSelectRectangle();
|
||||||
|
absl::Status CheckForCurrentMap();
|
||||||
|
void CheckForMousePan();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allows the user to make changes to the overworld map.
|
||||||
|
*/
|
||||||
void DrawOverworldCanvas();
|
void DrawOverworldCanvas();
|
||||||
|
|
||||||
|
absl::Status DrawTile16Selector();
|
||||||
void DrawTile8Selector();
|
void DrawTile8Selector();
|
||||||
void DrawTileSelector();
|
absl::Status DrawAreaGraphics();
|
||||||
|
absl::Status DrawTileSelector();
|
||||||
|
|
||||||
absl::Status LoadSpriteGraphics();
|
absl::Status LoadSpriteGraphics();
|
||||||
|
|
||||||
|
void DrawOverworldProperties();
|
||||||
|
|
||||||
absl::Status DrawExperimentalModal();
|
absl::Status DrawExperimentalModal();
|
||||||
|
|
||||||
|
absl::Status UpdateUsageStats();
|
||||||
|
void DrawUsageGrid();
|
||||||
|
void CalculateUsageStats();
|
||||||
|
|
||||||
|
absl::Status LoadAnimatedMaps();
|
||||||
|
void DrawDebugWindow();
|
||||||
|
|
||||||
|
auto gfx_group_editor() const { return gfx_group_editor_; }
|
||||||
|
|
||||||
enum class EditingMode {
|
enum class EditingMode {
|
||||||
DRAW_TILE,
|
DRAW_TILE,
|
||||||
ENTRANCES,
|
ENTRANCES,
|
||||||
@@ -118,16 +193,25 @@ class OverworldEditor : public Editor,
|
|||||||
ITEMS,
|
ITEMS,
|
||||||
SPRITES,
|
SPRITES,
|
||||||
TRANSPORTS,
|
TRANSPORTS,
|
||||||
MUSIC
|
MUSIC,
|
||||||
|
PAN
|
||||||
};
|
};
|
||||||
|
|
||||||
EditingMode current_mode = EditingMode::DRAW_TILE;
|
EditingMode current_mode = EditingMode::DRAW_TILE;
|
||||||
|
EditingMode previous_mode = EditingMode::DRAW_TILE;
|
||||||
|
|
||||||
int current_world_ = 0;
|
int current_world_ = 0;
|
||||||
int current_map_ = 0;
|
int current_map_ = 0;
|
||||||
|
int current_parent_ = 0;
|
||||||
|
int game_state_ = 1;
|
||||||
int current_tile16_ = 0;
|
int current_tile16_ = 0;
|
||||||
int selected_tile_ = 0;
|
int selected_tile_ = 0;
|
||||||
int game_state_ = 0;
|
|
||||||
|
int current_blockset_ = 0;
|
||||||
|
|
||||||
|
int selected_entrance_ = 0;
|
||||||
|
int selected_usage_map_ = 0xFFFF;
|
||||||
|
|
||||||
char map_gfx_[3] = "";
|
char map_gfx_[3] = "";
|
||||||
char map_palette_[3] = "";
|
char map_palette_[3] = "";
|
||||||
char spr_gfx_[3] = "";
|
char spr_gfx_[3] = "";
|
||||||
@@ -149,10 +233,21 @@ class OverworldEditor : public Editor,
|
|||||||
bool is_dragging_entrance_ = false;
|
bool is_dragging_entrance_ = false;
|
||||||
bool show_tile16_editor_ = false;
|
bool show_tile16_editor_ = false;
|
||||||
bool show_gfx_group_editor_ = false;
|
bool show_gfx_group_editor_ = false;
|
||||||
|
bool overworld_canvas_fullscreen_ = false;
|
||||||
|
bool middle_mouse_dragging_ = false;
|
||||||
|
|
||||||
bool IsMouseHoveringOverEntrance(const zelda3::OverworldEntrance &entrance,
|
bool is_dragging_entity_ = false;
|
||||||
ImVec2 canvas_p, ImVec2 scrolling);
|
zelda3::OverworldEntity* dragged_entity_;
|
||||||
zelda3::OverworldEntrance *dragged_entrance_;
|
zelda3::OverworldEntity* current_entity_;
|
||||||
|
|
||||||
|
int current_entrance_id_ = 0;
|
||||||
|
zelda3::overworld::OverworldEntrance current_entrance_;
|
||||||
|
int current_exit_id_ = 0;
|
||||||
|
zelda3::overworld::OverworldExit current_exit_;
|
||||||
|
int current_item_id_ = 0;
|
||||||
|
zelda3::overworld::OverworldItem current_item_;
|
||||||
|
int current_sprite_id_ = 0;
|
||||||
|
zelda3::Sprite current_sprite_;
|
||||||
|
|
||||||
bool show_experimental = false;
|
bool show_experimental = false;
|
||||||
std::string ow_tilemap_filename_ = "";
|
std::string ow_tilemap_filename_ = "";
|
||||||
@@ -168,24 +263,34 @@ class OverworldEditor : public Editor,
|
|||||||
Tile16Editor tile16_editor_;
|
Tile16Editor tile16_editor_;
|
||||||
GfxGroupEditor gfx_group_editor_;
|
GfxGroupEditor gfx_group_editor_;
|
||||||
PaletteEditor palette_editor_;
|
PaletteEditor palette_editor_;
|
||||||
zelda3::Overworld overworld_;
|
zelda3::overworld::Overworld overworld_;
|
||||||
|
|
||||||
gui::Canvas ow_map_canvas_;
|
gui::Canvas ow_map_canvas_{"owMapCanvas", ImVec2(0x200 * 8, 0x200 * 8),
|
||||||
gui::Canvas current_gfx_canvas_;
|
gui::CanvasGridSize::k64x64};
|
||||||
gui::Canvas blockset_canvas_;
|
gui::Canvas current_gfx_canvas_{"customGfxCanvas",
|
||||||
gui::Canvas graphics_bin_canvas_;
|
ImVec2(0x100 + 1, 0x10 * 0x40 + 1),
|
||||||
|
gui::CanvasGridSize::k32x32};
|
||||||
|
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100 + 1, 0x2000 + 1),
|
||||||
|
gui::CanvasGridSize::k32x32};
|
||||||
|
gui::Canvas graphics_bin_canvas_{
|
||||||
|
"graphicsBinCanvas", ImVec2(0x100 + 1, kNumSheetsToLoad * 0x40 + 1),
|
||||||
|
gui::CanvasGridSize::k16x16};
|
||||||
|
gui::Canvas properties_canvas_;
|
||||||
|
|
||||||
gfx::SNESPalette palette_;
|
gfx::SnesPalette palette_;
|
||||||
gfx::Bitmap selected_tile_bmp_;
|
gfx::Bitmap selected_tile_bmp_;
|
||||||
gfx::Bitmap tile16_blockset_bmp_;
|
gfx::Bitmap tile16_blockset_bmp_;
|
||||||
gfx::Bitmap current_gfx_bmp_;
|
gfx::Bitmap current_gfx_bmp_;
|
||||||
gfx::Bitmap all_gfx_bmp;
|
gfx::Bitmap all_gfx_bmp;
|
||||||
|
|
||||||
gfx::BitmapTable maps_bmp_;
|
gfx::BitmapTable maps_bmp_;
|
||||||
gfx::BitmapTable graphics_bin_;
|
|
||||||
gfx::BitmapTable current_graphics_set_;
|
gfx::BitmapTable current_graphics_set_;
|
||||||
gfx::BitmapTable sprite_previews_;
|
gfx::BitmapTable sprite_previews_;
|
||||||
|
gfx::BitmapTable animated_maps_;
|
||||||
|
|
||||||
|
OWBlockset refresh_blockset_;
|
||||||
|
|
||||||
|
gui::zeml::Node layout_node_;
|
||||||
absl::Status status_;
|
absl::Status status_;
|
||||||
};
|
};
|
||||||
} // namespace editor
|
} // namespace editor
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
#include "app/editor/screen_editor.h"
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "absl/status/statusor.h"
|
|
||||||
#include "absl/strings/str_format.h"
|
|
||||||
#include "absl/strings/string_view.h"
|
|
||||||
#include "app/core/common.h"
|
|
||||||
#include "app/core/constants.h"
|
|
||||||
#include "app/gfx/bitmap.h"
|
|
||||||
#include "app/gfx/snes_tile.h"
|
|
||||||
#include "app/gui/canvas.h"
|
|
||||||
#include "app/gui/icons.h"
|
|
||||||
#include "app/gui/input.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
ScreenEditor::ScreenEditor() { screen_canvas_.SetCanvasSize(ImVec2(512, 512)); }
|
|
||||||
|
|
||||||
void ScreenEditor::Update() {
|
|
||||||
TAB_BAR("##TabBar")
|
|
||||||
DrawInventoryMenuEditor();
|
|
||||||
DrawTitleScreenEditor();
|
|
||||||
DrawNamingScreenEditor();
|
|
||||||
DrawOverworldMapEditor();
|
|
||||||
DrawDungeonMapsEditor();
|
|
||||||
END_TAB_BAR()
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScreenEditor::DrawInventoryMenuEditor() {
|
|
||||||
TAB_ITEM("Inventory Menu")
|
|
||||||
|
|
||||||
static bool create = false;
|
|
||||||
if (!create && rom()->isLoaded()) {
|
|
||||||
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();
|
|
||||||
gui::DisplayPalette(palette_, create);
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
ImGui::Separator();
|
|
||||||
END_TAB_ITEM()
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScreenEditor::DrawTitleScreenEditor() {
|
|
||||||
TAB_ITEM("Title Screen")
|
|
||||||
END_TAB_ITEM()
|
|
||||||
}
|
|
||||||
void ScreenEditor::DrawNamingScreenEditor() {
|
|
||||||
TAB_ITEM("Naming Screen")
|
|
||||||
END_TAB_ITEM()
|
|
||||||
}
|
|
||||||
void ScreenEditor::DrawOverworldMapEditor() {
|
|
||||||
TAB_ITEM("Overworld Map")
|
|
||||||
END_TAB_ITEM()
|
|
||||||
}
|
|
||||||
void ScreenEditor::DrawDungeonMapsEditor() {
|
|
||||||
TAB_ITEM("Dungeon Maps")
|
|
||||||
END_TAB_ITEM()
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
|
||||||
#define YAZE_APP_EDITOR_SCREEN_EDITOR_H
|
|
||||||
|
|
||||||
#include <imgui/imgui.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include "app/core/constants.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/gui/color.h"
|
|
||||||
#include "app/gui/icons.h"
|
|
||||||
#include "app/rom.h"
|
|
||||||
#include "app/zelda3/screen/inventory.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
class ScreenEditor : public SharedROM {
|
|
||||||
public:
|
|
||||||
ScreenEditor();
|
|
||||||
void Update();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void DrawTitleScreenEditor();
|
|
||||||
void DrawNamingScreenEditor();
|
|
||||||
void DrawOverworldMapEditor();
|
|
||||||
void DrawDungeonMapsEditor();
|
|
||||||
void DrawInventoryMenuEditor();
|
|
||||||
|
|
||||||
void DrawToolset();
|
|
||||||
void DrawInventoryToolset();
|
|
||||||
|
|
||||||
Bytes all_gfx_;
|
|
||||||
zelda3::Inventory inventory_;
|
|
||||||
gfx::SNESPalette palette_;
|
|
||||||
gui::Canvas screen_canvas_;
|
|
||||||
gui::Canvas tilesheet_canvas_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif
|
|
||||||
85
src/app/editor/settings_editor.cc
Normal file
85
src/app/editor/settings_editor.cc
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/utils/flags.h"
|
||||||
|
#include "app/editor/utils/keyboard_shortcuts.h"
|
||||||
|
#include "app/editor/settings_editor.h"
|
||||||
|
#include "app/editor/utils/flags.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginChild;
|
||||||
|
using ImGui::BeginMenu;
|
||||||
|
using ImGui::BeginTabBar;
|
||||||
|
using ImGui::BeginTabItem;
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Checkbox;
|
||||||
|
using ImGui::EndChild;
|
||||||
|
using ImGui::EndMenu;
|
||||||
|
using ImGui::EndTabBar;
|
||||||
|
using ImGui::EndTabItem;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::TableHeader;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetBgColor;
|
||||||
|
using ImGui::TableSetColumnIndex;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
absl::Status SettingsEditor::Update() {
|
||||||
|
if (BeginTabBar("Settings", ImGuiTabBarFlags_None)) {
|
||||||
|
if (BeginTabItem("General")) {
|
||||||
|
DrawGeneralSettings();
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
if (BeginTabItem("Keyboard Shortcuts")) {
|
||||||
|
EndTabItem();
|
||||||
|
}
|
||||||
|
EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsEditor::DrawGeneralSettings() {
|
||||||
|
if (BeginTable("##SettingsTable", 2,
|
||||||
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
|
||||||
|
TableSetupColumn("Experiment Flags", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
250.0f);
|
||||||
|
TableSetupColumn("General Setting", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
0.0f);
|
||||||
|
|
||||||
|
TableHeadersRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (BeginChild("##GeneralSettingsStyleWrapper", ImVec2(0, 0),
|
||||||
|
ImGuiChildFlags_FrameStyle)) {
|
||||||
|
static FlagsMenu flags;
|
||||||
|
flags.Draw();
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (BeginChild("##GeneralSettingsWrapper", ImVec2(0, 0),
|
||||||
|
ImGuiChildFlags_FrameStyle)) {
|
||||||
|
Text("TODO: Add some settings here");
|
||||||
|
EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status SettingsEditor::DrawKeyboardShortcuts() {
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
232
src/app/editor/settings_editor.h
Normal file
232
src/app/editor/settings_editor.h
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SETTINGS_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_SETTINGS_EDITOR_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
// Simple representation for a tree
|
||||||
|
// (this is designed to be simple to understand for our demos, not to be
|
||||||
|
// efficient etc.)
|
||||||
|
struct ExampleTreeNode {
|
||||||
|
char Name[28];
|
||||||
|
ImGuiID UID = 0;
|
||||||
|
ExampleTreeNode* Parent = NULL;
|
||||||
|
ImVector<ExampleTreeNode*> Childs;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
bool HasData = false; // All leaves have data
|
||||||
|
bool DataIsEnabled = false;
|
||||||
|
int DataInt = 128;
|
||||||
|
ImVec2 DataVec2 = ImVec2(0.0f, 3.141592f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple representation of struct metadata/serialization data.
|
||||||
|
// (this is a minimal version of what a typical advanced application may
|
||||||
|
// provide)
|
||||||
|
struct ExampleMemberInfo {
|
||||||
|
const char* Name;
|
||||||
|
ImGuiDataType DataType;
|
||||||
|
int DataCount;
|
||||||
|
int Offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Metadata description of ExampleTreeNode struct.
|
||||||
|
static const ExampleMemberInfo ExampleTreeNodeMemberInfos[]{
|
||||||
|
{"Enabled", ImGuiDataType_Bool, 1,
|
||||||
|
offsetof(ExampleTreeNode, DataIsEnabled)},
|
||||||
|
{"MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataInt)},
|
||||||
|
{"MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataVec2)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static ExampleTreeNode* ExampleTree_CreateNode(const char* name,
|
||||||
|
const ImGuiID uid,
|
||||||
|
ExampleTreeNode* parent) {
|
||||||
|
ExampleTreeNode* node = IM_NEW(ExampleTreeNode);
|
||||||
|
snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name);
|
||||||
|
node->UID = uid;
|
||||||
|
node->Parent = parent;
|
||||||
|
if (parent) parent->Childs.push_back(node);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create example tree data
|
||||||
|
static ExampleTreeNode* ExampleTree_CreateDemoTree() {
|
||||||
|
static const char* root_names[] = {"Apple", "Banana", "Cherry",
|
||||||
|
"Kiwi", "Mango", "Orange",
|
||||||
|
"Pineapple", "Strawberry", "Watermelon"};
|
||||||
|
char name_buf[32];
|
||||||
|
ImGuiID uid = 0;
|
||||||
|
ExampleTreeNode* node_L0 = ExampleTree_CreateNode("<ROOT>", ++uid, NULL);
|
||||||
|
for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * 2; idx_L0++) {
|
||||||
|
snprintf(name_buf, 32, "%s %d", root_names[idx_L0 / 2], idx_L0 % 2);
|
||||||
|
ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0);
|
||||||
|
const int number_of_childs = (int)strlen(node_L1->Name);
|
||||||
|
for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++) {
|
||||||
|
snprintf(name_buf, 32, "Child %d", idx_L1);
|
||||||
|
ExampleTreeNode* node_L2 =
|
||||||
|
ExampleTree_CreateNode(name_buf, ++uid, node_L1);
|
||||||
|
node_L2->HasData = true;
|
||||||
|
if (idx_L1 == 0) {
|
||||||
|
snprintf(name_buf, 32, "Sub-child %d", 0);
|
||||||
|
ExampleTreeNode* node_L3 =
|
||||||
|
ExampleTree_CreateNode(name_buf, ++uid, node_L2);
|
||||||
|
node_L3->HasData = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node_L0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExampleAppPropertyEditor {
|
||||||
|
ImGuiTextFilter Filter;
|
||||||
|
|
||||||
|
void Draw(ExampleTreeNode* root_node) {
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F,
|
||||||
|
ImGuiInputFlags_Tooltip);
|
||||||
|
ImGui::PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
|
||||||
|
if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf,
|
||||||
|
IM_ARRAYSIZE(Filter.InputBuf),
|
||||||
|
ImGuiInputTextFlags_EscapeClearsAll))
|
||||||
|
Filter.Build();
|
||||||
|
ImGui::PopItemFlag();
|
||||||
|
|
||||||
|
ImGuiTableFlags table_flags = ImGuiTableFlags_Resizable |
|
||||||
|
ImGuiTableFlags_ScrollY |
|
||||||
|
ImGuiTableFlags_RowBg;
|
||||||
|
if (ImGui::BeginTable("##split", 2, table_flags)) {
|
||||||
|
ImGui::TableSetupColumn("Object", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
1.0f);
|
||||||
|
ImGui::TableSetupColumn("Contents", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
2.0f); // Default twice larger
|
||||||
|
// ImGui::TableSetupScrollFreeze(0, 1);
|
||||||
|
// ImGui::TableHeadersRow();
|
||||||
|
|
||||||
|
for (ExampleTreeNode* node : root_node->Childs)
|
||||||
|
if (Filter.PassFilter(node->Name)) // Filter root node
|
||||||
|
DrawTreeNode(node);
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawTreeNode(ExampleTreeNode* node) {
|
||||||
|
// Object tree node
|
||||||
|
ImGui::PushID((int)node->UID);
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None;
|
||||||
|
tree_flags |=
|
||||||
|
ImGuiTreeNodeFlags_SpanAllColumns |
|
||||||
|
ImGuiTreeNodeFlags_AllowOverlap; // Highlight whole row for visibility
|
||||||
|
tree_flags |=
|
||||||
|
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||||
|
ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we
|
||||||
|
// are likely to want to add
|
||||||
|
// selection afterwards
|
||||||
|
tree_flags |=
|
||||||
|
ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Left arrow support
|
||||||
|
bool node_open =
|
||||||
|
ImGui::TreeNodeEx("##Object", tree_flags, "%s", node->Name);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::TextDisabled("UID: 0x%08X", node->UID);
|
||||||
|
|
||||||
|
// Display child and data
|
||||||
|
if (node_open)
|
||||||
|
for (ExampleTreeNode* child : node->Childs) DrawTreeNode(child);
|
||||||
|
if (node_open && node->HasData) {
|
||||||
|
// In a typical application, the structure description would be derived
|
||||||
|
// from a data-driven system.
|
||||||
|
// - We try to mimic this with our ExampleMemberInfo structure and the
|
||||||
|
// ExampleTreeNodeMemberInfos[] array.
|
||||||
|
// - Limits and some details are hard-coded to simplify the demo.
|
||||||
|
// - Text and Selectable are less high than framed widgets, using
|
||||||
|
// AlignTextToFramePadding() we add vertical spacing to make the
|
||||||
|
// selectable lines equal high.
|
||||||
|
for (const ExampleMemberInfo& field_desc : ExampleTreeNodeMemberInfos) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::PushItemFlag(ImGuiItemFlags_NoTabStop | ImGuiItemFlags_NoNav,
|
||||||
|
true);
|
||||||
|
ImGui::Selectable(field_desc.Name, false,
|
||||||
|
ImGuiSelectableFlags_SpanAllColumns |
|
||||||
|
ImGuiSelectableFlags_AllowOverlap);
|
||||||
|
ImGui::PopItemFlag();
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::PushID(field_desc.Name);
|
||||||
|
void* field_ptr = (void*)(((unsigned char*)node) + field_desc.Offset);
|
||||||
|
switch (field_desc.DataType) {
|
||||||
|
case ImGuiDataType_Bool: {
|
||||||
|
IM_ASSERT(field_desc.DataCount == 1);
|
||||||
|
ImGui::Checkbox("##Editor", (bool*)field_ptr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImGuiDataType_S32: {
|
||||||
|
int v_min = INT_MIN, v_max = INT_MAX;
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
ImGui::DragScalarN("##Editor", field_desc.DataType, field_ptr,
|
||||||
|
field_desc.DataCount, 1.0f, &v_min, &v_max);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ImGuiDataType_Float: {
|
||||||
|
float v_min = 0.0f, v_max = 1.0f;
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
ImGui::SliderScalarN("##Editor", field_desc.DataType, field_ptr,
|
||||||
|
field_desc.DataCount, &v_min, &v_max);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node_open) ImGui::TreePop();
|
||||||
|
ImGui::PopID();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Demonstrate creating a simple property editor.
|
||||||
|
static void ShowExampleAppPropertyEditor(bool* p_open) {
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(430, 450), ImGuiCond_FirstUseEver);
|
||||||
|
if (!ImGui::Begin("Example: Property editor", p_open)) {
|
||||||
|
ImGui::End();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ExampleAppPropertyEditor property_editor;
|
||||||
|
static ExampleTreeNode* tree_data = ExampleTree_CreateDemoTree();
|
||||||
|
property_editor.Draw(tree_data);
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsEditor : public Editor {
|
||||||
|
public:
|
||||||
|
SettingsEditor() : Editor() { type_ = EditorType::kSettings; }
|
||||||
|
|
||||||
|
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:
|
||||||
|
void DrawGeneralSettings();
|
||||||
|
|
||||||
|
absl::Status DrawKeyboardShortcuts();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_SETTINGS_EDITOR_H_
|
||||||
278
src/app/editor/sprite/sprite_editor.cc
Normal file
278
src/app/editor/sprite/sprite_editor.cc
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
#include "sprite_editor.h"
|
||||||
|
|
||||||
|
#include "app/core/platform/file_dialog.h"
|
||||||
|
#include "app/editor/sprite/zsprite.h"
|
||||||
|
#include "app/gui/icons.h"
|
||||||
|
#include "app/gui/input.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginTable;
|
||||||
|
using ImGui::Button;
|
||||||
|
using ImGui::EndTable;
|
||||||
|
using ImGui::Selectable;
|
||||||
|
using ImGui::Separator;
|
||||||
|
using ImGui::TableHeadersRow;
|
||||||
|
using ImGui::TableNextColumn;
|
||||||
|
using ImGui::TableNextRow;
|
||||||
|
using ImGui::TableSetupColumn;
|
||||||
|
using ImGui::Text;
|
||||||
|
|
||||||
|
absl::Status SpriteEditor::Update() {
|
||||||
|
if (rom()->is_loaded() && !sheets_loaded_) {
|
||||||
|
// Load the values for current_sheets_ array
|
||||||
|
sheets_loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("##SpriteEditorTabs")) {
|
||||||
|
if (ImGui::BeginTabItem("Vanilla")) {
|
||||||
|
DrawVanillaSpriteEditor();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
if (ImGui::BeginTabItem("Custom")) {
|
||||||
|
DrawCustomSprites();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
return status_.ok() ? absl::OkStatus() : status_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawVanillaSpriteEditor() {
|
||||||
|
if (ImGui::BeginTable("##SpriteCanvasTable", 3, ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Sprites List", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch,
|
||||||
|
ImGui::GetContentRegionAvail().x);
|
||||||
|
TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawSpritesList();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
static int next_tab_id = 0;
|
||||||
|
|
||||||
|
if (ImGui::BeginTabBar("SpriteTabBar", kSpriteTabBarFlags)) {
|
||||||
|
if (ImGui::TabItemButton(ICON_MD_ADD, kSpriteTabBarFlags)) {
|
||||||
|
if (std::find(active_sprites_.begin(), active_sprites_.end(),
|
||||||
|
current_sprite_id_) != active_sprites_.end()) {
|
||||||
|
// Room is already open
|
||||||
|
next_tab_id++;
|
||||||
|
}
|
||||||
|
active_sprites_.push_back(next_tab_id++); // Add new tab
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit our regular tabs
|
||||||
|
for (int n = 0; n < active_sprites_.Size;) {
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
if (active_sprites_[n] > sizeof(core::kSpriteDefaultNames) / 4) {
|
||||||
|
active_sprites_.erase(active_sprites_.Data + n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTabItem(
|
||||||
|
core::kSpriteDefaultNames[active_sprites_[n]].data(), &open,
|
||||||
|
ImGuiTabItemFlags_None)) {
|
||||||
|
DrawSpriteCanvas();
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!open)
|
||||||
|
active_sprites_.erase(active_sprites_.Data + n);
|
||||||
|
else
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (sheets_loaded_) {
|
||||||
|
DrawCurrentSheets();
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawSpriteCanvas() {
|
||||||
|
static bool flip_x = false;
|
||||||
|
static bool flip_y = false;
|
||||||
|
if (ImGui::BeginChild(gui::GetID("##SpriteCanvas"),
|
||||||
|
ImGui::GetContentRegionAvail(), true)) {
|
||||||
|
sprite_canvas_.DrawBackground();
|
||||||
|
sprite_canvas_.DrawContextMenu();
|
||||||
|
sprite_canvas_.DrawGrid();
|
||||||
|
sprite_canvas_.DrawOverlay();
|
||||||
|
|
||||||
|
// Draw a table with OAM configuration
|
||||||
|
// X, Y, Tile, Palette, Priority, Flip X, Flip Y
|
||||||
|
if (ImGui::BeginTable("##OAMTable", 7, ImGuiTableFlags_Resizable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("X", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Y", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Tile", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Palette", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Priority", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Flip X", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableSetupColumn("Flip Y", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexWord("", &oam_config_.x);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexWord("", &oam_config_.y);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexByte("", &oam_config_.tile);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexByte("", &oam_config_.palette);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
gui::InputHexByte("", &oam_config_.priority);
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (ImGui::Checkbox("##XFlip", &flip_x)) {
|
||||||
|
oam_config_.flip_x = flip_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
if (ImGui::Checkbox("##YFlip", &flip_y)) {
|
||||||
|
oam_config_.flip_y = flip_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawAnimationFrames();
|
||||||
|
|
||||||
|
DrawCustomSpritesMetadata();
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawCurrentSheets() {
|
||||||
|
if (ImGui::BeginChild(gui::GetID("sheet_label"),
|
||||||
|
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
|
||||||
|
ImGuiWindowFlags_NoDecoration)) {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
std::string sheet_label = absl::StrFormat("Sheet %d", i);
|
||||||
|
gui::InputHexByte(sheet_label.c_str(), ¤t_sheets_[i]);
|
||||||
|
if (i % 2 == 0) ImGui::SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics_sheet_canvas_.DrawBackground();
|
||||||
|
graphics_sheet_canvas_.DrawContextMenu();
|
||||||
|
graphics_sheet_canvas_.DrawTileSelector(32);
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
graphics_sheet_canvas_.DrawBitmap(
|
||||||
|
rom()->bitmap_manager()[current_sheets_[i]], 1, (i * 0x40) + 1, 2);
|
||||||
|
}
|
||||||
|
graphics_sheet_canvas_.DrawGrid();
|
||||||
|
graphics_sheet_canvas_.DrawOverlay();
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawSpritesList() {
|
||||||
|
if (ImGui::BeginChild(gui::GetID("##SpritesList"),
|
||||||
|
ImVec2(ImGui::GetContentRegionAvail().x, 0), true,
|
||||||
|
ImGuiWindowFlags_NoDecoration)) {
|
||||||
|
int i = 0;
|
||||||
|
for (const auto each_sprite_name : core::kSpriteDefaultNames) {
|
||||||
|
rom()->resource_label()->SelectableLabelWithNameEdit(
|
||||||
|
current_sprite_id_ == i, "Sprite Names", core::UppercaseHexByte(i),
|
||||||
|
core::kSpriteDefaultNames[i].data());
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
current_sprite_id_ = i;
|
||||||
|
if (!active_sprites_.contains(i)) {
|
||||||
|
active_sprites_.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawAnimationFrames() {
|
||||||
|
if (ImGui::Button("Add Frame")) {
|
||||||
|
// Add a new frame
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Remove Frame")) {
|
||||||
|
// Remove the current frame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawCustomSprites() {
|
||||||
|
if (BeginTable("##CustomSpritesTable", 3,
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Borders |
|
||||||
|
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable,
|
||||||
|
ImVec2(0, 0))) {
|
||||||
|
TableSetupColumn("Metadata", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
TableSetupColumn("TIlesheets", ImGuiTableColumnFlags_WidthFixed, 256);
|
||||||
|
|
||||||
|
TableHeadersRow();
|
||||||
|
TableNextRow();
|
||||||
|
TableNextColumn();
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
DrawCustomSpritesMetadata();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawSpriteCanvas();
|
||||||
|
|
||||||
|
TableNextColumn();
|
||||||
|
DrawCurrentSheets();
|
||||||
|
|
||||||
|
EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteEditor::DrawCustomSpritesMetadata() {
|
||||||
|
// ZSprite Maker format open file dialog
|
||||||
|
if (ImGui::Button("Open ZSprite")) {
|
||||||
|
// Open ZSprite file
|
||||||
|
std::string file_path = FileDialogWrapper::ShowOpenFileDialog();
|
||||||
|
if (!file_path.empty()) {
|
||||||
|
zsprite::ZSprite zsprite;
|
||||||
|
status_ = zsprite.Load(file_path);
|
||||||
|
if (status_.ok()) {
|
||||||
|
custom_sprites_.push_back(zsprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto custom_sprite : custom_sprites_) {
|
||||||
|
Selectable("%s", custom_sprite.sprName.c_str());
|
||||||
|
if (ImGui::IsItemClicked()) {
|
||||||
|
current_sprite_id_ = 256 + stoi(custom_sprite.property_sprid.Text);
|
||||||
|
if (!active_sprites_.contains(current_sprite_id_)) {
|
||||||
|
active_sprites_.push_back(current_sprite_id_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto custom_sprite : custom_sprites_) {
|
||||||
|
// Draw the custom sprite metadata
|
||||||
|
Text("Sprite ID: %s", custom_sprite.property_sprid.Text.c_str());
|
||||||
|
Text("Sprite Name: %s", custom_sprite.property_sprname.Text.c_str());
|
||||||
|
Text("Sprite Palette: %s", custom_sprite.property_palette.Text.c_str());
|
||||||
|
Separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
117
src/app/editor/sprite/sprite_editor.h
Normal file
117
src/app/editor/sprite/sprite_editor.h
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||||
|
#define YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/editor/sprite/zsprite.h"
|
||||||
|
#include "app/editor/utils/editor.h"
|
||||||
|
#include "app/gui/canvas.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
constexpr ImGuiTabItemFlags kSpriteTabFlags =
|
||||||
|
ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip;
|
||||||
|
|
||||||
|
constexpr ImGuiTabBarFlags kSpriteTabBarFlags =
|
||||||
|
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
|
||||||
|
ImGuiTabBarFlags_FittingPolicyResizeDown |
|
||||||
|
ImGuiTabBarFlags_TabListPopupButton;
|
||||||
|
|
||||||
|
constexpr ImGuiTableFlags kSpriteTableFlags =
|
||||||
|
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
|
||||||
|
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
|
||||||
|
ImGuiTableFlags_BordersV;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class SpriteEditor
|
||||||
|
* @brief Allows the user to edit sprites.
|
||||||
|
*
|
||||||
|
* This class provides functionality for updating the sprite editor, drawing the
|
||||||
|
* editor table, drawing the sprite canvas, and drawing the current sheets.
|
||||||
|
*/
|
||||||
|
class SpriteEditor : public SharedRom, public Editor {
|
||||||
|
public:
|
||||||
|
SpriteEditor() { type_ = EditorType::kSprite; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Updates the sprite editor.
|
||||||
|
*
|
||||||
|
* @return An absl::Status indicating the success or failure of the update.
|
||||||
|
*/
|
||||||
|
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:
|
||||||
|
void DrawVanillaSpriteEditor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draws the sprites list.
|
||||||
|
*/
|
||||||
|
void DrawSpritesList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draws the sprite canvas.
|
||||||
|
*/
|
||||||
|
void DrawSpriteCanvas();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draws the current sheets.
|
||||||
|
*/
|
||||||
|
void DrawCurrentSheets();
|
||||||
|
|
||||||
|
void DrawCustomSprites();
|
||||||
|
|
||||||
|
void DrawCustomSpritesMetadata();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draws the animation frames manager.
|
||||||
|
*/
|
||||||
|
void DrawAnimationFrames();
|
||||||
|
|
||||||
|
ImVector<int> active_sprites_; /**< Active sprites. */
|
||||||
|
|
||||||
|
int current_sprite_id_; /**< Current sprite ID. */
|
||||||
|
uint8_t current_sheets_[8] = {0x00, 0x0A, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
bool sheets_loaded_ =
|
||||||
|
false; /**< Flag indicating whether the sheets are loaded or not. */
|
||||||
|
|
||||||
|
// OAM Configuration
|
||||||
|
struct OAMConfig {
|
||||||
|
uint16_t x; /**< X offset. */
|
||||||
|
uint16_t y; /**< Y offset. */
|
||||||
|
uint8_t tile; /**< Tile number. */
|
||||||
|
uint8_t palette; /**< Palette number. */
|
||||||
|
uint8_t priority; /**< Priority. */
|
||||||
|
bool flip_x; /**< Flip X. */
|
||||||
|
bool flip_y; /**< Flip Y. */
|
||||||
|
};
|
||||||
|
|
||||||
|
OAMConfig oam_config_; /**< OAM configuration. */
|
||||||
|
gui::Bitmap oam_bitmap_; /**< OAM bitmap. */
|
||||||
|
|
||||||
|
gui::Canvas sprite_canvas_{
|
||||||
|
"SpriteCanvas", ImVec2(0x200, 0x200),
|
||||||
|
gui::CanvasGridSize::k32x32}; /**< Sprite canvas. */
|
||||||
|
|
||||||
|
gui::Canvas graphics_sheet_canvas_{
|
||||||
|
"GraphicsSheetCanvas", ImVec2(0x80 * 2 + 2, 0x40 * 8 + 2),
|
||||||
|
gui::CanvasGridSize::k16x16}; /**< Graphics sheet canvas. */
|
||||||
|
|
||||||
|
std::vector<zsprite::ZSprite> custom_sprites_; /**< Sprites. */
|
||||||
|
|
||||||
|
absl::Status status_; /**< Status. */
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
||||||
396
src/app/editor/sprite/zsprite.h
Normal file
396
src/app/editor/sprite/zsprite.h
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
|
||||||
|
#define YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/gfx/snes_tile.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
/**
|
||||||
|
* @brief Namespace for the ZSprite format from Zarby's ZSpriteMaker.
|
||||||
|
*/
|
||||||
|
namespace zsprite {
|
||||||
|
|
||||||
|
struct OamTile {
|
||||||
|
OamTile(uint8_t x, uint8_t y, bool mx, bool my, uint16_t id, uint8_t pal,
|
||||||
|
bool s, uint8_t p)
|
||||||
|
: x(x),
|
||||||
|
y(y),
|
||||||
|
mirror_x(mx),
|
||||||
|
mirror_y(my),
|
||||||
|
id(id),
|
||||||
|
palette(pal),
|
||||||
|
size(s),
|
||||||
|
priority(p) {}
|
||||||
|
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
bool mirror_x;
|
||||||
|
bool mirror_y;
|
||||||
|
uint16_t id;
|
||||||
|
uint8_t palette;
|
||||||
|
bool size;
|
||||||
|
uint8_t priority;
|
||||||
|
uint8_t z;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnimationGroup {
|
||||||
|
AnimationGroup() = default;
|
||||||
|
AnimationGroup(uint8_t fs, uint8_t fe, uint8_t fsp, std::string fn)
|
||||||
|
: frame_start(fs), frame_end(fe), frame_speed(fsp), frame_name(fn) {}
|
||||||
|
|
||||||
|
std::string frame_name;
|
||||||
|
uint8_t frame_start;
|
||||||
|
uint8_t frame_end;
|
||||||
|
uint8_t frame_speed;
|
||||||
|
std::vector<OamTile> Tiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserRoutine {
|
||||||
|
UserRoutine(std::string n, std::string c) : name(n), code(c) {}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
std::string code;
|
||||||
|
int Count;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SubEditor {
|
||||||
|
std::vector<AnimationGroup> Frames;
|
||||||
|
std::vector<UserRoutine> user_routines;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteProperty {
|
||||||
|
bool IsChecked;
|
||||||
|
std::string Text;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ZSprite {
|
||||||
|
public:
|
||||||
|
absl::Status Load(const std::string& filename) {
|
||||||
|
std::ifstream fs(filename, std::ios::binary);
|
||||||
|
if (!fs.is_open()) {
|
||||||
|
return absl::NotFoundError("File not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> buffer(std::istreambuf_iterator<char>(fs), {});
|
||||||
|
|
||||||
|
int animation_count = *reinterpret_cast<int32_t*>(&buffer[0]);
|
||||||
|
int offset = sizeof(int);
|
||||||
|
|
||||||
|
for (int i = 0; i < animation_count; i++) {
|
||||||
|
std::string aname = std::string(&buffer[offset]);
|
||||||
|
offset += aname.size() + 1;
|
||||||
|
uint8_t afs = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
uint8_t afe = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
uint8_t afspeed = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
|
||||||
|
animations.push_back(AnimationGroup(afs, afe, afspeed, aname));
|
||||||
|
}
|
||||||
|
// RefreshAnimations();
|
||||||
|
|
||||||
|
int frame_count = *reinterpret_cast<int32_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (int i = 0; i < frame_count; i++) {
|
||||||
|
// editor.Frames[i] = new Frame();
|
||||||
|
editor.Frames.emplace_back();
|
||||||
|
// editor.AddUndo(i);
|
||||||
|
int tCount = *reinterpret_cast<int*>(&buffer[offset]);
|
||||||
|
offset += sizeof(int);
|
||||||
|
|
||||||
|
for (int j = 0; j < tCount; j++) {
|
||||||
|
ushort tid = *reinterpret_cast<ushort*>(&buffer[offset]);
|
||||||
|
offset += sizeof(ushort);
|
||||||
|
uint8_t tpal = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
bool tmx = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
bool tmy = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
uint8_t tprior = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
bool tsize = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
uint8_t tx = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
uint8_t ty = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
uint8_t tz = *reinterpret_cast<uint8_t*>(&buffer[offset]);
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
OamTile to(tx, ty, tmx, tmy, tid, tpal, tsize, tprior);
|
||||||
|
to.z = tz;
|
||||||
|
editor.Frames[i].Tiles.push_back(to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all sprites properties
|
||||||
|
property_blockable.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_canfall.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_collisionlayer.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_customdeath.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_damagesound.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_deflectarrows.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_deflectprojectiles.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_fast.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_harmless.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_impervious.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_imperviousarrow.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_imperviousmelee.IsChecked =
|
||||||
|
*reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_interaction.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_isboss.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_persist.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_shadow.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_smallshadow.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_statis.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_statue.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
property_watersprite.IsChecked = *reinterpret_cast<bool*>(&buffer[offset]);
|
||||||
|
offset += sizeof(bool);
|
||||||
|
|
||||||
|
property_prize.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_palette.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_oamnbr.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_hitbox.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_health.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
property_damage.Text =
|
||||||
|
std::to_string(*reinterpret_cast<uint8_t*>(&buffer[offset]));
|
||||||
|
offset += sizeof(uint8_t);
|
||||||
|
|
||||||
|
if (offset != buffer.size()) {
|
||||||
|
property_sprname.Text = std::string(&buffer[offset]);
|
||||||
|
offset += property_sprname.Text.size() + 1;
|
||||||
|
|
||||||
|
int actionL = buffer[offset];
|
||||||
|
offset += sizeof(int);
|
||||||
|
for (int i = 0; i < actionL; i++) {
|
||||||
|
std::string a = std::string(&buffer[offset]);
|
||||||
|
offset += a.size() + 1;
|
||||||
|
std::string b = std::string(&buffer[offset]);
|
||||||
|
offset += b.size() + 1;
|
||||||
|
userRoutines.push_back(UserRoutine(a, b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset != buffer.size()) {
|
||||||
|
property_sprid.Text = std::string(&buffer[offset]);
|
||||||
|
fs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserRoutines();
|
||||||
|
// userroutinesListbox.SelectedIndex = 0;
|
||||||
|
// RefreshScreen();
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status Save(const std::string& filename) {
|
||||||
|
std::ofstream fs(filename, std::ios::binary);
|
||||||
|
if (fs.is_open()) {
|
||||||
|
// Write data to the file
|
||||||
|
fs.write(reinterpret_cast<const char*>(animations.size()), sizeof(int));
|
||||||
|
for (const AnimationGroup& anim : animations) {
|
||||||
|
fs.write(anim.frame_name.c_str(), anim.frame_name.size() + 1);
|
||||||
|
fs.write(reinterpret_cast<const char*>(&anim.frame_start),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&anim.frame_end),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&anim.frame_speed),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.write(reinterpret_cast<const char*>(editor.Frames.size()),
|
||||||
|
sizeof(int));
|
||||||
|
for (int i = 0; i < editor.Frames.size(); i++) {
|
||||||
|
fs.write(reinterpret_cast<const char*>(editor.Frames[i].Tiles.size()),
|
||||||
|
sizeof(int));
|
||||||
|
|
||||||
|
for (int j = 0; j < editor.Frames[i].Tiles.size(); j++) {
|
||||||
|
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].id),
|
||||||
|
sizeof(ushort));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].palette),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(
|
||||||
|
&editor.Frames[i].Tiles[j].mirror_x),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(
|
||||||
|
&editor.Frames[i].Tiles[j].mirror_y),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(
|
||||||
|
&editor.Frames[i].Tiles[j].priority),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].size),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].x),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].y),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&editor.Frames[i].Tiles[j].z),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write other properties
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_blockable.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_canfall.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&property_collisionlayer.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_customdeath.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_damagesound.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_deflectarrows.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&property_deflectprojectiles.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_fast.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_harmless.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_impervious.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&property_imperviousarrow.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(
|
||||||
|
reinterpret_cast<const char*>(&property_imperviousmelee.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_interaction.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_isboss.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_persist.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_shadow.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_smallshadow.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_statis.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_statue.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_watersprite.IsChecked),
|
||||||
|
sizeof(bool));
|
||||||
|
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_prize.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_palette.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_oamnbr.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_hitbox.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_health.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_damage.Text),
|
||||||
|
sizeof(uint8_t));
|
||||||
|
|
||||||
|
fs.write(sprName.c_str(), sprName.size() + 1);
|
||||||
|
|
||||||
|
fs.write(reinterpret_cast<const char*>(userRoutines.size()), sizeof(int));
|
||||||
|
for (const UserRoutine& userR : userRoutines) {
|
||||||
|
fs.write(userR.name.c_str(), userR.name.size() + 1);
|
||||||
|
fs.write(userR.code.c_str(), userR.code.size() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.write(reinterpret_cast<const char*>(&property_sprid.Text),
|
||||||
|
sizeof(property_sprid.Text));
|
||||||
|
|
||||||
|
fs.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string sprName;
|
||||||
|
std::vector<AnimationGroup> animations;
|
||||||
|
std::vector<UserRoutine> userRoutines;
|
||||||
|
SubEditor editor;
|
||||||
|
|
||||||
|
SpriteProperty property_blockable;
|
||||||
|
SpriteProperty property_canfall;
|
||||||
|
SpriteProperty property_collisionlayer;
|
||||||
|
SpriteProperty property_customdeath;
|
||||||
|
SpriteProperty property_damagesound;
|
||||||
|
SpriteProperty property_deflectarrows;
|
||||||
|
SpriteProperty property_deflectprojectiles;
|
||||||
|
SpriteProperty property_fast;
|
||||||
|
SpriteProperty property_harmless;
|
||||||
|
SpriteProperty property_impervious;
|
||||||
|
SpriteProperty property_imperviousarrow;
|
||||||
|
SpriteProperty property_imperviousmelee;
|
||||||
|
SpriteProperty property_interaction;
|
||||||
|
SpriteProperty property_isboss;
|
||||||
|
SpriteProperty property_persist;
|
||||||
|
SpriteProperty property_shadow;
|
||||||
|
SpriteProperty property_smallshadow;
|
||||||
|
SpriteProperty property_statis;
|
||||||
|
SpriteProperty property_statue;
|
||||||
|
SpriteProperty property_watersprite;
|
||||||
|
SpriteProperty property_sprname;
|
||||||
|
|
||||||
|
SpriteProperty property_prize;
|
||||||
|
SpriteProperty property_palette;
|
||||||
|
SpriteProperty property_oamnbr;
|
||||||
|
SpriteProperty property_hitbox;
|
||||||
|
SpriteProperty property_health;
|
||||||
|
SpriteProperty property_damage;
|
||||||
|
|
||||||
|
SpriteProperty property_sprid;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace zsprite
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_SPRITE_ZSPRITE_H
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
|
||||||
#define YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
|
||||||
|
|
||||||
#include "absl/status/status.h"
|
|
||||||
|
|
||||||
namespace yaze {
|
|
||||||
namespace app {
|
|
||||||
namespace editor {
|
|
||||||
|
|
||||||
class SpriteEditor {
|
|
||||||
public:
|
|
||||||
absl::Status Update();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace editor
|
|
||||||
} // namespace app
|
|
||||||
} // namespace yaze
|
|
||||||
|
|
||||||
#endif // YAZE_APP_EDITOR_SPRITE_EDITOR_H
|
|
||||||
65
src/app/editor/utils/editor.h
Normal file
65
src/app/editor/utils/editor.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#ifndef YAZE_APP_CORE_EDITOR_H
|
||||||
|
#define YAZE_APP_CORE_EDITOR_H
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @namespace yaze::app::editor
|
||||||
|
* @brief Editors are the view controllers for the application.
|
||||||
|
*/
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
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_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_CORE_EDITOR_H
|
||||||
68
src/app/editor/utils/flags.h
Normal file
68
src/app/editor/utils/flags.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_UTILS_FLAGS_H
|
||||||
|
#define YAZE_APP_EDITOR_UTILS_FLAGS_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include "core/common.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using ImGui::BeginMenu;
|
||||||
|
using ImGui::Checkbox;
|
||||||
|
using ImGui::EndMenu;
|
||||||
|
using ImGui::MenuItem;
|
||||||
|
using ImGui::Separator;
|
||||||
|
|
||||||
|
struct FlagsMenu : public core::ExperimentFlags {
|
||||||
|
void Draw() {
|
||||||
|
if (BeginMenu("Overworld Flags")) {
|
||||||
|
Checkbox("Enable Overworld Sprites",
|
||||||
|
&mutable_flags()->overworld.kDrawOverworldSprites);
|
||||||
|
Separator();
|
||||||
|
Checkbox("Save Overworld Maps",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldMaps);
|
||||||
|
Checkbox("Save Overworld Entrances",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldEntrances);
|
||||||
|
Checkbox("Save Overworld Exits",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldExits);
|
||||||
|
Checkbox("Save Overworld Items",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldItems);
|
||||||
|
Checkbox("Save Overworld Properties",
|
||||||
|
&mutable_flags()->overworld.kSaveOverworldProperties);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Dungeon Flags")) {
|
||||||
|
Checkbox("Draw Dungeon Room Graphics",
|
||||||
|
&mutable_flags()->kDrawDungeonRoomGraphics);
|
||||||
|
Separator();
|
||||||
|
Checkbox("Save Dungeon Maps", &mutable_flags()->kSaveDungeonMaps);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeginMenu("Emulator Flags")) {
|
||||||
|
Checkbox("Load Audio Device", &mutable_flags()->kLoadAudioDevice);
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
Checkbox("Use built-in file dialog",
|
||||||
|
&mutable_flags()->kNewFileDialogWrapper);
|
||||||
|
Checkbox("Enable Console Logging", &mutable_flags()->kLogToConsole);
|
||||||
|
Checkbox("Enable Texture Streaming",
|
||||||
|
&mutable_flags()->kLoadTexturesAsStreaming);
|
||||||
|
Checkbox("Use Bitmap Manager", &mutable_flags()->kUseBitmapManager);
|
||||||
|
Checkbox("Log Instructions to Debugger",
|
||||||
|
&mutable_flags()->kLogInstructions);
|
||||||
|
Checkbox("Save All Palettes", &mutable_flags()->kSaveAllPalettes);
|
||||||
|
Checkbox("Save Gfx Groups", &mutable_flags()->kSaveGfxGroups);
|
||||||
|
Checkbox("Use New ImGui Input", &mutable_flags()->kUseNewImGuiInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_UTILS_FLAGS_H_
|
||||||
26
src/app/editor/utils/gfx_context.cc
Normal file
26
src/app/editor/utils/gfx_context.cc
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "app/editor/utils/gfx_context.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "app/editor/graphics/palette_editor.h"
|
||||||
|
#include "app/editor/utils/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/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
namespace context {
|
||||||
|
|
||||||
|
std::unordered_map<uint8_t, gfx::Paletteset> GfxContext::palettesets_;
|
||||||
|
|
||||||
|
}
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
36
src/app/editor/utils/gfx_context.h
Normal file
36
src/app/editor/utils/gfx_context.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||||
|
#define YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/editor/utils/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/gui/icons.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
namespace context {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Shared graphical context across editors.
|
||||||
|
*/
|
||||||
|
class GfxContext {
|
||||||
|
protected:
|
||||||
|
// Palettesets for the tile16 individual tiles
|
||||||
|
static std::unordered_map<uint8_t, gfx::Paletteset> palettesets_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace context
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_VRAM_CONTEXT_H
|
||||||
25
src/app/editor/utils/keyboard_shortcuts.h
Normal file
25
src/app/editor/utils/keyboard_shortcuts.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H
|
||||||
|
#define YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
struct KeyboardShortcuts {
|
||||||
|
enum class ShortcutType {
|
||||||
|
kCut,
|
||||||
|
kCopy,
|
||||||
|
kPaste,
|
||||||
|
kUndo,
|
||||||
|
kRedo,
|
||||||
|
kFind,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_UTILS_KEYBOARD_SHORTCUTS_H_
|
||||||
64
src/app/editor/utils/recent_files.h
Normal file
64
src/app/editor/utils/recent_files.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
|
||||||
|
#define YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace app {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
class RecentFilesManager {
|
||||||
|
public:
|
||||||
|
RecentFilesManager(const std::string& filename) : filename_(filename) {}
|
||||||
|
|
||||||
|
void AddFile(const std::string& filePath) {
|
||||||
|
// Add a file to the list, avoiding duplicates
|
||||||
|
auto it = std::find(recentFiles_.begin(), recentFiles_.end(), filePath);
|
||||||
|
if (it == recentFiles_.end()) {
|
||||||
|
recentFiles_.push_back(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Save() {
|
||||||
|
std::ofstream file(filename_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return; // Handle the error appropriately
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& filePath : recentFiles_) {
|
||||||
|
file << filePath << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Load() {
|
||||||
|
std::ifstream file(filename_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
return; // Handle the error appropriately
|
||||||
|
}
|
||||||
|
|
||||||
|
recentFiles_.clear();
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(file, line)) {
|
||||||
|
if (!line.empty()) {
|
||||||
|
recentFiles_.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<std::string>& GetRecentFiles() const {
|
||||||
|
return recentFiles_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string filename_;
|
||||||
|
std::vector<std::string> recentFiles_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace app
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_UTILS_RECENT_FILES_H
|
||||||
23
src/app/emu/CMakeLists.txt
Normal file
23
src/app/emu/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
add_executable(
|
||||||
|
yaze_emu
|
||||||
|
app/rom.cc
|
||||||
|
app/emu/debug/emu.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}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
yaze_emu PUBLIC
|
||||||
|
lib/
|
||||||
|
app/
|
||||||
|
${CMAKE_SOURCE_DIR}/src/
|
||||||
|
${PNG_INCLUDE_DIRS}
|
||||||
|
${SDL2_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(yaze_emu PUBLIC ${ABSL_TARGETS} ${SDL_TARGETS} ${PNG_LIBRARIES} ${CMAKE_DL_LIBS} ImGui)
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "app/emu/audio/apu.h"
|
#include "app/emu/audio/apu.h"
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -13,127 +15,193 @@
|
|||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
void APU::Init() {
|
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
|
||||||
// Set the clock frequency
|
static const double apuCyclesPerMasterPal = (32040 * 32) / (1364 * 312 * 50.0);
|
||||||
clock_.SetFrequency(kApuClockSpeed);
|
|
||||||
|
|
||||||
// Initialize Digital Signal Processor Callbacks
|
static const uint8_t bootRom[0x40] = {
|
||||||
dsp_.SetSampleFetcher([this](uint16_t address) -> uint8_t {
|
0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa,
|
||||||
return this->FetchSampleFromRam(address);
|
0xf4, 0x8f, 0xbb, 0xf5, 0x78, 0xcc, 0xf4, 0xd0, 0xfb, 0x2f, 0x19,
|
||||||
});
|
0xeb, 0xf4, 0xd0, 0xfc, 0x7e, 0xf4, 0xd0, 0x0b, 0xe4, 0xf5, 0xcb,
|
||||||
|
0xf4, 0xd7, 0x00, 0xfc, 0xd0, 0xf3, 0xab, 0x01, 0x10, 0xef, 0x7e,
|
||||||
|
0xf4, 0x10, 0xeb, 0xba, 0xf6, 0xda, 0x00, 0xba, 0xf4, 0xc4, 0xf4,
|
||||||
|
0xdd, 0x5d, 0xd0, 0xdb, 0x1f, 0x00, 0x00, 0xc0, 0xff};
|
||||||
|
|
||||||
dsp_.SetSamplePusher(
|
void Apu::Init() {
|
||||||
[this](int16_t sample) { this->PushToAudioBuffer(sample); });
|
ram.resize(0x10000);
|
||||||
|
for (int i = 0; i < 0x10000; i++) {
|
||||||
|
ram[i] = 0;
|
||||||
|
}
|
||||||
|
// Copy the boot rom into the ram at ffc0
|
||||||
|
for (int i = 0; i < 0x40; i++) {
|
||||||
|
ram[0xffc0 + i] = bootRom[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APU::Reset() {
|
void Apu::Reset() {
|
||||||
clock_.ResetAccumulatedTime();
|
spc700_.Reset(true);
|
||||||
spc700_.Reset();
|
|
||||||
dsp_.Reset();
|
dsp_.Reset();
|
||||||
|
for (int i = 0; i < 0x10000; i++) {
|
||||||
|
ram[i] = 0;
|
||||||
|
}
|
||||||
|
// Copy the boot rom into the ram at ffc0
|
||||||
|
for (int i = 0; i < 0x40; i++) {
|
||||||
|
ram[0xffc0 + i] = bootRom[i];
|
||||||
|
}
|
||||||
|
rom_readable_ = true;
|
||||||
|
dsp_adr_ = 0;
|
||||||
|
cycles_ = 0;
|
||||||
|
std::fill(in_ports_.begin(), in_ports_.end(), 0);
|
||||||
|
std::fill(out_ports_.begin(), out_ports_.end(), 0);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
timer_[i].cycles = 0;
|
||||||
|
timer_[i].divider = 0;
|
||||||
|
timer_[i].target = 0;
|
||||||
|
timer_[i].counter = 0;
|
||||||
|
timer_[i].enabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APU::Update() {
|
void Apu::RunCycles(uint64_t cycles) {
|
||||||
auto cycles_to_run = clock_.GetCycleCount();
|
uint64_t sync_to =
|
||||||
|
(uint64_t)cycles *
|
||||||
|
(memory_.pal_timing() ? apuCyclesPerMasterPal : apuCyclesPerMaster);
|
||||||
|
|
||||||
for (auto i = 0; i < cycles_to_run; ++i) {
|
while (cycles_ < sync_to) {
|
||||||
// Update the APU
|
spc700_.RunOpcode();
|
||||||
UpdateChannelSettings();
|
|
||||||
|
|
||||||
// Update the SPC700
|
|
||||||
uint8_t opcode = spc700_.read(spc700_.PC);
|
|
||||||
spc700_.ExecuteInstructions(opcode);
|
|
||||||
spc700_.PC++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessSamples();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void APU::Notify(uint32_t address, uint8_t data) {
|
void Apu::Cycle() {
|
||||||
if (address < 0x2140 || address > 0x2143) {
|
if ((cycles_ & 0x1f) == 0) {
|
||||||
return;
|
// every 32 cycles
|
||||||
|
dsp_.Cycle();
|
||||||
}
|
}
|
||||||
auto offset = address - 0x2140;
|
|
||||||
spc700_.write(offset, data);
|
|
||||||
|
|
||||||
// HACK - This is a temporary solution to get the APU to play audio
|
// handle timers
|
||||||
ports_[address - 0x2140] = data;
|
for (int i = 0; i < 3; i++) {
|
||||||
switch (address) {
|
if (timer_[i].cycles == 0) {
|
||||||
case 0x2140:
|
timer_[i].cycles = i == 2 ? 16 : 128;
|
||||||
if (data == BEGIN_SIGNAL) {
|
if (timer_[i].enabled) {
|
||||||
SignalReady();
|
timer_[i].divider++;
|
||||||
|
if (timer_[i].divider == timer_[i].target) {
|
||||||
|
timer_[i].divider = 0;
|
||||||
|
timer_[i].counter++;
|
||||||
|
timer_[i].counter &= 0xf;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
}
|
||||||
case 0x2141:
|
timer_[i].cycles--;
|
||||||
// TODO: Handle data byte transfer here
|
|
||||||
break;
|
|
||||||
case 0x2142:
|
|
||||||
// TODO: Handle the setup of destination address
|
|
||||||
break;
|
|
||||||
case 0x2143:
|
|
||||||
// TODO: Handle additional communication/commands
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cycles_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APU::ProcessSamples() {
|
uint8_t Apu::Read(uint16_t adr) {
|
||||||
// Fetch sample data from AudioRam
|
switch (adr) {
|
||||||
// Iterate over all voices
|
case 0xf0:
|
||||||
for (uint8_t voice_num = 0; voice_num < 8; voice_num++) {
|
case 0xf1:
|
||||||
// Fetch the sample data for the current voice from AudioRam
|
case 0xfa:
|
||||||
uint8_t sample = FetchSampleForVoice(voice_num);
|
case 0xfb:
|
||||||
|
case 0xfc: {
|
||||||
// Process the sample through DSP
|
return 0;
|
||||||
int16_t processed_sample = dsp_.ProcessSample(voice_num, sample);
|
}
|
||||||
|
case 0xf2: {
|
||||||
// Add the processed sample to the audio buffer
|
return dsp_adr_;
|
||||||
audio_samples_.push_back(processed_sample);
|
}
|
||||||
|
case 0xf3: {
|
||||||
|
return dsp_.Read(dsp_adr_ & 0x7f);
|
||||||
|
}
|
||||||
|
case 0xf4:
|
||||||
|
case 0xf5:
|
||||||
|
case 0xf6:
|
||||||
|
case 0xf7:
|
||||||
|
case 0xf8:
|
||||||
|
case 0xf9: {
|
||||||
|
return in_ports_[adr - 0xf4];
|
||||||
|
}
|
||||||
|
case 0xfd:
|
||||||
|
case 0xfe:
|
||||||
|
case 0xff: {
|
||||||
|
uint8_t ret = timer_[adr - 0xfd].counter;
|
||||||
|
timer_[adr - 0xfd].counter = 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if (rom_readable_ && adr >= 0xffc0) {
|
||||||
|
return bootRom[adr - 0xffc0];
|
||||||
uint8_t APU::FetchSampleForVoice(uint8_t voice_num) {
|
|
||||||
uint16_t address = CalculateAddressForVoice(voice_num);
|
|
||||||
return aram_.read(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t APU::CalculateAddressForVoice(uint8_t voice_num) {
|
|
||||||
// TODO: Calculate the address for the specified voice
|
|
||||||
return voice_num;
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t APU::GetNextSample() {
|
|
||||||
if (!audio_samples_.empty()) {
|
|
||||||
int16_t sample = audio_samples_.front();
|
|
||||||
audio_samples_.erase(audio_samples_.begin());
|
|
||||||
return sample;
|
|
||||||
}
|
}
|
||||||
return 0; // TODO: Return the last sample instead of 0.
|
return ram[adr];
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<int16_t>& APU::GetAudioSamples() const {
|
void Apu::Write(uint16_t adr, uint8_t val) {
|
||||||
return audio_samples_;
|
switch (adr) {
|
||||||
|
case 0xf0: {
|
||||||
|
break; // test register
|
||||||
|
}
|
||||||
|
case 0xf1: {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (!timer_[i].enabled && (val & (1 << i))) {
|
||||||
|
timer_[i].divider = 0;
|
||||||
|
timer_[i].counter = 0;
|
||||||
|
}
|
||||||
|
timer_[i].enabled = val & (1 << i);
|
||||||
|
}
|
||||||
|
if (val & 0x10) {
|
||||||
|
in_ports_[0] = 0;
|
||||||
|
in_ports_[1] = 0;
|
||||||
|
}
|
||||||
|
if (val & 0x20) {
|
||||||
|
in_ports_[2] = 0;
|
||||||
|
in_ports_[3] = 0;
|
||||||
|
}
|
||||||
|
rom_readable_ = val & 0x80;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xf2: {
|
||||||
|
dsp_adr_ = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xf3: {
|
||||||
|
if (dsp_adr_ < 0x80) dsp_.Write(dsp_adr_, val);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xf4:
|
||||||
|
case 0xf5:
|
||||||
|
case 0xf6:
|
||||||
|
case 0xf7: {
|
||||||
|
out_ports_[adr - 0xf4] = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xf8:
|
||||||
|
case 0xf9: {
|
||||||
|
in_ports_[adr - 0xf4] = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0xfa:
|
||||||
|
case 0xfb:
|
||||||
|
case 0xfc: {
|
||||||
|
timer_[adr - 0xfa].target = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ram[adr] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
void APU::UpdateChannelSettings() {
|
uint8_t Apu::SpcRead(uint16_t adr) {
|
||||||
// TODO: Implement this method to update the channel settings.
|
Cycle();
|
||||||
|
return Read(adr);
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t APU::GenerateSample(int channel) {
|
void Apu::SpcWrite(uint16_t adr, uint8_t val) {
|
||||||
// TODO: Implement this method to generate a sample for the specified channel.
|
Cycle();
|
||||||
|
Write(adr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void APU::ApplyEnvelope(int channel) {
|
void Apu::SpcIdle(bool waiting) { Cycle(); }
|
||||||
// TODO: Implement this method to apply an envelope to the specified channel.
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t APU::ReadDspMemory(uint16_t address) {
|
|
||||||
return dsp_.ReadGlobalReg(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
void APU::WriteDspMemory(uint16_t address, uint8_t value) {
|
|
||||||
dsp_.WriteGlobalReg(address, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
} // namespace emu
|
} // namespace emu
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
@@ -7,19 +7,37 @@
|
|||||||
|
|
||||||
#include "app/emu/audio/dsp.h"
|
#include "app/emu/audio/dsp.h"
|
||||||
#include "app/emu/audio/spc700.h"
|
#include "app/emu/audio/spc700.h"
|
||||||
#include "app/emu/cpu/clock.h"
|
|
||||||
#include "app/emu/memory/memory.h"
|
#include "app/emu/memory/memory.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
|
using namespace memory;
|
||||||
|
|
||||||
|
typedef struct Timer {
|
||||||
|
uint8_t cycles;
|
||||||
|
uint8_t divider;
|
||||||
|
uint8_t target;
|
||||||
|
uint8_t counter;
|
||||||
|
bool enabled;
|
||||||
|
} Timer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @class Apu
|
||||||
|
* @brief The Apu class represents the Audio Processing Unit (APU) of a system.
|
||||||
*
|
*
|
||||||
|
* The Apu class is responsible for generating audio samples and managing the
|
||||||
|
* APU state. It interacts with the Memory, AudioRam, and Clock classes to
|
||||||
|
* read/write data and update the clock. The class also implements the Observer
|
||||||
|
* interface to receive notifications from the system.
|
||||||
|
*
|
||||||
|
* @par IPL ROM Info
|
||||||
* 64 kilobytes of RAM are mapped across the 16-bit memory space of the SPC-700.
|
* 64 kilobytes of RAM are mapped across the 16-bit memory space of the SPC-700.
|
||||||
* Some regions of this space are overlaid with special hardware functions.
|
* Some regions of this space are overlaid with special hardware functions.
|
||||||
*
|
*
|
||||||
* Range Note
|
* @par Range Note
|
||||||
* $0000-00EF Zero Page RAM
|
* $0000-00EF Zero Page RAM
|
||||||
* $00F0-00FF Sound CPU Registers
|
* $00F0-00FF Sound CPU Registers
|
||||||
* $0100-01FF Stack Page RAM
|
* $0100-01FF Stack Page RAM
|
||||||
@@ -30,106 +48,51 @@ namespace emu {
|
|||||||
* underlying RAM can always be written to, and the high bit of the Control
|
* underlying RAM can always be written to, and the high bit of the Control
|
||||||
* register $F1 can be cleared to unmap the IPL ROM and allow read access to
|
* register $F1 can be cleared to unmap the IPL ROM and allow read access to
|
||||||
* this RAM.
|
* this RAM.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
class Apu {
|
||||||
const int kApuClockSpeed = 1024000; // 1.024 MHz
|
|
||||||
const int apuSampleRate = 32000; // 32 KHz
|
|
||||||
const int apuClocksPerSample = 64; // 64 clocks per sample
|
|
||||||
|
|
||||||
class APU : public Observer {
|
|
||||||
public:
|
public:
|
||||||
APU(MemoryImpl &memory, AudioRam &aram, Clock &clock)
|
Apu(MemoryImpl &memory) : memory_(memory) {}
|
||||||
: aram_(aram), clock_(clock), memory_(memory) {}
|
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
void Reset();
|
void Reset();
|
||||||
void Update();
|
|
||||||
void Notify(uint32_t address, uint8_t data) override;
|
|
||||||
|
|
||||||
void ProcessSamples();
|
void RunCycles(uint64_t cycles);
|
||||||
uint8_t FetchSampleForVoice(uint8_t voice_num);
|
uint8_t SpcRead(uint16_t address);
|
||||||
uint16_t CalculateAddressForVoice(uint8_t voice_num);
|
void SpcWrite(uint16_t address, uint8_t data);
|
||||||
int16_t GetNextSample();
|
void SpcIdle(bool waiting);
|
||||||
|
|
||||||
// Called upon a reset
|
void Cycle();
|
||||||
void Initialize() {
|
|
||||||
spc700_.Reset();
|
|
||||||
dsp_.Reset();
|
|
||||||
SignalReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set Port 0 = $AA and Port 1 = $BB
|
uint8_t Read(uint16_t address);
|
||||||
void SignalReady() {
|
void Write(uint16_t address, uint8_t data);
|
||||||
memory_.WriteByte(0x2140, READY_SIGNAL_0);
|
|
||||||
memory_.WriteByte(0x2141, READY_SIGNAL_1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteToPort(uint8_t portNum, uint8_t value) {
|
auto dsp() -> Dsp & { return dsp_; }
|
||||||
ports_[portNum] = value;
|
auto spc700() -> Spc700 & { return spc700_; }
|
||||||
switch (portNum) {
|
|
||||||
case 0:
|
|
||||||
memory_.WriteByte(0x2140, value);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
memory_.WriteByte(0x2141, value);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
memory_.WriteByte(0x2142, value);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
memory_.WriteByte(0x2143, value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateClock(int delta_time) { clock_.UpdateClock(delta_time); }
|
|
||||||
|
|
||||||
// Method to fetch a sample from AudioRam
|
|
||||||
uint8_t FetchSampleFromRam(uint16_t address) const {
|
|
||||||
return aram_.read(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to push a processed sample to the audio buffer
|
|
||||||
void PushToAudioBuffer(int16_t sample) { audio_samples_.push_back(sample); }
|
|
||||||
|
|
||||||
// Returns the audio samples for the current frame
|
|
||||||
const std::vector<int16_t> &GetAudioSamples() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Constants for communication
|
|
||||||
static const uint8_t READY_SIGNAL_0 = 0xAA;
|
|
||||||
static const uint8_t READY_SIGNAL_1 = 0xBB;
|
|
||||||
static const uint8_t BEGIN_SIGNAL = 0xCC;
|
|
||||||
|
|
||||||
// Port buffers (equivalent to $2140 to $2143 for the main CPU)
|
// Port buffers (equivalent to $2140 to $2143 for the main CPU)
|
||||||
uint8_t ports_[4] = {0};
|
std::array<uint8_t, 6> in_ports_; // includes 2 bytes of ram
|
||||||
|
std::array<uint8_t, 4> out_ports_;
|
||||||
|
std::vector<uint8_t> ram = std::vector<uint8_t>(0x10000, 0);
|
||||||
|
|
||||||
// Updates internal state based on APU register settings
|
private:
|
||||||
void UpdateChannelSettings();
|
bool rom_readable_ = false;
|
||||||
|
|
||||||
// Generates a sample for an audio channel
|
uint8_t dsp_adr_ = 0;
|
||||||
int16_t GenerateSample(int channel);
|
uint32_t cycles_ = 0;
|
||||||
|
|
||||||
// Applies an envelope to an audio channel
|
|
||||||
void ApplyEnvelope(int channel);
|
|
||||||
|
|
||||||
// Handles DSP (Digital Signal Processor) memory reads and writes
|
|
||||||
uint8_t ReadDspMemory(uint16_t address);
|
|
||||||
void WriteDspMemory(uint16_t address, uint8_t value);
|
|
||||||
|
|
||||||
// Member variables to store internal APU state and resources
|
|
||||||
AudioRam &aram_;
|
|
||||||
Clock &clock_;
|
|
||||||
MemoryImpl &memory_;
|
MemoryImpl &memory_;
|
||||||
|
std::array<Timer, 3> timer_;
|
||||||
|
|
||||||
DigitalSignalProcessor dsp_;
|
ApuCallbacks callbacks_ = {
|
||||||
Spc700 spc700_{aram_};
|
[&](uint16_t adr, uint8_t val) { SpcWrite(adr, val); },
|
||||||
std::vector<int16_t> audio_samples_;
|
[&](uint16_t adr) { return SpcRead(adr); },
|
||||||
|
[&](bool waiting) { SpcIdle(waiting); },
|
||||||
std::function<void()> ready_callback_;
|
};
|
||||||
|
Dsp dsp_{ram};
|
||||||
|
Spc700 spc700_{callbacks_};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
} // namespace emu
|
} // namespace emu
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
@@ -1,281 +1,637 @@
|
|||||||
#include "app/emu/audio/dsp.h"
|
#include "app/emu/audio/dsp.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "app/emu/memory/memory.h"
|
#include "app/emu/memory/memory.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
void DigitalSignalProcessor::Reset() {}
|
static const int rateValues[32] = {0, 2048, 1536, 1280, 1024, 768, 640, 512,
|
||||||
|
384, 320, 256, 192, 160, 128, 96, 80,
|
||||||
|
64, 48, 40, 32, 24, 20, 16, 12,
|
||||||
|
10, 8, 6, 5, 4, 3, 2, 1};
|
||||||
|
|
||||||
uint8_t DigitalSignalProcessor::ReadVoiceReg(uint8_t voice, uint8_t reg) const {
|
static const int rateOffsets[32] = {0, 0, 1040, 536, 0, 1040, 536, 0, 1040,
|
||||||
voice %= kNumVoices;
|
536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
|
||||||
switch (reg % kNumVoiceRegs) {
|
536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
|
||||||
case 0:
|
536, 0, 1040, 536, 0};
|
||||||
return voices_[voice].vol_left;
|
|
||||||
case 1:
|
static const int gaussValues[512] = {
|
||||||
return voices_[voice].vol_right;
|
0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
|
||||||
case 2:
|
0x000, 0x000, 0x000, 0x000, 0x000, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001,
|
||||||
return voices_[voice].pitch_low;
|
0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002,
|
||||||
case 3:
|
0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004,
|
||||||
return voices_[voice].pitch_high;
|
0x005, 0x005, 0x005, 0x005, 0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007,
|
||||||
case 4:
|
0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00a, 0x00a, 0x00a, 0x00b, 0x00b,
|
||||||
return voices_[voice].source_number;
|
0x00b, 0x00c, 0x00c, 0x00d, 0x00d, 0x00e, 0x00e, 0x00f, 0x00f, 0x00f, 0x010,
|
||||||
case 5:
|
0x010, 0x011, 0x011, 0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016,
|
||||||
return voices_[voice].adsr1;
|
0x017, 0x017, 0x018, 0x018, 0x019, 0x01a, 0x01b, 0x01b, 0x01c, 0x01d, 0x01d,
|
||||||
case 6:
|
0x01e, 0x01f, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026,
|
||||||
return voices_[voice].adsr2;
|
0x027, 0x028, 0x029, 0x02a, 0x02b, 0x02c, 0x02d, 0x02e, 0x02f, 0x030, 0x031,
|
||||||
case 7:
|
0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x03a, 0x03b, 0x03c, 0x03d,
|
||||||
return voices_[voice].gain;
|
0x03e, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04a, 0x04c,
|
||||||
case 8:
|
0x04d, 0x04e, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05a, 0x05c,
|
||||||
return voices_[voice].envx;
|
0x05e, 0x05f, 0x061, 0x063, 0x064, 0x066, 0x068, 0x06a, 0x06b, 0x06d, 0x06f,
|
||||||
case 9:
|
0x071, 0x073, 0x075, 0x076, 0x078, 0x07a, 0x07c, 0x07e, 0x080, 0x082, 0x084,
|
||||||
return voices_[voice].outx;
|
0x086, 0x089, 0x08b, 0x08d, 0x08f, 0x091, 0x093, 0x096, 0x098, 0x09a, 0x09c,
|
||||||
default:
|
0x09f, 0x0a1, 0x0a3, 0x0a6, 0x0a8, 0x0ab, 0x0ad, 0x0af, 0x0b2, 0x0b4, 0x0b7,
|
||||||
return 0; // This shouldn't happen, but it's good to have a default
|
0x0ba, 0x0bc, 0x0bf, 0x0c1, 0x0c4, 0x0c7, 0x0c9, 0x0cc, 0x0cf, 0x0d2, 0x0d4,
|
||||||
// case
|
0x0d7, 0x0da, 0x0dd, 0x0e0, 0x0e3, 0x0e6, 0x0e9, 0x0ec, 0x0ef, 0x0f2, 0x0f5,
|
||||||
|
0x0f8, 0x0fb, 0x0fe, 0x101, 0x104, 0x107, 0x10b, 0x10e, 0x111, 0x114, 0x118,
|
||||||
|
0x11b, 0x11e, 0x122, 0x125, 0x129, 0x12c, 0x130, 0x133, 0x137, 0x13a, 0x13e,
|
||||||
|
0x141, 0x145, 0x148, 0x14c, 0x150, 0x153, 0x157, 0x15b, 0x15f, 0x162, 0x166,
|
||||||
|
0x16a, 0x16e, 0x172, 0x176, 0x17a, 0x17d, 0x181, 0x185, 0x189, 0x18d, 0x191,
|
||||||
|
0x195, 0x19a, 0x19e, 0x1a2, 0x1a6, 0x1aa, 0x1ae, 0x1b2, 0x1b7, 0x1bb, 0x1bf,
|
||||||
|
0x1c3, 0x1c8, 0x1cc, 0x1d0, 0x1d5, 0x1d9, 0x1dd, 0x1e2, 0x1e6, 0x1eb, 0x1ef,
|
||||||
|
0x1f3, 0x1f8, 0x1fc, 0x201, 0x205, 0x20a, 0x20f, 0x213, 0x218, 0x21c, 0x221,
|
||||||
|
0x226, 0x22a, 0x22f, 0x233, 0x238, 0x23d, 0x241, 0x246, 0x24b, 0x250, 0x254,
|
||||||
|
0x259, 0x25e, 0x263, 0x267, 0x26c, 0x271, 0x276, 0x27b, 0x280, 0x284, 0x289,
|
||||||
|
0x28e, 0x293, 0x298, 0x29d, 0x2a2, 0x2a6, 0x2ab, 0x2b0, 0x2b5, 0x2ba, 0x2bf,
|
||||||
|
0x2c4, 0x2c9, 0x2ce, 0x2d3, 0x2d8, 0x2dc, 0x2e1, 0x2e6, 0x2eb, 0x2f0, 0x2f5,
|
||||||
|
0x2fa, 0x2ff, 0x304, 0x309, 0x30e, 0x313, 0x318, 0x31d, 0x322, 0x326, 0x32b,
|
||||||
|
0x330, 0x335, 0x33a, 0x33f, 0x344, 0x349, 0x34e, 0x353, 0x357, 0x35c, 0x361,
|
||||||
|
0x366, 0x36b, 0x370, 0x374, 0x379, 0x37e, 0x383, 0x388, 0x38c, 0x391, 0x396,
|
||||||
|
0x39b, 0x39f, 0x3a4, 0x3a9, 0x3ad, 0x3b2, 0x3b7, 0x3bb, 0x3c0, 0x3c5, 0x3c9,
|
||||||
|
0x3ce, 0x3d2, 0x3d7, 0x3dc, 0x3e0, 0x3e5, 0x3e9, 0x3ed, 0x3f2, 0x3f6, 0x3fb,
|
||||||
|
0x3ff, 0x403, 0x408, 0x40c, 0x410, 0x415, 0x419, 0x41d, 0x421, 0x425, 0x42a,
|
||||||
|
0x42e, 0x432, 0x436, 0x43a, 0x43e, 0x442, 0x446, 0x44a, 0x44e, 0x452, 0x455,
|
||||||
|
0x459, 0x45d, 0x461, 0x465, 0x468, 0x46c, 0x470, 0x473, 0x477, 0x47a, 0x47e,
|
||||||
|
0x481, 0x485, 0x488, 0x48c, 0x48f, 0x492, 0x496, 0x499, 0x49c, 0x49f, 0x4a2,
|
||||||
|
0x4a6, 0x4a9, 0x4ac, 0x4af, 0x4b2, 0x4b5, 0x4b7, 0x4ba, 0x4bd, 0x4c0, 0x4c3,
|
||||||
|
0x4c5, 0x4c8, 0x4cb, 0x4cd, 0x4d0, 0x4d2, 0x4d5, 0x4d7, 0x4d9, 0x4dc, 0x4de,
|
||||||
|
0x4e0, 0x4e3, 0x4e5, 0x4e7, 0x4e9, 0x4eb, 0x4ed, 0x4ef, 0x4f1, 0x4f3, 0x4f5,
|
||||||
|
0x4f6, 0x4f8, 0x4fa, 0x4fb, 0x4fd, 0x4ff, 0x500, 0x502, 0x503, 0x504, 0x506,
|
||||||
|
0x507, 0x508, 0x50a, 0x50b, 0x50c, 0x50d, 0x50e, 0x50f, 0x510, 0x511, 0x511,
|
||||||
|
0x512, 0x513, 0x514, 0x514, 0x515, 0x516, 0x516, 0x517, 0x517, 0x517, 0x518,
|
||||||
|
0x518, 0x518, 0x518, 0x518, 0x519, 0x519};
|
||||||
|
|
||||||
|
void Dsp::Reset() {
|
||||||
|
memset(ram, 0, sizeof(ram));
|
||||||
|
ram[0x7c] = 0xff; // set ENDx
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].pitch = 0;
|
||||||
|
channel[i].pitchCounter = 0;
|
||||||
|
channel[i].pitchModulation = false;
|
||||||
|
memset(channel[i].decodeBuffer, 0, sizeof(channel[i].decodeBuffer));
|
||||||
|
channel[i].bufferOffset = 0;
|
||||||
|
channel[i].srcn = 0;
|
||||||
|
channel[i].decodeOffset = 0;
|
||||||
|
channel[i].blockOffset = 0;
|
||||||
|
channel[i].brrHeader = 0;
|
||||||
|
channel[i].useNoise = false;
|
||||||
|
channel[i].startDelay = 0;
|
||||||
|
memset(channel[i].adsrRates, 0, sizeof(channel[i].adsrRates));
|
||||||
|
channel[i].adsrState = 0;
|
||||||
|
channel[i].sustainLevel = 0;
|
||||||
|
channel[i].gainSustainLevel = 0;
|
||||||
|
channel[i].useGain = false;
|
||||||
|
channel[i].gainMode = 0;
|
||||||
|
channel[i].directGain = false;
|
||||||
|
channel[i].gainValue = 0;
|
||||||
|
channel[i].preclampGain = 0;
|
||||||
|
channel[i].gain = 0;
|
||||||
|
channel[i].keyOn = false;
|
||||||
|
channel[i].keyOff = false;
|
||||||
|
channel[i].sampleOut = 0;
|
||||||
|
channel[i].volumeL = 0;
|
||||||
|
channel[i].volumeR = 0;
|
||||||
|
channel[i].echoEnable = false;
|
||||||
}
|
}
|
||||||
|
counter = 0;
|
||||||
|
dirPage = 0;
|
||||||
|
evenCycle = true;
|
||||||
|
mute = true;
|
||||||
|
reset = true;
|
||||||
|
masterVolumeL = 0;
|
||||||
|
masterVolumeR = 0;
|
||||||
|
sampleOutL = 0;
|
||||||
|
sampleOutR = 0;
|
||||||
|
echoOutL = 0;
|
||||||
|
echoOutR = 0;
|
||||||
|
noiseSample = 0x4000;
|
||||||
|
noiseRate = 0;
|
||||||
|
echoWrites = false;
|
||||||
|
echoVolumeL = 0;
|
||||||
|
echoVolumeR = 0;
|
||||||
|
feedbackVolume = 0;
|
||||||
|
echoBufferAdr = 0;
|
||||||
|
echoDelay = 0;
|
||||||
|
echoLength = 0;
|
||||||
|
echoBufferIndex = 0;
|
||||||
|
firBufferIndex = 0;
|
||||||
|
memset(firValues, 0, sizeof(firValues));
|
||||||
|
memset(firBufferL, 0, sizeof(firBufferL));
|
||||||
|
memset(firBufferR, 0, sizeof(firBufferR));
|
||||||
|
memset(sampleBuffer, 0, sizeof(sampleBuffer));
|
||||||
|
sampleOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DigitalSignalProcessor::WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value) {
|
void Dsp::NewFrame() {
|
||||||
voice %= kNumVoices;
|
lastFrameBoundary = sampleOffset;
|
||||||
switch (reg % kNumVoiceRegs) {
|
}
|
||||||
case 0:
|
|
||||||
voices_[voice].vol_left = static_cast<int8_t>(value);
|
void Dsp::Cycle() {
|
||||||
break;
|
sampleOutL = 0;
|
||||||
case 1:
|
sampleOutR = 0;
|
||||||
voices_[voice].vol_right = static_cast<int8_t>(value);
|
echoOutL = 0;
|
||||||
break;
|
echoOutR = 0;
|
||||||
case 2:
|
for (int i = 0; i < 8; i++) {
|
||||||
voices_[voice].pitch_low = value;
|
CycleChannel(i);
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
voices_[voice].pitch_high = value;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
voices_[voice].source_number = value;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
voices_[voice].adsr1 = value;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
voices_[voice].adsr2 = value;
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
voices_[voice].gain = value;
|
|
||||||
break;
|
|
||||||
// Note: envx and outx are read-only, so they don't have cases here
|
|
||||||
}
|
}
|
||||||
}
|
HandleEcho(); // also applies master volume
|
||||||
|
counter = counter == 0 ? 30720 : counter - 1;
|
||||||
// Set the callbacks
|
HandleNoise();
|
||||||
void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) { sample_fetcher_ = fetcher; }
|
evenCycle = !evenCycle;
|
||||||
|
// handle mute flag
|
||||||
void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) { sample_pusher_ = pusher; }
|
if (mute) {
|
||||||
|
sampleOutL = 0;
|
||||||
int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) {
|
sampleOutR = 0;
|
||||||
Voice const& voice = voices_[voice_num];
|
|
||||||
uint16_t sample_address = voice.source_number;
|
|
||||||
|
|
||||||
// Use the callback to fetch the sample
|
|
||||||
int16_t sample = static_cast<int16_t>(sample_fetcher_(sample_address) << 8);
|
|
||||||
return sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t DigitalSignalProcessor::ProcessSample(uint8_t voice_num, int16_t sample) {
|
|
||||||
Voice const& voice = voices_[voice_num];
|
|
||||||
|
|
||||||
// Adjust the pitch (for simplicity, we're just adjusting the sample value)
|
|
||||||
sample += voice.pitch_low + (voice.pitch_high << 8);
|
|
||||||
|
|
||||||
// Apply volume (separate for left and right for stereo sound)
|
|
||||||
int16_t left_sample = (sample * voice.vol_left) / 255;
|
|
||||||
int16_t right_sample = (sample * voice.vol_right) / 255;
|
|
||||||
|
|
||||||
// Combine stereo samples into a single 16-bit value
|
|
||||||
return (left_sample + right_sample) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DigitalSignalProcessor::MixSamples() {
|
|
||||||
int16_t mixed_sample = 0;
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < kNumVoices; i++) {
|
|
||||||
int16_t decoded_sample = DecodeSample(i);
|
|
||||||
int16_t processed_sample = ProcessSample(i, decoded_sample);
|
|
||||||
mixed_sample += processed_sample;
|
|
||||||
}
|
}
|
||||||
|
// put final sample in the samplebuffer
|
||||||
// Clamp the mixed sample to 16-bit range
|
sampleBuffer[(sampleOffset & 0x3ff) * 2] = sampleOutL;
|
||||||
if (mixed_sample > 32767) {
|
sampleBuffer[(sampleOffset++ & 0x3ff) * 2 + 1] = sampleOutR;
|
||||||
mixed_sample = 32767;
|
|
||||||
} else if (mixed_sample < -32768) {
|
|
||||||
mixed_sample = -32768;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the callback to push the mixed sample
|
|
||||||
sample_pusher_(mixed_sample);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DigitalSignalProcessor::UpdateEnvelope(uint8_t voice) {
|
static int clamp16(int val) {
|
||||||
uint8_t adsr1 = ReadVoiceReg(voice, 0x05);
|
return val < -0x8000 ? -0x8000 : (val > 0x7fff ? 0x7fff : val);
|
||||||
uint8_t adsr2 = ReadVoiceReg(voice, 0x06);
|
}
|
||||||
uint8_t gain = ReadVoiceReg(voice, 0x07);
|
|
||||||
|
|
||||||
uint8_t enableADSR = (adsr1 & 0x80) >> 7;
|
static int clip16(int val) { return (int16_t)(val & 0xffff); }
|
||||||
|
|
||||||
if (enableADSR) {
|
bool Dsp::CheckCounter(int rate) {
|
||||||
// Handle ADSR envelope
|
if (rate == 0) return false;
|
||||||
Voice& voice_obj = voices_[voice];
|
return ((counter + rateOffsets[rate]) % rateValues[rate]) == 0;
|
||||||
switch (voice_obj.state) {
|
}
|
||||||
case VoiceState::ATTACK:
|
|
||||||
// Update amplitude based on attack rate
|
void Dsp::HandleEcho() {
|
||||||
voice_obj.current_amplitude += AttackRate(adsr1);
|
// increment fir buffer index
|
||||||
if (voice_obj.current_amplitude >= ENVELOPE_MAX) {
|
firBufferIndex++;
|
||||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
firBufferIndex &= 0x7;
|
||||||
voice_obj.state = VoiceState::DECAY;
|
// get value out of ram
|
||||||
}
|
uint16_t adr = echoBufferAdr + echoBufferIndex;
|
||||||
break;
|
int16_t ramSample = aram_[adr] | (aram_[(adr + 1) & 0xffff] << 8);
|
||||||
case VoiceState::DECAY:
|
firBufferL[firBufferIndex] = ramSample >> 1;
|
||||||
// Update amplitude based on decay rate
|
ramSample = aram_[(adr + 2) & 0xffff] | (aram_[(adr + 3) & 0xffff] << 8);
|
||||||
voice_obj.current_amplitude -= DecayRate(adsr2);
|
firBufferR[firBufferIndex] = ramSample >> 1;
|
||||||
if (voice_obj.current_amplitude <= voice_obj.decay_level) {
|
// calculate FIR-sum
|
||||||
voice_obj.current_amplitude = voice_obj.decay_level;
|
int sumL = 0, sumR = 0;
|
||||||
voice_obj.state = VoiceState::SUSTAIN;
|
for (int i = 0; i < 8; i++) {
|
||||||
}
|
sumL += (firBufferL[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
|
||||||
break;
|
sumR += (firBufferR[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
|
||||||
case VoiceState::SUSTAIN:
|
if (i == 6) {
|
||||||
// Keep amplitude at the calculated decay level
|
// clip to 16-bit before last addition
|
||||||
voice_obj.current_amplitude = voice_obj.decay_level;
|
sumL = clip16(sumL);
|
||||||
break;
|
sumR = clip16(sumR);
|
||||||
case VoiceState::RELEASE:
|
|
||||||
// Update amplitude based on release rate
|
|
||||||
voice_obj.current_amplitude -= ReleaseRate(adsr2);
|
|
||||||
if (voice_obj.current_amplitude <= 0) {
|
|
||||||
voice_obj.current_amplitude = 0;
|
|
||||||
voice_obj.state = VoiceState::OFF;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
sumL = clamp16(sumL) & ~1;
|
||||||
|
sumR = clamp16(sumR) & ~1;
|
||||||
|
// apply master volume and modify output with sum
|
||||||
|
sampleOutL = clamp16(((sampleOutL * masterVolumeL) >> 7) +
|
||||||
|
((sumL * echoVolumeL) >> 7));
|
||||||
|
sampleOutR = clamp16(((sampleOutR * masterVolumeR) >> 7) +
|
||||||
|
((sumR * echoVolumeR) >> 7));
|
||||||
|
// get echo value
|
||||||
|
int echoL = clamp16(echoOutL + clip16((sumL * feedbackVolume) >> 7)) & ~1;
|
||||||
|
int echoR = clamp16(echoOutR + clip16((sumR * feedbackVolume) >> 7)) & ~1;
|
||||||
|
// write it to ram
|
||||||
|
if (echoWrites) {
|
||||||
|
aram_[adr] = echoL & 0xff;
|
||||||
|
aram_[(adr + 1) & 0xffff] = echoL >> 8;
|
||||||
|
aram_[(adr + 2) & 0xffff] = echoR & 0xff;
|
||||||
|
aram_[(adr + 3) & 0xffff] = echoR >> 8;
|
||||||
|
}
|
||||||
|
// handle indexes
|
||||||
|
if (echoBufferIndex == 0) {
|
||||||
|
echoLength = echoDelay * 4;
|
||||||
|
}
|
||||||
|
echoBufferIndex += 4;
|
||||||
|
if (echoBufferIndex >= echoLength) {
|
||||||
|
echoBufferIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::CycleChannel(int ch) {
|
||||||
|
// handle pitch counter
|
||||||
|
int pitch = channel[ch].pitch;
|
||||||
|
if (ch > 0 && channel[ch].pitchModulation) {
|
||||||
|
pitch += ((channel[ch - 1].sampleOut >> 5) * pitch) >> 10;
|
||||||
|
}
|
||||||
|
// get current brr header and get sample address
|
||||||
|
channel[ch].brrHeader = aram_[channel[ch].decodeOffset];
|
||||||
|
uint16_t samplePointer = dirPage + 4 * channel[ch].srcn;
|
||||||
|
if (channel[ch].startDelay == 0) samplePointer += 2;
|
||||||
|
uint16_t sampleAdr =
|
||||||
|
aram_[samplePointer] | (aram_[(samplePointer + 1) & 0xffff] << 8);
|
||||||
|
// handle starting of sample
|
||||||
|
if (channel[ch].startDelay > 0) {
|
||||||
|
if (channel[ch].startDelay == 5) {
|
||||||
|
// first keyed on
|
||||||
|
channel[ch].decodeOffset = sampleAdr;
|
||||||
|
channel[ch].blockOffset = 1;
|
||||||
|
channel[ch].bufferOffset = 0;
|
||||||
|
channel[ch].brrHeader = 0;
|
||||||
|
ram[0x7c] &= ~(1 << ch); // clear ENDx
|
||||||
|
}
|
||||||
|
channel[ch].gain = 0;
|
||||||
|
channel[ch].startDelay--;
|
||||||
|
channel[ch].pitchCounter = 0;
|
||||||
|
if (channel[ch].startDelay > 0 && channel[ch].startDelay < 4) {
|
||||||
|
channel[ch].pitchCounter = 0x4000;
|
||||||
|
}
|
||||||
|
pitch = 0;
|
||||||
|
}
|
||||||
|
// get sample
|
||||||
|
int sample = 0;
|
||||||
|
if (channel[ch].useNoise) {
|
||||||
|
sample = clip16(noiseSample * 2);
|
||||||
} else {
|
} else {
|
||||||
// Handle Gain envelope
|
sample = GetSample(ch);
|
||||||
// Extract mode from the gain byte
|
}
|
||||||
uint8_t mode = (gain & 0xE0) >> 5;
|
sample = ((sample * channel[ch].gain) >> 11) & ~1;
|
||||||
uint8_t rate = gain & 0x1F;
|
// handle reset and release
|
||||||
|
if (reset || (channel[ch].brrHeader & 0x03) == 1) {
|
||||||
Voice& voice_obj = voices_[voice];
|
channel[ch].adsrState = 3; // go to release
|
||||||
|
channel[ch].gain = 0;
|
||||||
switch (mode) {
|
}
|
||||||
case 0: // Direct Designation
|
// handle keyon/keyoff
|
||||||
case 1:
|
if (evenCycle) {
|
||||||
case 2:
|
if (channel[ch].keyOff) {
|
||||||
case 3:
|
channel[ch].adsrState = 3; // go to release
|
||||||
voice_obj.current_amplitude =
|
}
|
||||||
rate << 3; // Multiplying by 8 to scale to 0-255
|
if (channel[ch].keyOn) {
|
||||||
break;
|
channel[ch].startDelay = 5;
|
||||||
|
channel[ch].adsrState = 0; // go to attack
|
||||||
case 6: // Increase Mode (Linear)
|
channel[ch].keyOn = false;
|
||||||
voice_obj.current_amplitude += gainTimings[0][rate];
|
|
||||||
if (voice_obj.current_amplitude > ENVELOPE_MAX) {
|
|
||||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 7: // Increase Mode (Bent Line)
|
|
||||||
// Hypothetical behavior: Increase linearly at first, then increase
|
|
||||||
// more slowly You'll likely need to adjust this based on your
|
|
||||||
// specific requirements
|
|
||||||
if (voice_obj.current_amplitude < (ENVELOPE_MAX / 2)) {
|
|
||||||
voice_obj.current_amplitude += gainTimings[1][rate];
|
|
||||||
} else {
|
|
||||||
voice_obj.current_amplitude += gainTimings[1][rate] / 2;
|
|
||||||
}
|
|
||||||
if (voice_obj.current_amplitude > ENVELOPE_MAX) {
|
|
||||||
voice_obj.current_amplitude = ENVELOPE_MAX;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4: // Decrease Mode (Linear)
|
|
||||||
if (voice_obj.current_amplitude < gainTimings[2][rate]) {
|
|
||||||
voice_obj.current_amplitude = 0;
|
|
||||||
} else {
|
|
||||||
voice_obj.current_amplitude -= gainTimings[2][rate];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5: // Decrease Mode (Exponential)
|
|
||||||
voice_obj.current_amplitude -=
|
|
||||||
(voice_obj.current_amplitude * gainTimings[3][rate]) / ENVELOPE_MAX;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Default behavior can be handled here if necessary
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// handle envelope
|
||||||
|
if (channel[ch].startDelay == 0) {
|
||||||
void DigitalSignalProcessor::update_voice_state(uint8_t voice_num) {
|
HandleGain(ch);
|
||||||
if (voice_num >= kNumVoices) return;
|
}
|
||||||
|
// decode new brr samples if needed and update offsets
|
||||||
Voice& voice = voices_[voice_num];
|
if (channel[ch].pitchCounter >= 0x4000) {
|
||||||
switch (voice.state) {
|
DecodeBrr(ch);
|
||||||
case VoiceState::OFF:
|
if (channel[ch].blockOffset >= 7) {
|
||||||
// Reset current amplitude
|
if (channel[ch].brrHeader & 0x1) {
|
||||||
voice.current_amplitude = 0;
|
channel[ch].decodeOffset = sampleAdr;
|
||||||
break;
|
ram[0x7c] |= 1 << ch; // set ENDx
|
||||||
|
} else {
|
||||||
case VoiceState::ATTACK:
|
channel[ch].decodeOffset += 9;
|
||||||
// Increase the current amplitude at a rate defined by the ATTACK
|
|
||||||
// setting
|
|
||||||
voice.current_amplitude += AttackRate(voice.adsr1);
|
|
||||||
if (voice.current_amplitude >= ENVELOPE_MAX) {
|
|
||||||
voice.current_amplitude = ENVELOPE_MAX;
|
|
||||||
voice.state = VoiceState::DECAY;
|
|
||||||
voice.decay_level = CalculateDecayLevel(voice.adsr2);
|
|
||||||
}
|
}
|
||||||
break;
|
channel[ch].blockOffset = 1;
|
||||||
|
} else {
|
||||||
case VoiceState::DECAY:
|
channel[ch].blockOffset += 2;
|
||||||
// Decrease the current amplitude at a rate defined by the DECAY setting
|
}
|
||||||
voice.current_amplitude -= DecayRate(voice.adsr2);
|
}
|
||||||
if (voice.current_amplitude <= voice.decay_level) {
|
// update pitch counter
|
||||||
voice.current_amplitude = voice.decay_level;
|
channel[ch].pitchCounter &= 0x3fff;
|
||||||
voice.state = VoiceState::SUSTAIN;
|
channel[ch].pitchCounter += pitch;
|
||||||
}
|
if (channel[ch].pitchCounter > 0x7fff) channel[ch].pitchCounter = 0x7fff;
|
||||||
break;
|
// set outputs
|
||||||
|
ram[(ch << 4) | 8] = channel[ch].gain >> 4;
|
||||||
case VoiceState::SUSTAIN:
|
ram[(ch << 4) | 9] = sample >> 8;
|
||||||
// Keep the current amplitude at the decay level
|
channel[ch].sampleOut = sample;
|
||||||
break;
|
sampleOutL = clamp16(sampleOutL + ((sample * channel[ch].volumeL) >> 7));
|
||||||
|
sampleOutR = clamp16(sampleOutR + ((sample * channel[ch].volumeR) >> 7));
|
||||||
case VoiceState::RELEASE:
|
if (channel[ch].echoEnable) {
|
||||||
// Decrease the current amplitude at a rate defined by the RELEASE
|
echoOutL = clamp16(echoOutL + ((sample * channel[ch].volumeL) >> 7));
|
||||||
// setting
|
echoOutR = clamp16(echoOutR + ((sample * channel[ch].volumeR) >> 7));
|
||||||
voice.current_amplitude -= ReleaseRate(voice.adsr2);
|
|
||||||
if (voice.current_amplitude == 0) {
|
|
||||||
voice.state = VoiceState::OFF;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DigitalSignalProcessor::process_envelope(uint8_t voice_num) {
|
void Dsp::HandleGain(int ch) {
|
||||||
if (voice_num >= kNumVoices) return;
|
int newGain = channel[ch].gain;
|
||||||
|
int rate = 0;
|
||||||
Voice& voice = voices_[voice_num];
|
// handle gain mode
|
||||||
|
if (channel[ch].adsrState == 3) { // release
|
||||||
// Update the voice state first (based on keys, etc.)
|
rate = 31;
|
||||||
update_voice_state(voice_num);
|
newGain -= 8;
|
||||||
|
} else {
|
||||||
// Calculate the envelope value based on the current amplitude
|
if (!channel[ch].useGain) {
|
||||||
voice.envx = calculate_envelope_value(voice.current_amplitude);
|
rate = channel[ch].adsrRates[channel[ch].adsrState];
|
||||||
|
switch (channel[ch].adsrState) {
|
||||||
// Apply the envelope value to the audio output
|
case 0:
|
||||||
apply_envelope_to_output(voice_num);
|
newGain += rate == 31 ? 1024 : 32;
|
||||||
|
break; // attack
|
||||||
|
case 1:
|
||||||
|
newGain -= ((newGain - 1) >> 8) + 1;
|
||||||
|
break; // decay
|
||||||
|
case 2:
|
||||||
|
newGain -= ((newGain - 1) >> 8) + 1;
|
||||||
|
break; // sustain
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!channel[ch].directGain) {
|
||||||
|
rate = channel[ch].adsrRates[3];
|
||||||
|
switch (channel[ch].gainMode) {
|
||||||
|
case 0:
|
||||||
|
newGain -= 32;
|
||||||
|
break; // linear decrease
|
||||||
|
case 1:
|
||||||
|
newGain -= ((newGain - 1) >> 8) + 1;
|
||||||
|
break; // exponential decrease
|
||||||
|
case 2:
|
||||||
|
newGain += 32;
|
||||||
|
break; // linear increase
|
||||||
|
case 3:
|
||||||
|
newGain += (channel[ch].preclampGain < 0x600) ? 32 : 8;
|
||||||
|
break; // bent increase
|
||||||
|
}
|
||||||
|
} else { // direct gain
|
||||||
|
rate = 31;
|
||||||
|
newGain = channel[ch].gainValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// use sustain level according to mode
|
||||||
|
int sustainLevel = channel[ch].useGain ? channel[ch].gainSustainLevel
|
||||||
|
: channel[ch].sustainLevel;
|
||||||
|
if (channel[ch].adsrState == 1 && (newGain >> 8) == sustainLevel) {
|
||||||
|
channel[ch].adsrState = 2; // go to sustain
|
||||||
|
}
|
||||||
|
// store pre-clamped gain (for bent increase)
|
||||||
|
channel[ch].preclampGain = newGain & 0xffff;
|
||||||
|
// clamp gain
|
||||||
|
if (newGain < 0 || newGain > 0x7ff) {
|
||||||
|
newGain = newGain < 0 ? 0 : 0x7ff;
|
||||||
|
if (channel[ch].adsrState == 0) {
|
||||||
|
channel[ch].adsrState = 1; // go to decay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// store new value
|
||||||
|
if (CheckCounter(rate)) channel[ch].gain = newGain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int16_t Dsp::GetSample(int ch) {
|
||||||
|
int pos = (channel[ch].pitchCounter >> 12) + channel[ch].bufferOffset;
|
||||||
|
int offset = (channel[ch].pitchCounter >> 4) & 0xff;
|
||||||
|
int16_t news = channel[ch].decodeBuffer[(pos + 3) % 12];
|
||||||
|
int16_t olds = channel[ch].decodeBuffer[(pos + 2) % 12];
|
||||||
|
int16_t olders = channel[ch].decodeBuffer[(pos + 1) % 12];
|
||||||
|
int16_t oldests = channel[ch].decodeBuffer[pos % 12];
|
||||||
|
int out = (gaussValues[0xff - offset] * oldests) >> 11;
|
||||||
|
out += (gaussValues[0x1ff - offset] * olders) >> 11;
|
||||||
|
out += (gaussValues[0x100 + offset] * olds) >> 11;
|
||||||
|
out = clip16(out) + ((gaussValues[offset] * news) >> 11);
|
||||||
|
return clamp16(out) & ~1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::DecodeBrr(int ch) {
|
||||||
|
int shift = channel[ch].brrHeader >> 4;
|
||||||
|
int filter = (channel[ch].brrHeader & 0xc) >> 2;
|
||||||
|
int bOff = channel[ch].bufferOffset;
|
||||||
|
int old = channel[ch].decodeBuffer[bOff == 0 ? 11 : bOff - 1] >> 1;
|
||||||
|
int older = channel[ch].decodeBuffer[bOff == 0 ? 10 : bOff - 2] >> 1;
|
||||||
|
uint8_t curByte = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int s = 0;
|
||||||
|
if (i & 1) {
|
||||||
|
s = curByte & 0xf;
|
||||||
|
} else {
|
||||||
|
curByte = aram_[(channel[ch].decodeOffset + channel[ch].blockOffset +
|
||||||
|
(i >> 1)) &
|
||||||
|
0xffff];
|
||||||
|
s = curByte >> 4;
|
||||||
|
}
|
||||||
|
if (s > 7) s -= 16;
|
||||||
|
if (shift <= 0xc) {
|
||||||
|
s = (s << shift) >> 1;
|
||||||
|
} else {
|
||||||
|
s = (s >> 3) << 12;
|
||||||
|
}
|
||||||
|
switch (filter) {
|
||||||
|
case 1:
|
||||||
|
s += old + (-old >> 4);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
s += 2 * old + ((3 * -old) >> 5) - older + (older >> 4);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
s += 2 * old + ((13 * -old) >> 6) - older + ((3 * older) >> 4);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
channel[ch].decodeBuffer[bOff + i] = clamp16(s) * 2; // cuts off bit 15
|
||||||
|
older = old;
|
||||||
|
old = channel[ch].decodeBuffer[bOff + i] >> 1;
|
||||||
|
}
|
||||||
|
channel[ch].bufferOffset += 4;
|
||||||
|
if (channel[ch].bufferOffset >= 12) channel[ch].bufferOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::HandleNoise() {
|
||||||
|
if (CheckCounter(noiseRate)) {
|
||||||
|
int bit = (noiseSample & 1) ^ ((noiseSample >> 1) & 1);
|
||||||
|
noiseSample = ((noiseSample >> 1) & 0x3fff) | (bit << 14);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Dsp::Read(uint8_t adr) { return ram[adr]; }
|
||||||
|
|
||||||
|
void Dsp::Write(uint8_t adr, uint8_t val) {
|
||||||
|
int ch = adr >> 4;
|
||||||
|
switch (adr) {
|
||||||
|
case 0x00:
|
||||||
|
case 0x10:
|
||||||
|
case 0x20:
|
||||||
|
case 0x30:
|
||||||
|
case 0x40:
|
||||||
|
case 0x50:
|
||||||
|
case 0x60:
|
||||||
|
case 0x70: {
|
||||||
|
channel[ch].volumeL = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x01:
|
||||||
|
case 0x11:
|
||||||
|
case 0x21:
|
||||||
|
case 0x31:
|
||||||
|
case 0x41:
|
||||||
|
case 0x51:
|
||||||
|
case 0x61:
|
||||||
|
case 0x71: {
|
||||||
|
channel[ch].volumeR = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x02:
|
||||||
|
case 0x12:
|
||||||
|
case 0x22:
|
||||||
|
case 0x32:
|
||||||
|
case 0x42:
|
||||||
|
case 0x52:
|
||||||
|
case 0x62:
|
||||||
|
case 0x72: {
|
||||||
|
channel[ch].pitch = (channel[ch].pitch & 0x3f00) | val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x03:
|
||||||
|
case 0x13:
|
||||||
|
case 0x23:
|
||||||
|
case 0x33:
|
||||||
|
case 0x43:
|
||||||
|
case 0x53:
|
||||||
|
case 0x63:
|
||||||
|
case 0x73: {
|
||||||
|
channel[ch].pitch = ((channel[ch].pitch & 0x00ff) | (val << 8)) & 0x3fff;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x04:
|
||||||
|
case 0x14:
|
||||||
|
case 0x24:
|
||||||
|
case 0x34:
|
||||||
|
case 0x44:
|
||||||
|
case 0x54:
|
||||||
|
case 0x64:
|
||||||
|
case 0x74: {
|
||||||
|
channel[ch].srcn = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x05:
|
||||||
|
case 0x15:
|
||||||
|
case 0x25:
|
||||||
|
case 0x35:
|
||||||
|
case 0x45:
|
||||||
|
case 0x55:
|
||||||
|
case 0x65:
|
||||||
|
case 0x75: {
|
||||||
|
channel[ch].adsrRates[0] = (val & 0xf) * 2 + 1;
|
||||||
|
channel[ch].adsrRates[1] = ((val & 0x70) >> 4) * 2 + 16;
|
||||||
|
channel[ch].useGain = (val & 0x80) == 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x06:
|
||||||
|
case 0x16:
|
||||||
|
case 0x26:
|
||||||
|
case 0x36:
|
||||||
|
case 0x46:
|
||||||
|
case 0x56:
|
||||||
|
case 0x66:
|
||||||
|
case 0x76: {
|
||||||
|
channel[ch].adsrRates[2] = val & 0x1f;
|
||||||
|
channel[ch].sustainLevel = (val & 0xe0) >> 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x07:
|
||||||
|
case 0x17:
|
||||||
|
case 0x27:
|
||||||
|
case 0x37:
|
||||||
|
case 0x47:
|
||||||
|
case 0x57:
|
||||||
|
case 0x67:
|
||||||
|
case 0x77: {
|
||||||
|
channel[ch].directGain = (val & 0x80) == 0;
|
||||||
|
channel[ch].gainMode = (val & 0x60) >> 5;
|
||||||
|
channel[ch].adsrRates[3] = val & 0x1f;
|
||||||
|
channel[ch].gainValue = (val & 0x7f) * 16;
|
||||||
|
channel[ch].gainSustainLevel = (val & 0xe0) >> 5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0c: {
|
||||||
|
masterVolumeL = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x1c: {
|
||||||
|
masterVolumeR = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x2c: {
|
||||||
|
echoVolumeL = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x3c: {
|
||||||
|
echoVolumeR = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x4c: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].keyOn = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x5c: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].keyOff = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6c: {
|
||||||
|
reset = val & 0x80;
|
||||||
|
mute = val & 0x40;
|
||||||
|
echoWrites = (val & 0x20) == 0;
|
||||||
|
noiseRate = val & 0x1f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x7c: {
|
||||||
|
val = 0; // any write clears ENDx
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0d: {
|
||||||
|
feedbackVolume = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x2d: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].pitchModulation = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x3d: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].useNoise = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x4d: {
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
channel[i].echoEnable = val & (1 << i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x5d: {
|
||||||
|
dirPage = val << 8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x6d: {
|
||||||
|
echoBufferAdr = val << 8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x7d: {
|
||||||
|
echoDelay =
|
||||||
|
(val & 0xf) * 512; // 2048-byte steps, stereo sample is 4 bytes
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0x0f:
|
||||||
|
case 0x1f:
|
||||||
|
case 0x2f:
|
||||||
|
case 0x3f:
|
||||||
|
case 0x4f:
|
||||||
|
case 0x5f:
|
||||||
|
case 0x6f:
|
||||||
|
case 0x7f: {
|
||||||
|
firValues[ch] = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ram[adr] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dsp::GetSamples(int16_t* sample_data, int samples_per_frame,
|
||||||
|
bool pal_timing) {
|
||||||
|
// resample from 534 / 641 samples per frame to wanted value
|
||||||
|
float wantedSamples = (pal_timing ? 641.0 : 534.0);
|
||||||
|
double adder = wantedSamples / samples_per_frame;
|
||||||
|
double location = lastFrameBoundary - wantedSamples;
|
||||||
|
for (int i = 0; i < samples_per_frame; i++) {
|
||||||
|
sample_data[i * 2] = sample_buffer_[(((int)location) & 0x3ff) * 2];
|
||||||
|
sample_data[i * 2 + 1] = sample_buffer_[(((int)location) & 0x3ff) * 2 + 1];
|
||||||
|
location += adder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
} // namespace emu
|
} // namespace emu
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
@@ -5,17 +5,50 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/emu/audio/spc700.h"
|
||||||
#include "app/emu/memory/memory.h"
|
#include "app/emu/memory/memory.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
using SampleFetcher = std::function<uint8_t(uint16_t)>;
|
typedef struct DspChannel {
|
||||||
using SamplePusher = std::function<void(int16_t)>;
|
// pitch
|
||||||
|
uint16_t pitch;
|
||||||
|
uint16_t pitchCounter;
|
||||||
|
bool pitchModulation;
|
||||||
|
// brr decoding
|
||||||
|
int16_t decodeBuffer[12];
|
||||||
|
uint8_t bufferOffset;
|
||||||
|
uint8_t srcn;
|
||||||
|
uint16_t decodeOffset;
|
||||||
|
uint8_t blockOffset; // offset within brr block
|
||||||
|
uint8_t brrHeader;
|
||||||
|
bool useNoise;
|
||||||
|
uint8_t startDelay;
|
||||||
|
// adsr, envelope, gain
|
||||||
|
uint8_t adsrRates[4]; // attack, decay, sustain, gain
|
||||||
|
uint8_t adsrState; // 0: attack, 1: decay, 2: sustain, 3: release
|
||||||
|
uint8_t sustainLevel;
|
||||||
|
uint8_t gainSustainLevel;
|
||||||
|
bool useGain;
|
||||||
|
uint8_t gainMode;
|
||||||
|
bool directGain;
|
||||||
|
uint16_t gainValue; // for direct gain
|
||||||
|
uint16_t preclampGain; // for bent increase
|
||||||
|
uint16_t gain;
|
||||||
|
// keyon/off
|
||||||
|
bool keyOn;
|
||||||
|
bool keyOff;
|
||||||
|
// output
|
||||||
|
int16_t sampleOut; // final sample, to be multiplied by channel volume
|
||||||
|
int8_t volumeL;
|
||||||
|
int8_t volumeR;
|
||||||
|
bool echoEnable;
|
||||||
|
} DspChannel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* The S-DSP is a digital signal processor generating the sound data.
|
* The S-DSP is a digital signal processor generating the sound data.
|
||||||
*
|
*
|
||||||
* A DSP register can be selected with $F2, after which it can be read or
|
* A DSP register can be selected with $F2, after which it can be read or
|
||||||
@@ -34,280 +67,95 @@ using SamplePusher = std::function<void(int16_t)>;
|
|||||||
* There are 8 voices, numbered 0 to 7.
|
* There are 8 voices, numbered 0 to 7.
|
||||||
* Each voice X has 10 registers in the range $X0-$X9.
|
* Each voice X has 10 registers in the range $X0-$X9.
|
||||||
*
|
*
|
||||||
* Name Address Bits Notes
|
* | Name | Address | Bits | Notes |
|
||||||
* VOL (L) $X0 SVVV VVVV Left channel volume, signed.
|
* |---------|---------|-----------|--------------------------------------------------------|
|
||||||
* VOL (R) $X1 SVVV VVVV Right channel volume, signed.
|
* | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. | | VOL (R) |
|
||||||
* P (L) $X2 LLLL LLLL Low 8 bits of sample pitch.
|
* $X1 | SVVV VVVV | Right channel volume, signed. | | P (L) | $X2 |
|
||||||
* P (H) $X3 --HH HHHH High 6 bits of sample pitch.
|
* LLLL LLLL | Low 8 bits of sample pitch. | | P (H)
|
||||||
* SCRN $X4 SSSS SSSS Selects a sample source entry from the
|
* | $X3 | --HH HHHH | High 6 bits of sample pitch. | | SCRN | $X4 |
|
||||||
* directory ADSR (1) $X5 EDDD AAAA ADSR enable (E), decay rate (D),
|
* SSSS SSSS | Selects a sample source entry from the directory. | | ADSR
|
||||||
* attack rate (A).
|
* (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A).
|
||||||
* ADSR (2) $X6 SSSR RRRR Sustain level (S), release rate (R).
|
* | | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). | |
|
||||||
* GAIN $X7 0VVV VVVV 1MMV VVVV Mode (M), value (V).
|
* GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). | | ENVX |
|
||||||
* ENVX $X8 0VVV VVVV Reads current 7-bit value of ADSR/GAIN
|
* $X8 | 0VVV VVVV | Reads current 7-bit value of ADSR/GAIN envelope. | |
|
||||||
* envelope.
|
* OUTX | $X9 | SVVV VVVV | Reads signed 8-bit value of current sample
|
||||||
* OUTX $X9 SVVV VVVV Reads signed 8-bit value of current
|
* wave | | | | | multiplied by ENVX, before
|
||||||
* sample wave multiplied by ENVX, before applying VOL.
|
* applying VOL. |
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class DigitalSignalProcessor {
|
class Dsp {
|
||||||
private:
|
|
||||||
static const size_t kNumVoices = 8;
|
|
||||||
static const size_t kNumVoiceRegs = 10;
|
|
||||||
static const size_t kNumGlobalRegs = 15;
|
|
||||||
|
|
||||||
enum class VoiceState { OFF, ATTACK, DECAY, SUSTAIN, RELEASE };
|
|
||||||
|
|
||||||
struct Voice {
|
|
||||||
int8_t vol_left; // x0
|
|
||||||
int8_t vol_right; // x1
|
|
||||||
uint8_t pitch_low; // x2
|
|
||||||
uint8_t pitch_high; // x3
|
|
||||||
uint8_t source_number; // x4
|
|
||||||
uint8_t adsr1; // x5
|
|
||||||
uint8_t adsr2; // x6
|
|
||||||
uint8_t gain; // x7
|
|
||||||
uint8_t envx; // x8 (read-only)
|
|
||||||
int8_t outx; // x9 (read-only)
|
|
||||||
|
|
||||||
VoiceState state = VoiceState::OFF;
|
|
||||||
uint16_t current_amplitude = 0; // Current amplitude value used for ADSR
|
|
||||||
uint16_t decay_level; // Calculated decay level based on ADSR settings
|
|
||||||
};
|
|
||||||
Voice voices_[8];
|
|
||||||
|
|
||||||
// Global DSP registers
|
|
||||||
uint8_t mvol_left; // 0C
|
|
||||||
uint8_t mvol_right; // 0D
|
|
||||||
uint8_t evol_left; // 0E
|
|
||||||
uint8_t evol_right; // 0F
|
|
||||||
uint8_t kon; // 10
|
|
||||||
uint8_t koff; // 11
|
|
||||||
uint8_t flags; // 12
|
|
||||||
uint8_t endx; // 13 (read-only)
|
|
||||||
|
|
||||||
// Global registers
|
|
||||||
std::vector<uint8_t> globalRegs = std::vector<uint8_t>(kNumGlobalRegs, 0x00);
|
|
||||||
|
|
||||||
static const uint16_t ENVELOPE_MAX = 2047; // $7FF
|
|
||||||
|
|
||||||
// Attack times in ms
|
|
||||||
const std::vector<uint32_t> attackTimes = {
|
|
||||||
4100, 2600, 1500, 1000, 640, 380, 260, 160, 96, 64, 40, 24, 16, 10, 6, 0};
|
|
||||||
|
|
||||||
// Decay times in ms
|
|
||||||
const std::vector<uint32_t> decayTimes = {1200, 740, 440, 290,
|
|
||||||
180, 110, 74, 37};
|
|
||||||
|
|
||||||
// Release times in ms
|
|
||||||
const std::vector<uint32_t> releaseTimes = {
|
|
||||||
// "Infinite" is represented by a large value, e.g., UINT32_MAX
|
|
||||||
UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400,
|
|
||||||
7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500,
|
|
||||||
1200, 880, 740, 590, 440, 370, 290, 220,
|
|
||||||
180, 150, 110, 92, 74, 55, 37, 18};
|
|
||||||
|
|
||||||
// Gain timings for decrease linear, decrease exponential, etc.
|
|
||||||
// Organized by mode: [Linear Increase, Bentline Increase, Linear Decrease,
|
|
||||||
// Exponential Decrease]
|
|
||||||
const std::vector<std::vector<uint32_t>> gainTimings = {
|
|
||||||
{UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380,
|
|
||||||
320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32,
|
|
||||||
24, 20, 16, 12, 10, 8, 6, 4, 2},
|
|
||||||
{UINT32_MAX, 5400, 4600, 3500, 2600, 2300, 1800, 1300, 1100, 900,
|
|
||||||
670, 560, 450, 340, 280, 220, 170, 140, 110, 84,
|
|
||||||
70, 56, 42, 35, 28, 21, 18, 14, 11, 7,
|
|
||||||
/*3.5=*/3},
|
|
||||||
// Repeating the Linear Increase timings for Linear Decrease, since they
|
|
||||||
// are the same.
|
|
||||||
{UINT32_MAX, 3100, 2600, 2000, 1500, 1300, 1000, 770, 640, 510, 380,
|
|
||||||
320, 260, 190, 160, 130, 96, 80, 64, 48, 40, 32,
|
|
||||||
24, 20, 16, 12, 10, 8, 6, 4, 2},
|
|
||||||
{UINT32_MAX, 38000, 28000, 24000, 19000, 14000, 12000, 9400,
|
|
||||||
7100, 5900, 4700, 3500, 2900, 2400, 1800, 1500,
|
|
||||||
1200, 880, 740, 590, 440, 370, 290, 220,
|
|
||||||
180, 150, 110, 92, 55, 37, 18}};
|
|
||||||
|
|
||||||
// DSP Period Table
|
|
||||||
const std::vector<std::vector<uint16_t>> DigitalSignalProcessorPeriodTable = {
|
|
||||||
// ... Your DSP period table here ...
|
|
||||||
};
|
|
||||||
|
|
||||||
// DSP Period Offset
|
|
||||||
const std::vector<uint16_t> DigitalSignalProcessorPeriodOffset = {
|
|
||||||
// ... Your DSP period offsets here ...
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t calculate_envelope_value(uint16_t amplitude) const {
|
|
||||||
// Convert the 16-bit amplitude to an 8-bit envelope value
|
|
||||||
return amplitude >> 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
void apply_envelope_to_output(uint8_t voice_num) {
|
|
||||||
Voice& voice = voices_[voice_num];
|
|
||||||
|
|
||||||
// Scale the OUTX by the envelope value
|
|
||||||
// This might be a linear scaling, or more complex operations can be used
|
|
||||||
voice.outx = (voice.outx * voice.envx) / 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
SampleFetcher sample_fetcher_;
|
|
||||||
SamplePusher sample_pusher_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DigitalSignalProcessor() = default;
|
Dsp(std::vector<uint8_t>& aram) : aram_(aram) {}
|
||||||
|
|
||||||
|
void NewFrame();
|
||||||
|
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
||||||
void SetSampleFetcher(std::function<uint8_t(uint16_t)> fetcher);
|
void Cycle();
|
||||||
void SetSamplePusher(std::function<void(int16_t)> pusher);
|
|
||||||
|
|
||||||
// Read a byte from a voice register
|
void HandleEcho();
|
||||||
uint8_t ReadVoiceReg(uint8_t voice, uint8_t reg) const;
|
void CycleChannel(int ch);
|
||||||
|
|
||||||
// Write a byte to a voice register
|
void HandleNoise();
|
||||||
void WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value);
|
void HandleGain(int ch);
|
||||||
|
|
||||||
// Read a byte from a global register
|
bool CheckCounter(int rate);
|
||||||
uint8_t ReadGlobalReg(uint8_t reg) const {
|
|
||||||
return globalRegs[reg % kNumGlobalRegs];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write a byte to a global register
|
void DecodeBrr(int ch);
|
||||||
void WriteGlobalReg(uint8_t reg, uint8_t value) {
|
|
||||||
globalRegs[reg % kNumGlobalRegs] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t DecodeSample(uint8_t voice_num);
|
uint8_t Read(uint8_t adr);
|
||||||
int16_t ProcessSample(uint8_t voice_num, int16_t sample);
|
void Write(uint8_t adr, uint8_t val);
|
||||||
void MixSamples();
|
|
||||||
|
|
||||||
// Trigger a voice to start playing
|
int16_t GetSample(int ch);
|
||||||
void trigger_voice(uint8_t voice_num) {
|
|
||||||
if (voice_num >= kNumVoices) return;
|
|
||||||
|
|
||||||
Voice& voice = voices_[voice_num];
|
void GetSamples(int16_t* sample_data, int samples_per_frame, bool pal_timing);
|
||||||
voice.state = VoiceState::ATTACK;
|
|
||||||
// Initialize other state management variables if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release a voice (e.g., note release in ADSR)
|
private:
|
||||||
void release_voice(uint8_t voice_num) {
|
int16_t sample_buffer_[0x400 * 2]; // (1024 samples, *2 for stereo)
|
||||||
if (voice_num >= kNumVoices) return;
|
int16_t sample_offset_; // current offset in samplebuffer
|
||||||
|
|
||||||
Voice& voice = voices_[voice_num];
|
std::vector<uint8_t>& aram_;
|
||||||
if (voice.state != VoiceState::OFF) {
|
|
||||||
voice.state = VoiceState::RELEASE;
|
|
||||||
}
|
|
||||||
// Update other state management variables if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate envelope for a given voice
|
// mirror ram
|
||||||
void UpdateEnvelope(uint8_t voice);
|
uint8_t ram[0x80];
|
||||||
|
// 8 channels
|
||||||
// Voice-related functions (implementations)
|
DspChannel channel[8];
|
||||||
void set_voice_volume(int voice_num, int8_t left, int8_t right) {
|
// overarching
|
||||||
voices_[voice_num].vol_left = left;
|
uint16_t counter;
|
||||||
voices_[voice_num].vol_right = right;
|
uint16_t dirPage;
|
||||||
}
|
bool evenCycle;
|
||||||
|
bool mute;
|
||||||
void set_voice_pitch(int voice_num, uint16_t pitch) {
|
bool reset;
|
||||||
voices_[voice_num].pitch_low = pitch & 0xFF;
|
int8_t masterVolumeL;
|
||||||
voices_[voice_num].pitch_high = (pitch >> 8) & 0xFF;
|
int8_t masterVolumeR;
|
||||||
}
|
// accumulation
|
||||||
|
int16_t sampleOutL;
|
||||||
void set_voice_source_number(int voice_num, uint8_t srcn) {
|
int16_t sampleOutR;
|
||||||
voices_[voice_num].source_number = srcn;
|
int16_t echoOutL;
|
||||||
}
|
int16_t echoOutR;
|
||||||
|
// noise
|
||||||
void set_voice_adsr(int voice_num, uint8_t adsr1, uint8_t adsr2) {
|
int16_t noiseSample;
|
||||||
voices_[voice_num].adsr1 = adsr1;
|
uint8_t noiseRate;
|
||||||
voices_[voice_num].adsr2 = adsr2;
|
// echo
|
||||||
}
|
bool echoWrites;
|
||||||
|
int8_t echoVolumeL;
|
||||||
void set_voice_gain(int voice_num, uint8_t gain) {
|
int8_t echoVolumeR;
|
||||||
voices_[voice_num].gain = gain;
|
int8_t feedbackVolume;
|
||||||
}
|
uint16_t echoBufferAdr;
|
||||||
|
uint16_t echoDelay;
|
||||||
uint8_t read_voice_envx(int voice_num) { return voices_[voice_num].envx; }
|
uint16_t echoLength;
|
||||||
|
uint16_t echoBufferIndex;
|
||||||
int8_t read_voice_outx(int voice_num) { return voices_[voice_num].outx; }
|
uint8_t firBufferIndex;
|
||||||
|
int8_t firValues[8];
|
||||||
// Global DSP functions
|
int16_t firBufferL[8];
|
||||||
void set_master_volume(int8_t left, int8_t right) {
|
int16_t firBufferR[8];
|
||||||
mvol_left = left;
|
// sample ring buffer (1024 samples, *2 for stereo)
|
||||||
mvol_right = right;
|
int16_t sampleBuffer[0x400 * 2];
|
||||||
}
|
uint16_t sampleOffset; // current offset in samplebuffer
|
||||||
|
uint32_t lastFrameBoundary;
|
||||||
void set_echo_volume(int8_t left, int8_t right) {
|
|
||||||
evol_left = left;
|
|
||||||
evol_right = right;
|
|
||||||
}
|
|
||||||
|
|
||||||
void update_voice_state(uint8_t voice_num);
|
|
||||||
|
|
||||||
// Override the key_on and key_off methods to utilize the new state management
|
|
||||||
void key_on(uint8_t value) {
|
|
||||||
for (uint8_t i = 0; i < kNumVoices; i++) {
|
|
||||||
if (value & (1 << i)) {
|
|
||||||
trigger_voice(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void key_off(uint8_t value) {
|
|
||||||
for (uint8_t i = 0; i < kNumVoices; i++) {
|
|
||||||
if (value & (1 << i)) {
|
|
||||||
release_voice(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_flags(uint8_t value) {
|
|
||||||
flags = value;
|
|
||||||
// More logic may be needed here depending on flag behaviors
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t read_endx() { return endx; }
|
|
||||||
|
|
||||||
uint16_t AttackRate(uint8_t adsr1) {
|
|
||||||
// Convert the ATTACK portion of adsr1 into a rate of amplitude change
|
|
||||||
// You might need to adjust this logic based on the exact ADSR
|
|
||||||
// implementation details
|
|
||||||
return (adsr1 & 0x0F) * 16; // Just a hypothetical conversion
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t DecayRate(uint8_t adsr2) {
|
|
||||||
// Convert the DECAY portion of adsr2 into a rate of amplitude change
|
|
||||||
return ((adsr2 >> 4) & 0x07) * 8; // Hypothetical conversion
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t ReleaseRate(uint8_t adsr2) {
|
|
||||||
// Convert the RELEASE portion of adsr2 into a rate of amplitude change
|
|
||||||
return (adsr2 & 0x0F) * 16; // Hypothetical conversion
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t CalculateDecayLevel(uint8_t adsr2) {
|
|
||||||
// Calculate the decay level based on the SUSTAIN portion of adsr2
|
|
||||||
// This is the level the amplitude will decay to before entering the SUSTAIN
|
|
||||||
// phase Again, adjust based on your implementation details
|
|
||||||
return ((adsr2 >> 4) & 0x07) * 256; // Hypothetical conversion
|
|
||||||
}
|
|
||||||
|
|
||||||
// Envelope processing for all voices
|
|
||||||
// Goes through each voice and processes its envelope.
|
|
||||||
void process_envelopes() {
|
|
||||||
for (size_t i = 0; i < kNumVoices; ++i) {
|
|
||||||
process_envelope(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Envelope processing for a specific voice
|
|
||||||
// For a given voice, update its state (ADSR), calculate the envelope value,
|
|
||||||
// and apply the envelope to the audio output.
|
|
||||||
void process_envelope(uint8_t voice_num);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
} // namespace emu
|
} // namespace emu
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|||||||
@@ -3,30 +3,86 @@
|
|||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace app {
|
namespace app {
|
||||||
namespace emu {
|
namespace emu {
|
||||||
|
namespace audio {
|
||||||
|
|
||||||
|
// adressing modes
|
||||||
|
|
||||||
|
uint16_t Spc700::ind() {
|
||||||
|
read(PC);
|
||||||
|
return X | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::idx() {
|
||||||
|
uint8_t pointer = ReadOpcode();
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return read_word(((pointer + X) & 0xff) | (PSW.P << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dpx() {
|
||||||
|
uint16_t res = ((ReadOpcode() + X) & 0xff) | (PSW.P << 8);
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dp_y() {
|
||||||
|
uint16_t res = ((ReadOpcode() + Y) & 0xff) | (PSW.P << 8);
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::abs_x() {
|
||||||
|
uint16_t res = (ReadOpcodeWord() + X) & 0xffff;
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::abs_y() {
|
||||||
|
uint16_t res = (ReadOpcodeWord() + Y) & 0xffff;
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::idy() {
|
||||||
|
uint8_t pointer = ReadOpcode();
|
||||||
|
uint16_t adr = read_word(pointer | (PSW.P << 8));
|
||||||
|
callbacks_.idle(false);
|
||||||
|
return (adr + Y) & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dp_imm(uint8_t* srcVal) {
|
||||||
|
*srcVal = ReadOpcode();
|
||||||
|
return ReadOpcode() | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::ind_ind(uint8_t* srcVal) {
|
||||||
|
read(PC);
|
||||||
|
*srcVal = read(Y | (PSW.P << 8));
|
||||||
|
return X | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Spc700::abs_bit(uint16_t* adr) {
|
||||||
|
uint16_t adrBit = ReadOpcodeWord();
|
||||||
|
*adr = adrBit & 0x1fff;
|
||||||
|
return adrBit >> 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::dp_word(uint16_t* low) {
|
||||||
|
uint8_t adr = ReadOpcode();
|
||||||
|
*low = adr | (PSW.P << 8);
|
||||||
|
return ((adr + 1) & 0xff) | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::ind_p() {
|
||||||
|
read(PC);
|
||||||
|
return X++ | (PSW.P << 8);
|
||||||
|
}
|
||||||
|
|
||||||
// Immediate
|
// Immediate
|
||||||
uint8_t Spc700::imm() {
|
uint16_t Spc700::imm() { return PC++; }
|
||||||
PC++;
|
|
||||||
return read(PC);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Direct page
|
// Direct page
|
||||||
uint8_t Spc700::dp() {
|
uint8_t Spc700::dp() {
|
||||||
PC++;
|
return ReadOpcode() | (PSW.P << 8);
|
||||||
uint8_t offset = read(PC);
|
|
||||||
return read((PSW.P << 8) + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t& Spc700::mutable_dp() {
|
|
||||||
PC++;
|
|
||||||
uint8_t offset = read(PC);
|
|
||||||
return mutable_read((PSW.P << 8) + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t Spc700::get_dp_addr() {
|
|
||||||
PC++;
|
|
||||||
uint8_t offset = read(PC);
|
|
||||||
return (PSW.P << 8) + offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct page indexed by X
|
// Direct page indexed by X
|
||||||
@@ -46,23 +102,24 @@ uint8_t Spc700::dp_plus_y() {
|
|||||||
// Indexed indirect (add index before 16-bit lookup).
|
// Indexed indirect (add index before 16-bit lookup).
|
||||||
uint16_t Spc700::dp_plus_x_indirect() {
|
uint16_t Spc700::dp_plus_x_indirect() {
|
||||||
PC++;
|
PC++;
|
||||||
uint16_t addr = read_16(PC + X);
|
uint16_t addr = read_word(PC + X);
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indirect indexed (add index after 16-bit lookup).
|
// Indirect indexed (add index after 16-bit lookup).
|
||||||
uint16_t Spc700::dp_indirect_plus_y() {
|
uint16_t Spc700::dp_indirect_plus_y() {
|
||||||
PC++;
|
PC++;
|
||||||
uint16_t offset = read_16(PC);
|
uint16_t offset = read_word(PC);
|
||||||
return offset + Y;
|
return offset + Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t Spc700::abs() {
|
uint16_t Spc700::dp_dp(uint8_t* src) {
|
||||||
PC++;
|
*src = read(ReadOpcode() | (PSW.P << 8));
|
||||||
uint16_t addr = read(PC) | (read(PC) << 8);
|
return ReadOpcode() | (PSW.P << 8);
|
||||||
return addr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t Spc700::abs() { return ReadOpcodeWord(); }
|
||||||
|
|
||||||
int8_t Spc700::rel() {
|
int8_t Spc700::rel() {
|
||||||
PC++;
|
PC++;
|
||||||
return static_cast<int8_t>(read(PC));
|
return static_cast<int8_t>(read(PC));
|
||||||
@@ -89,6 +146,7 @@ uint16_t Spc700::addr_plus_i_indexed() {
|
|||||||
return read(addr) | (read(addr + 1) << 8);
|
return read(addr) | (read(addr + 1) << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace audio
|
||||||
} // namespace emu
|
} // namespace emu
|
||||||
} // namespace app
|
} // namespace app
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user