4 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
scawful
d94b7a3e81 backend-infra-engineer: Pre-0.2.2 snapshot (2023) 2023-12-29 22:43:40 -05:00
233 changed files with 51675 additions and 6827 deletions

View File

@@ -2,8 +2,14 @@ name: CMake
on:
push:
paths:
- 'src/**'
- 'test/**'
branches: [ "master" ]
pull_request:
paths:
- 'src/**'
- 'test/**'
branches: [ "master" ]
env:
@@ -18,13 +24,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Install Video Libs
run: sudo apt install libglew-dev
- name: Install Audio Libs
run: sudo apt install libwavpack-dev
- name: Install Abseil-cpp
run: sudo apt install libabsl-dev
- name: 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

@@ -1,81 +1,34 @@
# CMake Specifications --------------------------------------------------------
# CMake Specifications
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)
# C++ Standard Specifications
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 ----------------------------------------------
# Abseil Standard Specifications
include(cmake/absl.cmake)
add_subdirectory(src/lib/abseil-cpp)
include(cmake/openssl.cmake)
set(Protobuf_PROTOC_EXECUTABLE "/Users/scawful/code/protobuf/bin/protoc")
set(Protobuf_INCLUDE_DIRS "/Users/scawful/code/protobuf/include/")
set(PROTOBUF_INCLUDE_PATH ${CMAKE_CURRENT_BINARY_DIR}
CACHE INTERNAL "Path to generated protobuf files.")
include_directories(${PROTOBUF_INCLUDE_PATH})
# Video Libraries -------------------------------------------------------------
# Video Libraries
find_package(PNG REQUIRED)
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
include(cmake/sdl2.cmake)
# 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)
# Asar ------------------------------------------------------------------------
# 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/spc700/spc700.def)
# ImGui -----------------------------------------------------------------------
# ImGui
include(cmake/imgui.cmake)
# Project Files
add_subdirectory(src)
add_subdirectory(test)

2857
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -15,24 +15,39 @@ Building and installation
1. Clone the repository
```
git clone --recurse-submodules https://github.com/scawful/yaze.git
```
2. Create the build directory and configuration
```
cmake -S . -B build
```
3. Build and run.
```
cmake --build build
```
## Documentation
- For users, please refer to [getting_started.md](docs/getting-started.md) for instructions on how to use yaze.
- For developers, please refer to the [documentation](https://scawful.github.io/yaze/index.html) for information on the project's infrastructure.
License
--------
YAZE is distributed under the [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.txt) license.
SDL2, ImGUI and Abseil are subject to respective licenses.
SDL2, ImGui and Abseil are subject to respective licenses.
Screenshots
--------
![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/d8f0039d-d2e4-47d7-b420-554b20ac626f)
![image](https://github.com/scawful/yaze/assets/47263509/34b36666-cbea-420b-af90-626099470ae4)

Binary file not shown.

BIN
assets/font/NotoSansJP.ttf Normal file

Binary file not shown.

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,14 +21,22 @@ 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
${IMGUI_PATH}/imgui_demo.cpp
${IMGUI_PATH}/imgui_draw.cpp
${IMGUI_PATH}/imgui_widgets.cpp
${IMGUI_PATH}/backends/imgui_impl_sdl.cpp
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer.cpp
${IMGUI_PATH}/backends/imgui_impl_sdl2.cpp
${IMGUI_PATH}/backends/imgui_impl_sdlrenderer2.cpp
${IMGUI_PATH}/misc/cpp/imgui_stdlib.cpp
${IMGUI_FILE_DLG_PATH}/ImGuiFileDialog.cpp
${IMGUI_COLOR_TEXT_EDIT_PATH}/TextEditor.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()

6
cmake/sdl2.cmake Normal file
View File

@@ -0,0 +1,6 @@
# SDL2
if (UNIX)
add_subdirectory(src/lib/SDL)
else()
find_package(SDL2)
endif()

View File

@@ -1,3 +1,6 @@
# Build Instructions
## Windows
For VSCode users, use the following CMake extensions with MinGW-w64
@@ -15,3 +18,10 @@ Install the following packages using `pacman -S <package-name>`
`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

@@ -1,26 +0,0 @@
## September 2022
- Drawing Overworld maps to the screen
- Drawing entrance data on the overworld
- Drawing 2bpp inventory graphics data
- Started the YazeDelta project for version control.
## August 2022
- Added ValidateCompressionResults to ROM::Compress
- Improved Overworld systems in preparation for drawing maps.
## July 2022
- Display current overworld map graphics tile sheets.
- Added CreateAllGraphicsData to the ROM class
- Added Google Abseil C++ library for error handling, string manipulation
- Refactor ROM class to use smart pointers and STL containers
## June 2022
- Implemented LC_LZ2 Decompression
- Created Bitmap class for displaying SNES Graphics
- Added Overworld and OverworldMap class definitions
- Built user interface using ImGui and SDL2
- Started YAZE

66
docs/compression.md Normal file
View File

@@ -0,0 +1,66 @@
# LC_LZ2 Compression
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:
- `BUILD_HEADER(command, length)`: Macro to build a header from a command and a length.
- Command Constants: Constants to represent different commands like `kCommandDirectCopy`, `kCommandByteFill`, etc.
- Length and Mode Constants: Such as `kMaxLengthNormalHeader`, `kNintendoMode1`, etc.
### Data Structures:
#### 1. CompressionCommand:
- **arguments**: 2D array representing the command arguments for each possible command.
- **cmd_size**: Array storing the size of each possible command.
- **data_size**: Array storing the size of the data processed by each possible command.
#### 2. CompressionPiece:
- **command**: Represents the compression command.
- **length**: Length of the compressed data piece.
- **argument_length**: Length of the argument.
- **argument**: Argument as a string.
- **next**: Pointer to the next compression piece.
#### 3. CompressionContext (for Compression V3):
- Contains vectors to store raw and compressed data, compression pieces, and compression string.
- Various counters and flags for compression control.
- Current compression command details.
## Compression Functions
### Version 1:
- **Byte Repeat**: `CheckByteRepeat`
- **Word Repeat**: `CheckWordRepeat`
- **Increasing Byte**: `CheckIncByte`
- **Intra Copy**: `CheckIntraCopy`
- **Validation and Alternatives**: `ValidateForByteGain` & `CompressionCommandAlternative`
### Version 2:
- **Byte Repeat**: `CheckByteRepeatV2`
- **Word Repeat**: `CheckWordRepeatV2`
- **Increasing Byte**: `CheckIncByteV2`
- **Intra Copy**: `CheckIntraCopyV2`
- **Validation and Alternatives**: `ValidateForByteGainV2` & `CompressionCommandAlternativeV2`
### Version 3:
Using `CompressionContext` to handle compression.
- **Initialization**: `InitializeCompression`
- **Command Checks**: Such as `CheckByteRepeatV3`
- **Determining Best Compression**: `DetermineBestCompression`
- **Handling Direct Copy**: `HandleDirectCopy`
- **Adding Compression to Chain**: `AddCompressionToChain`
## Decompression Functions:
- `SetBuffer`: Prepares a buffer from data.
- `memfill`: Fills memory.
- **Decompression**: Such as `DecompressV2`, `DecompressGraphics`, and `DecompressOverworld`.
## Utility Functions:
- **Printing**: Such as `PrintCompressionPiece` and `PrintCompressionChain`.
- **Compression String Creation**: `CreateCompressionString`
- **Compression Result Validation**: Such as `ValidateCompressionResult` and its V3 variant.
- **Compression Piece Manipulation**: Like `SplitCompressionPiece` and its V3 variant.

23
docs/getting-started.md Normal file
View File

@@ -0,0 +1,23 @@
# Getting Started with YAZE
This software allows you to modify the classic SNES game "The Legend of Zelda: A Link to the Past" by editing its ROM file. With this editor, you can change various aspects of the game, such as the maps, sprites, items, and more.
Please note that this project is currently a work in progress, and some features may not be fully implemented or may be subject to change.
## Prerequisites
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
## Usage
To use the Link to the Past ROM Editor, follow these steps:
Open the ROM file using the "File" menu.
...
Save your changes using the "File" menu.
Backup files are enabled by default. Each save will produce a timestamped copy of your ROM before you last saved.
That's it! With these instructions, you should be able to get started with using YAZE. Happy editing!

140
docs/infrastructure.md Normal file
View File

@@ -0,0 +1,140 @@
# YAZE Infrastructure Overview
For developers to reference.
## Directory Structure
- **.github/workflows**: Contains `yaze_test` workflow config.
- **assets**: Hosts assets like fonts.
- **cmake**: Contains CMake configurations.
- **docs**: Contains documentation for users and developers.
- [Getting Started](./getting-started.md)
- [LC_LZ2 Compression](./compression.md)
- **src**: Contains source files.
- **app**: Contains the GUI editor `yaze`
- **cli**: Contains the command line interface `z3ed`
- **lib**: Contains git submodule dependencies.
- Abseil-cpp
- Asar
- ImGui
- ImGuiFileDialog
- ImGuiColorTextEdit
- imgui_memory_editor
- SDL2
- **test**: Contains testing interface `yaze_test`
### Flow of Control
- [app/yaze.cc](../src/app/yaze.cc)
- Initializes `absl::FailureSignalHandler` for stack tracing.
- Runs the `core::Controller` loop.
- [app/core/controller.cc](../src/app/core/controller.cc)
- Initializes SDLRenderer and SDLWindow
- Initializes ImGui, fonts, themes, and clipboard.
- Handles user input from keyboard and mouse.
- Updates `editor::MasterEditor`
- Renders the output to the screen.
- Handles the teardown of SDL and ImGui resources.
- [app/editor/master_editor.cc](../src/app/editor/master_editor.cc)
- Handles the main menu bar.
- File
- Open - [app::ROM::LoadFromFile](../src/app/rom.cc#l=90)
- Save - [app::ROM::SaveToFile](../src/app/rom.cc#l=301)
- Edit
- View
- Emulator
- HEX Editor
- ASM Editor
- Palette Editor
- Memory Viewer
- ImGui Demo
- GUI Tools
- Runtime Metrics
- Style Editor
- Help
- Handles `absl::Status` errors as popups delivered to the user.
- Update all the editors in a tab view.
- [app/editor/assembly_editor.cc](../src/app/editor/assembly_editor.cc)
- [app/editor/dungeon_editor.cc](../src/app/editor/dungeon_editor.cc)
- [app/editor/graphics_editor.cc](../src/app/editor/graphics_editor.cc)
- [app/editor/music_editor.cc](../src/app/editor/music_editor.cc)
- [app/editor/overworld_editor.cc](../src/app/editor/overworld_editor.cc)
- [app/editor/screen_editor.cc](../src/app/editor/screen_editor.cc)
- [app/editor/sprite_editor.cc](../src/app/editor/sprite_editor.cc)
## ROM
- [app/rom.cc](../src/app/rom.cc)
- [app/rom.h](../src/app/rom.h)
---
This `ROM` class provides methods to manipulate and access data from a ROM.
- **Key Methods**:
- `Load2BppGraphics()`: Loads 2BPP graphics data from specified sheets.
- `LoadAllGraphicsData()`: Loads all graphics data, both compressed and uncompressed, converting where necessary.
- `LoadFromFile(const absl::string_view& filename, bool z3_load)`: Loads ROM data from a file. It also handles headers and Zelda 3 specific data if requested.
- `LoadFromPointer(uchar* data, size_t length)`: Loads ROM data from a provided pointer.
- `LoadFromBytes(const Bytes& data)`: Loads ROM data from bytes.
- `LoadAllPalettes()`: Loads all color palettes used in the ROM. This includes palettes for various elements like sprites, shields, swords, etc.
- `UpdatePaletteColor(...)`: Updates a specific color within a named palette group.
- **Internal Data Structures**:
- `rom_data_`: A container that holds the ROM data.
- `graphics_bin_`: Holds the graphics data.
- `palette_groups_`: A map containing various palette groups, each having its own set of color palettes.
- **Special Notes**:
- The class interacts with various external functionalities, such as decompression algorithms (`gfx::DecompressV2`) and color conversion (`gfx::SnesTo8bppSheet`).
- Headers in the ROM data, if present, are identified and removed.
- 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.
## Bitmap
- [app/gfx/bitmap.cc](../src/app/gfx/bitmap.cc)
- [app/gfx/bitmap.h](../src/app/gfx/bitmap.cc)
---
This class is responsible for creating, managing, and manipulating bitmap data, which can be displayed on the screen using the ImGui library.
### Key Attributes:
1. **Width, Height, Depth, and Data Size**: These represent the dimensions and data size of the bitmap.
2. **Pixel Data**: Points to the raw data of the bitmap.
3. **Texture and Surface**: Use SDL to manage the graphical representation of the bitmap data. Both these attributes have custom deleters, ensuring proper resource management.
### Main Functions:
1. **Constructors**: Multiple constructors allow for different ways to create a Bitmap instance, like specifying width, height, depth, and data.
2. **Create**: This set of overloaded functions provides ways to create a bitmap from different data sources.
3. **CreateFromSurface**: Allows for the creation of a bitmap from an SDL_Surface.
4. **Apply**: Changes the bitmap's data to a new set of Bytes.
5. **Texture Operations**:
- **CreateTexture**: Creates an SDL_Texture from the bitmap's data for rendering.
- **UpdateTexture**: Updates the SDL_Texture with the latest bitmap data.
6. **SaveSurfaceToFile**: Saves the SDL_Surface to a file.
7. **SetSurface**: Assigns a new SDL_Surface to the bitmap.
8. **Palette Functions**:
- **ApplyPalette (Overloaded)**: This allows for the application of a SNESPalette or a standard SDL_Color palette to the bitmap.
9. **WriteToPixel**: Directly writes a value to a specified position in the pixel data.
## Z3ED cli
| Command | Arg | Params | Status |
|---------|-----|--------|--------|
| Apply BPS Patch | -a | rom_file bps_file | In progress |
| Create BPS Patch | -c | bps_file src_file modified_file | Not started |
| Open ROM | -o | rom_file | Complete |
| Backup ROM | -b | rom_file [new_file] | In progress |
| Expand ROM | -x | rom_file file_size | Not started |
| Transfer Tile16 | -t | src_rom dest_rom tile32_id_list(csv) | Complete |
| Export Graphics | -e | rom_file bin_file | In progress |
| Import Graphics | -i | bin_file rom_file | Not started |
| SNES to PC Address | -s | address | Complete |
| PC to SNES Address | -p | address | Complete |
## Further Development Ideas
- Extend `zelda3` namespace with additional functionalities.
- Optimize program performance.
- Introduce new features in the GUI editor.

Binary file not shown.

View File

@@ -1,40 +0,0 @@
\documentclass[12pt, oneside]{report}
\title{Yet Another Zelda3 Editor}
\author{Justin Scofield\thanks{Special thanks to JaredBrian, Zarby89}}
\date{June 2022 - October 2022}
\pagestyle{headings}
\begin{document}
\maketitle
\tableofcontents
\chapter{Introduction}
{\bf Yet Another Zelda3 Editor} is a multi-purpose editor for the retro video game title {\it {"The Legend of Zelda: A Link to the Past"}} for the Super Nintendo Entertainment System. The editor only supports the US version.
\section{Getting Started}
\section{Loading from ROM}
\section{Saving to ROM}
\chapter{Overworld}
The editor provides an interface for the user to make various changes to the overworld maps. These changes include the manpulation of the maps tiles, palettes, entrances, exits, sprites, area music, and other properties. Here we will explain the basics of the tile system.
\section{Tile System}
\section{Map Toolset}
\section{Map Canvas}
\chapter{Dungeons}
\chapter{Palettes}
\chapter{Sprites}
\chapter{Screens}
\section{Inventory}
\section{Heads-up Display}
\chapter{Modules}
\end{document}

View File

@@ -1,100 +1,80 @@
# yaze source files -----------------------------------------------------------
set(
YAZE_APP_CORE_SRC
app/core/common.cc
app/core/controller.cc
)
set(
YAZE_APP_EDITOR_SRC
app/editor/assembly_editor.cc
app/editor/dungeon_editor.cc
app/editor/master_editor.cc
app/editor/music_editor.cc
app/editor/overworld_editor.cc
app/editor/palette_editor.cc
app/editor/screen_editor.cc
app/core/labeling.cc
app/emu/emulator.cc
)
set(
YAZE_APP_GFX_SRC
app/gfx/bitmap.cc
app/gfx/compression.cc
app/gfx/scad_format.cc
app/gfx/snes_palette.cc
app/gfx/snes_tile.cc
)
set(
YAZE_APP_ZELDA3_SRC
app/zelda3/inventory.cc
app/zelda3/overworld_map.cc
app/zelda3/overworld.cc
app/zelda3/title_screen.cc
app/zelda3/sprite.cc
app/gfx/snes_color.cc
app/gfx/tilesheet.cc
)
set(
YAZE_GUI_SRC
gui/canvas.cc
gui/input.cc
gui/style.cc
gui/widgets.cc
gui/color.cc
app/gui/asset_browser.cc
app/gui/canvas.cc
app/gui/input.cc
app/gui/style.cc
app/gui/color.cc
app/gui/zeml.cc
)
# executable creation ---------------------------------------------------------
add_executable(
yaze
app/yaze.cc
app/rom.cc
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${ASAR_STATIC_SRC}
${SNES_SPC_SOURCES}
${IMGUI_SRC}
)
# including libraries ---------------------------------------------------------
target_include_directories(
yaze PUBLIC
lib/
app/
${CMAKE_SOURCE_DIR}/src/
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
lib/SDL_mixer/include/
${GLEW_INCLUDE_DIRS}
lib/asar/src/asar/
lib/snes_spc/snes_spc/
set(
YAZE_APP_EMU_SRC
app/emu/audio/apu.cc
app/emu/audio/spc700.cc
app/emu/audio/dsp.cc
app/emu/audio/internal/addressing.cc
app/emu/audio/internal/instructions.cc
app/emu/cpu/internal/addressing.cc
app/emu/cpu/internal/instructions.cc
app/emu/cpu/cpu.cc
app/emu/video/ppu.cc
app/emu/memory/dma.cc
app/emu/memory/memory.cc
app/emu/snes.cc
)
set(SDL_TARGETS SDL2::SDL2)
if(WIN32 OR MINGW)
list(PREPEND SDL_TARGETS SDL2::SDL2main)
list(PREPEND SDL_TARGETS SDL2::SDL2main ws2_32)
add_definitions(-DSDL_MAIN_HANDLED)
endif()
# linking libraries -----------------------------------------------------------
target_link_libraries(
yaze PUBLIC
${ABSL_TARGETS}
${SDL_TARGETS}
${SDLMIXER_LIBRARY}
SDL2_mixer
${PNG_LIBRARIES}
${GLEW_LIBRARIES}
${OPENGL_LIBRARIES}
${CMAKE_DL_LIBS}
asar-static
snes_spc
ImGui
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()
if(APPLE)
list(APPEND YAZE_APP_CORE_SRC
app/core/platform/file_dialog.mm
app/core/platform/app_delegate.mm
app/core/platform/font_loader.mm
app/core/platform/clipboard.mm
app/core/platform/file_path.mm
)
find_library(COCOA_LIBRARY Cocoa)
if(NOT COCOA_LIBRARY)
message(FATAL_ERROR "Cocoa not found")
endif()
set(CMAKE_EXE_LINKER_FLAGS "-framework ServiceManagement -framework Foundation -framework Cocoa")
endif()
include(app/CMakeLists.txt)
# include(cli/CMakeLists.txt) Excluded for now, macOS include breaks action build
if (UNIX)
target_compile_definitions(yaze PRIVATE "linux")
@@ -109,7 +89,7 @@ if(MACOS)
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
# MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/cmake/yaze.plist.in
)
elseif(UNIX)
set_target_properties(yaze
@@ -129,55 +109,4 @@ set_target_properties(yaze
)
endif()
# add_subdirectory(app/delta)
# add_executable(
# yaze_delta
# app/delta/delta.cc
# app/delta/viewer.cc
# app/delta/service.cc
# app/delta/client.cc
# app/rom.cc
# ${YAZE_APP_ASM_SRC}
# ${YAZE_APP_CORE_SRC}
# ${YAZE_APP_EDITOR_SRC}
# ${YAZE_APP_GFX_SRC}
# ${YAZE_APP_ZELDA3_SRC}
# ${YAZE_GUI_SRC}
# ${IMGUI_SRC}
# ${ASAR_STATIC_SRC}
# )
# target_include_directories(
# yaze_delta PUBLIC
# lib/
# app/
# lib/asar/src/
# ${ASAR_INCLUDE_DIR}
# ${CMAKE_SOURCE_DIR}/src/
# ${PNG_INCLUDE_DIRS}
# ${SDL2_INCLUDE_DIR}
# ${GLEW_INCLUDE_DIRS}
# )
# target_link_libraries(
# yaze_delta PUBLIC
# ${ABSL_TARGETS}
# ${SDL_TARGETS}
# ${PNG_LIBRARIES}
# ${GLEW_LIBRARIES}
# ${OPENGL_LIBRARIES}
# ${CMAKE_DL_LIBS}
# delta-service
# asar-static
# ImGui
# )
# target_compile_definitions(yaze_delta PRIVATE "linux")
# target_compile_definitions(yaze_delta PRIVATE "stricmp=strcasecmp")
# set (source "${CMAKE_SOURCE_DIR}/assets")
# set (destination "${CMAKE_CURRENT_BINARY_DIR}/assets")
# add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
# COMMAND ${CMAKE_COMMAND} -E create_symlink ${source} ${destination}
# DEPENDS ${destination}
# COMMENT "symbolic link resources folder from ${source} => ${destination}")
add_subdirectory(test)

40
src/app/CMakeLists.txt Normal file
View File

@@ -0,0 +1,40 @@
include(app/editor/CMakeLists.txt)
include(app/zelda3/CMakeLists.txt)
add_executable(
yaze
app/yaze.cc
app/rom.cc
${YAZE_APP_EMU_SRC}
${YAZE_APP_CORE_SRC}
${YAZE_APP_EDITOR_SRC}
${YAZE_APP_GFX_SRC}
${YAZE_APP_ZELDA3_SRC}
${YAZE_GUI_SRC}
${IMGUI_SRC}
${IMGUI_TEST_ENGINE_SOURCES}
)
target_include_directories(
yaze PUBLIC
lib/
app/
${CMAKE_SOURCE_DIR}/src/
${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine
${PNG_INCLUDE_DIRS}
${SDL2_INCLUDE_DIR}
)
target_link_libraries(
yaze PUBLIC
${ABSL_TARGETS}
${SDL_TARGETS}
${PNG_LIBRARIES}
${CMAKE_DL_LIBS}
ImGuiTestEngine
ImGui
)
if (APPLE)
target_link_libraries(yaze PUBLIC ${COCOA_LIBRARY})
endif()

View File

@@ -1,20 +1,66 @@
#include "common.h"
#include "imgui/imgui.h"
#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
#include <stack>
#include <string>
#include "absl/strings/str_format.h"
namespace yaze {
namespace app {
namespace core {
unsigned int SnesToPc(unsigned int addr) {
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;
}
unsigned int temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
uint32_t temp = (addr & 0x7FFF) + ((addr / 2) & 0xFF8000);
return (temp + 0x0);
}
uint32_t PcToSnes(uint32_t addr) {
uint8_t *b = reinterpret_cast<uint8_t *>(&addr);
b[2] = static_cast<uint8_t>(b[2] * 2);
if (b[1] >= 0x80) {
b[2] += 1;
} else {
b[1] += 0x80;
}
return addr;
}
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr) {
uint32_t result = 0;
result = (bank << 16) | addr;
return result;
}
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3) {
return (addr1 << 16) | (addr2 << 8) | addr3;
}
@@ -58,6 +104,84 @@ bool StringReplace(std::string &str, const std::string &from,
return true;
}
void stle(uint8_t *const p_arr, size_t const p_index, unsigned const p_val) {
uint8_t v = (p_val >> (8 * p_index)) & 0xff;
p_arr[p_index] = v;
}
void stle0(uint8_t *const p_arr, unsigned const p_val) {
stle(p_arr, 0, p_val);
}
void stle1(uint8_t *const p_arr, unsigned const p_val) {
stle(p_arr, 1, p_val);
}
void stle2(uint8_t *const p_arr, unsigned const p_val) {
stle(p_arr, 2, p_val);
}
void stle3(uint8_t *const p_arr, unsigned const p_val) {
stle(p_arr, 3, p_val);
}
void stle16b(uint8_t *const p_arr, uint16_t const p_val) {
stle0(p_arr, p_val);
stle1(p_arr, p_val);
}
// "Store little endian 16-bit value using a byte pointer, offset by an
// index before dereferencing"
void stle16b_i(uint8_t *const p_arr, size_t const p_index,
uint16_t const p_val) {
stle16b(p_arr + (p_index * 2), p_val);
}
// "load little endian value at the given byte offset and shift to get its
// value relative to the base offset (powers of 256, essentially)"
unsigned ldle(uint8_t const *const p_arr, unsigned const p_index) {
uint32_t v = p_arr[p_index];
v <<= (8 * p_index);
return v;
}
// Helper function to get the first byte in a little endian number
uint32_t ldle0(uint8_t const *const p_arr) { return ldle(p_arr, 0); }
// Helper function to get the second byte in a little endian number
uint32_t ldle1(uint8_t const *const p_arr) { return ldle(p_arr, 1); }
// Helper function to get the third byte in a little endian number
uint32_t ldle2(uint8_t const *const p_arr) { return ldle(p_arr, 2); }
// Helper function to get the third byte in a little endian number
uint32_t ldle3(uint8_t const *const p_arr) { return ldle(p_arr, 3); }
// Load little endian halfword (16-bit) dereferenced from
uint16_t ldle16b(uint8_t const *const p_arr) {
uint16_t v = 0;
v |= (ldle0(p_arr) | ldle1(p_arr));
return v;
}
// Load little endian halfword (16-bit) dereferenced from an arrays of bytes.
// This version provides an index that will be multiplied by 2 and added to the
// base address.
uint16_t ldle16b_i(uint8_t const *const p_arr, size_t const p_index) {
return ldle16b(p_arr + (2 * 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,19 +1,232 @@
#ifndef YAZE_CORE_COMMON_H
#define YAZE_CORE_COMMON_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 {
unsigned int SnesToPc(unsigned int addr);
/**
* @class ExperimentFlags
* @brief A class to manage experimental feature flags.
*/
class ExperimentFlags {
public:
struct Flags {
// Bitmap manager abstraction to manage graphics bin of Rom.
bool kUseBitmapManager = true;
// Log instructions to the GUI debugger.
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.
bool kSaveAllPalettes = false;
// Flag to enable the saving of gfx groups to the rom.
bool kSaveGfxGroups = false;
// Flag to enable the change queue, which could have any anonymous
// save routine for the Rom. In practice, just the overworld tilemap
// and tile32 save.
bool kSaveWithChangeQueue = false;
// Attempt to run the dungeon room draw routine when opening a room.
bool kDrawDungeonRoomGraphics = true;
// Use the new platform specific file dialog wrappers.
bool kNewFileDialogWrapper = true;
// Platform specific loading of fonts from the system. Currently
// only supports macOS.
bool kLoadSystemFonts = true;
// 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;
virtual ~ExperimentFlags() = default;
auto flags() const {
if (!flags_) {
flags_ = std::make_shared<Flags>();
}
Flags *flags = flags_.get();
return flags;
}
Flags *mutable_flags() {
if (!flags_) {
flags_ = std::make_shared<Flags>();
}
return flags_.get();
}
private:
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:
NotifyValue() : value_(), modified_(false), temp_value_() {}
NotifyValue(const T &value)
: value_(value), modified_(false), temp_value_() {}
void set(const T &value) {
value_ = value;
modified_ = true;
}
const T &get() {
modified_ = false;
return value_;
}
T &mutable_get() {
modified_ = false;
temp_value_ = value_;
return temp_value_;
}
void apply_changes() {
if (temp_value_ != value_) {
value_ = temp_value_;
modified_ = true;
}
}
void operator=(const T &value) { set(value); }
operator T() { return get(); }
bool modified() const { return modified_; }
private:
T value_;
bool modified_;
T temp_value_;
};
class ImGuiIdIssuer {
private:
static std::stack<ImGuiID> idStack;
public:
// Generate and push a new ID onto the stack
static ImGuiID GetNewID() {
static int counter = 1; // Start from 1 to ensure uniqueness
ImGuiID child_id = ImGui::GetID((void *)(intptr_t)counter++);
idStack.push(child_id);
return child_id;
}
// Pop all IDs from the stack (can be called explicitly or upon program exit)
static void Cleanup() {
while (!idStack.empty()) {
idStack.pop();
}
}
};
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);
uint32_t MapBankToWordAddress(uint8_t bank, uint16_t addr);
int AddressFromBytes(uint8_t addr1, uint8_t addr2, uint8_t addr3);
int HexToDec(char *input, int length);
bool StringReplace(std::string &str, const std::string &from,
const std::string &to);
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);
uint16_t ldle16b(uint8_t const *const p_arr);
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

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,31 @@
#include "controller.h"
#include <SDL.h>
#include <SDL_mixer.h>
#include <imgui/backends/imgui_impl_sdl.h>
#include <imgui/backends/imgui_impl_sdlrenderer.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "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>
#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "app/core/platform/font_loader.h"
#include "app/editor/master_editor.h"
#include "gui/icons.h"
#include "gui/style.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
namespace yaze {
namespace app {
@@ -21,35 +33,174 @@ 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 HandleKeyDown(SDL_Event &event) {
void ImGui_ImplSDL2_SetClipboardText(void *user_data, const char *text) {
SDL_SetClipboardText(text);
}
const char *ImGui_ImplSDL2_GetClipboardText(void *user_data) {
return SDL_GetClipboardText();
}
void InitializeClipboard() {
ImGuiIO &io = ImGui::GetIO();
io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
io.ClipboardUserData = nullptr;
}
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));
@@ -58,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) {
@@ -76,24 +268,47 @@ 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
bool Controller::isActive() const { return active_; }
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
absl::Status Controller::onEntry() {
RETURN_IF_ERROR(CreateWindow())
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();
}
void Controller::onInput() {
void Controller::OnInput() {
int wheel = 0;
SDL_Event event;
ImGuiIO &io = ImGui::GetIO();
@@ -101,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);
@@ -124,7 +339,6 @@ void Controller::onInput() {
break;
}
break;
default:
break;
}
@@ -133,44 +347,77 @@ void Controller::onInput() {
HandleMouseMovement(wheel);
}
void Controller::onLoad() { master_editor_.UpdateScreen(); }
absl::Status Controller::OnLoad() {
if (master_editor_.quit()) {
active_ = false;
}
NewMasterFrame();
RETURN_IF_ERROR(master_editor_.Update());
return absl::OkStatus();
}
void Controller::doRender() const {
SDL_RenderClear(renderer_.get());
void Controller::DoRender() const {
ImGui::Render();
ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
SDL_RenderClear(renderer_.get());
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData(), renderer_.get());
SDL_RenderPresent(renderer_.get());
}
void Controller::onExit() const {
ImGui_ImplSDLRenderer_Shutdown();
void Controller::OnExit() {
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::CreateWindow() {
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
absl::Status Controller::CreateSDL_Window() {
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 {
SDL_DisplayMode displayMode;
SDL_GetCurrentDisplayMode(0, &displayMode);
int screenWidth = displayMode.w * 0.8;
int screenHeight = displayMode.h * 0.8;
window_ = std::unique_ptr<SDL_Window, sdl_deleter>(
SDL_CreateWindow("Yet Another Zelda3 Editor", // window title
SDL_WINDOWPOS_UNDEFINED, // initial x position
SDL_WINDOWPOS_UNDEFINED, // initial y position
kScreenWidth, // width, in pixels
kScreenHeight, // height, in pixels
screenWidth, // width, in pixels
screenHeight, // height, in pixels
SDL_WINDOW_RESIZABLE),
sdl_deleter());
if (window_ == nullptr) {
return absl::InternalError(
absl::StrFormat("SDL_CreateWindow: %s\n", SDL_GetError()));
}
// Initialize SDL_mixer
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) {
printf("SDL_mixer could not initialize! SDL_mixer Error: %s\n",
Mix_GetError());
}
}
return absl::OkStatus();
}
@@ -191,36 +438,143 @@ absl::Status Controller::CreateRenderer() {
}
absl::Status Controller::CreateGuiContext() {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
if (flags()->kUseNewImGuiInput) {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
}
// Initialize ImGui for SDL
ImGui_ImplSDL2_InitForSDLRenderer(window_.get(), renderer_.get());
ImGui_ImplSDLRenderer_Init(renderer_.get());
ImGui_ImplSDLRenderer2_Init(renderer_.get());
// Load available fonts
const ImGuiIO &io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF("assets/font/Karla-Regular.ttf", 14.0f);
if (flags()->kLoadSystemFonts) {
LoadSystemFonts();
} else {
RETURN_IF_ERROR(LoadFontFamilies());
}
// merge in icons from Google Material Design
// Set the default style
gui::ColorsYaze();
// Build a new ImGui frame
ImGui_ImplSDLRenderer2_NewFrame();
ImGui_ImplSDL2_NewFrame();
return absl::OkStatus();
}
absl::Status Controller::LoadFontFamilies() const {
ImGuiIO &io = ImGui::GetIO();
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;
// Icon configuration
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, 18.0f, &icons_config,
icons_ranges);
io.Fonts->AddFontFromFileTTF("assets/font/Roboto-Medium.ttf", 14.0f);
io.Fonts->AddFontFromFileTTF("assets/font/Cousine-Regular.ttf", 14.0f);
io.Fonts->AddFontFromFileTTF("assets/font/DroidSans.ttf", 16.0f);
// Set the default style
gui::ColorsYaze();
// Japanese font configuration
ImFontConfig japanese_font_config;
japanese_font_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
// Build a new ImGui frame
ImGui_ImplSDLRenderer_NewFrame();
ImGui_ImplSDL2_NewFrame(window_.get());
// List of fonts to be loaded
std::vector<const char *> font_paths = {KARLA_REGULAR, ROBOTO_MEDIUM,
COUSINE_REGULAR, IBM_PLEX_JP};
// Load fonts with associated icon and Japanese merges
for (const auto &font_path : font_paths) {
float font_size =
(font_path == DROID_SANS) ? FONT_SIZE_DROID_SANS : FONT_SIZE_DEFAULT;
std::string actual_font_path;
#ifdef __APPLE__
#if TARGET_OS_IOS == 1
const std::string kBundlePath = GetBundleResourcePath();
actual_font_path = kBundlePath + font_path;
#else
actual_font_path = 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", actual_font_path));
}
// Merge icon set
std::string actual_icon_font_path = "";
const char *icon_font_path = FONT_ICON_FILE_NAME_MD;
#if defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS == 1
const std::string kIconBundlePath = GetBundleResourcePath();
actual_icon_font_path = kIconBundlePath + "MaterialIcons-Regular.ttf";
#else
actual_icon_font_path = 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
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();
}

View File

@@ -2,17 +2,20 @@
#define YAZE_APP_CORE_CONTROLLER_H
#include <SDL.h>
#include <imgui/backends/imgui_impl_sdl.h>
#include <imgui/backends/imgui_impl_sdlrenderer.h>
#include <imgui/imgui.h>
#include <imgui/imgui_internal.h>
#include "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/editor/master_editor.h"
#include "gui/icons.h"
#include "gui/style.h"
#include "app/editor/utils/editor.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
int main(int argc, char **argv);
@@ -20,32 +23,57 @@ namespace yaze {
namespace app {
namespace core {
class Controller {
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;
absl::Status onEntry();
void onInput();
void onLoad();
void onLoadDelta();
void doRender() const;
void onExit() const;
bool IsActive() const { return active_; }
absl::Status OnEntry(std::string filename = "");
void OnInput();
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 { SDL_DestroyWindow(p); }
void operator()(SDL_Renderer *p) const { SDL_DestroyRenderer(p); }
void operator()(SDL_Window *p) const {
if (p) {
SDL_DestroyWindow(p);
}
}
void operator()(SDL_Renderer *p) const {
if (p) {
SDL_DestroyRenderer(p);
}
}
void operator()(SDL_Texture *p) const { SDL_DestroyTexture(p); }
};
absl::Status CreateWindow();
absl::Status CreateRenderer();
absl::Status CreateGuiContext();
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_;
};

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

@@ -0,0 +1,18 @@
#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
void InitializeCocoa();
#ifdef __cplusplus
} // extern "C"
#endif
#endif // TARGET_OS_MAC
#endif // YAZE_APP_CORE_PLATFORM_APP_DELEGATE_H

View File

@@ -0,0 +1,243 @@
// AppDelegate.mm
#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;
@end
@implementation AppDelegate
// - (void)changeApplicationIcon {
// NSImage *newIcon = [NSImage imageNamed:@"newIcon"];
// [NSApp setApplicationIconImage:newIcon];
// }
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self setupMenus];
}
- (void)setupMenus {
NSMenu *mainMenu = [NSApp mainMenu];
NSMenuItem *fileMenuItem = [mainMenu itemWithTitle:@"File"];
if (!fileMenuItem) {
NSMenu *fileMenu = [[NSMenu alloc] initWithTitle:@"File"];
fileMenuItem = [[NSMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""];
[fileMenuItem setSubmenu:fileMenu];
NSMenuItem *openItem = [[NSMenuItem alloc] initWithTitle:@"Open"
action:@selector(openFileAction:)
keyEquivalent:@"o"];
[fileMenu addItem:openItem];
// Open Recent
NSMenu *openRecentMenu = [[NSMenu alloc] initWithTitle:@"Open Recent"];
NSMenuItem *openRecentMenuItem = [[NSMenuItem alloc] initWithTitle:@"Open Recent"
action:nil
keyEquivalent:@""];
[openRecentMenuItem setSubmenu:openRecentMenu];
[fileMenu addItem:openRecentMenuItem];
// Add a separator
[fileMenu addItem:[NSMenuItem separatorItem]];
// Save
NSMenuItem *saveItem = [[NSMenuItem alloc] initWithTitle:@"Save" action:nil keyEquivalent:@"s"];
[fileMenu addItem:saveItem];
// Separator
[fileMenu addItem:[NSMenuItem separatorItem]];
// Options submenu
NSMenu *optionsMenu = [[NSMenu alloc] initWithTitle:@"Options"];
NSMenuItem *optionsMenuItem = [[NSMenuItem alloc] initWithTitle:@"Options"
action:nil
keyEquivalent:@""];
[optionsMenuItem setSubmenu:optionsMenu];
// Flag checkmark field
NSMenuItem *flagItem = [[NSMenuItem alloc] initWithTitle:@"Flag"
action:@selector(toggleFlagAction:)
keyEquivalent:@""];
[flagItem setTarget:self];
[flagItem setState:NSControlStateValueOff];
[optionsMenu addItem:flagItem];
[fileMenu addItem:optionsMenuItem];
[mainMenu insertItem:fileMenuItem atIndex:1];
}
NSMenuItem *editMenuItem = [mainMenu itemWithTitle:@"Edit"];
if (!editMenuItem) {
NSMenu *editMenu = [[NSMenu alloc] initWithTitle:@"Edit"];
editMenuItem = [[NSMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""];
[editMenuItem setSubmenu:editMenu];
NSMenuItem *undoItem = [[NSMenuItem alloc] initWithTitle:@"Undo" action:nil keyEquivalent:@"z"];
[editMenu addItem:undoItem];
NSMenuItem *redoItem = [[NSMenuItem alloc] initWithTitle:@"Redo" action:nil keyEquivalent:@"Z"];
[editMenu addItem:redoItem];
// Add a separator
[editMenu addItem:[NSMenuItem separatorItem]];
NSMenuItem *cutItem = [[NSMenuItem alloc] initWithTitle:@"Cut"
action:@selector(cutAction:)
keyEquivalent:@"x"];
[editMenu addItem:cutItem];
NSMenuItem *copyItem = [[NSMenuItem alloc] initWithTitle:@"Copy" action:nil keyEquivalent:@"c"];
[editMenu addItem:copyItem];
NSMenuItem *pasteItem = [[NSMenuItem alloc] initWithTitle:@"Paste"
action:nil
keyEquivalent:@"v"];
[editMenu addItem:pasteItem];
// Add a separator
[editMenu addItem:[NSMenuItem separatorItem]];
NSMenuItem *selectAllItem = [[NSMenuItem alloc] initWithTitle:@"Select All"
action:nil
keyEquivalent:@"a"];
[editMenu addItem:selectAllItem];
[mainMenu insertItem:editMenuItem atIndex:2];
}
NSMenuItem *viewMenuItem = [mainMenu itemWithTitle:@"View"];
if (!viewMenuItem) {
NSMenu *viewMenu = [[NSMenu alloc] initWithTitle:@"View"];
viewMenuItem = [[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""];
[viewMenuItem setSubmenu:viewMenu];
// Emulator view button
NSMenuItem *emulatorViewItem = [[NSMenuItem alloc] initWithTitle:@"Emulator View"
action:nil
keyEquivalent:@"1"];
[viewMenu addItem:emulatorViewItem];
// Hex Editor View
NSMenuItem *hexEditorViewItem = [[NSMenuItem alloc] initWithTitle:@"Hex Editor View"
action:nil
keyEquivalent:@"2"];
[viewMenu addItem:hexEditorViewItem];
// Disassembly view button
NSMenuItem *disassemblyViewItem = [[NSMenuItem alloc] initWithTitle:@"Disassembly View"
action:nil
keyEquivalent:@"3"];
[viewMenu addItem:disassemblyViewItem];
// Memory view button
NSMenuItem *memoryViewItem = [[NSMenuItem alloc] initWithTitle:@"Memory View"
action:nil
keyEquivalent:@"4"];
[viewMenu addItem:memoryViewItem];
// Add a separator
[viewMenu addItem:[NSMenuItem separatorItem]];
// Toggle fullscreen button
NSMenuItem *toggleFullscreenItem = [[NSMenuItem alloc] initWithTitle:@"Toggle Fullscreen"
action:nil
keyEquivalent:@"f"];
[viewMenu addItem:toggleFullscreenItem];
[mainMenu insertItem:viewMenuItem atIndex:3];
}
NSMenuItem *helpMenuItem = [mainMenu itemWithTitle:@"Help"];
if (!helpMenuItem) {
NSMenu *helpMenu = [[NSMenu alloc] initWithTitle:@"Help"];
helpMenuItem = [[NSMenuItem alloc] initWithTitle:@"Help" action:nil keyEquivalent:@""];
[helpMenuItem setSubmenu:helpMenu];
// URL to online documentation
NSMenuItem *documentationItem = [[NSMenuItem alloc] initWithTitle:@"Documentation"
action:nil
keyEquivalent:@"?"];
[helpMenu addItem:documentationItem];
[mainMenu insertItem:helpMenuItem atIndex:4];
}
}
// Action method for the New menu item
- (void)newFileAction:(id)sender {
NSLog(@"New File action triggered");
}
- (void)toggleFlagAction:(id)sender {
NSMenuItem *flagItem = (NSMenuItem *)sender;
if ([flagItem state] == NSControlStateValueOff) {
[flagItem setState:NSControlStateValueOn];
} else {
[flagItem setState:NSControlStateValueOff];
}
}
- (void)openFileAction:(id)sender {
if (!yaze::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 {
// TODO: Implement
}
- (void)openRecentFileAction:(id)sender {
NSLog(@"Open Recent File action triggered");
}
extern "C" void InitializeCocoa() {
@autoreleasepool {
AppDelegate *delegate = [[AppDelegate alloc] init];
[NSApplication sharedApplication];
[NSApp setDelegate:delegate];
[NSApp finishLaunching];
}
}
@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

@@ -0,0 +1,27 @@
#ifndef YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
#define YAZE_APP_CORE_PLATFORM_CLIPBOARD_H
#ifdef _WIN32
void CopyImageToClipboard(const std::vector<uint8_t>& data);
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);
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);
void GetImageFromClipboard(std::vector<uint8_t>& data, int& width, int& height);
#endif
#endif // YAZE_APP_CORE_PLATFORM_CLIPBOARD_H

View File

@@ -0,0 +1,45 @@
#include "clipboard.h"
#include <iostream>
#include <vector>
#ifdef TARGET_OS_MAC
#import <Cocoa/Cocoa.h>
void CopyImageToClipboard(const std::vector<uint8_t>& pngData) {
NSData* data = [NSData dataWithBytes:pngData.data() length:pngData.size()];
NSImage* image = [[NSImage alloc] initWithData:data];
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
[pasteboard writeObjects:@[ image ]];
}
void GetImageFromClipboard(std::vector<uint8_t>& pixel_data, int& width, int& height) {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSArray* classArray = [NSArray arrayWithObject:[NSImage class]];
NSDictionary* options = [NSDictionary dictionary];
NSImage* image = [pasteboard readObjectsForClasses:classArray options:options].firstObject;
if (!image) {
width = height = 0;
return;
}
// Assuming the image is in an RGBA format
CGImageRef cgImage = [image CGImageForProposedRect:nil context:nil hints:nil];
width = (int)CGImageGetWidth(cgImage);
height = (int)CGImageGetHeight(cgImage);
size_t bytesPerRow = 4 * width;
size_t totalBytes = bytesPerRow * height;
pixel_data.resize(totalBytes);
CGContextRef context = CGBitmapContextCreate(
pixel_data.data(), width, height, 8, bytesPerRow, CGColorSpaceCreateDeviceRGB(),
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGContextRelease(context);
}
#endif

View File

@@ -0,0 +1,96 @@
#ifndef YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#define YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H
#include <string>
#ifdef _WIN32
// Include Windows-specific headers
#include <shobjidl.h>
#include <windows.h>
class FileDialogWrapper {
public:
static std::string ShowOpenFileDialog() {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
IFileDialog *pfd = NULL;
HRESULT hr =
CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileDialog, reinterpret_cast<void **>(&pfd));
std::string file_path_windows;
if (SUCCEEDED(hr)) {
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr)) {
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr)) {
// Get the file path
PWSTR pszFilePath;
psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
char str[128];
wcstombs(str, pszFilePath, 128);
file_path_windows = str;
psiResult->Release();
CoTaskMemFree(pszFilePath);
}
}
pfd->Release();
}
CoUninitialize();
return file_path_windows;
}
};
#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 {
public:
static std::string ShowOpenFileDialog() {
// Linux-specific file dialog implementation using GTK
// ...
return "file_path_linux";
}
};
#else
#error "Unsupported platform."
#endif
#endif // YAZE_APP_CORE_PLATFORM_FILE_DIALOG_H

View File

@@ -0,0 +1,116 @@
#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];
[openPanel setCanChooseDirectories:NO];
[openPanel setAllowsMultipleSelection:NO];
if ([openPanel runModal] == NSModalResponseOK) {
NSURL* url = [[openPanel URLs] objectAtIndex:0];
NSString* path = [url path];
return std::string([path UTF8String]);
}
return "";
}
std::string 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

@@ -0,0 +1,81 @@
#include "app/core/platform/font_loader.h"
#include "imgui/imgui.h"
#include <string>
#include <vector>
#ifdef _WIN32
#include <Windows.h>
int CALLBACK EnumFontFamExProc(const LOGFONT* lpelfe, const TEXTMETRIC* lpntme,
DWORD FontType, LPARAM lParam) {
// Step 3: Load the font into ImGui
ImGuiIO& io = ImGui::GetIO();
io.Fonts->AddFontFromFileTTF(lpelfe->lfFaceName, 16.0f);
return 1;
}
void LoadSystemFonts() {
HKEY hKey;
std::vector<std::string> fontPaths;
// Open the registry key where fonts are listed
if (RegOpenKeyEx(
HKEY_LOCAL_MACHINE,
TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"), 0,
KEY_READ, &hKey) == ERROR_SUCCESS) {
DWORD valueCount;
DWORD maxValueNameSize;
DWORD maxValueDataSize;
// Query the number of entries and the maximum size of the names and values
RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &valueCount,
&maxValueNameSize, &maxValueDataSize, NULL, NULL);
char* valueName = new char[maxValueNameSize + 1]; // +1 for null terminator
BYTE* valueData = new BYTE[maxValueDataSize + 1]; // +1 for null terminator
// Enumerate all font entries
for (DWORD i = 0; i < valueCount; i++) {
DWORD valueNameSize = maxValueNameSize + 1; // +1 for null terminator
DWORD valueDataSize = maxValueDataSize + 1; // +1 for null terminator
DWORD valueType;
// Clear buffers
memset(valueName, 0, valueNameSize);
memset(valueData, 0, valueDataSize);
// Get the font name and file path
if (RegEnumValue(hKey, i, valueName, &valueNameSize, NULL, &valueType,
valueData, &valueDataSize) == ERROR_SUCCESS) {
if (valueType == REG_SZ) {
// Add the font file path to the vector
std::string fontPath((char*)valueData);
fontPaths.push_back(fontPath);
}
}
}
delete[] valueName;
delete[] valueData;
RegCloseKey(hKey);
}
ImGuiIO& io = ImGui::GetIO();
for (const auto& fontPath : fontPaths) {
io.Fonts->AddFontFromFileTTF(fontPath.c_str(), 16.0f);
}
}
#elif defined(__linux__)
void LoadSystemFonts() {
// Load Linux System Fonts into ImGui
// ...
}
#endif

View File

@@ -0,0 +1,26 @@
// FontLoader.h
#ifndef FONTLOADER_H
#define FONTLOADER_H
#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

@@ -0,0 +1,74 @@
// FontLoader.mm
#include "app/core/platform/font_loader.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" ];
for (NSString *fontName in fontNames) {
NSFont *font = [NSFont fontWithName:fontName size:14.0];
if (!font) {
NSLog(@"Font not found: %@", fontName);
continue;
}
CTFontDescriptorRef fontDescriptor =
CTFontDescriptorCreateWithNameAndSize((CFStringRef)font.fontName, font.pointSize);
CFURLRef fontURL = (CFURLRef)CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontURLAttribute);
NSString *fontPath = [(NSURL *)fontURL path];
CFRelease(fontDescriptor);
if (fontPath != nil && [[NSFileManager defaultManager] isReadableFileAtPath:fontPath]) {
// Load the font into ImGui
ImGuiIO &io = ImGui::GetIO();
ImFontConfig icons_config;
icons_config.MergeMode = true;
icons_config.GlyphOffset.y = 5.0f;
icons_config.GlyphMinAdvanceX = 13.0f;
icons_config.PixelSnapH = true;
static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
static const float ICON_FONT_SIZE = 18.0f;
ImFont *imFont = io.Fonts->AddFontFromFileTTF([fontPath UTF8String], 14.0f);
if (!imFont) {
NSLog(@"Failed to load font: %@", fontPath);
}
io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_MD, ICON_FONT_SIZE, &icons_config,
icons_ranges);
} else {
NSLog(@"Font file not accessible: %@", fontPath);
}
if (fontURL) {
CFRelease(fontURL);
}
}
}
#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

@@ -1,32 +0,0 @@
add_library(delta-service delta.proto)
target_link_libraries(delta-service
PUBLIC
protobuf::libprotobuf
gRPC::grpc
gRPC::grpc++
)
target_include_directories(delta-service
PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
${PROTOBUF_INCLUDE_PATH}
)
get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
# compile the message types
protobuf_generate(TARGET delta-service LANGUAGE cpp)
# compile the GRPC services
protobuf_generate(
TARGET
delta-service
LANGUAGE
grpc
GENERATE_EXTENSIONS
.grpc.pb.h
.grpc.pb.cc
PLUGIN
"protoc-gen-grpc=${grpc_cpp_plugin_location}"
)

View File

@@ -1,54 +0,0 @@
#include "client.h"
#include <google/protobuf/message.h>
#include <grpc/support/log.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include "absl/status/status.h"
#include "src/app/delta/delta.grpc.pb.h"
#include "src/app/delta/delta.pb.h"
namespace yaze {
namespace app {
namespace delta {
using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Server;
using grpc::ServerBuilder;
using grpc::Status;
void Client::CreateChannel() {
auto channel = grpc::CreateChannel("localhost:50051",
grpc::InsecureChannelCredentials());
stub_ = ::YazeDelta::NewStub(channel);
}
absl::Status Client::InitRepo(std::string author_name,
std::string project_name) {
Repository new_repo;
new_repo.set_author_name(author_name);
new_repo.set_project_name(project_name);
new_repo.mutable_tree()->Add();
InitRequest request;
request.set_allocated_repo(&new_repo);
InitResponse response;
Status status = stub_->Init(&rpc_context, request, &response);
if (!status.ok()) {
std::cerr << status.error_code() << ": " << status.error_message()
<< std::endl;
return absl::InternalError(status.error_message());
}
return absl::OkStatus();
}
} // namespace delta
} // namespace app
} // namespace yaze

View File

@@ -1,44 +0,0 @@
#ifndef YAZE_APP_DELTA_CLIENT_H
#define YAZE_APP_DELTA_CLIENT_H
#include <google/protobuf/message.h>
#include <grpc/support/log.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include <vector>
#include "absl/status/status.h"
#include "src/app/delta/delta.grpc.pb.h"
#include "src/app/delta/delta.pb.h"
namespace yaze {
namespace app {
namespace delta {
using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Server;
using grpc::ServerBuilder;
using grpc::Status;
class Client {
public:
Client() = default;
void CreateChannel();
absl::Status InitRepo(std::string author_name, std::string project_name);
private:
ClientContext rpc_context;
std::vector<Repository> repos_;
std::unique_ptr<YazeDelta::Stub> stub_;
};
} // namespace delta
} // namespace app
} // namespace yaze
#endif

View File

@@ -1,32 +0,0 @@
#if defined(_WIN32)
#define main SDL_main
#endif
#include "absl/debugging/failure_signal_handler.h"
#include "absl/debugging/symbolize.h"
#include "app/core/controller.h"
#include "app/delta/viewer.h"
int main(int argc, char** argv) {
absl::InitializeSymbolizer(argv[0]);
absl::FailureSignalHandlerOptions options;
absl::InstallFailureSignalHandler(options);
yaze::app::core::Controller controller;
yaze::app::delta::Viewer viewer;
auto entry_status = controller.onEntry();
if (!entry_status.ok()) {
return EXIT_FAILURE;
}
while (controller.isActive()) {
controller.onInput();
viewer.Update();
controller.doRender();
}
controller.onExit();
return EXIT_SUCCESS;
}

View File

@@ -1,105 +0,0 @@
syntax = "proto3";
option cc_enable_arenas = true;
service YazeDelta {
rpc Init(InitRequest) returns (InitResponse) {}
rpc Clone(CloneRequest) returns (CloneResponse) {}
rpc Push(PushRequest) returns (PushResponse) {}
rpc Pull(PullRequest) returns (PullResponse) {}
rpc Sync(stream SyncRequest) returns (stream SyncResponse) {}
rpc CreateBranch(CreateBranchRequest) returns (CreateBranchResponse) {}
rpc DeleteBranch(DeleteBranchRequest) returns (DeleteBranchResponse) {}
rpc Merge(MergeRequest) returns (MergeResponse) {}
rpc UndoMerge(UndoMergeRequest) returns (UndoMergeResponse) {}
}
enum ChangeType {
OVERWORLD_MAP = 0;
DUNGEON_MAP = 1;
MONOLOGUE = 2;
PALETTE = 3;
OBJECT = 4;
ASSEMBLY = 5;
MISC = 6;
}
message Delta {
int64 offset = 1;
int64 length = 2;
bytes data = 3;
ChangeType type = 4;
}
message Commit {
int64 commit_id = 1;
int64 parent_commit_id = 2;
string author_name = 3;
string message_header = 4;
optional string message_body = 5;
repeated Delta delta = 6;
int64 signature = 7;
}
message Branch {
string branch_name = 1;
optional string parent_name = 2;
repeated Commit commits = 3;
}
message Repository {
string project_name = 1;
string author_name = 2;
int64 signature = 3;
optional bool locked = 4;
optional string password = 5;
repeated Branch tree = 6;
}
message InitRequest {
Repository repo = 1;
}
message InitResponse {
int32 response = 1;
}
message CloneRequest {}
message CloneResponse {}
message PushRequest {
string author_name = 1;
string repository_name= 2;
string branch_name = 3;
repeated Commit commits = 4;
}
message PushResponse {}
message PullRequest {
string repository_name = 1;
string branch_name = 2;
repeated Commit commits = 3;
}
message PullResponse {}
message SyncRequest {}
message SyncResponse {}
message CreateBranchRequest {}
message CreateBranchResponse {}
message DeleteBranchRequest {}
message DeleteBranchResponse {}
message MergeRequest {}
message MergeResponse {}
message UndoMergeRequest {}
message UndoMergeResponse {}

View File

@@ -1,87 +0,0 @@
#include "service.h"
#include <google/protobuf/message.h>
#include <grpc/support/log.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include <fstream>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "src/app/delta/delta.grpc.pb.h"
#include "src/app/delta/delta.pb.h"
namespace yaze {
namespace app {
namespace delta {
using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Server;
using grpc::ServerBuilder;
using grpc::Status;
namespace {
auto FindRepository(std::vector<Repository>& repos, const std::string& name) {
for (auto& repo : repos) {
if (repo.project_name() == name) {
return repo.mutable_tree();
}
}
}
auto FindBranch(google::protobuf::RepeatedPtrField<Branch>* repo,
const std::string& branch_name) {
for (auto it = repo->begin(); it != repo->end(); ++it) {
if (it->branch_name() == branch_name) {
return it->mutable_commits();
}
}
}
} // namespace
Status DeltaService::Init(grpc::ServerContext* context,
const InitRequest* request, InitResponse* reply) {
std::filesystem::create_directories("./.yaze");
repos_.push_back(request->repo());
// std::ofstream commit_stream("./.yaze/commits");
// for (const auto& repo : repos_) {
// for (const auto& branch : repo.tree()) {
// for (const auto& commit : branch.commits()) {
// commit_stream << commit.DebugString();
// }
// }
// }
return Status::OK;
}
Status DeltaService::Push(grpc::ServerContext* context,
const PushRequest* request, PushResponse* reply) {
const auto& repository_name = request->repository_name();
const auto& branch_name = request->branch_name();
auto repo = FindRepository(repos_, repository_name);
auto mutable_commits = FindBranch(repo, branch_name);
auto size = request->commits().size();
for (int i = 1; i < size; ++i) {
*mutable_commits->Add() = request->commits().at(i);
}
return Status::OK;
}
Status DeltaService::Pull(grpc::ServerContext* context,
const PullRequest* request, PullResponse* reply) {
return Status::OK;
}
Status DeltaService::Clone(grpc::ServerContext* context,
const CloneRequest* request, CloneResponse* reply) {
return Status::OK;
}
} // namespace delta
} // namespace app
} // namespace yaze

View File

@@ -1,48 +0,0 @@
#ifndef YAZE_APP_DELTA_SERVICE_H
#define YAZE_APP_DELTA_SERVICE_H
#include <filesystem>
#include <google/protobuf/message.h>
#include <grpc/support/log.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#include <vector>
#include "absl/status/status.h"
#include "src/app/delta/delta.grpc.pb.h"
#include "src/app/delta/delta.pb.h"
namespace yaze {
namespace app {
namespace delta {
using grpc::Status;
class DeltaService final : public ::YazeDelta::Service {
public:
Status Init(grpc::ServerContext* context, const InitRequest* request,
InitResponse* reply) override;
Status Push(grpc::ServerContext* context, const PushRequest* request,
PushResponse* reply) override;
Status Pull(grpc::ServerContext* context, const PullRequest* request,
PullResponse* reply) override;
Status Clone(grpc::ServerContext* context, const CloneRequest* request,
CloneResponse* reply) override;
auto Repos() const { return repos_; }
private:
std::vector<Repository> repos_;
};
} // namespace delta
} // namespace app
} // namespace yaze
#endif

View File

@@ -1,229 +0,0 @@
#include "viewer.h"
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#include "absl/status/status.h"
#include "app/core/constants.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"
#include "gui/canvas.h"
#include "gui/icons.h"
#include "gui/input.h"
namespace yaze {
namespace app {
namespace delta {
namespace {
constexpr ImGuiWindowFlags kMainEditorFlags =
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_MenuBar |
ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar;
void NewMasterFrame() {
const ImGuiIO& io = ImGui::GetIO();
ImGui::NewFrame();
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImVec2 dimensions(io.DisplaySize.x, io.DisplaySize.y);
ImGui::SetNextWindowSize(dimensions, ImGuiCond_Always);
if (!ImGui::Begin("##YazeMain", nullptr, kMainEditorFlags)) {
ImGui::End();
return;
}
}
} // namespace
void Viewer::Update() {
NewMasterFrame();
DrawYazeMenu();
DrawFileDialog();
ImGui::Text(ICON_MD_CHANGE_HISTORY);
ImGui::SameLine();
ImGui::Text("%s", rom_.GetTitle());
ImGui::Separator();
ImGui::Button(ICON_MD_SYNC);
ImGui::SameLine();
ImGui::Button(ICON_MD_ARROW_UPWARD);
ImGui::SameLine();
ImGui::Button(ICON_MD_ARROW_DOWNWARD);
ImGui::SameLine();
ImGui::Button(ICON_MD_MERGE);
ImGui::SameLine();
ImGui::Button(ICON_MD_MANAGE_HISTORY);
ImGui::SameLine();
ImGui::Button(ICON_MD_LAN);
ImGui::SameLine();
ImGui::Button(ICON_MD_COMMIT);
ImGui::SameLine();
ImGui::Button(ICON_MD_DIFFERENCE);
ImGui::Separator();
ImGui::SetNextItemWidth(75.f);
ImGui::Button(ICON_MD_SEND);
ImGui::SameLine();
ImGui::InputText("Server Address", &client_address_);
ImGui::SetNextItemWidth(75.f);
ImGui::Button(ICON_MD_DOWNLOAD);
ImGui::SameLine();
ImGui::InputText("Repository Source", &client_address_);
ImGui::Separator();
DrawBranchTree();
ImGui::End();
}
void Viewer::DrawFileDialog() {
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
if (ImGuiFileDialog::Instance()->IsOk()) {
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
rom_.LoadFromFile(filePathName);
}
ImGuiFileDialog::Instance()->Close();
}
}
void Viewer::DrawYazeMenu() {
MENU_BAR()
DrawFileMenu();
DrawViewMenu();
END_MENU_BAR()
}
void Viewer::DrawFileMenu() const {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "Ctrl+O")) {
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Open ROM",
".sfc,.smc", ".");
}
MENU_ITEM2("Save", "Ctrl+S") {}
ImGui::EndMenu();
}
}
void Viewer::DrawViewMenu() {
static bool show_imgui_metrics = false;
static bool show_imgui_style_editor = false;
static bool show_memory_editor = false;
static bool show_imgui_demo = false;
if (show_imgui_metrics) {
ImGui::ShowMetricsWindow(&show_imgui_metrics);
}
if (show_memory_editor) {
static MemoryEditor mem_edit;
mem_edit.DrawWindow("Memory Editor", (void*)&rom_, rom_.size());
}
if (show_imgui_demo) {
ImGui::ShowDemoWindow();
}
if (show_imgui_style_editor) {
ImGui::Begin("Style Editor (ImGui)", &show_imgui_style_editor);
ImGui::ShowStyleEditor();
ImGui::End();
}
if (ImGui::BeginMenu("View")) {
ImGui::MenuItem("HEX Editor", nullptr, &show_memory_editor);
ImGui::MenuItem("ImGui Demo", nullptr, &show_imgui_demo);
ImGui::Separator();
if (ImGui::BeginMenu("GUI Tools")) {
ImGui::MenuItem("Metrics (ImGui)", nullptr, &show_imgui_metrics);
ImGui::MenuItem("Style Editor (ImGui)", nullptr,
&show_imgui_style_editor);
ImGui::EndMenu();
}
ImGui::EndMenu();
}
}
void Viewer::DrawBranchTree() {
static ImGuiTableFlags flags =
ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH |
ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg |
ImGuiTableFlags_NoBordersInBody;
if (ImGui::BeginTable("3ways", 3, flags)) {
// The first column will use the default _WidthStretch when ScrollX is Off
// and _WidthFixed when ScrollX is On
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide);
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed,
10 * 12.0f);
ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthFixed,
10 * 18.0f);
ImGui::TableHeadersRow();
// Simple storage to output a dummy file-system.
struct MyTreeNode {
const char* Name;
const char* Type;
int Size;
int ChildIdx;
int ChildCount;
static void DisplayNode(const MyTreeNode* node,
const MyTreeNode* all_nodes) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
const bool is_folder = (node->ChildCount > 0);
if (is_folder) {
bool open =
ImGui::TreeNodeEx(node->Name, ImGuiTreeNodeFlags_SpanFullWidth);
ImGui::TableNextColumn();
ImGui::TextDisabled("--");
ImGui::TableNextColumn();
ImGui::TextUnformatted(node->Type);
if (open) {
for (int child_n = 0; child_n < node->ChildCount; child_n++)
DisplayNode(&all_nodes[node->ChildIdx + child_n], all_nodes);
ImGui::TreePop();
}
} else {
ImGui::TreeNodeEx(
node->Name, ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_Bullet |
ImGuiTreeNodeFlags_NoTreePushOnOpen |
ImGuiTreeNodeFlags_SpanFullWidth);
ImGui::TableNextColumn();
ImGui::Text("%d", node->Size);
ImGui::TableNextColumn();
ImGui::TextUnformatted(node->Type);
}
}
};
static const MyTreeNode nodes[] = {
{"lttp-redux", "Repository", -1, 1, 3},
{"main", "Branch", -1, 4, 2},
{"hyrule-castle", "Branch", -1, 4, 2},
{"lost-woods", "Branch", -1, 6, 3},
{"Added some bushes", "Commit", 1024, -1, -1},
{"Constructed a new house", "Commit", 123000, -1, -1},
{"File1_b.wav", "Commit", 456000, -1, -1},
{"Image001.png", "Commit", 203128, -1, -1},
{"Copy of Image001.png", "Commit", 203256, -1, -1},
{"Copy of Image001 (Final2).png", "Commit", 203512, -1, -1},
};
MyTreeNode::DisplayNode(&nodes[0], nodes);
ImGui::EndTable();
}
}
} // namespace delta
} // namespace app
} // namespace yaze

View File

@@ -1,45 +0,0 @@
#ifndef YAZE_APP_DELTA_VIEWER_H
#define YAZE_APP_DELTA_VIEWER_H
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <imgui/imgui.h>
#include <imgui/misc/cpp/imgui_stdlib.h>
#include <imgui_memory_editor.h>
#include "absl/status/status.h"
#include "app/core/constants.h"
#include "app/delta/client.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"
#include "gui/canvas.h"
#include "gui/icons.h"
#include "gui/input.h"
namespace yaze {
namespace app {
namespace delta {
class Viewer {
public:
void Update();
private:
void DrawFileDialog();
void DrawYazeMenu();
void DrawFileMenu() const;
void DrawViewMenu();
void DrawBranchTree();
std::string client_address_;
ROM rom_;
Client client_;
};
} // namespace delta
} // namespace app
} // namespace yaze
#endif

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

@@ -1,115 +0,0 @@
#include "assembly_editor.h"
#include "core/constants.h"
#include "gui/widgets.h"
namespace yaze {
namespace app {
namespace editor {
AssemblyEditor::AssemblyEditor() {
text_editor_.SetLanguageDefinition(gui::widgets::GetAssemblyLanguageDef());
text_editor_.SetPalette(TextEditor::GetDarkPalette());
}
void AssemblyEditor::Update() {
ImGui::Begin("Assembly Editor", &file_is_loaded_);
MENU_BAR()
DrawFileMenu();
DrawEditMenu();
END_MENU_BAR()
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
text_editor_.Render("##asm_editor");
ImGui::End();
}
void AssemblyEditor::InlineUpdate() {
ChangeActiveFile("assets/asm/template_song.asm");
auto cpos = text_editor_.GetCursorPosition();
SetEditorText();
ImGui::Text("%6d/%-6d %6d lines | %s | %s | %s | %s", cpos.mLine + 1,
cpos.mColumn + 1, text_editor_.GetTotalLines(),
text_editor_.IsOverwrite() ? "Ovr" : "Ins",
text_editor_.CanUndo() ? "*" : " ",
text_editor_.GetLanguageDefinition().mName.c_str(),
current_file_.c_str());
text_editor_.Render("##asm_editor", ImVec2(0, 0));
}
void AssemblyEditor::ChangeActiveFile(const std::string& filename) {
current_file_ = filename;
}
void AssemblyEditor::DrawFileMenu() {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Open", "Ctrl+O")) {
ImGuiFileDialog::Instance()->OpenDialog(
"ChooseASMFileDlg", "Open ASM file", ".asm,.txt", ".");
}
if (ImGui::MenuItem("Save", "Ctrl+S")) {
// TODO: Implement this
}
ImGui::EndMenu();
}
if (ImGuiFileDialog::Instance()->Display("ChooseASMFileDlg")) {
if (ImGuiFileDialog::Instance()->IsOk()) {
ChangeActiveFile(ImGuiFileDialog::Instance()->GetFilePathName());
}
ImGuiFileDialog::Instance()->Close();
}
}
void AssemblyEditor::DrawEditMenu() {
if (ImGui::BeginMenu("Edit")) {
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
text_editor_.Undo();
}
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
text_editor_.Redo();
}
ImGui::Separator();
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
text_editor_.Cut();
}
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
text_editor_.Copy();
}
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
text_editor_.Paste();
}
ImGui::Separator();
if (ImGui::MenuItem("Find", "Ctrl+F")) {
// TODO: Implement this.
}
ImGui::EndMenu();
}
}
void AssemblyEditor::SetEditorText() {
if (!file_is_loaded_) {
std::ifstream t(current_file_);
if (t.good()) {
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
text_editor_.SetText(str);
file_is_loaded_ = true;
}
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,38 +0,0 @@
#ifndef YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#define YAZE_APP_EDITOR_ASSEMBLY_EDITOR_H
#include <ImGuiColorTextEdit/TextEditor.h>
#include <ImGuiFileDialog/ImGuiFileDialog.h>
#include <fstream>
#include <istream>
#include <string>
namespace yaze {
namespace app {
namespace editor {
class AssemblyEditor {
public:
AssemblyEditor();
void Update();
void InlineUpdate();
void ChangeActiveFile(const std::string &);
private:
void DrawFileMenu();
void DrawEditMenu();
void SetEditorText();
bool file_is_loaded_ = false;
std::string current_file_;
TextEditor text_editor_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif

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

@@ -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,61 +0,0 @@
#include "dungeon_editor.h"
#include "gui/icons.h"
namespace yaze {
namespace app {
namespace editor {
void DungeonEditor::Update() {
DrawToolset();
canvas_.DrawBackground();
canvas_.DrawContextMenu();
canvas_.DrawGrid();
canvas_.DrawOverlay();
}
void DungeonEditor::DrawToolset() {
if (ImGui::BeginTable("DWToolset", 9, toolset_table_flags_, ImVec2(0, 0))) {
ImGui::TableSetupColumn("#undoTool");
ImGui::TableSetupColumn("#redoTool");
ImGui::TableSetupColumn("#history");
ImGui::TableSetupColumn("#separator");
ImGui::TableSetupColumn("#bg1Tool");
ImGui::TableSetupColumn("#bg2Tool");
ImGui::TableSetupColumn("#bg3Tool");
ImGui::TableSetupColumn("#itemTool");
ImGui::TableSetupColumn("#spriteTool");
ImGui::TableNextColumn();
ImGui::Button(ICON_MD_UNDO);
ImGui::TableNextColumn();
ImGui::Button(ICON_MD_REDO);
ImGui::TableNextColumn();
ImGui::Button(ICON_MD_MANAGE_HISTORY);
ImGui::TableNextColumn();
ImGui::Text(ICON_MD_MORE_VERT);
ImGui::TableNextColumn();
ImGui::Button(ICON_MD_FILTER_1);
ImGui::TableNextColumn();
ImGui::Button(ICON_MD_FILTER_2);
ImGui::TableNextColumn();
ImGui::Button(ICON_MD_FILTER_3);
ImGui::TableNextColumn();
ImGui::Button(ICON_MD_GRASS);
ImGui::TableNextColumn();
ImGui::Button(ICON_MD_PEST_CONTROL_RODENT);
ImGui::EndTable();
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,26 +0,0 @@
#ifndef YAZE_APP_EDITOR_DUNGEONEDITOR_H
#define YAZE_APP_EDITOR_DUNGEONEDITOR_H
#include <imgui/imgui.h>
#include "gui/canvas.h"
#include "gui/icons.h"
namespace yaze {
namespace app {
namespace editor {
class DungeonEditor {
public:
void Update();
private:
void DrawToolset();
gui::Canvas canvas_;
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif

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

@@ -0,0 +1,73 @@
#ifndef YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
#define YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H
#include "imgui/imgui.h"
#include <cmath>
#include "absl/status/status.h"
#include "absl/status/statusor.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"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
namespace editor {
/**
* @class GfxGroupEditor
* @brief Manage graphics group configurations in a Rom.
*/
class GfxGroupEditor : public SharedRom {
public:
absl::Status Update();
void DrawBlocksetViewer(bool sheet_only = false);
void DrawRoomsetViewer();
void DrawSpritesetViewer(bool sheet_only = false);
void DrawPaletteViewer();
void SetSelectedBlockset(uint8_t blockset) { selected_blockset_ = blockset; }
void SetSelectedRoomset(uint8_t roomset) { selected_roomset_ = roomset; }
void SetSelectedSpriteset(uint8_t spriteset) {
selected_spriteset_ = spriteset;
}
void InitBlockset(gfx::Bitmap* tile16_blockset) {
tile16_blockset_bmp_ = tile16_blockset;
}
private:
int preview_palette_id_ = 0;
int last_sheet_id_ = 0;
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::PaletteGroup palette_group_;
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 overworld_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_GFX_GROUP_EDITOR_H

View File

@@ -0,0 +1,844 @@
#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 "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/core/platform/clipboard.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/style.h"
#include "app/rom.h"
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 |
ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable |
ImGuiTableFlags_SizingFixedFit;
constexpr ImGuiTabBarFlags kGfxEditTabBarFlags =
ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable |
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()
CLEAR_AND_RETURN_STATUS(status_)
return absl::OkStatus();
}
absl::Status GraphicsEditor::UpdateGfxEdit() {
TAB_ITEM("Sheet Editor")
if (ImGui::BeginTable("##GfxEditTable", 3, kGfxEditTableFlags,
ImVec2(0, 0))) {
for (const auto& name :
{"Tilesheets", "Current Graphics", "Palette Controls"})
ImGui::TableSetupColumn(name);
ImGui::TableHeadersRow();
NEXT_COLUMN();
status_ = UpdateGfxSheetList();
NEXT_COLUMN();
if (rom()->is_loaded()) {
DrawGfxEditToolset();
status_ = UpdateGfxTabView();
}
NEXT_COLUMN();
if (rom()->is_loaded()) {
status_ = UpdatePaletteColumn();
}
}
ImGui::EndTable();
END_TAB_ITEM()
return absl::OkStatus();
}
void GraphicsEditor::DrawGfxEditToolset() {
if (ImGui::BeginTable("##GfxEditToolset", 9, ImGuiTableFlags_SizingFixedFit,
ImVec2(0, 0))) {
for (const auto& name :
{"Select", "Pencil", "Fill", "Copy Sheet", "Paste Sheet", "Zoom Out",
"Zoom In", "Current Color", "Tile Size"})
ImGui::TableSetupColumn(name);
TableNextColumn();
if (Button(ICON_MD_SELECT_ALL)) {
gfx_edit_mode_ = GfxEditMode::kSelect;
}
TableNextColumn();
if (Button(ICON_MD_DRAW)) {
gfx_edit_mode_ = GfxEditMode::kPencil;
}
HOVER_HINT("Draw with current color");
TableNextColumn();
if (Button(ICON_MD_FORMAT_COLOR_FILL)) {
gfx_edit_mode_ = GfxEditMode::kFill;
}
HOVER_HINT("Fill with current color");
TableNextColumn();
if (Button(ICON_MD_CONTENT_COPY)) {
std::vector<uint8_t> png_data =
rom()->bitmap_manager().shared_bitmap(current_sheet_).GetPngData();
CopyImageToClipboard(png_data);
}
HOVER_HINT("Copy to Clipboard");
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()
->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");
TableNextColumn();
if (Button(ICON_MD_ZOOM_OUT)) {
if (current_scale_ >= 0.0f) {
current_scale_ -= 1.0f;
}
}
TableNextColumn();
if (Button(ICON_MD_ZOOM_IN)) {
if (current_scale_ <= 16.0f) {
current_scale_ += 1.0f;
}
}
TableNextColumn();
auto bitmap = rom()->bitmap_manager()[current_sheet_];
auto palette = bitmap.palette();
for (int i = 0; i < 8; i++) {
ImGui::SameLine();
auto color =
ImVec4(palette[i].rgb().x / 255.0f, palette[i].rgb().y / 255.0f,
palette[i].rgb().z / 255.0f, 255.0f);
if (ImGui::ColorButton(absl::StrFormat("Palette Color %d", i).c_str(),
color)) {
current_color_ = color;
}
}
TableNextColumn();
gui::InputHexByte("Tile Size", &tile_size_);
ImGui::EndTable();
}
}
absl::Status GraphicsEditor::UpdateGfxSheetList() {
ImGui::BeginChild(
"##GfxEditChild", ImVec2(0, 0), true,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysVerticalScrollbar);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
// TODO: Update the interaction for multi select on sheets
static ImGuiSelectionBasicStorage selection;
ImGuiMultiSelectFlags flags =
ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(
flags, selection.Size, 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_;
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_));
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
current_sheet_ = key;
open_sheets_.insert(key);
}
// Add a slightly transparent rectangle behind the text
ImVec2 text_pos(graphics_bin_canvas_.zero_point().x + 2,
graphics_bin_canvas_.zero_point().y + 2);
ImVec2 text_size =
ImGui::CalcTextSize(absl::StrFormat("%02X", key).c_str());
ImVec2 rent_min(text_pos.x, text_pos.y);
ImVec2 rent_max(text_pos.x + text_size.x, text_pos.y + text_size.y);
graphics_bin_canvas_.draw_list()->AddRectFilled(rent_min, rent_max,
IM_COL32(0, 125, 0, 128));
graphics_bin_canvas_.draw_list()->AddText(
text_pos, IM_COL32(125, 255, 125, 255),
absl::StrFormat("%02X", key).c_str());
}
graphics_bin_canvas_.DrawGrid(16.0f);
graphics_bin_canvas_.DrawOverlay();
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::EndChild();
}
ImGui::PopStyleVar();
ms_io = ImGui::EndMultiSelect();
selection.ApplyRequests(ms_io);
ImGui::EndChild();
return absl::OkStatus();
}
absl::Status GraphicsEditor::UpdateGfxTabView() {
static int next_tab_id = 0;
if (ImGui::BeginTabBar("##GfxEditTabBar", kGfxEditTabBarFlags)) {
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing |
ImGuiTabItemFlags_NoTooltip)) {
open_sheets_.insert(next_tab_id++);
}
for (auto& sheet_id : open_sheets_) {
bool open = true;
if (ImGui::BeginTabItem(absl::StrFormat("%d", sheet_id).c_str(), &open,
ImGuiTabItemFlags_None)) {
current_sheet_ = sheet_id;
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
release_queue_.push(sheet_id);
}
if (ImGui::IsItemHovered()) {
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
release_queue_.push(sheet_id);
child_window_sheets_.insert(sheet_id);
}
}
const auto child_id =
absl::StrFormat("##GfxEditPaletteChildWindow%d", sheet_id);
ImGui::BeginChild(child_id.c_str(), ImVec2(0, 0), true,
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_AlwaysVerticalScrollbar |
ImGuiWindowFlags_AlwaysHorizontalScrollbar);
gfx::Bitmap& current_bitmap =
*rom()->mutable_bitmap_manager()->mutable_bitmap(sheet_id);
auto draw_tile_event = [&]() {
current_sheet_canvas_.DrawTileOnBitmap(tile_size_, &current_bitmap,
current_color_);
rom()->UpdateBitmap(&current_bitmap, true);
};
current_sheet_canvas_.UpdateColorPainter(
rom()->bitmap_manager()[sheet_id], current_color_, draw_tile_event,
tile_size_, current_scale_);
ImGui::EndChild();
ImGui::EndTabItem();
}
if (!open) release_queue_.push(sheet_id);
}
ImGui::EndTabBar();
}
// Release any tabs that were closed
while (!release_queue_.empty()) {
auto sheet_id = release_queue_.top();
open_sheets_.erase(sheet_id);
release_queue_.pop();
}
// Draw any child windows that were created
if (!child_window_sheets_.empty()) {
int id_to_release = -1;
for (const auto& id : child_window_sheets_) {
bool active = true;
ImGui::SetNextWindowPos(ImGui::GetIO().MousePos, ImGuiCond_Once);
ImGui::SetNextWindowSize(ImVec2(0x100 + 1 * 16, 0x40 + 1 * 16),
ImGuiCond_Once);
ImGui::Begin(absl::StrFormat("##GfxEditPaletteChildWindow%d", id).c_str(),
&active, ImGuiWindowFlags_AlwaysUseWindowPadding);
current_sheet_ = id;
// ImVec2(0x100, 0x40),
current_sheet_canvas_.UpdateColorPainter(
rom()->bitmap_manager()[id], current_color_,
[&]() {
},
tile_size_, current_scale_);
ImGui::End();
if (active == false) {
id_to_release = id;
}
}
if (id_to_release != -1) {
child_window_sheets_.erase(id_to_release);
}
}
return absl::OkStatus();
}
absl::Status GraphicsEditor::UpdatePaletteColumn() {
if (rom()->is_loaded()) {
auto palette_group = *rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[edit_palette_group_name_index_]);
auto palette = palette_group.palette(edit_palette_index_);
gui::TextWithSeparators("ROM Palette");
ImGui::SetNextItemWidth(100.f);
ImGui::Combo("Palette Group", (int*)&edit_palette_group_name_index_,
kPaletteGroupAddressesKeys,
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
ImGui::SetNextItemWidth(100.f);
gui::InputHex("Palette Group Index", &edit_palette_index_);
gui::SelectablePalettePipeline(edit_palette_sub_index_, refresh_graphics_,
palette);
if (refresh_graphics_ && !open_sheets_.empty()) {
RETURN_IF_ERROR(
rom()->bitmap_manager()[current_sheet_].ApplyPaletteWithTransparent(
palette, edit_palette_sub_index_));
rom()->UpdateBitmap(
rom()->mutable_bitmap_manager()->mutable_bitmap(current_sheet_),
true);
refresh_graphics_ = false;
}
}
return absl::OkStatus();
}
absl::Status GraphicsEditor::UpdateLinkGfxView() {
TAB_ITEM("Player Animations")
if (ImGui::BeginTable("##PlayerAnimationTable", 3, kGfxEditTableFlags,
ImVec2(0, 0))) {
for (const auto& name : {"Canvas", "Animation Steps", "Properties"})
ImGui::TableSetupColumn(name);
ImGui::TableHeadersRow();
NEXT_COLUMN();
link_canvas_.DrawBackground();
link_canvas_.DrawGrid(16.0f);
int i = 0;
for (auto [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();
}
absl::Status GraphicsEditor::UpdateScadView() {
TAB_ITEM("Prototype")
RETURN_IF_ERROR(DrawToolset())
if (open_memory_editor_) {
ImGui::Begin("Memory Editor", &open_memory_editor_);
RETURN_IF_ERROR(DrawMemoryEditor())
ImGui::End();
}
BEGIN_TABLE("#gfxEditTable", 4, kGfxEditFlags)
SETUP_COLUMN("File Import (BIN, CGX, ROM)")
SETUP_COLUMN("Palette (COL)")
ImGui::TableSetupColumn("Tilemaps and Objects (SCR, PNL, OBJ)",
ImGuiTableColumnFlags_WidthFixed);
SETUP_COLUMN("Graphics Preview")
TABLE_HEADERS()
NEXT_COLUMN() {
status_ = DrawCgxImport();
status_ = DrawClipboardImport();
status_ = DrawFileImport();
status_ = DrawExperimentalFeatures();
}
NEXT_COLUMN() { status_ = DrawPaletteControls(); }
NEXT_COLUMN()
gui::BitmapCanvasPipeline(scr_canvas_, scr_bitmap_, 0x200, 0x200, 0x20,
scr_loaded_, false, 0);
status_ = DrawScrImport();
NEXT_COLUMN()
if (super_donkey_) {
if (refresh_graphics_) {
for (int i = 0; i < graphics_bin_.size(); i++) {
status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
rom()->UpdateBitmap(&graphics_bin_[i]);
}
refresh_graphics_ = false;
}
// Load the full graphics space from `super_donkey_1.bin`
gui::GraphicsBinCanvasPipeline(0x100, 0x40, 0x20, num_sheets_to_load_, 3,
super_donkey_, graphics_bin_);
} else if (cgx_loaded_ && col_file_) {
// Load the CGX graphics
gui::BitmapCanvasPipeline(import_canvas_, cgx_bitmap_, 0x100, 16384, 0x20,
cgx_loaded_, true, 5);
} else {
// Load the BIN/Clipboard Graphics
gui::BitmapCanvasPipeline(import_canvas_, bin_bitmap_, 0x100, 16384, 0x20,
gfx_loaded_, true, 2);
}
END_TABLE()
END_TAB_ITEM()
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawToolset() {
if (ImGui::BeginTable("GraphicsToolset", 2, ImGuiTableFlags_SizingFixedFit,
ImVec2(0, 0))) {
for (const auto& name : kGfxToolsetColumnNames)
ImGui::TableSetupColumn(name.data());
TableNextColumn();
if (Button(ICON_MD_MEMORY)) {
if (!open_memory_editor_) {
open_memory_editor_ = true;
} else {
open_memory_editor_ = false;
}
}
TEXT_COLUMN("Open Memory Editor") // Separator
ImGui::EndTable();
}
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawCgxImport() {
gui::TextWithSeparators("Cgx Import");
InputInt("BPP", &current_bpp_);
InputText("##CGXFile", cgx_file_name_, sizeof(cgx_file_name_));
SameLine();
gui::FileDialogPipeline("ImportCgxKey", ".CGX,.cgx\0", "Open CGX", [this]() {
strncpy(cgx_file_path_,
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
sizeof(cgx_file_path_));
strncpy(cgx_file_name_,
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
sizeof(cgx_file_name_));
is_open_ = true;
cgx_loaded_ = true;
});
if (ImGui::Button("Copy CGX Path")) {
ImGui::SetClipboardText(cgx_file_path_);
}
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();
}
absl::Status GraphicsEditor::DrawScrImport() {
InputText("##ScrFile", scr_file_name_, sizeof(scr_file_name_));
gui::FileDialogPipeline(
"ImportScrKey", ".SCR,.scr,.BAK\0", "Open SCR", [this]() {
strncpy(scr_file_path_,
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
sizeof(scr_file_path_));
strncpy(scr_file_name_,
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
sizeof(scr_file_name_));
is_open_ = true;
scr_loaded_ = true;
});
InputInt("SCR Mod", &scr_mod_value_);
if (ImGui::Button("Load Scr Data")) {
status_ =
gfx::scad_format::LoadScr(scr_file_path_, scr_mod_value_, scr_data_);
decoded_scr_data_.resize(0x100 * 0x100);
status_ = gfx::scad_format::DrawScrWithCgx(current_bpp_, scr_data_,
decoded_scr_data_, decoded_cgx_);
scr_bitmap_.Create(0x100, 0x100, 8, decoded_scr_data_);
if (scr_loaded_) {
scr_bitmap_.ApplyPalette(decoded_col_);
rom()->RenderBitmap(&scr_bitmap_);
}
}
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawPaletteControls() {
gui::TextWithSeparators("COL Import");
InputText("##ColFile", col_file_name_, sizeof(col_file_name_));
SameLine();
gui::FileDialogPipeline(
"ImportColKey", ".COL,.col,.BAK,.bak\0", "Open COL", [this]() {
strncpy(col_file_path_,
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
sizeof(col_file_path_));
strncpy(col_file_name_,
ImGuiFileDialog::Instance()->GetCurrentFileName().c_str(),
sizeof(col_file_name_));
status_ = temp_rom_.LoadFromFile(col_file_path_,
/*z3_load=*/false);
auto col_data_ = gfx::GetColFileData(temp_rom_.data());
if (col_file_palette_group_.size() != 0) {
col_file_palette_group_.Clear();
}
auto col_file_palette_group_status =
gfx::CreatePaletteGroupFromColFile(col_data_);
if (col_file_palette_group_status.ok()) {
col_file_palette_group_ = col_file_palette_group_status.value();
}
col_file_palette_ = gfx::SnesPalette(col_data_);
// gigaleak dev format based code
decoded_col_ = gfx::scad_format::DecodeColFile(col_file_path_);
col_file_ = true;
is_open_ = true;
});
if (ImGui::Button("Copy Col Path")) {
ImGui::SetClipboardText(col_file_path_);
}
if (rom()->is_loaded()) {
gui::TextWithSeparators("ROM Palette");
gui::InputHex("Palette Index", &current_palette_index_);
ImGui::Combo("Palette", &current_palette_, kPaletteGroupAddressesKeys,
IM_ARRAYSIZE(kPaletteGroupAddressesKeys));
}
if (col_file_palette_.size() != 0) {
gui::SelectablePalettePipeline(current_palette_index_, refresh_graphics_,
col_file_palette_);
}
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawObjImport() {
gui::TextWithSeparators("OBJ Import");
InputText("##ObjFile", obj_file_path_, sizeof(obj_file_path_));
SameLine();
gui::FileDialogPipeline(
"ImportObjKey", ".obj,.OBJ,.bak,.BAK\0", "Open OBJ", [this]() {
strncpy(file_path_,
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
sizeof(file_path_));
status_ = temp_rom_.LoadFromFile(file_path_);
is_open_ = true;
});
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawTilemapImport() {
gui::TextWithSeparators("Tilemap Import");
InputText("##TMapFile", tilemap_file_path_, sizeof(tilemap_file_path_));
SameLine();
gui::FileDialogPipeline(
"ImportTilemapKey", ".DAT,.dat,.BIN,.bin,.hex,.HEX\0", "Open Tilemap",
[this]() {
strncpy(tilemap_file_path_,
ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
sizeof(tilemap_file_path_));
status_ = tilemap_rom_.LoadFromFile(tilemap_file_path_);
// Extract the high and low bytes from the file.
auto decomp_sheet = gfx::lc_lz2::DecompressV2(
tilemap_rom_.data(), gfx::lc_lz2::kNintendoMode1);
tilemap_loaded_ = true;
is_open_ = true;
});
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawFileImport() {
gui::TextWithSeparators("BIN Import");
InputText("##ROMFile", file_path_, sizeof(file_path_));
SameLine();
gui::FileDialogPipeline("ImportDlgKey", ".bin,.hex\0", "Open BIN", [this]() {
strncpy(file_path_, ImGuiFileDialog::Instance()->GetFilePathName().c_str(),
sizeof(file_path_));
status_ = temp_rom_.LoadFromFile(file_path_);
is_open_ = true;
});
if (Button("Copy File Path")) {
ImGui::SetClipboardText(file_path_);
}
gui::InputHex("BIN Offset", &current_offset_);
gui::InputHex("BIN Size", &bin_size_);
if (Button("Decompress BIN")) {
if (strlen(file_path_) > 0) {
RETURN_IF_ERROR(DecompressImportData(bin_size_))
} else {
return absl::InvalidArgumentError(
"Please select a file before importing.");
}
}
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawClipboardImport() {
gui::TextWithSeparators("Clipboard Import");
if (Button("Paste From Clipboard")) {
const char* text = ImGui::GetClipboardText();
if (text) {
const auto clipboard_data = Bytes(text, text + strlen(text));
ImGui::MemFree((void*)text);
status_ = temp_rom_.LoadFromBytes(clipboard_data);
is_open_ = true;
open_memory_editor_ = true;
}
}
gui::InputHex("Offset", &clipboard_offset_);
gui::InputHex("Size", &clipboard_size_);
gui::InputHex("Num Sheets", &num_sheets_to_load_);
if (Button("Decompress Clipboard Data")) {
if (temp_rom_.is_loaded()) {
status_ = DecompressImportData(0x40000);
} else {
status_ = absl::InvalidArgumentError(
"Please paste data into the clipboard before "
"decompressing.");
}
}
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawExperimentalFeatures() {
gui::TextWithSeparators("Experimental");
if (Button("Decompress Super Donkey Full")) {
if (strlen(file_path_) > 0) {
RETURN_IF_ERROR(DecompressSuperDonkey())
} else {
return absl::InvalidArgumentError(
"Please select `super_donkey_1.bin` before "
"importing.");
}
}
ImGui::SetItemTooltip(
"Requires `super_donkey_1.bin` to be imported under the "
"BIN import section.");
return absl::OkStatus();
}
absl::Status GraphicsEditor::DrawMemoryEditor() {
std::string title = "Memory Editor";
if (is_open_) {
static MemoryEditor mem_edit;
mem_edit.DrawWindow(title.c_str(), temp_rom_.data(), temp_rom_.size());
}
return absl::OkStatus();
}
absl::Status GraphicsEditor::DecompressImportData(int size) {
ASSIGN_OR_RETURN(import_data_, gfx::lc_lz2::DecompressV2(
temp_rom_.data(), current_offset_, size))
auto converted_sheet = gfx::SnesTo8bppSheet(import_data_, 3);
bin_bitmap_.Create(core::kTilesheetWidth, 0x2000, core::kTilesheetDepth,
converted_sheet);
if (rom()->is_loaded()) {
auto palette_group = rom()->palette_group().overworld_animated;
z3_rom_palette_ = palette_group[current_palette_];
if (col_file_) {
status_ = bin_bitmap_.ApplyPalette(col_file_palette_);
} else {
status_ = bin_bitmap_.ApplyPalette(z3_rom_palette_);
}
}
rom()->RenderBitmap(&bin_bitmap_);
gfx_loaded_ = true;
return absl::OkStatus();
}
absl::Status GraphicsEditor::DecompressSuperDonkey() {
int i = 0;
for (const auto& offset : kSuperDonkeyTiles) {
int offset_value =
std::stoi(offset, nullptr, 16); // convert hex string to int
ASSIGN_OR_RETURN(
auto decompressed_data,
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
graphics_bin_[i] =
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, converted_sheet);
if (col_file_) {
status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
}
rom()->RenderBitmap(&graphics_bin_[i]);
i++;
}
for (const auto& offset : kSuperDonkeySprites) {
int offset_value =
std::stoi(offset, nullptr, 16); // convert hex string to int
ASSIGN_OR_RETURN(
auto decompressed_data,
gfx::lc_lz2::DecompressV2(temp_rom_.data(), offset_value, 0x1000))
auto converted_sheet = gfx::SnesTo8bppSheet(decompressed_data, 3);
graphics_bin_[i] =
gfx::Bitmap(core::kTilesheetWidth, core::kTilesheetHeight,
core::kTilesheetDepth, converted_sheet);
if (col_file_) {
status_ = graphics_bin_[i].ApplyPalette(
col_file_palette_group_[current_palette_index_]);
} else {
// ROM palette
auto palette_group = rom()->palette_group().get_group(
kPaletteGroupAddressesKeys[current_palette_]);
z3_rom_palette_ = *palette_group->mutable_palette(current_palette_index_);
status_ = graphics_bin_[i].ApplyPalette(z3_rom_palette_);
}
rom()->RenderBitmap(&graphics_bin_[i]);
i++;
}
super_donkey_ = true;
num_sheets_to_load_ = i;
return absl::OkStatus();
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -0,0 +1,205 @@
#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 "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_tile.h"
#include "app/gui/asset_browser.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
namespace editor {
// "99973","A3D80",
const std::string kSuperDonkeyTiles[] = {
"97C05", "98219", "9871E", "98C00", "99084", "995AF", "99DE0", "9A27E",
"9A741", "9AC31", "9B07E", "9B55C", "9B963", "9BB99", "9C009", "9C4B4",
"9C92B", "9CDD6", "9D2C2", "9E037", "9E527", "9EA56", "9EF65", "9FCD1",
"A0193", "A059E", "A0B17", "A0FB6", "A14A5", "A1988", "A1E66", "A232B",
"A27F0", "A2B6E", "A302C", "A3453", "A38CA", "A42BB", "A470C", "A4BA9",
"A5089", "A5385", "A5742", "A5BCC", "A6017", "A6361", "A66F8"};
const std::string kSuperDonkeySprites[] = {
"A8E5D", "A9435", "A9934", "A9D83", "AA2F1", "AA6D4", "AABE4", "AB127",
"AB65A", "ABBDD", "AC38D", "AC797", "ACCC8", "AD0AE", "AD245", "AD554",
"ADAAC", "ADECC", "AE453", "AE9D2", "AEF40", "AF3C9", "AF92E", "AFE9D",
"B03D2", "B09AC", "B0F0C", "B1430", "B1859", "B1E01", "B229A", "B2854",
"B2D27", "B31D7", "B3B58", "B40B5", "B45A5", "B4D64", "B5031", "B555F",
"B5F30", "B6858", "B70DD", "B7526", "B79EC", "B7C83", "B80F7", "B85CC",
"B8A3F", "B8F97", "B94F2", "B9A20", "B9E9A", "BA3A2", "BA8F6", "BACDC",
"BB1F9", "BB781", "BBCCA", "BC26D", "BC7D4", "BCBB0", "BD082", "BD5FC",
"BE115", "BE5C2", "BEB63", "BF0CB", "BF607", "BFA55", "BFD71", "C017D",
"C0567", "C0981", "C0BA7", "C116D", "C166A", "C1FE0", "C24CE", "C2B19"};
/**
* @class GraphicsEditor
* @brief Allows the user to edit graphics sheets from the game or view
* prototype graphics.
*
* The GraphicsEditor class is responsible for providing functionality to edit
* graphics sheets from the game or view prototype graphics of Link to the Past
* from the CGX, SCR, and OBJ formats. It provides various methods to update
* different components of the graphics editor, such as the graphics edit tab,
* link graphics view, and prototype graphics viewer. It also includes import
* functions for different file formats, as well as other utility functions for
* drawing toolsets, palette controls, clipboard imports, experimental features,
* and memory editor.
*/
class GraphicsEditor : public SharedRom, public Editor {
public:
GraphicsEditor() { type_ = EditorType::kGraphics; }
absl::Status Update() override;
absl::Status Undo() override { return absl::UnimplementedError("Undo"); }
absl::Status Redo() override { return absl::UnimplementedError("Redo"); }
absl::Status Cut() override { return absl::UnimplementedError("Cut"); }
absl::Status Copy() override { return absl::UnimplementedError("Copy"); }
absl::Status Paste() override { return absl::UnimplementedError("Paste"); }
absl::Status Find() override { return absl::UnimplementedError("Find"); }
private:
enum class GfxEditMode {
kSelect,
kPencil,
kFill,
};
// Graphics Editor Tab
absl::Status UpdateGfxEdit();
absl::Status UpdateGfxSheetList();
absl::Status UpdateGfxTabView();
absl::Status UpdatePaletteColumn();
void DrawGfxEditToolset();
// Link Graphics Edit Tab
absl::Status UpdateLinkGfxView();
// Prototype Graphics Viewer
absl::Status UpdateScadView();
// Import Functions
absl::Status DrawCgxImport();
absl::Status DrawScrImport();
absl::Status DrawFileImport();
absl::Status DrawObjImport();
absl::Status DrawTilemapImport();
// Other Functions
absl::Status DrawToolset();
absl::Status DrawPaletteControls();
absl::Status DrawClipboardImport();
absl::Status DrawExperimentalFeatures();
absl::Status DrawMemoryEditor();
absl::Status DecompressImportData(int size);
absl::Status DecompressSuperDonkey();
// Member Variables
ImVec4 current_color_;
uint16_t current_sheet_ = 0;
uint8_t tile_size_ = 0x01;
std::set<uint16_t> open_sheets_;
std::set<uint16_t> child_window_sheets_;
std::stack<uint16_t> release_queue_;
uint64_t edit_palette_group_name_index_ = 0;
uint64_t edit_palette_group_index_ = 0;
uint64_t edit_palette_index_ = 0;
uint64_t edit_palette_sub_index_ = 0;
float sheet_scale_ = 2.0f;
float current_scale_ = 4.0f;
// Prototype Graphics Viewer
int current_palette_ = 0;
uint64_t current_offset_ = 0;
uint64_t current_size_ = 0;
uint64_t current_palette_index_ = 0;
int current_bpp_ = 0;
int scr_mod_value_ = 0;
uint64_t num_sheets_to_load_ = 1;
uint64_t bin_size_ = 0;
uint64_t clipboard_offset_ = 0;
uint64_t clipboard_size_ = 0;
bool refresh_graphics_ = false;
bool open_memory_editor_ = false;
bool gfx_loaded_ = false;
bool is_open_ = false;
bool super_donkey_ = false;
bool col_file_ = false;
bool cgx_loaded_ = false;
bool scr_loaded_ = false;
bool obj_loaded_ = false;
bool tilemap_loaded_ = false;
char file_path_[256] = "";
char col_file_path_[256] = "";
char col_file_name_[256] = "";
char cgx_file_path_[256] = "";
char cgx_file_name_[256] = "";
char scr_file_path_[256] = "";
char scr_file_name_[256] = "";
char obj_file_path_[256] = "";
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 overworld_;
MemoryEditor cgx_memory_editor_;
MemoryEditor col_memory_editor_;
PaletteEditor palette_editor_;
Bytes import_data_;
Bytes graphics_buffer_;
std::vector<uint8_t> decoded_cgx_;
std::vector<uint8_t> cgx_data_;
std::vector<uint8_t> extra_cgx_data_;
std::vector<SDL_Color> decoded_col_;
std::vector<uint8_t> scr_data_;
std::vector<uint8_t> decoded_scr_data_;
gfx::Bitmap cgx_bitmap_;
gfx::Bitmap scr_bitmap_;
gfx::Bitmap bin_bitmap_;
gfx::Bitmap link_full_sheet_;
gfx::BitmapTable graphics_bin_;
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_;
gui::Canvas import_canvas_;
gui::Canvas scr_canvas_;
gui::Canvas super_donkey_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_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#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

@@ -0,0 +1,118 @@
#ifndef YAZE_APP_EDITOR_TILE16EDITOR_H
#define YAZE_APP_EDITOR_TILE16EDITOR_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/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/overworld.h"
namespace yaze {
namespace app {
namespace editor {
/**
* @brief Popup window to edit Tile16 data
*/
class Tile16Editor : public context::GfxContext, public SharedRom {
public:
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();
absl::Status DrawTileEditControls();
absl::Status UpdateTransferTileCanvas();
absl::Status LoadTile8();
absl::Status SetCurrentTile(int id);
private:
bool map_blockset_loaded_ = false;
bool transfer_started_ = false;
bool transfer_blockset_loaded_ = false;
int current_tile16_ = 0;
int current_tile8_ = 0;
uint8_t current_palette_ = 0;
core::NotifyValue<uint32_t> notify_tile16;
core::NotifyValue<uint8_t> notify_palette;
// Various options for the Tile16 Editor
bool x_flip;
bool y_flip;
bool priority_tile;
int tile_size;
uint8_t* all_tiles_types_;
// Tile16 blockset for selecting the tile to edit
gui::Canvas blockset_canvas_{"blocksetCanvas", ImVec2(0x100, 0x4000),
gui::CanvasGridSize::k32x32};
gfx::Bitmap* tile16_blockset_bmp_;
// Canvas for editing the selected tile
gui::Canvas tile16_edit_canvas_{"Tile16EditCanvas", ImVec2(0x40, 0x40),
gui::CanvasGridSize::k64x64};
gfx::Bitmap* current_tile16_bmp_;
// Tile8 canvas to get the tile to drawing in the tile16_edit_canvas_
gui::Canvas tile8_source_canvas_{
"Tile8SourceCanvas",
ImVec2(core::kTilesheetWidth * 4, core::kTilesheetHeight * 0x10 * 4),
gui::CanvasGridSize::k32x32};
gfx::Bitmap current_gfx_bmp_;
gui::Canvas transfer_canvas_;
gfx::Bitmap transfer_blockset_bmp_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_;
std::vector<gfx::Bitmap> current_gfx_individual_;
std::vector<uint8_t> current_tile16_data_;
std::vector<uint8_t> tile8_gfx_data_;
PaletteEditor palette_editor_;
gfx::SnesPalette palette_;
zelda3::overworld::Overworld transfer_overworld_;
gfx::BitmapTable graphics_bin_;
Rom transfer_rom_;
absl::Status transfer_status_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif // YAZE_APP_EDITOR_TILE16EDITOR_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,68 +1,137 @@
#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/editor/assembly_editor.h"
#include "app/editor/dungeon_editor.h"
#include "app/editor/music_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/palette_editor.h"
#include "app/editor/screen_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"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/rom.h"
#include "gui/canvas.h"
#include "gui/icons.h"
#include "gui/input.h"
namespace yaze {
namespace app {
namespace editor {
class MasterEditor {
/**
* @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:
void SetupScreen(std::shared_ptr<SDL_Renderer> renderer);
void UpdateScreen();
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,
std::string filename = "");
absl::Status Update();
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();
void DrawInfoPopup();
void DrawYazeMenu();
void DrawFileMenu() const;
void DrawFileMenu();
void DrawEditMenu();
void DrawViewMenu();
void DrawTestMenu();
void DrawProjectMenu();
void DrawHelpMenu();
void DrawOverworldEditor();
void DrawDungeonEditor();
void DrawPaletteEditor();
void DrawMusicEditor();
void DrawScreenEditor();
void DrawSpriteEditor();
void LoadRom();
void SaveRom();
absl::Status OpenProject();
bool quit_ = false;
bool about_ = false;
bool rom_info_ = false;
bool backup_rom_ = false;
bool save_new_auto_ = true;
bool show_status_ = false;
bool rom_assets_loaded_ = false;
absl::Status status_;
absl::Status prev_status_;
std::shared_ptr<SDL_Renderer> sdl_renderer_;
absl::Status status_;
emu::Emulator emulator_;
Project current_project_;
AssemblyEditor assembly_editor_;
DungeonEditor dungeon_editor_;
GraphicsEditor graphics_editor_;
MusicEditor music_editor_;
OverworldEditor overworld_editor_;
PaletteEditor palette_editor_;
ScreenEditor screen_editor_;
MusicEditor music_editor_;
ROM rom_;
SpriteEditor sprite_editor_;
SettingsEditor settings_editor_;
MessageEditor message_editor_;
MemoryEditorWithDiffChecker memory_editor_;
ImVector<int> active_tabs_;
std::vector<Editor*> active_editors_;
Editor* current_editor_ = nullptr;
};
} // namespace editor

View File

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

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,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/assembly_editor.h"
#include "gui/canvas.h"
#include "gui/icons.h"
#include "gui/input.h"
#include "snes_spc/demo/demo_util.h"
#include "snes_spc/demo/wave_writer.h"
#include "snes_spc/snes_spc/spc.h"
#include "app/editor/code/assembly_editor.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/input.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,48 +170,26 @@ 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);
if (is_playing && !has_loaded_song) {
has_loaded_song = true;
}
// // If there is no music playing
// if (Mix_PlayingMusic() == 0) {
// Mix_PlayMusic(current_song_, -1);
// }
// // If music is being played
// else {
// // If the music is paused
// if (Mix_PausedMusic() == 1) {
// // Resume the music
// Mix_ResumeMusic();
// }
// // If the music is playing
// else {
// // Pause the music
// Mix_PauseMusic();
// }
// }
}
gui::ItemLabel("Select a song to edit: ", gui::ItemLabelFlags::Left);
ImGui::Combo("#songs_in_game", &selected_option, kGameSongs, 30);
gui::ItemLabel("Controls: ", gui::ItemLabelFlags::Left);
if (ImGui::BeginTable("SongToolset", 5, toolset_table_flags_, ImVec2(0, 0))) {
if (ImGui::BeginTable("SongToolset", 6, toolset_table_flags_, ImVec2(0, 0))) {
ImGui::TableSetupColumn("#play");
ImGui::TableSetupColumn("#rewind");
ImGui::TableSetupColumn("#fastforward");
ImGui::TableSetupColumn("#volume");
ImGui::TableSetupColumn("#debug");
ImGui::TableSetupColumn("#slider");
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;
@@ -271,6 +198,9 @@ void MusicEditor::DrawToolset() {
BUTTON_COLUMN(ICON_MD_FAST_REWIND)
BUTTON_COLUMN(ICON_MD_FAST_FORWARD)
BUTTON_COLUMN(ICON_MD_VOLUME_UP)
if (ImGui::Button(ICON_MD_ACCESS_TIME)) {
music_tracker_.LoadSongs(*rom());
}
ImGui::TableNextColumn();
ImGui::SliderInt("Volume", &current_volume, 0, 100);
ImGui::EndTable();

View File

@@ -1,17 +1,19 @@
#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/assembly_editor.h"
#include "gui/canvas.h"
#include "gui/icons.h"
#include "gui/input.h"
#include "snes_spc/demo/demo_util.h"
#include "snes_spc/demo/wave_writer.h"
#include "snes_spc/snes_spc/spc.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"
#include "app/rom.h"
#include "app/zelda3/music/tracker.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 {
@@ -52,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 {
/**
* @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();
@@ -63,7 +79,9 @@ class MusicEditor {
void DrawSongToolset();
void DrawToolset();
Mix_Music* current_song_ = NULL;
zelda3::music::Tracker music_tracker_;
// Mix_Music* current_song_ = NULL;
AssemblyEditor assembly_editor_;
ImGuiTableFlags toolset_table_flags_ = ImGuiTableFlags_SizingFixedFit;

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>
@@ -10,14 +10,21 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "app/editor/palette_editor.h"
#include "app/core/common.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/zeml.h"
#include "app/rom.h"
#include "app/zelda3/overworld.h"
#include "gui/canvas.h"
#include "gui/icons.h"
#include "app/zelda3/overworld/overworld.h"
namespace yaze {
namespace app {
@@ -31,43 +38,180 @@ 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",
"#undoTool", "#redoTool", "#separator2", "#zoomOutTool",
"#zoomInTool", "#separator", "#drawTool", "#history",
"#entranceTool", "#exitTool", "#itemTool", "#spriteTool",
"#transportTool", "#musicTool"};
"#transportTool", "#musicTool", "#separator3", "#tilemapTool",
"propertiesTool"};
static constexpr absl::string_view kOverworldSettingsColumnNames[] = {
"##1stCol", "##gfxCol", "##palCol", "##sprgfxCol",
"##sprpalCol", "##msgidCol", "##2ndCol"};
constexpr ImGuiTableFlags kOWMapFlags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
constexpr ImGuiTableFlags kToolsetTableFlags = ImGuiTableFlags_SizingFixedFit;
constexpr ImGuiTableFlags kOWEditFlags =
ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV;
class OverworldEditor {
constexpr absl::string_view kWorldList =
"Light World\0Dark World\0Extra World\0";
constexpr absl::string_view kGamePartComboString = "Part 0\0Part 1\0Part 2\0";
constexpr absl::string_view kTileSelectorTab = "##TileSelectorTabBar";
constexpr absl::string_view kOWEditTable = "##OWEditTable";
constexpr absl::string_view kOWMapTable = "#MapSettingsTable";
class EntranceContext {
public:
absl::Status Update();
absl::Status Undo() const { return absl::UnimplementedError("Undo"); }
absl::Status Redo() const { return absl::UnimplementedError("Redo"); }
absl::Status Cut() const { return absl::UnimplementedError("Cut"); }
absl::Status Copy() const { return absl::UnimplementedError("Copy"); }
absl::Status Paste() const { return absl::UnimplementedError("Paste"); }
void SetupROM(ROM &rom) { rom_ = rom; }
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 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_; }
/**
* @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();
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 CheckForOverworldEdits();
void CheckForSelectRectangle();
absl::Status CheckForCurrentMap();
void CheckForMousePan();
/**
* @brief Allows the user to make changes to the overworld map.
*/
void DrawOverworldCanvas();
void DrawTile16Selector();
absl::Status DrawTile16Selector();
void DrawTile8Selector();
void DrawAreaGraphics();
void DrawTileSelector();
absl::Status LoadGraphics();
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,
EXITS,
ITEMS,
SPRITES,
TRANSPORTS,
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 current_blockset_ = 0;
int selected_entrance_ = 0;
int selected_usage_map_ = 0xFFFF;
char map_gfx_[3] = "";
char map_palette_[3] = "";
char spr_gfx_[3] = "";
@@ -75,41 +219,79 @@ class OverworldEditor {
char message_id_[5] = "";
char staticgfx[16];
uint32_t tilemap_file_offset_high_ = 0;
uint32_t tilemap_file_offset_low_ = 0;
uint32_t light_maps_to_load_ = 0x51;
uint32_t dark_maps_to_load_ = 0x2A;
uint32_t sp_maps_to_load_ = 0x07;
bool opt_enable_grid = true;
bool all_gfx_loaded_ = false;
bool map_blockset_loaded_ = false;
bool selected_tile_loaded_ = false;
bool update_selected_tile_ = true;
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;
ImGuiTableFlags toolset_table_flags = ImGuiTableFlags_SizingFixedFit;
ImGuiTableFlags ow_map_flags = ImGuiTableFlags_Borders;
ImGuiTableFlags ow_edit_flags = ImGuiTableFlags_Reorderable |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchSame;
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_ = "";
std::string tile32_configuration_filename_ = "";
Bytes selected_tile_data_;
std::unordered_map<int, gfx::Bitmap> graphics_bin_;
std::unordered_map<int, gfx::Bitmap> current_graphics_set_;
std::unordered_map<int, gfx::Bitmap> maps_bmp_;
std::unordered_map<int, gfx::Bitmap> sprite_previews_;
std::vector<Bytes> tile16_individual_data_;
std::vector<gfx::Bitmap> tile16_individual_;
ROM rom_;
PaletteEditor palette_editor_;
zelda3::Overworld overworld_;
std::vector<Bytes> tile8_individual_data_;
std::vector<gfx::Bitmap> tile8_individual_;
gfx::SNESPalette palette_;
Tile16Editor tile16_editor_;
GfxGroupEditor gfx_group_editor_;
PaletteEditor palette_editor_;
zelda3::overworld::Overworld overworld_;
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::Bitmap selected_tile_bmp_;
gfx::Bitmap tile16_blockset_bmp_;
gfx::Bitmap current_gfx_bmp_;
gfx::Bitmap all_gfx_bmp;
gui::Canvas ow_map_canvas_;
gui::Canvas current_gfx_canvas_;
gui::Canvas blockset_canvas_;
gui::Canvas graphics_bin_canvas_;
gfx::BitmapTable maps_bmp_;
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
} // namespace app

View File

@@ -1,128 +0,0 @@
#include "palette_editor.h"
#include <imgui/imgui.h>
#include "absl/status/status.h"
#include "app/gfx/snes_palette.h"
#include "gui/canvas.h"
#include "gui/icons.h"
namespace yaze {
namespace app {
namespace editor {
absl::Status PaletteEditor::Update() {
for (int i = 0; i < 11; ++i) {
if (ImGui::TreeNode(kPaletteCategoryNames[i].data())) {
auto size = rom_.GetPaletteGroup(kPaletteGroupNames[i].data()).size;
auto palettes = rom_.GetPaletteGroup(kPaletteGroupNames[i].data());
for (int j = 0; j < size; j++) {
ImGui::Text("%d", j);
auto palette = palettes[j];
for (int n = 0; n < size; n++) {
ImGui::PushID(n);
if ((n % 8) != 0)
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.y);
ImGuiColorEditFlags palette_button_flags =
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoPicker;
if (ImGui::ColorButton("##palette", palette[n].RGB(),
palette_button_flags, ImVec2(20, 20)))
current_color_ =
ImVec4(palette[n].rgb.x, palette[n].rgb.y, palette[n].rgb.z,
current_color_.w); // Preserve alpha!
ImGui::PopID();
}
}
ImGui::TreePop();
}
}
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;
static ImVec4 saved_palette[256] = {};
if (loaded && !init) {
for (int n = 0; n < palette.size_; n++) {
saved_palette[n].x = palette.GetColor(n).rgb.x / 255;
saved_palette[n].y = palette.GetColor(n).rgb.y / 255;
saved_palette[n].z = palette.GetColor(n).rgb.z / 255;
saved_palette[n].w = 255; // Alpha
}
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")) {
ImGui::Text("Current Overworld Palette");
ImGui::Separator();
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");
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;
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);
ImGuiColorEditFlags palette_button_flags = ImGuiColorEditFlags_NoAlpha |
ImGuiColorEditFlags_NoPicker |
ImGuiColorEditFlags_NoTooltip;
if (ImGui::ColorButton("##palette", saved_palette[n],
palette_button_flags, 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();
}
}
} // namespace editor
} // namespace app
} // namespace yaze

View File

@@ -1,42 +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/rom.h"
#include "gui/canvas.h"
#include "gui/icons.h"
namespace yaze {
namespace app {
namespace editor {
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"};
class PaletteEditor {
public:
absl::Status Update();
void DisplayPalette(gfx::SNESPalette& palette, bool loaded);
auto SetupROM(ROM& rom) { rom_ = rom; }
private:
ImVec4 current_color_;
ROM rom_;
};
} // namespace editor
} // namespace app
} // namespace yaze
#endif

View File

@@ -1,201 +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 "gui/canvas.h"
#include "gui/icons.h"
#include "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();
DrawMosaicEditor();
END_TAB_BAR()
}
void ScreenEditor::DrawWorldGrid(int world, int h, int w) {
const float time = (float)ImGui::GetTime();
int i = 0;
if (world == 1) {
i = 64;
} else if (world == 2) {
i = 128;
}
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++) {
if (x > 0) ImGui::SameLine();
ImGui::PushID(y * 4 + x);
std::string label = absl::StrCat(" #", absl::StrFormat("%x", i));
if (ImGui::Selectable(label.c_str(), mosaic_tiles_[i] != 0, 0,
ImVec2(35, 25))) {
mosaic_tiles_[i] ^= 1;
}
ImGui::PopID();
i++;
}
}
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::DrawMosaicEditor() {
TAB_ITEM("Mosaic Transitions")
if (ImGui::BeginTable("Worlds", 3, ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("Light World");
ImGui::TableSetupColumn("Dark World");
ImGui::TableSetupColumn("Special World");
ImGui::TableHeadersRow();
ImGui::TableNextColumn();
DrawWorldGrid(0);
ImGui::TableNextColumn();
DrawWorldGrid(1);
ImGui::TableNextColumn();
DrawWorldGrid(2, 4);
ImGui::EndTable();
}
gui::InputHex("Routine Location", &overworldCustomMosaicASM);
if (ImGui::Button("Generate Mosaic Assembly")) {
auto mosaic =
rom_.PatchOverworldMosaic(mosaic_tiles_, overworldCustomMosaicASM);
if (!mosaic.ok()) {
std::cout << mosaic;
}
}
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,60 +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/rom.h"
#include "app/zelda3/inventory.h"
#include "gui/canvas.h"
#include "gui/color.h"
#include "gui/icons.h"
namespace yaze {
namespace app {
namespace editor {
using MosaicArray = std::array<int, core::kNumOverworldMaps>;
static int overworldCustomMosaicASM = 0x1301D0;
class ScreenEditor {
public:
ScreenEditor();
void SetupROM(ROM &rom) {
rom_ = rom;
inventory_.SetupROM(rom_);
}
void Update();
private:
void DrawMosaicEditor();
void DrawTitleScreenEditor();
void DrawNamingScreenEditor();
void DrawOverworldMapEditor();
void DrawDungeonMapsEditor();
void DrawInventoryMenuEditor();
void DrawToolset();
void DrawInventoryToolset();
void DrawWorldGrid(int world, int h = 8, int w = 8);
char mosaic_tiles_[core::kNumOverworldMaps];
ROM rom_;
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

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

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