3 Commits

Author SHA1 Message Date
scawful
75bf38fa71 backend-infra-engineer: Pre-0.2.2 2024 Q3 snapshot 2024-07-31 12:42:04 -04:00
scawful
92cc574e15 backend-infra-engineer: Pre-0.2.2 2024 Q2 snapshot 2024-04-14 15:49:57 -05:00
scawful
546093360f backend-infra-engineer: Pre-0.2.2 2024 Q1 snapshot 2024-02-09 21:44:12 -05:00
204 changed files with 31011 additions and 13058 deletions

View File

@@ -34,6 +34,9 @@ jobs:
- name: Install Audio Libs
run: sudo apt install libwavpack-dev
- name: Install Abseil-cpp
run: sudo apt install libabsl-dev
- 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.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type

45
.github/workflows/doxy.yml vendored Normal file
View 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
View File

@@ -7,9 +7,6 @@
[submodule "src/lib/ImGuiColorTextEdit"]
path = src/lib/ImGuiColorTextEdit
url = https://github.com/BalazsJako/ImGuiColorTextEdit.git
[submodule "src/lib/sneshacking"]
path = src/lib/sneshacking
url = https://github.com/Skarsnik/sneshacking.git
[submodule "assets/asm/alttp-hacker-workspace"]
path = assets/asm/alttp-hacker-workspace
url = https://github.com/scawful/alttp-hacker-workspace.git
@@ -22,9 +19,6 @@
[submodule "src/lib/asar"]
path = src/lib/asar
url = https://github.com/RPGHacker/asar.git
[submodule "src/lib/snes_spc"]
path = src/lib/snes_spc
url = https://github.com/blarggs-audio-libraries/snes_spc.git
[submodule "src/lib/SDL_mixer"]
path = src/lib/SDL_mixer
url = https://github.com/libsdl-org/SDL_mixer.git
[submodule "src/lib/imgui_test_engine"]
path = src/lib/imgui_test_engine
url = https://github.com/ocornut/imgui_test_engine.git

View File

@@ -3,55 +3,32 @@ cmake_minimum_required(VERSION 3.10)
# Yet Another Zelda3 Editor
# by scawful
project(yaze VERSION 0.01)
project(yaze VERSION 0.10)
# C++ Standard Specifications
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
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_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
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(BUILD_SHARED_LIBS ON)
set(BUILD_SHARED_LIBS OFF)
set(CMAKE_FIND_FRAMEWORK LAST)
# Abseil Standard Specifications
include(cmake/absl.cmake)
add_subdirectory(src/lib/abseil-cpp)
include(cmake/openssl.cmake)
# Video Libraries
find_package(PNG REQUIRED)
find_package(OpenGL REQUIRED)
include(cmake/sdl2.cmake)
# Asar
# add_subdirectory(src/lib/asar/src)
# 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)
add_subdirectory(src/lib/asar/src)
include(cmake/asar.cmake)
# ImGui
include(cmake/imgui.cmake)
# Project Files
add_subdirectory(src)
add_subdirectory(test)
add_subdirectory(src)

2857
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,7 @@ Building and installation
## Documentation
- For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze.
- For developers, please refer to [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
--------
@@ -44,9 +44,10 @@ SDL2, ImGui and Abseil are subject to respective licenses.
Screenshots
--------
![image](https://user-images.githubusercontent.com/47263509/194669806-2b0da68d-9d38-4f52-bcce-c60ee861092c.png)
![image](https://github.com/scawful/yaze/assets/47263509/8b62b142-1de4-4ca4-8c49-d50c08ba4c8e)
![image](https://github.com/scawful/yaze/assets/47263509/8913f7ff-6345-4295-ae05-782fd3949eb5)
![image](https://github.com/scawful/yaze/assets/47263509/d8f0039d-d2e4-47d7-b420-554b20ac626f)
![image](https://github.com/scawful/yaze/assets/47263509/34b36666-cbea-420b-af90-626099470ae4)
![image](https://github.com/scawful/yaze/assets/47263509/e1cf3edb-a59e-4f0a-b4e0-d68925803e58)

View 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"
}
}

View File

@@ -1,3 +1,4 @@
find_package(absl)
set(ABSL_PROPAGATE_CXX_STD ON)
set(ABSL_CXX_STANDARD 17)
set(ABSL_USE_GOOGLETEST_HEAD ON)

View File

@@ -21,6 +21,14 @@ target_include_directories(ImGuiColorTextEdit PUBLIC ${IMGUI_PATH})
target_compile_definitions(ImGuiColorTextEdit PUBLIC
IMGUI_IMPL_OPENGL_LOADER_CUSTOM=<SDL2/SDL_opengl.h> GL_GLEXT_PROTOTYPES=1)
set(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(
IMGUI_SRC
${IMGUI_PATH}/imgui.cpp

View File

@@ -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()

View File

@@ -1,19 +1,6 @@
# SDL2, SDL2_image and SDL2_mixer
# SDL2
if (UNIX)
add_subdirectory(src/lib/SDL)
else()
find_package(SDL2)
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)
endif()

View File

@@ -1,17 +1,27 @@
For VSCode users, use the following CMake extensions with MinGW-w64
https://marketplace.visualstudio.com/items?itemName=twxs.cmake
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
https://www.msys2.org/
Add to environment variables `C:\msys64\mingw64\bin`
Install the following packages using `pacman -S <package-name>`
`mingw-w64-x86_64-gcc`
`mingw-w64-x86_64-gcc-libs`
`mingw-w64-x86_64-cmake`
`mingw-w64-x86_64-glew`
`mingw-w64-x86_64-lib-png`
# Build Instructions
## Windows
For VSCode users, use the following CMake extensions with MinGW-w64
https://marketplace.visualstudio.com/items?itemName=twxs.cmake
https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools
https://www.msys2.org/
Add to environment variables `C:\msys64\mingw64\bin`
Install the following packages using `pacman -S <package-name>`
`mingw-w64-x86_64-gcc`
`mingw-w64-x86_64-gcc-libs`
`mingw-w64-x86_64-cmake`
`mingw-w64-x86_64-glew`
`mingw-w64-x86_64-lib-png`
# macOS
- Clang 15.0.1 x86_64-apple-darrwin22.5.0
- SDL2 Source v2.26.5
- Removed snes_spc
- Removed asar_static

View File

@@ -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++.
Currently, the Compress and Uncompress methods from Hyrule Magic are used and all other compression methods are considered deprecated.
## Key Definitions
### Constants and Macros:
@@ -62,6 +64,3 @@ Using `CompressionContext` to handle compression.
- **Compression String Creation**: `CreateCompressionString`
- **Compression Result Validation**: Such as `ValidateCompressionResult` 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.

View File

@@ -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)
- Basic knowledge of hexadecimal and binary data
## Installation
To install YAZE, follow these steps based on your platform:
### Windows
### MacOS
### GNU/Linux
## Usage
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.
...

View File

@@ -23,59 +23,6 @@ For developers to reference.
- SDL2
- **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
- [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.
- 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
- [app/gfx/bitmap.cc](../src/app/gfx/bitmap.cc)

View File

@@ -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

View File

@@ -2,22 +2,8 @@ set(
YAZE_APP_CORE_SRC
app/core/common.cc
app/core/controller.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
app/core/labeling.cc
app/emu/emulator.cc
)
set(
@@ -27,33 +13,22 @@ set(
app/gfx/scad_format.cc
app/gfx/snes_palette.cc
app/gfx/snes_tile.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
app/gfx/snes_color.cc
app/gfx/tilesheet.cc
)
set(
YAZE_GUI_SRC
app/gui/asset_browser.cc
app/gui/canvas.cc
app/gui/input.cc
app/gui/style.cc
app/gui/widgets.cc
app/gui/color.cc
app/gui/pipeline.cc
app/gui/zeml.cc
)
set(
YAZE_APP_EMU_SRC
app/emu/emulator.cc
app/emu/audio/apu.cc
app/emu/audio/spc700.cc
app/emu/audio/dsp.cc
@@ -75,9 +50,10 @@ if(WIN32 OR MINGW)
add_definitions(-DSDL_MAIN_HANDLED)
endif()
if (WIN32 OR MINGW OR UNIX)
if (WIN32 OR MINGW OR UNIX AND NOT APPLE)
list(APPEND YAZE_APP_CORE_SRC
app/core/platform/font_loader.cc
app/core/platform/clipboard.cc
)
endif()
@@ -87,6 +63,7 @@ if(APPLE)
app/core/platform/app_delegate.mm
app/core/platform/font_loader.mm
app/core/platform/clipboard.mm
app/core/platform/file_path.mm
)
find_library(COCOA_LIBRARY Cocoa)
@@ -97,7 +74,7 @@ if(APPLE)
endif()
include(app/CMakeLists.txt)
include(cli/CMakeLists.txt)
# include(cli/CMakeLists.txt) Excluded for now, macOS include breaks action build
if (UNIX)
target_compile_definitions(yaze PRIVATE "linux")
@@ -130,4 +107,6 @@ set_target_properties(yaze
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LINK_FLAGS "${CMAKE_CURRENT_SOURCE_DIR}/yaze.res"
)
endif()
endif()
add_subdirectory(test)

View File

@@ -1,3 +1,6 @@
include(app/editor/CMakeLists.txt)
include(app/zelda3/CMakeLists.txt)
add_executable(
yaze
app/yaze.cc
@@ -9,14 +12,15 @@ add_executable(
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
${IMGUI_TEST_ENGINE_SOURCES}
)
target_include_directories(
yaze PUBLIC
lib/
app/
lib/SDL_mixer/include/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
)
@@ -27,7 +31,7 @@ target_link_libraries(
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
SDL2_mixer
ImGuiTestEngine
ImGui
)

View File

@@ -1,6 +1,6 @@
#include "common.h"
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <chrono>
#include <cstdint>
@@ -9,12 +9,31 @@
#include <stack>
#include <string>
#include "absl/strings/str_format.h"
namespace yaze {
namespace app {
namespace core {
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) {
if (addr >= 0x808000) {
addr -= 0x808000;
@@ -24,8 +43,15 @@ uint32_t SnesToPc(uint32_t addr) {
}
uint32_t PcToSnes(uint32_t addr) {
if (addr >= 0x400000) return -1;
addr = ((addr << 1) & 0x7F0000) | (addr & 0x7FFF) | 0x8000;
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
b[2] = static_cast<uint8_t>(b[2] * 2);
if (b[1] >= 0x80) {
b[2] += 1;
} else {
b[1] += 0x80;
}
return addr;
}
@@ -148,6 +174,14 @@ uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
// Initialize the static member
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 app
} // namespace yaze

View File

@@ -1,41 +1,52 @@
#ifndef YAZE_CORE_COMMON_H
#define YAZE_CORE_COMMON_H
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <chrono>
#include <cstdint>
#include <fstream>
#include <functional>
#include <iostream>
#include <memory>
#include <stack>
#include <string>
namespace yaze {
namespace app {
/**
* @namespace yaze::app::core
* @brief Core application logic and utilities.
*/
namespace core {
/**
* @class ExperimentFlags
* @brief A class to manage experimental feature flags.
*/
class ExperimentFlags {
public:
struct Flags {
// Load and render overworld sprites to the screen. Unstable.
bool kDrawOverworldSprites = false;
// Bitmap manager abstraction to manage graphics bin of ROM.
// Bitmap manager abstraction to manage graphics bin of Rom.
bool kUseBitmapManager = true;
// Log instructions to the GUI debugger.
bool kLogInstructions = false;
bool kLogInstructions = true;
// Flag to enable ImGui input config flags. Currently is
// handled manually by controller class but should be
// ported away from that eventually.
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;
// Flag to enable the saving of gfx groups to the rom.
bool kSaveGfxGroups = false;
// Flag to enable the change queue, which could have any anonymous
// save routine for the ROM. In practice, just the overworld tilemap
// save routine for the Rom. In practice, just the overworld tilemap
// and tile32 save.
bool kSaveWithChangeQueue = false;
@@ -49,7 +60,38 @@ class ExperimentFlags {
// only supports macOS.
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;
@@ -72,6 +114,11 @@ class ExperimentFlags {
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>
class NotifyValue {
public:
@@ -113,81 +160,6 @@ class NotifyValue {
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 {
private:
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 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 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);
void stle32b(uint8_t *const p_arr, uint32_t const p_val);
uint16_t ldle16b(uint8_t const *const p_arr);
void stle32b_i(uint8_t *const p_arr, size_t const p_index,
uint32_t const p_val);
void stle16b(uint8_t *const p_arr, uint16_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 app
} // namespace yaze
#endif
#endif

View File

@@ -5,10 +5,6 @@
#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 END_TAB_BAR() \
ImGui::EndTabBar(); \
@@ -19,11 +15,6 @@
ImGui::EndTabItem(); \
}
#define MENU_BAR() if (ImGui::BeginMenuBar()) {
#define END_MENU_BAR() \
ImGui::EndMenuBar(); \
}
#define MENU_ITEM(w) if (ImGui::MenuItem(w))
#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) \
{ \
auto error = expression; \
@@ -121,9 +121,9 @@
using ushort = unsigned short;
using uint = unsigned int;
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 {
OWBlockset light_world; // 64 maps
OWBlockset dark_world; // 64 maps
@@ -135,7 +135,8 @@ namespace yaze {
namespace app {
namespace core {
constexpr float kYazeVersion = 0.05;
constexpr uint32_t kRedPen = 0xFF0000FF;
constexpr float kYazeVersion = 0.2;
// ============================================================================
// Magic numbers
@@ -185,96 +186,7 @@ constexpr int text_data2 = 0x75F40;
constexpr int pointers_dictionaries = 0x74703;
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_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
@@ -292,146 +204,90 @@ constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
constexpr int GraveLinkSpecialHole = 0x46DD9; // 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
// ============================================================================
static const absl::string_view RoomEffect[] = {"Nothing",
"Nothing",
"Moving Floor",
"Moving Water",
"Trinexx Shell",
"Red Flashes",
"Light Torch to See Floor",
"Ganon's Darkness"};
static const std::string RoomEffect[] = {"Nothing",
"Nothing",
"Moving Floor",
"Moving Water",
"Trinexx Shell",
"Red Flashes",
"Light Torch to See Floor",
"Ganon's Darkness"};
static const absl::string_view RoomTag[] = {"Nothing",
static const std::string RoomTag[] = {"Nothing",
"NW Kill Enemy to Open",
"NE Kill Enemy to Open",
"SW Kill Enemy to Open",
"SE Kill Enemy to Open",
"W Kill Enemy to Open",
"E Kill Enemy to Open",
"N Kill Enemy to Open",
"S Kill Enemy to Open",
"Clear Quadrant to Open",
"Clear Full Tile to Open",
"NW Kill Enemy to Open",
"NE Kill Enemy to Open",
"SW Kill Enemy to Open",
"SE Kill Enemy to Open",
"W Kill Enemy to Open",
"E Kill Enemy to Open",
"N Kill Enemy to Open",
"S Kill Enemy to Open",
"Clear Quadrant to Open",
"Clear Full Tile to Open",
"NW Push Block to Open",
"NE Push Block to Open",
"SW Push Block to Open",
"SE Push Block to Open",
"W Push Block to Open",
"E Push Block to Open",
"N Push Block to Open",
"S Push Block to Open",
"Push Block to Open",
"Pull Lever to Open",
"Collect Prize to Open",
"NW Push Block to Open",
"NE Push Block to Open",
"SW Push Block to Open",
"SE Push Block to Open",
"W Push Block to Open",
"E Push Block to Open",
"N Push Block to Open",
"S Push Block to Open",
"Push Block to Open",
"Pull Lever to Open",
"Collect Prize to Open",
"Hold Switch Open Door",
"Toggle Switch to Open Door",
"Turn off Water",
"Turn on Water",
"Water Gate",
"Water Twin",
"Moving Wall Right",
"Moving Wall Left",
"Crash",
"Crash",
"Push Switch Exploding Wall",
"Holes 0",
"Open Chest (Holes 0)",
"Holes 1",
"Holes 2",
"Defeat Boss for Dungeon Prize",
"Hold Switch Open Door",
"Toggle Switch to Open Door",
"Turn off Water",
"Turn on Water",
"Water Gate",
"Water Twin",
"Moving Wall Right",
"Moving Wall Left",
"Crash",
"Crash",
"Push Switch Exploding Wall",
"Holes 0",
"Open Chest (Holes 0)",
"Holes 1",
"Holes 2",
"Defeat Boss for Dungeon Prize",
"SE Kill Enemy to Push Block",
"Trigger Switch Chest",
"Pull Lever Exploding Wall",
"NW Kill Enemy for Chest",
"NE Kill Enemy for Chest",
"SW Kill Enemy for Chest",
"SE Kill Enemy for Chest",
"W Kill Enemy for Chest",
"E Kill Enemy for Chest",
"N Kill Enemy for Chest",
"S Kill Enemy for Chest",
"Clear Quadrant for Chest",
"Clear Full Tile for Chest",
"SE Kill Enemy to Push Block",
"Trigger Switch Chest",
"Pull Lever Exploding Wall",
"NW Kill Enemy for Chest",
"NE Kill Enemy for Chest",
"SW Kill Enemy for Chest",
"SE Kill Enemy for Chest",
"W Kill Enemy for Chest",
"E Kill Enemy for Chest",
"N Kill Enemy for Chest",
"S Kill Enemy for Chest",
"Clear Quadrant for Chest",
"Clear Full Tile for Chest",
"Light Torches to Open",
"Holes 3",
"Holes 4",
"Holes 5",
"Holes 6",
"Agahnim Room",
"Holes 7",
"Holes 8",
"Open Chest for Holes 8",
"Push Block for Chest",
"Clear Room for Triforce Door",
"Light Torches for Chest",
"Kill Boss Again"};
"Light Torches to Open",
"Holes 3",
"Holes 4",
"Holes 5",
"Holes 6",
"Agahnim Room",
"Holes 7",
"Holes 8",
"Open Chest for Holes 8",
"Push Block for Chest",
"Clear Room for Triforce Door",
"Light Torches for Chest",
"Kill Boss Again"};
static const absl::string_view SecretItemNames[] = {
static const std::string SecretItemNames[] = {
"Nothing", "Green Rupee", "Rock hoarder", "Bee", "Health pack",
"Bomb", "Heart ", "Blue Rupee",
@@ -443,7 +299,7 @@ static const absl::string_view SecretItemNames[] = {
"Hole", "Warp", "Staircase", "Bombable", "Switch"};
static const absl::string_view TileTypeNames[] = {
static const std::string TileTypeNames[] = {
"$00 Nothing (standard floor)",
"$01 Collision",
"$02 Collision",
@@ -701,7 +557,7 @@ static const absl::string_view TileTypeNames[] = {
"$FE Door X top? (unused?)",
"$FF Door X top? (unused?)"};
static const absl::string_view kSpriteDefaultNames[]{
static const std::string kSpriteDefaultNames[]{
"00 Raven",
"01 Vulture",
"02 Flying Stalfos Head",
@@ -960,7 +816,7 @@ static const absl::string_view kSpriteDefaultNames[]{
"FF",
};
static const absl::string_view overlordnames[] = {
static const std::string overlordnames[] = {
"Overlord_SpritePositionTarget",
"Overlord_AllDirectionMetalBallFactory",
"Overlord_CascadeMetalBallFactory",

View File

@@ -1,11 +1,22 @@
#include "controller.h"
#include <SDL.h>
#include <SDL_mixer.h>
#include <imgui/backends/imgui_impl_sdl2.h>
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imgui.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>
@@ -22,15 +33,100 @@ namespace core {
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() {
ImGuiIO &io = ImGui::GetIO();
io.KeyMap[ImGuiKey_LeftSuper] = SDL_GetScancodeFromKey(SDLK_LGUI);
io.KeyMap[ImGuiKey_Backspace] = SDL_GetScancodeFromKey(SDLK_BACKSPACE);
io.KeyMap[ImGuiKey_LeftShift] = SDL_GetScancodeFromKey(SDLK_LSHIFT);
io.KeyMap[ImGuiKey_Enter] = SDL_GetScancodeFromKey(SDLK_RETURN);
io.KeyMap[ImGuiKey_UpArrow] = SDL_GetScancodeFromKey(SDLK_UP);
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_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) {
@@ -48,24 +144,63 @@ void InitializeClipboard() {
io.ClipboardUserData = nullptr;
}
void HandleKeyDown(SDL_Event &event) {
void HandleKeyDown(SDL_Event &event, editor::MasterEditor &editor) {
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) {
case SDLK_UP:
case SDLK_DOWN:
case SDLK_RETURN:
case SDLK_BACKSPACE:
case SDLK_LSHIFT:
case SDLK_LCTRL:
case SDLK_TAB:
io.KeysDown[event.key.keysym.scancode] = (event.type == SDL_KEYDOWN);
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:
break;
}
}
void HandleKeyUp(SDL_Event &event) {
void HandleKeyUp(SDL_Event &event, editor::MasterEditor &editor) {
ImGuiIO &io = ImGui::GetIO();
int key = event.key.keysym.scancode;
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.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 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) {
@@ -92,17 +268,42 @@ void HandleMouseMovement(int &wheel) {
io.MousePos = ImVec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
io.MouseDown[0] = buttons & SDL_BUTTON(SDL_BUTTON_LEFT);
io.MouseDown[1] = buttons & SDL_BUTTON(SDL_BUTTON_RIGHT);
io.MouseDown[2] = buttons & SDL_BUTTON(SDL_BUTTON_MIDDLE);
io.MouseWheel = static_cast<float>(wheel);
}
} // 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(CreateRenderer())
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();
master_editor_.SetupScreen(renderer_);
master_editor_.SetupScreen(renderer_, filename);
active_ = true;
return absl::OkStatus();
}
@@ -115,10 +316,10 @@ void Controller::OnInput() {
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
HandleKeyDown(event);
HandleKeyDown(event, master_editor_);
break;
case SDL_KEYUP:
HandleKeyUp(event);
HandleKeyUp(event, master_editor_);
break;
case SDL_TEXTINPUT:
io.AddInputCharactersUTF8(event.text.text);
@@ -146,26 +347,57 @@ void Controller::OnInput() {
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 {
ImGui::Render();
SDL_RenderClear(renderer_.get());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get());
SDL_RenderPresent(renderer_.get());
}
void Controller::OnExit() {
master_editor_.Shutdown();
Mix_CloseAudio();
ImGui_ImplSDLRenderer2_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
if (flags()->kLoadAudioDevice) {
SDL_PauseAudioDevice(audio_device_, 1);
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();
SDL_Quit();
}
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(
absl::StrFormat("SDL_Init: %s\n", SDL_GetError()));
} else {
@@ -186,11 +418,6 @@ absl::Status Controller::CreateSDL_Window() {
return absl::InternalError(
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();
}
@@ -215,8 +442,9 @@ absl::Status Controller::CreateGuiContext() {
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
if (flags()->kUseNewImGuiInput) {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
}
@@ -235,7 +463,7 @@ absl::Status Controller::CreateGuiContext() {
// Build a new ImGui frame
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame(window_.get());
ImGui_ImplSDL2_NewFrame();
return absl::OkStatus();
}
@@ -243,13 +471,13 @@ absl::Status Controller::CreateGuiContext() {
absl::Status Controller::LoadFontFamilies() const {
ImGuiIO &io = ImGui::GetIO();
// Define constants
static const char *KARLA_REGULAR = "assets/font/Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "assets/font/Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "assets/font/Cousine-Regular.ttf";
static const char *DROID_SANS = "assets/font/DroidSans.ttf";
static const char *NOTO_SANS_JP = "assets/font/NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "assets/font/IBMPlexSansJP-Bold.ttf";
const char *font_path = "assets/font/";
static const char *KARLA_REGULAR = "Karla-Regular.ttf";
static const char *ROBOTO_MEDIUM = "Roboto-Medium.ttf";
static const char *COUSINE_REGULAR = "Cousine-Regular.ttf";
static const char *DROID_SANS = "DroidSans.ttf";
static const char *NOTO_SANS_JP = "NotoSansJP.ttf";
static const char *IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
static const float FONT_SIZE_DEFAULT = 14.0f;
static const float FONT_SIZE_DROID_SANS = 16.0f;
static const float ICON_FONT_SIZE = 18.0f;
@@ -278,23 +506,78 @@ absl::Status Controller::LoadFontFamilies() const {
float font_size =
(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(
absl::StrFormat("Failed to load font from %s", font_path));
absl::StrFormat("Failed to load font from %s", actual_font_path));
}
// 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);
// 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());
}
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 app
} // namespace yaze
} // namespace yaze

View File

@@ -2,17 +2,18 @@
#define YAZE_APP_CORE_CONTROLLER_H
#include <SDL.h>
#include <imgui/backends/imgui_impl_sdl2.h>
#include <imgui/backends/imgui_impl_sdlrenderer2.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "imgui/backends/imgui_impl_sdl2.h"
#include "imgui/backends/imgui_impl_sdlrenderer2.h"
#include "imgui/imconfig.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include <memory>
#include "absl/status/status.h"
#include "app/core/common.h"
#include "app/core/editor.h"
#include "app/editor/master_editor.h"
#include "app/editor/utils/editor.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
@@ -22,15 +23,31 @@ namespace yaze {
namespace app {
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 {
public:
bool IsActive() const { return active_; }
absl::Status OnEntry();
absl::Status OnEntry(std::string filename = "");
void OnInput();
void OnLoad();
absl::Status OnLoad();
void DoRender() const;
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:
struct sdl_deleter {
void operator()(SDL_Window *p) const {
@@ -46,16 +63,17 @@ class Controller : public ExperimentFlags {
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; }
friend int ::main(int argc, char **argv);
bool active_;
Platform platform_;
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_Renderer> renderer_;
};
@@ -64,4 +82,4 @@ class Controller : public ExperimentFlags {
} // namespace app
} // namespace yaze
#endif // YAZE_APP_CORE_CONTROLLER_H
#endif // YAZE_APP_CORE_CONTROLLER_H

View File

@@ -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
View 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
View 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_

View File

@@ -1,6 +1,8 @@
#ifndef YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
#define YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H
#ifdef TARGET_OS_MAC
#ifdef __cplusplus
extern "C" {
#endif
@@ -11,4 +13,6 @@ void InitializeCocoa();
} // extern "C"
#endif
#endif // TARGET_OS_MAC
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H

View File

@@ -1,12 +1,26 @@
// 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/controller.h"
#import "app/core/platform/file_dialog.h"
#import "app/editor/utils/editor.h"
#import "app/rom.h"
#if defined(__APPLE__) && defined(__MACH__)
/* Apple OSX and iOS (Darwin). */
#include <TargetConditionals.h>
#import <CoreText/CoreText.h>
#if TARGET_IPHONE_SIMULATOR == 1
/* iOS in Xcode simulator */
#elif TARGET_OS_IPHONE == 1
/* iOS */
#elif TARGET_OS_MAC == 1
/* macOS */
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
- (void)setupMenus;
// - (void)changeApplicationIcon;
@@ -195,7 +209,14 @@
}
- (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 {
@@ -215,4 +236,8 @@ extern "C" void InitializeCocoa() {
}
}
@end
@end
#endif
#endif

View 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) {}

View File

@@ -8,6 +8,7 @@ void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#elif defined(__APPLE__)
#include <cstdint>
#include <vector>
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__)
#include <cstdint>
#include <vector>
void CopyImageToClipboard(const std::vector<uint8_t>& data) {
std::cout << "CopyImageToClipboard() is not implemented on Linux."
<< std::endl;
}
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width,
int& height) {
std::cout << "GetImageFromClipboard() is not implemented on Linux."
<< std::endl;
}
void CopyImageToClipboard(const std::vector<uint8_t>& data);
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#endif

View File

@@ -3,6 +3,7 @@
#include <iostream>
#include <vector>
#ifdef TARGET_OS_MAC
#import <Cocoa/Cocoa.h>
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);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGContextRelease(context);
}
}
#endif

View File

@@ -1,3 +1,6 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#include <string>
#ifdef _WIN32
@@ -42,13 +45,39 @@ class FileDialogWrapper {
#elif defined(__APPLE__)
#include "TargetConditionals.h"
#include <string>
#include <vector>
#ifdef TARGET_OS_MAC
// Other kinds of Mac OS
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);
};
#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__)
class FileDialogWrapper {
@@ -62,4 +91,6 @@ class FileDialogWrapper {
#else
#error "Unsupported platform."
#endif
#endif
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H

View File

@@ -1,6 +1,49 @@
#import <Cocoa/Cocoa.h>
#include <string>
#include <vector>
#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() {
NSOpenPanel* openPanel = [NSOpenPanel openPanel];
[openPanel setCanChooseFiles:YES];
@@ -15,3 +58,59 @@ std::string FileDialogWrapper::ShowOpenFileDialog() {
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__

View 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

View 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

View File

@@ -1,6 +1,6 @@
#include "app/core/platform/font_loader.h"
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <string>
#include <vector>

View File

@@ -2,14 +2,25 @@
#ifndef FONTLOADER_H
#define FONTLOADER_H
// Function declaration for loading system fonts into ImGui
void LoadSystemFonts();
#include "TargetConditionals.h"
#ifdef _WIN32
#include <Windows.h>
// Windows specific function declaration for loading system fonts into ImGui
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
DWORD FontType, LPARAM lParam);
#elif __APPLE__
#ifdef TARGET_OS_MAC
void LoadSystemFonts();
#elif TARGET_OS_IPHONE
void LoadSystemFonts();
#endif
#endif
#endif // FONTLOADER_H

View File

@@ -1,11 +1,30 @@
// FontLoader.mm
#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"
#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() {
// List of common macOS system fonts
NSArray *fontNames = @[ @"Helvetica", @"Times New Roman", @"Courier", @"Arial", @"Verdana" ];
@@ -47,4 +66,9 @@ void LoadSystemFonts() {
CFRelease(fontURL);
}
}
}
}
#else
// Unsupported platform
#endif
#endif

138
src/app/core/project.h Normal file
View 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
View 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

View 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
)

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View 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", &current_room_id_);
gui::InputHex("Palette ID", &current_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", &current_entrance.entrance_id_);
gui::InputHexWord("Room ID", &current_entrance.room_, 50.f, true);
SameLine();
gui::InputHexByte("Dungeon ID", &current_entrance.dungeon_id_, 50.f, true);
gui::InputHexByte("Blockset", &current_entrance.blockset_, 50.f, true);
SameLine();
gui::InputHexByte("Music", &current_entrance.music_, 50.f, true);
SameLine();
gui::InputHexByte("Floor", &current_entrance.floor_);
Separator();
gui::InputHexWord("Player X ", &current_entrance.x_position_);
SameLine();
gui::InputHexWord("Player Y ", &current_entrance.y_position_);
gui::InputHexWord("Camera X", &current_entrance.camera_trigger_x_);
SameLine();
gui::InputHexWord("Camera Y", &current_entrance.camera_trigger_y_);
gui::InputHexWord("Scroll X ", &current_entrance.camera_x_);
SameLine();
gui::InputHexWord("Scroll Y ", &current_entrance.camera_y_);
gui::InputHexWord("Exit", &current_entrance.exit_, 50.f, true);
Separator();
Text("Camera Boundaries");
Separator();
Text("\t\t\t\t\tNorth East South West");
gui::InputHexByte("Quadrant", &current_entrance.camera_boundary_qn_, 50.f,
true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_qe_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_qs_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_qw_, 50.f, true);
gui::InputHexByte("Full room", &current_entrance.camera_boundary_fn_, 50.f,
true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_fe_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_entrance.camera_boundary_fs_, 50.f, true);
SameLine();
gui::InputHexByte("", &current_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

View 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

View File

@@ -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", &current_room_id_);
gui::InputHex("Palette ID", &current_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

View File

@@ -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

View 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

View File

@@ -1,33 +1,48 @@
#ifndef 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 "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/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/widgets.h"
#include "app/gui/style.h"
#include "app/rom.h"
#include "app/zelda3/overworld.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
namespace editor {
class GfxGroupEditor : public SharedROM {
/**
* @class GfxGroupEditor
* @brief Manage graphics group configurations in a Rom.
*/
class GfxGroupEditor : public SharedRom {
public:
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:
int preview_palette_id_ = 0;
@@ -35,20 +50,21 @@ class GfxGroupEditor : public SharedROM {
uint8_t selected_blockset_ = 0;
uint8_t selected_roomset_ = 0;
uint8_t selected_spriteset_ = 0;
uint8_t selected_paletteset_ = 0;
gui::Canvas blockset_canvas_;
gui::Canvas roomset_canvas_;
gui::Canvas spriteset_canvas_;
gfx::SNESPalette palette_;
gfx::SnesPalette palette_;
gfx::PaletteGroup palette_group_;
gfx::Bitmap tile16_blockset_bmp_;
gfx::Bitmap* tile16_blockset_bmp_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_;
gui::BitmapViewer gfx_group_viewer_;
zelda3::Overworld overworld_;
zelda3::overworld::Overworld overworld_;
};
} // namespace editor

View File

@@ -1,22 +1,23 @@
#include "app/editor/graphics_editor.h"
#include "graphics_editor.h"
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "absl/status/statusor.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/compression.h"
#include "app/gfx/scad_format.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/canvas.h"
#include "app/gui/color.h"
#include "app/gui/input.h"
#include "app/gui/pipeline.h"
#include "app/gui/style.h"
#include "app/rom.h"
@@ -24,10 +25,17 @@ namespace yaze {
namespace app {
namespace editor {
using gfx::kPaletteGroupAddressesKeys;
using ImGui::Button;
using ImGui::InputInt;
using ImGui::InputText;
using ImGui::SameLine;
using ImGui::TableNextColumn;
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
constexpr ImGuiTableFlags kGfxEditTableFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable |
@@ -39,9 +47,20 @@ constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
ImGuiTabBarFlags_FittingPolicyResizeDown |
ImGuiTabBarFlags_TabListPopupButton;
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
absl::Status GraphicsEditor::Update() {
TAB_BAR("##TabBar")
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_ = UpdateLinkGfxView();
END_TAB_BAR()
@@ -50,12 +69,13 @@ absl::Status GraphicsEditor::Update() {
}
absl::Status GraphicsEditor::UpdateGfxEdit() {
TAB_ITEM("Graphics Editor")
TAB_ITEM("Sheet Editor")
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
ImVec2(0, 0))) {
for (const auto& name : kGfxEditColumnNames)
ImGui::TableSetupColumn(name.data());
for (const auto& name :
{"Tilesheets", "Current Graphics", "Palette Controls"})
ImGui::TableSetupColumn(name);
ImGui::TableHeadersRow();
@@ -63,13 +83,13 @@ absl::Status GraphicsEditor::UpdateGfxEdit() {
status_ = UpdateGfxSheetList();
NEXT_COLUMN();
if (rom()->isLoaded()) {
if (rom()->is_loaded()) {
DrawGfxEditToolset();
status_ = UpdateGfxTabView();
}
NEXT_COLUMN();
if (rom()->isLoaded()) {
if (rom()->is_loaded()) {
status_ = UpdatePaletteColumn();
}
}
@@ -87,76 +107,77 @@ void GraphicsEditor::DrawGfxEditToolset() {
"Zoom In", "Current Color", "Tile Size"})
ImGui::TableSetupColumn(name);
ImGui::TableNextColumn();
TableNextColumn();
if (Button(ICON_MD_SELECT_ALL)) {
gfx_edit_mode_ = GfxEditMode::kSelect;
}
ImGui::TableNextColumn();
TableNextColumn();
if (Button(ICON_MD_DRAW)) {
gfx_edit_mode_ = GfxEditMode::kPencil;
}
HOVER_HINT("Draw with current color");
ImGui::TableNextColumn();
TableNextColumn();
if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
gfx_edit_mode_ = GfxEditMode::kFill;
}
HOVER_HINT("Fill with current color");
ImGui::TableNextColumn();
TableNextColumn();
if (Button(ICON_MD_CONTENT_COPY)) {
std::vector<uint8_t> png_data =
rom()->bitmap_manager().GetBitmap(current_sheet_)->GetPngData();
rom()->bitmap_manager().shared_bitmap(current_sheet_).GetPngData();
CopyImageToClipboard(png_data);
}
HOVER_HINT("Copy to Clipboard");
ImGui::TableNextColumn();
TableNextColumn();
if (Button(ICON_MD_CONTENT_PASTE)) {
std::vector<uint8_t> png_data;
int width, height;
GetImageFromClipboard(png_data, width, height);
if (png_data.size() > 0) {
rom()
->bitmap_manager()
.GetBitmap(current_sheet_)
->LoadFromPngData(png_data, width, height);
rom()->UpdateBitmap(rom()
->mutable_bitmap_manager()
->mutable_bitmap(current_sheet_)
.get());
->mutable_bitmap_manager()
->mutable_bitmap(current_sheet_)
->Create(width, height, 8, png_data);
rom()->UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_));
}
}
HOVER_HINT("Paste from Clipboard");
ImGui::TableNextColumn();
TableNextColumn();
if (Button(ICON_MD_ZOOM_OUT)) {
if (current_scale_ >= 0.0f) {
current_scale_ -= 1.0f;
}
}
ImGui::TableNextColumn();
TableNextColumn();
if (Button(ICON_MD_ZOOM_IN)) {
if (current_scale_ <= 16.0f) {
current_scale_ += 1.0f;
}
}
ImGui::TableNextColumn();
TableNextColumn();
auto bitmap = rom()->bitmap_manager()[current_sheet_];
auto palette = bitmap->palette();
auto palette = bitmap.palette();
for (int i = 0; i < 8; i++) {
ImGui::SameLine();
auto color =
ImVec4(palette[i].GetRGB().x / 255.0f, palette[i].GetRGB().y / 255.0f,
palette[i].GetRGB().z / 255.0f, 255.0f);
ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
palette[i].rgb().z / 255.0f, 255.0f);
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
color)) {
current_color_ = color;
}
}
ImGui::TableNextColumn();
gui::InputHexByte("Tile Size", &tile_size_, 0x01);
TableNextColumn();
gui::InputHexByte("Tile Size", &tile_size_);
ImGui::EndTable();
}
@@ -167,53 +188,68 @@ absl::Status GraphicsEditor::UpdateGfxSheetList() {
"##GfxEditChild", ImVec2(0, 0), true,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
// TODO: Update the interaction for multi select on sheets
static ImGuiSelectionBasicStorage selection;
ImGuiMultiSelectFlags flags =
ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(
flags, selection.Size, 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()) {
ImGui::BeginChild(absl::StrFormat("##GfxSheet%02X", key).c_str(),
ImVec2(0x100 + 1, 0x40 + 1), true,
ImGuiWindowFlags_NoDecoration);
ImGui::PopStyleVar();
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)) {
current_sheet_ = key;
open_sheets_.insert(key);
}
graphics_bin_canvas_.DrawBackground(ImVec2(0x100 + 1, 0x40 + 1));
graphics_bin_canvas_.DrawContextMenu();
if (value.is_active()) {
auto texture = value.texture();
graphics_bin_canvas_.draw_list()->AddImage(
(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
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
graphics_bin_canvas_.zero_point().y + 2);
ImVec2 text_size =
ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str());
ImVec2 rent_min(text_pos.x, text_pos.y);
ImVec2 rent_max(text_pos.x + text_size.x, text_pos.y + text_size.y);
graphics_bin_canvas_.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());
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
current_sheet_ = key;
open_sheets_.insert(key);
}
};
graphics_bin_canvas_.UpdateEvent(
select_tile_event, ImVec2(0x100 + 1, 0x40 + 1), 0x20, sheet_scale_,
/*grid_size=*/16.0f);
// Add a slightly transparent rectangle behind the text
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
graphics_bin_canvas_.zero_point().y + 2);
ImVec2 text_size =
ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str());
ImVec2 rent_min(text_pos.x, text_pos.y);
ImVec2 rent_max(text_pos.x + text_size.x, text_pos.y + text_size.y);
graphics_bin_canvas_.draw_list()->AddRectFilled(rent_min, rent_max,
IM_COL32(0, 125, 0, 128));
graphics_bin_canvas_.draw_list()->AddText(
text_pos, IM_COL32(125, 255, 125, 255),
absl::StrFormat("%02X", key).c_str());
}
graphics_bin_canvas_.DrawGrid(16.0f);
graphics_bin_canvas_.DrawOverlay();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::EndChild();
}
ImGui::PopStyleVar();
ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io);
ImGui::EndChild();
return absl::OkStatus();
}
@@ -222,8 +258,8 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
static int next_tab_id = 0;
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
if (ImGui::TabItemButton(
"+", ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
ImGuiTabItemFlags_NoTooltip)) {
open_sheets_.insert(next_tab_id++);
}
@@ -249,18 +285,19 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
gfx::Bitmap& current_bitmap =
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
auto draw_tile_event = [&]() {
gfx::Bitmap& current_bitmap =
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, current_bitmap,
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, &current_bitmap,
current_color_);
rom()->UpdateBitmap(&current_bitmap);
rom()->UpdateBitmap(&current_bitmap, true);
};
auto size = ImVec2(0x80, 0x20);
current_sheet_canvas_.UpdateColorPainter(
*rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
size, tile_size_, current_scale_, 8.0f);
rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
tile_size_, current_scale_);
ImGui::EndChild();
ImGui::EndTabItem();
}
@@ -289,12 +326,13 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
&active, ImGuiWindowFlags_AlwaysUseWindowPadding);
current_sheet_ = id;
// ImVec2(0x100, 0x40),
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();
if (active == false) {
@@ -310,12 +348,10 @@ absl::Status GraphicsEditor::UpdateGfxTabView() {
}
absl::Status GraphicsEditor::UpdatePaletteColumn() {
auto palette_group = rom()->palette_group(
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
auto palette = palette_group[edit_palette_index_];
if (rom()->isLoaded()) {
if (rom()->is_loaded()) {
auto palette_group = *rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
auto palette = palette_group.palette(edit_palette_index_);
gui::TextWithSeparators("ROM Palette");
ImGui::SetNextItemWidth(100.f);
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
@@ -323,36 +359,66 @@ absl::Status GraphicsEditor::UpdatePaletteColumn() {
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
ImGui::SetNextItemWidth(100.f);
gui::InputHex("Palette Group Index", &edit_palette_index_);
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
palette);
if (refresh_graphics_ && !open_sheets_.empty()) {
RETURN_IF_ERROR(
rom()->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();
}
absl::Status GraphicsEditor::UpdateLinkGfxView() {
TAB_ITEM("Player Animations")
const auto link_gfx_offset = 0x80000;
const auto link_gfx_length = 0x7000;
if (ImGui::BeginTable("##PlayerAnimationTable", 3, kGfxEditTableFlags,
ImVec2(0, 0))) {
for (const auto& name : {"Canvas", "Animation Steps", "Properties"})
ImGui::TableSetupColumn(name);
// Load Links graphics from the ROM
RETURN_IF_ERROR(rom()->LoadLinkGraphics());
ImGui::TableHeadersRow();
// 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
NEXT_COLUMN();
link_canvas_.DrawBackground();
link_canvas_.DrawGrid(16.0f);
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()
return absl::OkStatus();
@@ -394,7 +460,7 @@ absl::Status GraphicsEditor::UpdateScadView() {
if (super_donkey_) {
if (refresh_graphics_) {
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_]);
rom()->UpdateBitmap(&graphics_bin_[i]);
}
@@ -424,7 +490,7 @@ absl::Status GraphicsEditor::DrawToolset() {
for (const auto& name : kGfxToolsetColumnNames)
ImGui::TableSetupColumn(name.data());
ImGui::TableNextColumn();
TableNextColumn();
if (Button(ICON_MD_MEMORY)) {
if (!open_memory_editor_) {
open_memory_editor_ = true;
@@ -457,19 +523,21 @@ absl::Status GraphicsEditor::DrawCgxImport() {
is_open_ = true;
cgx_loaded_ = true;
});
gui::ButtonPipe("Copy CGX Path",
[this]() { ImGui::SetClipboardText(cgx_file_path_); });
gui::ButtonPipe("Load CGX Data", [this]() {
status_ = gfx::LoadCgx(current_bpp_, cgx_file_path_, cgx_data_,
decoded_cgx_, extra_cgx_data_);
if (ImGui::Button("Copy CGX Path")) {
ImGui::SetClipboardText(cgx_file_path_);
}
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_) {
cgx_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&cgx_bitmap_);
}
});
}
return absl::OkStatus();
}
@@ -491,19 +559,20 @@ absl::Status GraphicsEditor::DrawScrImport() {
InputInt("SCR Mod", &scr_mod_value_);
gui::ButtonPipe("Load Scr Data", [this]() {
status_ = gfx::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
if (ImGui::Button("Load Scr Data")) {
status_ =
gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
decoded_scr_data_.resize(0x100 * 0x100);
status_ = gfx::DrawScrWithCgx(current_bpp_, scr_data_, decoded_scr_data_,
decoded_cgx_);
status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_,
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_) {
scr_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&scr_bitmap_);
}
});
}
return absl::OkStatus();
}
@@ -527,19 +596,24 @@ absl::Status GraphicsEditor::DrawPaletteControls() {
if (col_file_palette_group_.size() != 0) {
col_file_palette_group_.Clear();
}
col_file_palette_group_ = gfx::CreatePaletteGroupFromColFile(col_data_);
col_file_palette_ = gfx::SNESPalette(col_data_);
auto col_file_palette_group_status =
gfx::CreatePaletteGroupFromColFile(col_data_);
if (col_file_palette_group_status.ok()) {
col_file_palette_group_ = col_file_palette_group_status.value();
}
col_file_palette_ = gfx::SnesPalette(col_data_);
// gigaleak dev format based code
decoded_col_ = gfx::DecodeColFile(col_file_path_);
decoded_col_ = gfx::scad_format::DecodeColFile(col_file_path_);
col_file_ = true;
is_open_ = true;
});
gui::ButtonPipe("Copy COL Path",
[this]() { ImGui::SetClipboardText(col_file_path_); });
if (ImGui::Button("Copy Col Path")) {
ImGui::SetClipboardText(col_file_path_);
}
if (rom()->isLoaded()) {
if (rom()->is_loaded()) {
gui::TextWithSeparators("ROM Palette");
gui::InputHex("Palette Index", &current_palette_index_);
ImGui::Combo("Palette", &current_palette_, kPaletteGroupAddressesKeys,
@@ -609,8 +683,9 @@ absl::Status GraphicsEditor::DrawFileImport() {
is_open_ = true;
});
gui::ButtonPipe("Copy File Path",
[this]() { ImGui::SetClipboardText(file_path_); });
if (Button("Copy File Path")) {
ImGui::SetClipboardText(file_path_);
}
gui::InputHex("BIN Offset", &current_offset_);
gui::InputHex("BIN Size", &bin_size_);
@@ -629,7 +704,7 @@ absl::Status GraphicsEditor::DrawFileImport() {
absl::Status GraphicsEditor::DrawClipboardImport() {
gui::TextWithSeparators("Clipboard Import");
gui::ButtonPipe("Paste from Clipboard", [this]() {
if (Button("Paste From Clipboard")) {
const char* text = ImGui::GetClipboardText();
if (text) {
const auto clipboard_data = Bytes(text, text + strlen(text));
@@ -638,20 +713,20 @@ absl::Status GraphicsEditor::DrawClipboardImport() {
is_open_ = true;
open_memory_editor_ = true;
}
});
}
gui::InputHex("Offset", &clipboard_offset_);
gui::InputHex("Size", &clipboard_size_);
gui::InputHex("Num Sheets", &num_sheets_to_load_);
gui::ButtonPipe("Decompress Clipboard Data", [this]() {
if (temp_rom_.isLoaded()) {
if (Button("Decompress Clipboard Data")) {
if (temp_rom_.is_loaded()) {
status_ = DecompressImportData(0x40000);
} else {
status_ = absl::InvalidArgumentError(
"Please paste data into the clipboard before "
"decompressing.");
}
});
}
return absl::OkStatus();
}
@@ -690,13 +765,13 @@ absl::Status GraphicsEditor::DecompressImportData(int size) {
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth,
converted_sheet);
if (rom()->isLoaded()) {
auto palette_group = rom()->palette_group("ow_main");
if (rom()->is_loaded()) {
auto palette_group = rom()->palette_group().overworld_animated;
z3_rom_palette_ = palette_group[current_palette_];
if (col_file_) {
bin_bitmap_.ApplyPalette(col_file_palette_);
status_ = bin_bitmap_.ApplyPalette(col_file_palette_);
} 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,
core::kTilesheetDepth, converted_sheet);
if (col_file_) {
graphics_bin_[i].ApplyPalette(
status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
auto palette_group =
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = palette_group[current_palette_index_];
graphics_bin_[i].ApplyPalette(z3_rom_palette_);
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
}
rom()->RenderBitmap(&graphics_bin_[i]);
@@ -744,14 +820,14 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, converted_sheet);
if (col_file_) {
graphics_bin_[i].ApplyPalette(
status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
auto palette_group =
rom()->palette_group(kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = palette_group[current_palette_index_];
graphics_bin_[i].ApplyPalette(z3_rom_palette_);
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
}
rom()->RenderBitmap(&graphics_bin_[i]);
@@ -765,4 +841,4 @@ absl::Status GraphicsEditor::DecompressSuperDonkey() {
} // namespace editor
} // namespace app
} // namespace yaze
} // namespace yaze

View File

@@ -1,21 +1,22 @@
#ifndef YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#define YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "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/snes_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/gui/pipeline.h"
#include "app/rom.h"
#include "app/zelda3/overworld.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
@@ -43,28 +44,32 @@ const std::string kSuperDonkeySprites[] = {
"BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D",
"C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"};
constexpr const char* kPaletteGroupAddressesKeys[] = {
"ow_main", "ow_aux", "ow_animated", "hud",
"global_sprites", "armors", "swords", "shields",
"sprites_aux1", "sprites_aux2", "sprites_aux3", "dungeon_main",
"grass", "3d_object", "ow_mini_map",
};
static constexpr std::string_view kGfxEditColumnNames[] = {
"Tilesheets", "Current Graphics", "Palette Controls"};
static constexpr absl::string_view kGfxToolsetColumnNames[] = {
"#memoryEditor",
"##separator_gfx1",
};
constexpr ImGuiTableFlags kGfxEditFlags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
class GraphicsEditor : public SharedROM {
/**
* @class GraphicsEditor
* @brief Allows the user to edit graphics sheets from the game or view
* prototype graphics.
*
* The GraphicsEditor class is responsible for providing functionality to edit
* graphics sheets from the game or view prototype graphics of Link to the Past
* from the CGX, SCR, and OBJ formats. It provides various methods to update
* different components of the graphics editor, such as the graphics edit tab,
* link graphics view, and prototype graphics viewer. It also includes import
* functions for different file formats, as well as other utility functions for
* drawing toolsets, palette controls, clipboard imports, experimental features,
* and memory editor.
*/
class GraphicsEditor : public SharedRom, public Editor {
public:
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:
enum class GfxEditMode {
@@ -152,11 +157,13 @@ class GraphicsEditor : public SharedROM {
char tilemap_file_path_[256] = "";
char tilemap_file_name_[256] = "";
gui::GfxSheetAssetBrowser asset_browser_;
GfxEditMode gfx_edit_mode_ = GfxEditMode::kSelect;
ROM temp_rom_;
ROM tilemap_rom_;
zelda3::Overworld overworld_;
Rom temp_rom_;
Rom tilemap_rom_;
zelda3::overworld::Overworld overworld_;
MemoryEditor cgx_memory_editor_;
MemoryEditor col_memory_editor_;
PaletteEditor palette_editor_;
@@ -176,13 +183,18 @@ class GraphicsEditor : public SharedROM {
gfx::BitmapTable clipboard_graphics_bin_;
gfx::BitmapTable link_graphics_;
gfx::PaletteGroup col_file_palette_group_;
gfx::SNESPalette z3_rom_palette_;
gfx::SNESPalette col_file_palette_;
gfx::SNESPalette link_palette_;
gfx::SnesPalette z3_rom_palette_;
gfx::SnesPalette col_file_palette_;
gfx::SnesPalette link_palette_;
gui::Canvas import_canvas_;
gui::Canvas scr_canvas_;
gui::Canvas super_donkey_canvas_;
gui::Canvas 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_;
};
@@ -190,4 +202,4 @@ class GraphicsEditor : public SharedROM {
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H
#endif // YAZE_APP_EDITOR_GRAPHICS_EDITOR_H

View 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", &current_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

View 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

View 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",
&current_dungeon.floor_rooms[floor_number].at(selected_room));
gui::InputHexWord("Boss Room", &current_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

View 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

View 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(&current_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(&current_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(&current_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", &notify_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(&current_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(&current_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

View File

@@ -1,44 +1,57 @@
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
#define YAZE_APP_EDITOR_TILE16EDITOR_H
#include <imgui/imgui.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/editor/graphics/palette_editor.h"
#include "app/editor/utils/editor.h"
#include "app/editor/utils/gfx_context.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/rom.h"
#include "app/zelda3/overworld.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
namespace editor {
class Tile16Editor : public SharedROM {
/**
* @brief Popup window to edit Tile16 data
*/
class Tile16Editor : public context::GfxContext, public SharedRom {
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 DrawToCurrentTile16(ImVec2 pos);
absl::Status UpdateTile16Edit();
void DrawTileEditControls();
absl::Status DrawTileEditControls();
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 SetCurrentTile(int id);
private:
bool map_blockset_loaded_ = false;
bool transfer_started_ = false;
@@ -48,38 +61,36 @@ class Tile16Editor : public SharedROM {
int current_tile8_ = 0;
uint8_t current_palette_ = 0;
core::NotifyValue<uint8_t> notify_tile16;
core::NotifyValue<uint32_t> notify_tile16;
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
bool x_flip;
bool y_flip;
bool priority_tile;
int tile_size;
uint8_t* all_tiles_types_;
// Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_;
gfx::Bitmap tile16_blockset_bmp_;
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
gui::CanvasGridSize::k32x32};
gfx::Bitmap* tile16_blockset_bmp_;
// Canvas for editing the selected tile
gui::Canvas tile16_edit_canvas_;
gfx::Bitmap current_tile16_bmp_;
gfx::Bitmap current_tile8_bmp_;
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
gui::CanvasGridSize::k64x64};
gfx::Bitmap* current_tile16_bmp_;
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
gui::Canvas tile8_source_canvas_;
gui::Canvas tile8_source_canvas_{
"Tile8SourceCanvas",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_;
gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_;
gfx::Bitmap transfer_current_bmp_;
std::vector<Bytes> tile16_individual_data_;
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> tile8_gfx_data_;
PaletteEditor palette_editor_;
gfx::SNESPalette palette_;
zelda3::Overworld transfer_overworld_;
gfx::SnesPalette palette_;
zelda3::overworld::Overworld transfer_overworld_;
gfx::BitmapTable graphics_bin_;
ROM transfer_rom_;
Rom transfer_rom_;
absl::Status transfer_status_;
core::TaskManager<std::function<void(int)>> task_manager_;
};
} // namespace editor

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,30 @@
#ifndef YAZE_APP_EDITOR_MASTER_EDITOR_H
#define YAZE_APP_EDITOR_MASTER_EDITOR_H
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#define IMGUI_DEFINE_MATH_OPERATORS
#include "ImGuiColorTextEdit/TextEditor.h"
#include "ImGuiFileDialog/ImGuiFileDialog.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "imgui_memory_editor.h"
#include "absl/status/status.h"
#include "app/core/common.h"
#include "app/core/constants.h"
#include "app/gui/pipeline.h"
#include "app/editor/context/gfx_context.h"
#include "app/editor/dungeon_editor.h"
#include "app/editor/graphics_editor.h"
#include "app/editor/modules/assembly_editor.h"
#include "app/editor/modules/music_editor.h"
#include "app/editor/modules/palette_editor.h"
#include "app/core/project.h"
#include "app/editor/code/assembly_editor.h"
#include "app/editor/code/memory_editor.h"
#include "app/editor/dungeon/dungeon_editor.h"
#include "app/editor/graphics/graphics_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/screen_editor.h"
#include "app/editor/message/message_editor.h"
#include "app/editor/music/music_editor.h"
#include "app/editor/overworld_editor.h"
#include "app/editor/screen_editor.h"
#include "app/editor/sprite_editor.h"
#include "app/editor/settings_editor.h"
#include "app/editor/sprite/sprite_editor.h"
#include "app/editor/utils/gfx_context.h"
#include "app/emu/emulator.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
@@ -32,18 +37,51 @@ namespace yaze {
namespace app {
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:
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();
void Shutdown() { overworld_editor_.Shutdown(); }
auto emulator() -> emu::Emulator& { return emulator_; }
auto quit() { return quit_; }
auto overworld_editor() -> OverworldEditor& { return overworld_editor_; }
private:
void ManageActiveEditors();
void ManageKeyboardShortcuts();
void OpenRomOrProject(const std::string& filename);
void DrawFileDialog();
void DrawStatusPopup();
void DrawAboutPopup();
@@ -53,11 +91,20 @@ class MasterEditor : public SharedROM,
void DrawFileMenu();
void DrawEditMenu();
void DrawViewMenu();
void DrawTestMenu();
void DrawProjectMenu();
void DrawHelpMenu();
void LoadRom();
void SaveRom();
absl::Status OpenProject();
bool quit_ = false;
bool about_ = false;
bool rom_info_ = false;
bool backup_rom_ = true;
bool backup_rom_ = false;
bool save_new_auto_ = true;
bool show_status_ = false;
bool rom_assets_loaded_ = false;
@@ -68,6 +115,8 @@ class MasterEditor : public SharedROM,
emu::Emulator emulator_;
Project current_project_;
AssemblyEditor assembly_editor_;
DungeonEditor dungeon_editor_;
GraphicsEditor graphics_editor_;
@@ -76,12 +125,17 @@ class MasterEditor : public SharedROM,
PaletteEditor palette_editor_;
ScreenEditor screen_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 app
} // namespace yaze
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H
#endif // YAZE_APP_EDITOR_MASTER_EDITOR_H

View File

@@ -1,10 +1,9 @@
#include "sprite_editor.h"
#include "master_editor.h"
namespace yaze {
namespace app {
namespace editor {
absl::Status SpriteEditor::Update() { return absl::OkStatus(); }
} // namespace editor
} // namespace app

View 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

View 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(&current_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

View 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

View File

@@ -0,0 +1,7 @@
#include "message_editor.h"
namespace yaze {
namespace app {
namespace editor {} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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(&current_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(&current_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", &notify_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(&current_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(&current_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(&current_gfx_individual_[i]);
// }
return absl::OkStatus();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,71 +1,18 @@
#include "music_editor.h"
#include <SDL_mixer.h>
#include <imgui/imgui.h>
#include "imgui/imgui.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/icons.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 app {
namespace editor {
namespace {
#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() {
absl::Status MusicEditor::Update() {
if (ImGui::BeginTable("MusicEditorColumns", 2, music_editor_flags_,
ImVec2(0, 0))) {
ImGui::TableSetupColumn("Assembly");
@@ -83,6 +30,8 @@ void MusicEditor::Update() {
ImGui::EndTable();
}
return absl::OkStatus();
}
void MusicEditor::DrawChannels() {
@@ -221,31 +170,8 @@ void MusicEditor::DrawToolset() {
static bool has_loaded_song = false;
const int MAX_VOLUME = 100;
if (is_playing) {
if (!has_loaded_song) {
// 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();
// }
// }
if (is_playing && !has_loaded_song) {
has_loaded_song = true;
}
gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
@@ -264,7 +190,6 @@ void MusicEditor::DrawToolset() {
ImGui::TableNextColumn();
if (ImGui::Button(is_playing ? ICON_MD_STOP : ICON_MD_PLAY_ARROW)) {
if (is_playing) {
Mix_HaltMusic();
has_loaded_song = false;
}
is_playing = !is_playing;

View File

@@ -1,11 +1,11 @@
#ifndef 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 "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/icons.h"
#include "app/gui/input.h"
@@ -54,9 +54,23 @@ static const char* kGameSongs[] = {"Title",
static constexpr absl::string_view kSongNotes[] = {
"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:
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:
void DrawChannels();
@@ -65,7 +79,7 @@ class MusicEditor : public SharedROM {
void DrawSongToolset();
void DrawToolset();
zelda3::Tracker music_tracker_;
zelda3::music::Tracker music_tracker_;
// Mix_Music* current_song_ = NULL;

View 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 *&current_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", &centerX);
gui::InputHex("Center Y", &centerY);
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

View 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 *&current_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

View 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 &current_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

View File

@@ -1,7 +1,7 @@
#ifndef YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#define YAZE_APP_EDITOR_OVERWORLDEDITOR_H
#include <imgui/imgui.h>
#include "imgui/imgui.h"
#include <cmath>
#include <unordered_map>
@@ -11,18 +11,20 @@
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "app/core/common.h"
#include "app/core/editor.h"
#include "app/editor/modules/gfx_group_editor.h"
#include "app/editor/modules/palette_editor.h"
#include "app/editor/modules/tile16_editor.h"
#include "app/editor/graphics/gfx_group_editor.h"
#include "app/editor/graphics/palette_editor.h"
#include "app/editor/graphics/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/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/pipeline.h"
#include "app/gui/zeml.h"
#include "app/rom.h"
#include "app/zelda3/overworld.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
@@ -36,12 +38,14 @@ static constexpr uint kTile8DisplayHeight = 64;
static constexpr float kInputFieldSize = 30.f;
static constexpr absl::string_view kToolsetColumnNames[] = {
"#undoTool", "#redoTool", "#drawTool", "#separator2",
"#zoomOutTool", "#zoomInTool", "#separator", "#history",
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
"#transportTool", "#musicTool", "#separator3", "#tilemapTool"};
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
"#zoomInTool", "#separator", "#drawTool", "#history",
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
"propertiesTool"};
constexpr ImGuiTableFlags kOWMapFlags = ImGuiTableFlags_Borders;
constexpr ImGuiTableFlags kOWMapFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit;
constexpr ImGuiTableFlags kOWEditFlags =
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
@@ -57,60 +61,131 @@ constexpr absl::string_view kTileSelectorTab = "##TileSelectorTabBar";
constexpr absl::string_view kOWEditTable = "##OWEditTable";
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,
public SharedROM,
public SharedRom,
public context::GfxContext,
public EntranceContext,
public core::ExperimentFlags {
public:
OverworldEditor() { type_ = EditorType::kOverworld; }
void InitializeZeml();
absl::Status Update() final;
absl::Status Undo() { return absl::UnimplementedError("Undo"); }
absl::Status Redo() { return absl::UnimplementedError("Redo"); }
absl::Status Cut() { return absl::UnimplementedError("Cut"); }
absl::Status Copy() { return absl::UnimplementedError("Copy"); }
absl::Status Paste() { return absl::UnimplementedError("Paste"); }
absl::Status Find() { return absl::UnimplementedError("Find Unused Tiles"); }
auto overworld() { return &overworld_; }
void Shutdown() {
for (auto &bmp : tile16_individual_) {
bmp.Cleanup();
}
for (auto &[i, bmp] : maps_bmp_) {
bmp.Cleanup();
}
for (auto &[i, bmp] : graphics_bin_) {
bmp.Cleanup();
}
for (auto &[i, bmp] : current_graphics_set_) {
bmp.Cleanup();
}
}
/**
* @brief
*/
int jump_to_tab() { return jump_to_tab_; }
int jump_to_tab_ = -1;
/**
* @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();
private:
absl::Status UpdateFullscreenCanvas();
absl::Status DrawToolset();
void DrawOverworldMapSettings();
void DrawOverworldEntrances(ImVec2 canvas_p, ImVec2 scrolling);
void DrawOverworldMaps();
void RefreshChildMap(int i);
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 DrawOverworldMaps();
void DrawOverworldEdits();
void RenderUpdatedMapBitmap(const ImVec2 &click_position,
const Bytes &tile_data);
void SaveOverworldChanges();
void DetermineActiveMap(const ImVec2 &mouse_position);
void RenderUpdatedMapBitmap(const ImVec2& click_position,
const Bytes& tile_data);
void CheckForOverworldEdits();
void CheckForCurrentMap();
void CheckForSelectRectangle();
absl::Status CheckForCurrentMap();
void CheckForMousePan();
/**
* @brief Allows the user to make changes to the overworld map.
*/
void DrawOverworldCanvas();
absl::Status DrawTile16Selector();
void DrawTile8Selector();
void DrawTileSelector();
absl::Status DrawAreaGraphics();
absl::Status DrawTileSelector();
absl::Status LoadSpriteGraphics();
void DrawOverworldProperties();
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 {
DRAW_TILE,
ENTRANCES,
@@ -118,16 +193,25 @@ class OverworldEditor : public Editor,
ITEMS,
SPRITES,
TRANSPORTS,
MUSIC
MUSIC,
PAN
};
EditingMode current_mode = EditingMode::DRAW_TILE;
EditingMode previous_mode = EditingMode::DRAW_TILE;
int current_world_ = 0;
int current_map_ = 0;
int current_parent_ = 0;
int game_state_ = 1;
int current_tile16_ = 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_palette_[3] = "";
char spr_gfx_[3] = "";
@@ -149,10 +233,21 @@ class OverworldEditor : public Editor,
bool is_dragging_entrance_ = false;
bool show_tile16_editor_ = false;
bool show_gfx_group_editor_ = false;
bool overworld_canvas_fullscreen_ = false;
bool middle_mouse_dragging_ = false;
bool IsMouseHoveringOverEntrance(const zelda3::OverworldEntrance &entrance,
ImVec2 canvas_p, ImVec2 scrolling);
zelda3::OverworldEntrance *dragged_entrance_;
bool is_dragging_entity_ = false;
zelda3::OverworldEntity* dragged_entity_;
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;
std::string ow_tilemap_filename_ = "";
@@ -168,24 +263,34 @@ class OverworldEditor : public Editor,
Tile16Editor tile16_editor_;
GfxGroupEditor gfx_group_editor_;
PaletteEditor palette_editor_;
zelda3::Overworld overworld_;
zelda3::overworld::Overworld overworld_;
gui::Canvas ow_map_canvas_;
gui::Canvas current_gfx_canvas_;
gui::Canvas blockset_canvas_;
gui::Canvas graphics_bin_canvas_;
gui::Canvas ow_map_canvas_{"owMapCanvas", ImVec2(0x200 * 8, 0x200 * 8),
gui::CanvasGridSize::k64x64};
gui::Canvas current_gfx_canvas_{"customGfxCanvas",
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 tile16_blockset_bmp_;
gfx::Bitmap current_gfx_bmp_;
gfx::Bitmap all_gfx_bmp;
gfx::BitmapTable maps_bmp_;
gfx::BitmapTable graphics_bin_;
gfx::BitmapTable current_graphics_set_;
gfx::BitmapTable sprite_previews_;
gfx::BitmapTable animated_maps_;
OWBlockset refresh_blockset_;
gui::zeml::Node layout_node_;
absl::Status status_;
};
} // namespace editor

View File

@@ -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

View File

@@ -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

View 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

View 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_

View 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(), &current_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

View 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

View 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

View File

@@ -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

View 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

View 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_

View 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

View 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

View 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_

View 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

View 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)

View File

@@ -1,5 +1,7 @@
#include "app/emu/audio/apu.h"
#include <SDL.h>
#include <cstdint>
#include <functional>
#include <iostream>
@@ -13,127 +15,193 @@
namespace yaze {
namespace app {
namespace emu {
namespace audio {
void APU::Init() {
// Set the clock frequency
clock_.SetFrequency(kApuClockSpeed);
static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
static const double apuCyclesPerMasterPal = (32040 * 32) / (1364 * 312 * 50.0);
// Initialize Digital Signal Processor Callbacks
dsp_.SetSampleFetcher([this](uint16_t address) -> uint8_t {
return this->FetchSampleFromRam(address);
});
static const uint8_t bootRom[0x40] = {
0xcd, 0xef, 0xbd, 0xe8, 0x00, 0xc6, 0x1d, 0xd0, 0xfc, 0x8f, 0xaa,
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(
[this](int16_t sample) { this->PushToAudioBuffer(sample); });
void Apu::Init() {
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() {
clock_.ResetAccumulatedTime();
spc700_.Reset();
void Apu::Reset() {
spc700_.Reset(true);
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() {
auto cycles_to_run = clock_.GetCycleCount();
void Apu::RunCycles(uint64_t cycles) {
uint64_t sync_to =
(uint64_t)cycles *
(memory_.pal_timing() ? apuCyclesPerMasterPal : apuCyclesPerMaster);
for (auto i = 0; i < cycles_to_run; ++i) {
// Update the APU
UpdateChannelSettings();
// Update the SPC700
uint8_t opcode = spc700_.read(spc700_.PC);
spc700_.ExecuteInstructions(opcode);
spc700_.PC++;
while (cycles_ < sync_to) {
spc700_.RunOpcode();
}
ProcessSamples();
}
void APU::Notify(uint32_t address, uint8_t data) {
if (address < 0x2140 || address > 0x2143) {
return;
void Apu::Cycle() {
if ((cycles_ & 0x1f) == 0) {
// 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
ports_[address - 0x2140] = data;
switch (address) {
case 0x2140:
if (data == BEGIN_SIGNAL) {
SignalReady();
// handle timers
for (int i = 0; i < 3; i++) {
if (timer_[i].cycles == 0) {
timer_[i].cycles = i == 2 ? 16 : 128;
if (timer_[i].enabled) {
timer_[i].divider++;
if (timer_[i].divider == timer_[i].target) {
timer_[i].divider = 0;
timer_[i].counter++;
timer_[i].counter &= 0xf;
}
}
break;
case 0x2141:
// 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;
}
timer_[i].cycles--;
}
cycles_++;
}
void APU::ProcessSamples() {
// Fetch sample data from AudioRam
// Iterate over all voices
for (uint8_t voice_num = 0; voice_num < 8; voice_num++) {
// Fetch the sample data for the current voice from AudioRam
uint8_t sample = FetchSampleForVoice(voice_num);
// Process the sample through DSP
int16_t processed_sample = dsp_.ProcessSample(voice_num, sample);
// Add the processed sample to the audio buffer
audio_samples_.push_back(processed_sample);
uint8_t Apu::Read(uint16_t adr) {
switch (adr) {
case 0xf0:
case 0xf1:
case 0xfa:
case 0xfb:
case 0xfc: {
return 0;
}
case 0xf2: {
return dsp_adr_;
}
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;
}
}
}
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;
if (rom_readable_ && adr >= 0xffc0) {
return bootRom[adr - 0xffc0];
}
return 0; // TODO: Return the last sample instead of 0.
return ram[adr];
}
const std::vector<int16_t>& APU::GetAudioSamples() const {
return audio_samples_;
void Apu::Write(uint16_t adr, uint8_t val) {
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() {
// TODO: Implement this method to update the channel settings.
uint8_t Apu::SpcRead(uint16_t adr) {
Cycle();
return Read(adr);
}
int16_t APU::GenerateSample(int channel) {
// TODO: Implement this method to generate a sample for the specified channel.
void Apu::SpcWrite(uint16_t adr, uint8_t val) {
Cycle();
Write(adr, val);
}
void APU::ApplyEnvelope(int channel) {
// 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);
}
void Apu::SpcIdle(bool waiting) { Cycle(); }
} // namespace audio
} // namespace emu
} // namespace app
} // namespace yaze

View File

@@ -7,19 +7,37 @@
#include "app/emu/audio/dsp.h"
#include "app/emu/audio/spc700.h"
#include "app/emu/cpu/clock.h"
#include "app/emu/memory/memory.h"
namespace yaze {
namespace app {
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.
* Some regions of this space are overlaid with special hardware functions.
*
* Range Note
* @par Range Note
* $0000-00EF Zero Page RAM
* $00F0-00FF Sound CPU Registers
* $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
* register $F1 can be cleared to unmap the IPL ROM and allow read access to
* this RAM.
*
*/
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 {
class Apu {
public:
APU(MemoryImpl &memory, AudioRam &aram, Clock &clock)
: aram_(aram), clock_(clock), memory_(memory) {}
Apu(MemoryImpl &memory) : memory_(memory) {}
void Init();
void Reset();
void Update();
void Notify(uint32_t address, uint8_t data) override;
void ProcessSamples();
uint8_t FetchSampleForVoice(uint8_t voice_num);
uint16_t CalculateAddressForVoice(uint8_t voice_num);
int16_t GetNextSample();
void RunCycles(uint64_t cycles);
uint8_t SpcRead(uint16_t address);
void SpcWrite(uint16_t address, uint8_t data);
void SpcIdle(bool waiting);
// Called upon a reset
void Initialize() {
spc700_.Reset();
dsp_.Reset();
SignalReady();
}
void Cycle();
// Set Port 0 = $AA and Port 1 = $BB
void SignalReady() {
memory_.WriteByte(0x2140, READY_SIGNAL_0);
memory_.WriteByte(0x2141, READY_SIGNAL_1);
}
uint8_t Read(uint16_t address);
void Write(uint16_t address, uint8_t data);
void WriteToPort(uint8_t portNum, uint8_t value) {
ports_[portNum] = value;
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;
auto dsp() -> Dsp & { return dsp_; }
auto spc700() -> Spc700 & { return spc700_; }
// 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
void UpdateChannelSettings();
private:
bool rom_readable_ = false;
// Generates a sample for an audio channel
int16_t GenerateSample(int channel);
uint8_t dsp_adr_ = 0;
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_;
std::array<Timer, 3> timer_;
DigitalSignalProcessor dsp_;
Spc700 spc700_{aram_};
std::vector<int16_t> audio_samples_;
std::function<void()> ready_callback_;
ApuCallbacks callbacks_ = {
[&](uint16_t adr, uint8_t val) { SpcWrite(adr, val); },
[&](uint16_t adr) { return SpcRead(adr); },
[&](bool waiting) { SpcIdle(waiting); },
};
Dsp dsp_{ram};
Spc700 spc700_{callbacks_};
};
} // namespace audio
} // namespace emu
} // namespace app
} // namespace yaze

View File

@@ -1,281 +1,637 @@
#include "app/emu/audio/dsp.h"
#include <cstring>
#include "app/emu/memory/memory.h"
namespace yaze {
namespace app {
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 {
voice %= kNumVoices;
switch (reg % kNumVoiceRegs) {
case 0:
return voices_[voice].vol_left;
case 1:
return voices_[voice].vol_right;
case 2:
return voices_[voice].pitch_low;
case 3:
return voices_[voice].pitch_high;
case 4:
return voices_[voice].source_number;
case 5:
return voices_[voice].adsr1;
case 6:
return voices_[voice].adsr2;
case 7:
return voices_[voice].gain;
case 8:
return voices_[voice].envx;
case 9:
return voices_[voice].outx;
default:
return 0; // This shouldn't happen, but it's good to have a default
// case
static const int rateOffsets[32] = {0, 0, 1040, 536, 0, 1040, 536, 0, 1040,
536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
536, 0, 1040, 536, 0, 1040, 536, 0, 1040,
536, 0, 1040, 536, 0};
static const int gaussValues[512] = {
0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000,
0x000, 0x000, 0x000, 0x000, 0x000, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001,
0x001, 0x001, 0x001, 0x001, 0x001, 0x002, 0x002, 0x002, 0x002, 0x002, 0x002,
0x002, 0x003, 0x003, 0x003, 0x003, 0x003, 0x004, 0x004, 0x004, 0x004, 0x004,
0x005, 0x005, 0x005, 0x005, 0x006, 0x006, 0x006, 0x006, 0x007, 0x007, 0x007,
0x008, 0x008, 0x008, 0x009, 0x009, 0x009, 0x00a, 0x00a, 0x00a, 0x00b, 0x00b,
0x00b, 0x00c, 0x00c, 0x00d, 0x00d, 0x00e, 0x00e, 0x00f, 0x00f, 0x00f, 0x010,
0x010, 0x011, 0x011, 0x012, 0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x016,
0x017, 0x017, 0x018, 0x018, 0x019, 0x01a, 0x01b, 0x01b, 0x01c, 0x01d, 0x01d,
0x01e, 0x01f, 0x020, 0x020, 0x021, 0x022, 0x023, 0x024, 0x024, 0x025, 0x026,
0x027, 0x028, 0x029, 0x02a, 0x02b, 0x02c, 0x02d, 0x02e, 0x02f, 0x030, 0x031,
0x032, 0x033, 0x034, 0x035, 0x036, 0x037, 0x038, 0x03a, 0x03b, 0x03c, 0x03d,
0x03e, 0x040, 0x041, 0x042, 0x043, 0x045, 0x046, 0x047, 0x049, 0x04a, 0x04c,
0x04d, 0x04e, 0x050, 0x051, 0x053, 0x054, 0x056, 0x057, 0x059, 0x05a, 0x05c,
0x05e, 0x05f, 0x061, 0x063, 0x064, 0x066, 0x068, 0x06a, 0x06b, 0x06d, 0x06f,
0x071, 0x073, 0x075, 0x076, 0x078, 0x07a, 0x07c, 0x07e, 0x080, 0x082, 0x084,
0x086, 0x089, 0x08b, 0x08d, 0x08f, 0x091, 0x093, 0x096, 0x098, 0x09a, 0x09c,
0x09f, 0x0a1, 0x0a3, 0x0a6, 0x0a8, 0x0ab, 0x0ad, 0x0af, 0x0b2, 0x0b4, 0x0b7,
0x0ba, 0x0bc, 0x0bf, 0x0c1, 0x0c4, 0x0c7, 0x0c9, 0x0cc, 0x0cf, 0x0d2, 0x0d4,
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) {
voice %= kNumVoices;
switch (reg % kNumVoiceRegs) {
case 0:
voices_[voice].vol_left = static_cast<int8_t>(value);
break;
case 1:
voices_[voice].vol_right = static_cast<int8_t>(value);
break;
case 2:
voices_[voice].pitch_low = value;
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
void Dsp::NewFrame() {
lastFrameBoundary = sampleOffset;
}
void Dsp::Cycle() {
sampleOutL = 0;
sampleOutR = 0;
echoOutL = 0;
echoOutR = 0;
for (int i = 0; i < 8; i++) {
CycleChannel(i);
}
}
// Set the callbacks
void DigitalSignalProcessor::SetSampleFetcher(SampleFetcher fetcher) { sample_fetcher_ = fetcher; }
void DigitalSignalProcessor::SetSamplePusher(SamplePusher pusher) { sample_pusher_ = pusher; }
int16_t DigitalSignalProcessor::DecodeSample(uint8_t voice_num) {
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;
HandleEcho(); // also applies master volume
counter = counter == 0 ? 30720 : counter - 1;
HandleNoise();
evenCycle = !evenCycle;
// handle mute flag
if (mute) {
sampleOutL = 0;
sampleOutR = 0;
}
// Clamp the mixed sample to 16-bit range
if (mixed_sample > 32767) {
mixed_sample = 32767;
} else if (mixed_sample < -32768) {
mixed_sample = -32768;
}
// Use the callback to push the mixed sample
sample_pusher_(mixed_sample);
// put final sample in the samplebuffer
sampleBuffer[(sampleOffset & 0x3ff) * 2] = sampleOutL;
sampleBuffer[(sampleOffset++ & 0x3ff) * 2 + 1] = sampleOutR;
}
void DigitalSignalProcessor::UpdateEnvelope(uint8_t voice) {
uint8_t adsr1 = ReadVoiceReg(voice, 0x05);
uint8_t adsr2 = ReadVoiceReg(voice, 0x06);
uint8_t gain = ReadVoiceReg(voice, 0x07);
static int clamp16(int val) {
return val < -0x8000 ? -0x8000 : (val > 0x7fff ? 0x7fff : val);
}
uint8_t enableADSR = (adsr1 & 0x80) >> 7;
static int clip16(int val) { return (int16_t)(val & 0xffff); }
if (enableADSR) {
// Handle ADSR envelope
Voice& voice_obj = voices_[voice];
switch (voice_obj.state) {
case VoiceState::ATTACK:
// Update amplitude based on attack rate
voice_obj.current_amplitude += AttackRate(adsr1);
if (voice_obj.current_amplitude >= ENVELOPE_MAX) {
voice_obj.current_amplitude = ENVELOPE_MAX;
voice_obj.state = VoiceState::DECAY;
}
break;
case VoiceState::DECAY:
// Update amplitude based on decay rate
voice_obj.current_amplitude -= DecayRate(adsr2);
if (voice_obj.current_amplitude <= voice_obj.decay_level) {
voice_obj.current_amplitude = voice_obj.decay_level;
voice_obj.state = VoiceState::SUSTAIN;
}
break;
case VoiceState::SUSTAIN:
// Keep amplitude at the calculated decay level
voice_obj.current_amplitude = voice_obj.decay_level;
break;
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;
bool Dsp::CheckCounter(int rate) {
if (rate == 0) return false;
return ((counter + rateOffsets[rate]) % rateValues[rate]) == 0;
}
void Dsp::HandleEcho() {
// increment fir buffer index
firBufferIndex++;
firBufferIndex &= 0x7;
// get value out of ram
uint16_t adr = echoBufferAdr + echoBufferIndex;
int16_t ramSample = aram_[adr] | (aram_[(adr + 1) & 0xffff] << 8);
firBufferL[firBufferIndex] = ramSample >> 1;
ramSample = aram_[(adr + 2) & 0xffff] | (aram_[(adr + 3) & 0xffff] << 8);
firBufferR[firBufferIndex] = ramSample >> 1;
// calculate FIR-sum
int sumL = 0, sumR = 0;
for (int i = 0; i < 8; i++) {
sumL += (firBufferL[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
sumR += (firBufferR[(firBufferIndex + i + 1) & 0x7] * firValues[i]) >> 6;
if (i == 6) {
// clip to 16-bit before last addition
sumL = clip16(sumL);
sumR = clip16(sumR);
}
}
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 {
// Handle Gain envelope
// Extract mode from the gain byte
uint8_t mode = (gain & 0xE0) >> 5;
uint8_t rate = gain & 0x1F;
Voice& voice_obj = voices_[voice];
switch (mode) {
case 0: // Direct Designation
case 1:
case 2:
case 3:
voice_obj.current_amplitude =
rate << 3; // Multiplying by 8 to scale to 0-255
break;
case 6: // Increase Mode (Linear)
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;
sample = GetSample(ch);
}
sample = ((sample * channel[ch].gain) >> 11) & ~1;
// handle reset and release
if (reset || (channel[ch].brrHeader & 0x03) == 1) {
channel[ch].adsrState = 3; // go to release
channel[ch].gain = 0;
}
// handle keyon/keyoff
if (evenCycle) {
if (channel[ch].keyOff) {
channel[ch].adsrState = 3; // go to release
}
if (channel[ch].keyOn) {
channel[ch].startDelay = 5;
channel[ch].adsrState = 0; // go to attack
channel[ch].keyOn = false;
}
}
}
void DigitalSignalProcessor::update_voice_state(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
Voice& voice = voices_[voice_num];
switch (voice.state) {
case VoiceState::OFF:
// Reset current amplitude
voice.current_amplitude = 0;
break;
case VoiceState::ATTACK:
// 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);
// handle envelope
if (channel[ch].startDelay == 0) {
HandleGain(ch);
}
// decode new brr samples if needed and update offsets
if (channel[ch].pitchCounter >= 0x4000) {
DecodeBrr(ch);
if (channel[ch].blockOffset >= 7) {
if (channel[ch].brrHeader & 0x1) {
channel[ch].decodeOffset = sampleAdr;
ram[0x7c] |= 1 << ch; // set ENDx
} else {
channel[ch].decodeOffset += 9;
}
break;
case VoiceState::DECAY:
// 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) {
voice.current_amplitude = voice.decay_level;
voice.state = VoiceState::SUSTAIN;
}
break;
case VoiceState::SUSTAIN:
// Keep the current amplitude at the decay level
break;
case VoiceState::RELEASE:
// Decrease the current amplitude at a rate defined by the RELEASE
// setting
voice.current_amplitude -= ReleaseRate(voice.adsr2);
if (voice.current_amplitude == 0) {
voice.state = VoiceState::OFF;
}
break;
channel[ch].blockOffset = 1;
} else {
channel[ch].blockOffset += 2;
}
}
// update pitch counter
channel[ch].pitchCounter &= 0x3fff;
channel[ch].pitchCounter += pitch;
if (channel[ch].pitchCounter > 0x7fff) channel[ch].pitchCounter = 0x7fff;
// set outputs
ram[(ch << 4) | 8] = channel[ch].gain >> 4;
ram[(ch << 4) | 9] = sample >> 8;
channel[ch].sampleOut = sample;
sampleOutL = clamp16(sampleOutL + ((sample * channel[ch].volumeL) >> 7));
sampleOutR = clamp16(sampleOutR + ((sample * channel[ch].volumeR) >> 7));
if (channel[ch].echoEnable) {
echoOutL = clamp16(echoOutL + ((sample * channel[ch].volumeL) >> 7));
echoOutR = clamp16(echoOutR + ((sample * channel[ch].volumeR) >> 7));
}
}
void DigitalSignalProcessor::process_envelope(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
Voice& voice = voices_[voice_num];
// Update the voice state first (based on keys, etc.)
update_voice_state(voice_num);
// Calculate the envelope value based on the current amplitude
voice.envx = calculate_envelope_value(voice.current_amplitude);
// Apply the envelope value to the audio output
apply_envelope_to_output(voice_num);
void Dsp::HandleGain(int ch) {
int newGain = channel[ch].gain;
int rate = 0;
// handle gain mode
if (channel[ch].adsrState == 3) { // release
rate = 31;
newGain -= 8;
} else {
if (!channel[ch].useGain) {
rate = channel[ch].adsrRates[channel[ch].adsrState];
switch (channel[ch].adsrState) {
case 0:
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 app
} // namespace yaze

View File

@@ -5,17 +5,50 @@
#include <functional>
#include <vector>
#include "app/emu/audio/spc700.h"
#include "app/emu/memory/memory.h"
namespace yaze {
namespace app {
namespace emu {
namespace audio {
using SampleFetcher = std::function<uint8_t(uint16_t)>;
using SamplePusher = std::function<void(int16_t)>;
typedef struct DspChannel {
// 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.
*
* 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.
* Each voice X has 10 registers in the range $X0-$X9.
*
* Name Address Bits Notes
* VOL (L) $X0 SVVV VVVV Left channel volume, signed.
* VOL (R) $X1 SVVV VVVV Right channel volume, signed.
* P (L) $X2 LLLL LLLL Low 8 bits of sample pitch.
* P (H) $X3 --HH HHHH High 6 bits of sample pitch.
* SCRN $X4 SSSS SSSS Selects a sample source entry from the
* directory ADSR (1) $X5 EDDD AAAA ADSR enable (E), decay rate (D),
* attack rate (A).
* ADSR (2) $X6 SSSR RRRR Sustain level (S), release rate (R).
* GAIN $X7 0VVV VVVV 1MMV VVVV Mode (M), value (V).
* ENVX $X8 0VVV VVVV Reads current 7-bit value of ADSR/GAIN
* envelope.
* OUTX $X9 SVVV VVVV Reads signed 8-bit value of current
* sample wave multiplied by ENVX, before applying VOL.
*
* | Name | Address | Bits | Notes |
* |---------|---------|-----------|--------------------------------------------------------|
* | VOL (L) | $X0 | SVVV VVVV | Left channel volume, signed. | | VOL (R) |
* $X1 | SVVV VVVV | Right channel volume, signed. | | P (L) | $X2 |
* LLLL LLLL | Low 8 bits of sample pitch. | | P (H)
* | $X3 | --HH HHHH | High 6 bits of sample pitch. | | SCRN | $X4 |
* SSSS SSSS | Selects a sample source entry from the directory. | | ADSR
* (1)| $X5 | EDDD AAAA | ADSR enable (E), decay rate (D), attack rate (A).
* | | ADSR (2)| $X6 | SSSR RRRR | Sustain level (S), release rate (R). | |
* GAIN | $X7 | 0VVV VVVV 1MMV VVVV | Mode (M), value (V). | | ENVX |
* $X8 | 0VVV VVVV | Reads current 7-bit value of ADSR/GAIN envelope. | |
* OUTX | $X9 | SVVV VVVV | Reads signed 8-bit value of current sample
* wave | | | | | multiplied by ENVX, before
* applying VOL. |
*/
class DigitalSignalProcessor {
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_;
class Dsp {
public:
DigitalSignalProcessor() = default;
Dsp(std::vector<uint8_t>& aram) : aram_(aram) {}
void NewFrame();
void Reset();
void SetSampleFetcher(std::function<uint8_t(uint16_t)> fetcher);
void SetSamplePusher(std::function<void(int16_t)> pusher);
void Cycle();
// Read a byte from a voice register
uint8_t ReadVoiceReg(uint8_t voice, uint8_t reg) const;
void HandleEcho();
void CycleChannel(int ch);
// Write a byte to a voice register
void WriteVoiceReg(uint8_t voice, uint8_t reg, uint8_t value);
void HandleNoise();
void HandleGain(int ch);
// Read a byte from a global register
uint8_t ReadGlobalReg(uint8_t reg) const {
return globalRegs[reg % kNumGlobalRegs];
}
bool CheckCounter(int rate);
// Write a byte to a global register
void WriteGlobalReg(uint8_t reg, uint8_t value) {
globalRegs[reg % kNumGlobalRegs] = value;
}
void DecodeBrr(int ch);
int16_t DecodeSample(uint8_t voice_num);
int16_t ProcessSample(uint8_t voice_num, int16_t sample);
void MixSamples();
uint8_t Read(uint8_t adr);
void Write(uint8_t adr, uint8_t val);
// Trigger a voice to start playing
void trigger_voice(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
int16_t GetSample(int ch);
Voice& voice = voices_[voice_num];
voice.state = VoiceState::ATTACK;
// Initialize other state management variables if needed
}
void GetSamples(int16_t* sample_data, int samples_per_frame, bool pal_timing);
// Release a voice (e.g., note release in ADSR)
void release_voice(uint8_t voice_num) {
if (voice_num >= kNumVoices) return;
private:
int16_t sample_buffer_[0x400 * 2]; // (1024 samples, *2 for stereo)
int16_t sample_offset_; // current offset in samplebuffer
Voice& voice = voices_[voice_num];
if (voice.state != VoiceState::OFF) {
voice.state = VoiceState::RELEASE;
}
// Update other state management variables if needed
}
std::vector<uint8_t>& aram_;
// Calculate envelope for a given voice
void UpdateEnvelope(uint8_t voice);
// Voice-related functions (implementations)
void set_voice_volume(int voice_num, int8_t left, int8_t right) {
voices_[voice_num].vol_left = left;
voices_[voice_num].vol_right = right;
}
void set_voice_pitch(int voice_num, uint16_t pitch) {
voices_[voice_num].pitch_low = pitch & 0xFF;
voices_[voice_num].pitch_high = (pitch >> 8) & 0xFF;
}
void set_voice_source_number(int voice_num, uint8_t srcn) {
voices_[voice_num].source_number = srcn;
}
void set_voice_adsr(int voice_num, uint8_t adsr1, uint8_t adsr2) {
voices_[voice_num].adsr1 = adsr1;
voices_[voice_num].adsr2 = adsr2;
}
void set_voice_gain(int voice_num, uint8_t gain) {
voices_[voice_num].gain = gain;
}
uint8_t read_voice_envx(int voice_num) { return voices_[voice_num].envx; }
int8_t read_voice_outx(int voice_num) { return voices_[voice_num].outx; }
// Global DSP functions
void set_master_volume(int8_t left, int8_t right) {
mvol_left = left;
mvol_right = right;
}
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);
// mirror ram
uint8_t ram[0x80];
// 8 channels
DspChannel channel[8];
// overarching
uint16_t counter;
uint16_t dirPage;
bool evenCycle;
bool mute;
bool reset;
int8_t masterVolumeL;
int8_t masterVolumeR;
// accumulation
int16_t sampleOutL;
int16_t sampleOutR;
int16_t echoOutL;
int16_t echoOutR;
// noise
int16_t noiseSample;
uint8_t noiseRate;
// echo
bool echoWrites;
int8_t echoVolumeL;
int8_t echoVolumeR;
int8_t feedbackVolume;
uint16_t echoBufferAdr;
uint16_t echoDelay;
uint16_t echoLength;
uint16_t echoBufferIndex;
uint8_t firBufferIndex;
int8_t firValues[8];
int16_t firBufferL[8];
int16_t firBufferR[8];
// sample ring buffer (1024 samples, *2 for stereo)
int16_t sampleBuffer[0x400 * 2];
uint16_t sampleOffset; // current offset in samplebuffer
uint32_t lastFrameBoundary;
};
} // namespace audio
} // namespace emu
} // namespace app
} // namespace yaze

View File

@@ -3,30 +3,86 @@
namespace yaze {
namespace app {
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
uint8_t Spc700::imm() {
PC++;
return read(PC);
}
uint16_t Spc700::imm() { return PC++; }
// Direct page
uint8_t Spc700::dp() {
PC++;
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;
return ReadOpcode() | (PSW.P << 8);
}
// Direct page indexed by X
@@ -46,23 +102,24 @@ uint8_t Spc700::dp_plus_y() {
// Indexed indirect (add index before 16-bit lookup).
uint16_t Spc700::dp_plus_x_indirect() {
PC++;
uint16_t addr = read_16(PC + X);
uint16_t addr = read_word(PC + X);
return addr;
}
// Indirect indexed (add index after 16-bit lookup).
uint16_t Spc700::dp_indirect_plus_y() {
PC++;
uint16_t offset = read_16(PC);
uint16_t offset = read_word(PC);
return offset + Y;
}
uint16_t Spc700::abs() {
PC++;
uint16_t addr = read(PC) | (read(PC) << 8);
return addr;
uint16_t Spc700::dp_dp(uint8_t* src) {
*src = read(ReadOpcode() | (PSW.P << 8));
return ReadOpcode() | (PSW.P << 8);
}
uint16_t Spc700::abs() { return ReadOpcodeWord(); }
int8_t Spc700::rel() {
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);
}
} // namespace audio
} // namespace emu
} // namespace app
} // namespace yaze

Some files were not shown because too many files have changed in this diff Show More